summaryrefslogtreecommitdiff
path: root/pjsip/src
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip/src')
-rw-r--r--pjsip/src/pjsip-simple/errno.c116
-rw-r--r--pjsip/src/pjsip-simple/evsub.c2169
-rw-r--r--pjsip/src/pjsip-simple/evsub_msg.c304
-rw-r--r--pjsip/src/pjsip-simple/iscomposing.c218
-rw-r--r--pjsip/src/pjsip-simple/mwi.c598
-rw-r--r--pjsip/src/pjsip-simple/pidf.c365
-rw-r--r--pjsip/src/pjsip-simple/presence.c941
-rw-r--r--pjsip/src/pjsip-simple/presence_body.c288
-rw-r--r--pjsip/src/pjsip-simple/publishc.c790
-rw-r--r--pjsip/src/pjsip-simple/rpid.c279
-rw-r--r--pjsip/src/pjsip-simple/xpidf.c301
-rw-r--r--pjsip/src/pjsip-ua/sip_100rel.c905
-rw-r--r--pjsip/src/pjsip-ua/sip_inv.c4491
-rw-r--r--pjsip/src/pjsip-ua/sip_reg.c1333
-rw-r--r--pjsip/src/pjsip-ua/sip_replaces.c384
-rw-r--r--pjsip/src/pjsip-ua/sip_timer.c1062
-rw-r--r--pjsip/src/pjsip-ua/sip_xfer.c630
-rw-r--r--pjsip/src/pjsip/sip_auth_aka.c204
-rw-r--r--pjsip/src/pjsip/sip_auth_client.c1189
-rw-r--r--pjsip/src/pjsip/sip_auth_msg.c343
-rw-r--r--pjsip/src/pjsip/sip_auth_parser.c308
-rw-r--r--pjsip/src/pjsip/sip_auth_parser_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_auth_server.c225
-rw-r--r--pjsip/src/pjsip/sip_config.c54
-rw-r--r--pjsip/src/pjsip/sip_dialog.c2250
-rw-r--r--pjsip/src/pjsip/sip_dialog_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_endpoint.c1245
-rw-r--r--pjsip/src/pjsip/sip_endpoint_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_errno.c211
-rw-r--r--pjsip/src/pjsip/sip_msg.c2218
-rw-r--r--pjsip/src/pjsip/sip_multipart.c658
-rw-r--r--pjsip/src/pjsip/sip_parser.c2379
-rw-r--r--pjsip/src/pjsip/sip_parser_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_resolve.c520
-rw-r--r--pjsip/src/pjsip/sip_tel_uri.c448
-rw-r--r--pjsip/src/pjsip/sip_tel_uri_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_transaction.c3277
-rw-r--r--pjsip/src/pjsip/sip_transport.c1942
-rw-r--r--pjsip/src/pjsip/sip_transport_loop.c509
-rw-r--r--pjsip/src/pjsip/sip_transport_tcp.c1417
-rw-r--r--pjsip/src/pjsip/sip_transport_tls.c1610
-rw-r--r--pjsip/src/pjsip/sip_transport_udp.c1095
-rw-r--r--pjsip/src/pjsip/sip_transport_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_ua_layer.c978
-rw-r--r--pjsip/src/pjsip/sip_uri.c729
-rw-r--r--pjsip/src/pjsip/sip_util.c1850
-rw-r--r--pjsip/src/pjsip/sip_util_proxy.c389
-rw-r--r--pjsip/src/pjsip/sip_util_proxy_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_util_statefull.c192
-rw-r--r--pjsip/src/pjsip/sip_util_wrap.cpp24
-rw-r--r--pjsip/src/pjsua-lib/pjsua_acc.c3078
-rw-r--r--pjsip/src/pjsua-lib/pjsua_aud.c2148
-rw-r--r--pjsip/src/pjsua-lib/pjsua_call.c4397
-rw-r--r--pjsip/src/pjsua-lib/pjsua_core.c2909
-rw-r--r--pjsip/src/pjsua-lib/pjsua_dump.c974
-rw-r--r--pjsip/src/pjsua-lib/pjsua_im.c745
-rw-r--r--pjsip/src/pjsua-lib/pjsua_media.c2695
-rw-r--r--pjsip/src/pjsua-lib/pjsua_pres.c2402
-rw-r--r--pjsip/src/pjsua-lib/pjsua_vid.c2166
-rw-r--r--pjsip/src/test/dlg_core_test.c23
-rw-r--r--pjsip/src/test/dns_test.c618
-rw-r--r--pjsip/src/test/inv_offer_answer_test.c691
-rw-r--r--pjsip/src/test/main.c92
-rw-r--r--pjsip/src/test/main_rtems.c12
-rw-r--r--pjsip/src/test/main_win32.c1
-rw-r--r--pjsip/src/test/msg_err_test.c104
-rw-r--r--pjsip/src/test/msg_logger.c104
-rw-r--r--pjsip/src/test/msg_test.c2084
-rw-r--r--pjsip/src/test/multipart_test.c266
-rw-r--r--pjsip/src/test/regc_test.c1162
-rw-r--r--pjsip/src/test/test.c398
-rw-r--r--pjsip/src/test/test.h127
-rw-r--r--pjsip/src/test/transport_loop_test.c127
-rw-r--r--pjsip/src/test/transport_tcp_test.c155
-rw-r--r--pjsip/src/test/transport_test.c771
-rw-r--r--pjsip/src/test/transport_udp_test.c128
-rw-r--r--pjsip/src/test/tsx_basic_test.c157
-rw-r--r--pjsip/src/test/tsx_bench.c280
-rw-r--r--pjsip/src/test/tsx_uac_test.c1456
-rw-r--r--pjsip/src/test/tsx_uas_test.c1658
-rw-r--r--pjsip/src/test/txdata_test.c856
-rw-r--r--pjsip/src/test/uri_test.c1097
82 files changed, 75487 insertions, 0 deletions
diff --git a/pjsip/src/pjsip-simple/errno.c b/pjsip/src/pjsip-simple/errno.c
new file mode 100644
index 0000000..e1bdb95
--- /dev/null
+++ b/pjsip/src/pjsip-simple/errno.c
@@ -0,0 +1,116 @@
+/* $Id: errno.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/errno.h>
+#include <pj/string.h>
+
+/* PJSIP-SIMPLE's own error codes/messages
+ * MUST KEEP THIS ARRAY SORTED!!
+ * Message must be limited to 64 chars!
+ */
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+static const struct
+{
+ int code;
+ const char *msg;
+} err_str[] =
+{
+ /* Event errors */
+ { PJSIP_SIMPLE_ENOPKG, "No SIP event package with the specified name" },
+ { PJSIP_SIMPLE_EPKGEXISTS, "SIP event package already exist" },
+
+ /* Presence errors */
+ { PJSIP_SIMPLE_ENOTSUBSCRIBE, "Expecting SUBSCRIBE request" },
+ { PJSIP_SIMPLE_ENOPRESENCE, "No presence associated with the subscription" },
+ { PJSIP_SIMPLE_ENOPRESENCEINFO, "No presence info in the server subscription" },
+ { PJSIP_SIMPLE_EBADCONTENT, "Bad Content-Type for presence" },
+ { PJSIP_SIMPLE_EBADPIDF, "Bad PIDF content for presence" },
+ { PJSIP_SIMPLE_EBADXPIDF, "Bad XPIDF content for presence" },
+ { PJSIP_SIMPLE_EBADRPID, "Invalid or bad RPID document"},
+
+ /* isComposing errors. */
+ { PJSIP_SIMPLE_EBADISCOMPOSE, "Bad isComposing indication/XML message" },
+};
+
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+/*
+ * pjsipsimple_strerror()
+ */
+PJ_DEF(pj_str_t) pjsipsimple_strerror( pj_status_t statcode,
+ char *buf, pj_size_t bufsize )
+{
+ pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+ if (statcode >= PJSIP_SIMPLE_ERRNO_START &&
+ statcode < PJSIP_SIMPLE_ERRNO_START + PJ_ERRNO_SPACE_SIZE)
+ {
+ /* Find the error in the table.
+ * Use binary search!
+ */
+ int first = 0;
+ int n = PJ_ARRAY_SIZE(err_str);
+
+ while (n > 0) {
+ int half = n/2;
+ int mid = first + half;
+
+ if (err_str[mid].code < statcode) {
+ first = mid+1;
+ n -= (half+1);
+ } else if (err_str[mid].code > statcode) {
+ n = half;
+ } else {
+ first = mid;
+ break;
+ }
+ }
+
+
+ if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+ pj_str_t msg;
+
+ msg.ptr = (char*)err_str[first].msg;
+ msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ }
+ }
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+ /* Error not found. */
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown pjsip-simple error %d",
+ statcode);
+
+ return errstr;
+}
+
diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c
new file mode 100644
index 0000000..b1dfca5
--- /dev/null
+++ b/pjsip/src/pjsip-simple/evsub.c
@@ -0,0 +1,2169 @@
+/* $Id: evsub.c 4082 2012-04-24 13:09:14Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/evsub.h>
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_dialog.h>
+#include <pjsip/sip_auth.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_event.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "evsub.c"
+
+/*
+ * Global constant
+ */
+
+/* Let's define this enum, so that it'll trigger compilation error
+ * when somebody define the same enum in sip_msg.h
+ */
+enum
+{
+ PJSIP_SUBSCRIBE_METHOD = PJSIP_OTHER_METHOD,
+ PJSIP_NOTIFY_METHOD = PJSIP_OTHER_METHOD
+};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_subscribe_method =
+{
+ (pjsip_method_e) PJSIP_SUBSCRIBE_METHOD,
+ { "SUBSCRIBE", 9 }
+};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_notify_method =
+{
+ (pjsip_method_e) PJSIP_NOTIFY_METHOD,
+ { "NOTIFY", 6 }
+};
+
+/**
+ * SUBSCRIBE method constant.
+ */
+PJ_DEF(const pjsip_method*) pjsip_get_subscribe_method()
+{
+ return &pjsip_subscribe_method;
+}
+
+/**
+ * NOTIFY method constant.
+ */
+PJ_DEF(const pjsip_method*) pjsip_get_notify_method()
+{
+ return &pjsip_notify_method;
+}
+
+
+/*
+ * Static prototypes.
+ */
+static void mod_evsub_on_tsx_state(pjsip_transaction*, pjsip_event*);
+static pj_status_t mod_evsub_unload(void);
+
+
+/*
+ * State names.
+ */
+static pj_str_t evsub_state_names[] =
+{
+ { "NULL", 4},
+ { "SENT", 4},
+ { "ACCEPTED", 8},
+ { "PENDING", 7},
+ { "ACTIVE", 6},
+ { "TERMINATED", 10},
+ { "UNKNOWN", 7}
+};
+
+/*
+ * Timer constants.
+ */
+
+/* Number of seconds to send SUBSCRIBE before the actual expiration */
+#define TIME_UAC_REFRESH PJSIP_EVSUB_TIME_UAC_REFRESH
+
+/* Time to wait for the final NOTIFY after sending unsubscription */
+#define TIME_UAC_TERMINATE PJSIP_EVSUB_TIME_UAC_TERMINATE
+
+/* If client responds NOTIFY with non-2xx final response (such as 401),
+ * wait for this seconds for further NOTIFY, otherwise client will
+ * unsubscribe
+ */
+#define TIME_UAC_WAIT_NOTIFY PJSIP_EVSUB_TIME_UAC_WAIT_NOTIFY
+
+
+/*
+ * Timer id
+ */
+enum timer_id
+{
+ /* No timer. */
+ TIMER_TYPE_NONE,
+
+ /* Time to refresh client subscription.
+ * The action is to call on_client_refresh() callback.
+ */
+ TIMER_TYPE_UAC_REFRESH,
+
+ /* UAS timeout after to subscription refresh.
+ * The action is to call on_server_timeout() callback.
+ */
+ TIMER_TYPE_UAS_TIMEOUT,
+
+ /* UAC waiting for final NOTIFY after unsubscribing
+ * The action is to terminate.
+ */
+ TIMER_TYPE_UAC_TERMINATE,
+
+ /* UAC waiting for further NOTIFY after sending non-2xx response to
+ * NOTIFY. The action is to unsubscribe.
+ */
+ TIMER_TYPE_UAC_WAIT_NOTIFY,
+
+ /* Max nb of timer types. */
+ TIMER_TYPE_MAX
+};
+
+static const char *timer_names[] =
+{
+ "None",
+ "UAC_REFRESH",
+ "UAS_TIMEOUT"
+ "UAC_TERMINATE",
+ "UAC_WAIT_NOTIFY",
+ "INVALID_TIMER"
+};
+
+/*
+ * Definition of event package.
+ */
+struct evpkg
+{
+ PJ_DECL_LIST_MEMBER(struct evpkg);
+
+ pj_str_t pkg_name;
+ pjsip_module *pkg_mod;
+ unsigned pkg_expires;
+ pjsip_accept_hdr *pkg_accept;
+};
+
+
+/*
+ * Event subscription module (mod-evsub).
+ */
+static struct mod_evsub
+{
+ pjsip_module mod;
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ struct evpkg pkg_list;
+ pjsip_allow_events_hdr *allow_events_hdr;
+
+} mod_evsub =
+{
+ {
+ NULL, NULL, /* prev, next. */
+ { "mod-evsub", 9 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ &mod_evsub_unload, /* unload() */
+ NULL, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ &mod_evsub_on_tsx_state, /* on_tsx_state() */
+ }
+};
+
+
+/*
+ * Event subscription session.
+ */
+struct pjsip_evsub
+{
+ char obj_name[PJ_MAX_OBJ_NAME]; /**< Name. */
+ pj_pool_t *pool; /**< Pool. */
+ pjsip_endpoint *endpt; /**< Endpoint instance. */
+ pjsip_dialog *dlg; /**< Underlying dialog. */
+ struct evpkg *pkg; /**< The event package. */
+ unsigned option; /**< Options. */
+ pjsip_evsub_user user; /**< Callback. */
+ pj_bool_t call_cb; /**< Notify callback? */
+ pjsip_role_e role; /**< UAC=subscriber, UAS=notifier */
+ pjsip_evsub_state state; /**< Subscription state. */
+ pj_str_t state_str; /**< String describing the state. */
+ pjsip_evsub_state dst_state; /**< Pending state to be set. */
+ pj_str_t dst_state_str;/**< Pending state to be set. */
+ pj_str_t term_reason; /**< Termination reason. */
+ pjsip_method method; /**< Method that established subscr.*/
+ pjsip_event_hdr *event; /**< Event description. */
+ pjsip_expires_hdr *expires; /**< Expires header */
+ pjsip_accept_hdr *accept; /**< Local Accept header. */
+ pjsip_hdr sub_hdr_list; /**< User-defined header. */
+
+ pj_time_val refresh_time; /**< Time to refresh. */
+ pj_timer_entry timer; /**< Internal timer. */
+ int pending_tsx; /**< Number of pending transactions.*/
+ pjsip_transaction *pending_sub; /**< Pending UAC SUBSCRIBE tsx. */
+
+ void *mod_data[PJSIP_MAX_MODULE]; /**< Module data. */
+};
+
+
+/*
+ * This is the structure that will be "attached" to dialog.
+ * The purpose is to allow multiple subscriptions inside a dialog.
+ */
+struct dlgsub
+{
+ PJ_DECL_LIST_MEMBER(struct dlgsub);
+ pjsip_evsub *sub;
+};
+
+
+/* Static vars. */
+static const pj_str_t STR_EVENT = { "Event", 5 };
+static const pj_str_t STR_EVENT_S = { "Event", 5 };
+static const pj_str_t STR_SUB_STATE = { "Subscription-State", 18 };
+static const pj_str_t STR_TERMINATED = { "terminated", 10 };
+static const pj_str_t STR_ACTIVE = { "active", 6 };
+static const pj_str_t STR_PENDING = { "pending", 7 };
+static const pj_str_t STR_TIMEOUT = { "timeout", 7};
+
+
+/*
+ * On unload module.
+ */
+static pj_status_t mod_evsub_unload(void)
+{
+ pjsip_endpt_release_pool(mod_evsub.endpt, mod_evsub.pool);
+ mod_evsub.pool = NULL;
+
+ return PJ_SUCCESS;
+}
+
+/* Proto for pjsipsimple_strerror().
+ * Defined in errno.c
+ */
+PJ_DECL(pj_str_t) pjsipsimple_strerror( pj_status_t statcode,
+ char *buf, pj_size_t bufsize );
+
+/*
+ * Init and register module.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt)
+{
+ pj_status_t status;
+ pj_str_t method_tags[] = {
+ { "SUBSCRIBE", 9},
+ { "NOTIFY", 6}
+ };
+
+ status = pj_register_strerror(PJSIP_SIMPLE_ERRNO_START,
+ PJ_ERRNO_SPACE_SIZE,
+ &pjsipsimple_strerror);
+ pj_assert(status == PJ_SUCCESS);
+
+ PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(mod_evsub.mod.id == -1, PJ_EINVALIDOP);
+
+ /* Keep endpoint for future reference: */
+ mod_evsub.endpt = endpt;
+
+ /* Init event package list: */
+ pj_list_init(&mod_evsub.pkg_list);
+
+ /* Create pool: */
+ mod_evsub.pool = pjsip_endpt_create_pool(endpt, "evsub", 512, 512);
+ if (!mod_evsub.pool)
+ return PJ_ENOMEM;
+
+ /* Register module: */
+ status = pjsip_endpt_register_module(endpt, &mod_evsub.mod);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Create Allow-Events header: */
+ mod_evsub.allow_events_hdr = pjsip_allow_events_hdr_create(mod_evsub.pool);
+
+ /* Register SIP-event specific headers parser: */
+ pjsip_evsub_init_parser();
+
+ /* Register new methods SUBSCRIBE and NOTIFY in Allow-ed header */
+ pjsip_endpt_add_capability(endpt, &mod_evsub.mod, PJSIP_H_ALLOW, NULL,
+ 2, method_tags);
+
+ /* Done. */
+ return PJ_SUCCESS;
+
+on_error:
+ if (mod_evsub.pool) {
+ pjsip_endpt_release_pool(endpt, mod_evsub.pool);
+ mod_evsub.pool = NULL;
+ }
+ mod_evsub.endpt = NULL;
+ return status;
+}
+
+
+/*
+ * Get the instance of the module.
+ */
+PJ_DEF(pjsip_module*) pjsip_evsub_instance(void)
+{
+ PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, NULL);
+
+ return &mod_evsub.mod;
+}
+
+
+/*
+ * Get the event subscription instance in the transaction.
+ */
+PJ_DEF(pjsip_evsub*) pjsip_tsx_get_evsub(pjsip_transaction *tsx)
+{
+ return (pjsip_evsub*) tsx->mod_data[mod_evsub.mod.id];
+}
+
+
+/*
+ * Set event subscription's module data.
+ */
+PJ_DEF(void) pjsip_evsub_set_mod_data( pjsip_evsub *sub, unsigned mod_id,
+ void *data )
+{
+ PJ_ASSERT_ON_FAIL(mod_id < PJSIP_MAX_MODULE, return);
+ sub->mod_data[mod_id] = data;
+}
+
+
+/*
+ * Get event subscription's module data.
+ */
+PJ_DEF(void*) pjsip_evsub_get_mod_data( pjsip_evsub *sub, unsigned mod_id )
+{
+ PJ_ASSERT_RETURN(mod_id < PJSIP_MAX_MODULE, NULL);
+ return sub->mod_data[mod_id];
+}
+
+
+/*
+ * Find registered event package with matching name.
+ */
+static struct evpkg* find_pkg(const pj_str_t *event_name)
+{
+ struct evpkg *pkg;
+
+ pkg = mod_evsub.pkg_list.next;
+ while (pkg != &mod_evsub.pkg_list) {
+
+ if (pj_stricmp(&pkg->pkg_name, event_name) == 0) {
+ return pkg;
+ }
+
+ pkg = pkg->next;
+ }
+
+ return NULL;
+}
+
+/*
+ * Register an event package
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_register_pkg( pjsip_module *pkg_mod,
+ const pj_str_t *event_name,
+ unsigned expires,
+ unsigned accept_cnt,
+ const pj_str_t accept[])
+{
+ struct evpkg *pkg;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(pkg_mod && event_name, PJ_EINVAL);
+ PJ_ASSERT_RETURN(accept_cnt < PJ_ARRAY_SIZE(pkg->pkg_accept->values),
+ PJ_ETOOMANY);
+
+ /* Make sure evsub module has been initialized */
+ PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, PJ_EINVALIDOP);
+
+ /* Make sure no module with the specified name already registered: */
+
+ PJ_ASSERT_RETURN(find_pkg(event_name) == NULL, PJSIP_SIMPLE_EPKGEXISTS);
+
+
+ /* Create new event package: */
+
+ pkg = PJ_POOL_ALLOC_T(mod_evsub.pool, struct evpkg);
+ pkg->pkg_mod = pkg_mod;
+ pkg->pkg_expires = expires;
+ pj_strdup(mod_evsub.pool, &pkg->pkg_name, event_name);
+
+ pkg->pkg_accept = pjsip_accept_hdr_create(mod_evsub.pool);
+ pkg->pkg_accept->count = accept_cnt;
+ for (i=0; i<accept_cnt; ++i) {
+ pj_strdup(mod_evsub.pool, &pkg->pkg_accept->values[i], &accept[i]);
+ }
+
+ /* Add to package list: */
+
+ pj_list_push_back(&mod_evsub.pkg_list, pkg);
+
+ /* Add to Allow-Events header: */
+
+ if (mod_evsub.allow_events_hdr->count !=
+ PJ_ARRAY_SIZE(mod_evsub.allow_events_hdr->values))
+ {
+ mod_evsub.allow_events_hdr->values[mod_evsub.allow_events_hdr->count] =
+ pkg->pkg_name;
+ ++mod_evsub.allow_events_hdr->count;
+ }
+
+ /* Add to endpoint's Accept header */
+ pjsip_endpt_add_capability(mod_evsub.endpt, &mod_evsub.mod,
+ PJSIP_H_ACCEPT, NULL,
+ pkg->pkg_accept->count,
+ pkg->pkg_accept->values);
+
+
+ /* Done */
+
+ PJ_LOG(5,(THIS_FILE, "Event pkg \"%.*s\" registered by %.*s",
+ (int)event_name->slen, event_name->ptr,
+ (int)pkg_mod->name.slen, pkg_mod->name.ptr));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Retrieve Allow-Events header
+ */
+PJ_DEF(const pjsip_hdr*) pjsip_evsub_get_allow_events_hdr(pjsip_module *m)
+{
+ struct mod_evsub *mod;
+
+ if (m == NULL)
+ m = pjsip_evsub_instance();
+
+ mod = (struct mod_evsub*)m;
+
+ return (pjsip_hdr*) mod->allow_events_hdr;
+}
+
+
+/*
+ * Update expiration time.
+ */
+static void update_expires( pjsip_evsub *sub, pj_uint32_t interval )
+{
+ pj_gettimeofday(&sub->refresh_time);
+ sub->refresh_time.sec += interval;
+}
+
+
+/*
+ * Schedule timer.
+ */
+static void set_timer( pjsip_evsub *sub, int timer_id,
+ pj_int32_t seconds)
+{
+ if (sub->timer.id != TIMER_TYPE_NONE) {
+ PJ_LOG(5,(sub->obj_name, "%s %s timer",
+ (timer_id==sub->timer.id ? "Updating" : "Cancelling"),
+ timer_names[sub->timer.id]));
+ pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+ sub->timer.id = TIMER_TYPE_NONE;
+ }
+
+ if (timer_id != TIMER_TYPE_NONE) {
+ pj_time_val timeout;
+
+ PJ_ASSERT_ON_FAIL(seconds > 0, return);
+ PJ_ASSERT_ON_FAIL(timer_id>TIMER_TYPE_NONE && timer_id<TIMER_TYPE_MAX,
+ return);
+
+ timeout.sec = seconds;
+ timeout.msec = 0;
+ sub->timer.id = timer_id;
+
+ pjsip_endpt_schedule_timer(sub->endpt, &sub->timer, &timeout);
+
+ PJ_LOG(5,(sub->obj_name, "Timer %s scheduled in %d seconds",
+ timer_names[sub->timer.id], timeout.sec));
+ }
+}
+
+
+/*
+ * Destroy session.
+ */
+static void evsub_destroy( pjsip_evsub *sub )
+{
+ struct dlgsub *dlgsub_head, *dlgsub;
+
+ PJ_LOG(4,(sub->obj_name, "Subscription destroyed"));
+
+ /* Kill timer */
+ set_timer(sub, TIMER_TYPE_NONE, 0);
+
+ /* Remove this session from dialog's list of subscription */
+ dlgsub_head = (struct dlgsub *) sub->dlg->mod_data[mod_evsub.mod.id];
+ dlgsub = dlgsub_head->next;
+ while (dlgsub != dlgsub_head) {
+
+ if (dlgsub->sub == sub) {
+ pj_list_erase(dlgsub);
+ break;
+ }
+
+ dlgsub = dlgsub->next;
+ }
+
+ /* Decrement dialog's session */
+ pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod);
+}
+
+/*
+ * Set subscription session state.
+ */
+static void set_state( pjsip_evsub *sub, pjsip_evsub_state state,
+ const pj_str_t *state_str, pjsip_event *event,
+ const pj_str_t *reason)
+{
+ pjsip_evsub_state prev_state = sub->state;
+ pj_str_t old_state_str = sub->state_str;
+ pjsip_event dummy_event;
+
+ sub->state = state;
+
+ if (state_str && state_str->slen)
+ pj_strdup_with_null(sub->pool, &sub->state_str, state_str);
+ else
+ sub->state_str = evsub_state_names[state];
+
+ if (reason && sub->term_reason.slen==0)
+ pj_strdup(sub->pool, &sub->term_reason, reason);
+
+ PJ_LOG(4,(sub->obj_name,
+ "Subscription state changed %.*s --> %.*s",
+ (int)old_state_str.slen,
+ old_state_str.ptr,
+ (int)sub->state_str.slen,
+ sub->state_str.ptr));
+ pj_log_push_indent();
+
+ /* don't call the callback with NULL event, it may crash the app! */
+ if (!event) {
+ PJSIP_EVENT_INIT_USER(dummy_event, 0, 0, 0, 0);
+ event = &dummy_event;
+ }
+
+ if (sub->user.on_evsub_state && sub->call_cb)
+ (*sub->user.on_evsub_state)(sub, event);
+
+ if (state == PJSIP_EVSUB_STATE_TERMINATED &&
+ prev_state != PJSIP_EVSUB_STATE_TERMINATED)
+ {
+ if (sub->pending_tsx == 0) {
+ evsub_destroy(sub);
+ }
+ }
+
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Timer callback.
+ */
+static void on_timer( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pjsip_evsub *sub;
+ int timer_id;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ sub = (pjsip_evsub*) entry->user_data;
+
+ pjsip_dlg_inc_lock(sub->dlg);
+
+ timer_id = entry->id;
+ entry->id = TIMER_TYPE_NONE;
+
+ switch (timer_id) {
+
+ case TIMER_TYPE_UAC_REFRESH:
+ /* Time for UAC to refresh subscription */
+ if (sub->user.on_client_refresh && sub->call_cb) {
+ (*sub->user.on_client_refresh)(sub);
+ } else {
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_LOG(5,(sub->obj_name, "Refreshing subscription."));
+ pj_log_push_indent();
+ status = pjsip_evsub_initiate(sub, NULL,
+ sub->expires->ivalue,
+ &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_evsub_send_request(sub, tdata);
+
+ pj_log_pop_indent();
+ }
+ break;
+
+ case TIMER_TYPE_UAS_TIMEOUT:
+ /* Refresh from UAC has not been received */
+ if (sub->user.on_server_timeout && sub->call_cb) {
+ (*sub->user.on_server_timeout)(sub);
+ } else {
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_LOG(5,(sub->obj_name, "Timeout waiting for refresh. "
+ "Sending NOTIFY to terminate."));
+ pj_log_push_indent();
+ status = pjsip_evsub_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, &STR_TIMEOUT, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_evsub_send_request(sub, tdata);
+
+ pj_log_pop_indent();
+ }
+ break;
+
+ case TIMER_TYPE_UAC_TERMINATE:
+ {
+ pj_str_t timeout = {"timeout", 7};
+
+ PJ_LOG(5,(sub->obj_name, "Timeout waiting for final NOTIFY. "
+ "Terminating.."));
+ pj_log_push_indent();
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL,
+ &timeout);
+ pj_log_pop_indent();
+ }
+ break;
+
+ case TIMER_TYPE_UAC_WAIT_NOTIFY:
+ {
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_LOG(5,(sub->obj_name,
+ "Timeout waiting for subsequent NOTIFY (we did "
+ "send non-2xx response for previous NOTIFY). "
+ "Unsubscribing.."));
+ pj_log_push_indent();
+ status = pjsip_evsub_initiate( sub, NULL, 0, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_evsub_send_request(sub, tdata);
+
+ pj_log_pop_indent();
+ }
+ break;
+
+ default:
+ pj_assert(!"Invalid timer id");
+ }
+
+ pjsip_dlg_dec_lock(sub->dlg);
+}
+
+
+/*
+ * Create subscription session, used for both client and notifier.
+ */
+static pj_status_t evsub_create( pjsip_dialog *dlg,
+ pjsip_role_e role,
+ const pjsip_evsub_user *user_cb,
+ const pj_str_t *event,
+ unsigned option,
+ pjsip_evsub **p_evsub )
+{
+ pjsip_evsub *sub;
+ struct evpkg *pkg;
+ struct dlgsub *dlgsub_head, *dlgsub;
+ pj_status_t status;
+
+ /* Make sure there's package register for the event name: */
+
+ pkg = find_pkg(event);
+ if (pkg == NULL)
+ return PJSIP_SIMPLE_ENOPKG;
+
+
+ /* Must lock dialog before using pool etc. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Init attributes: */
+
+ sub = PJ_POOL_ZALLOC_T(dlg->pool, struct pjsip_evsub);
+ sub->pool = dlg->pool;
+ sub->endpt = dlg->endpt;
+ sub->dlg = dlg;
+ sub->pkg = pkg;
+ sub->role = role;
+ sub->call_cb = PJ_TRUE;
+ sub->option = option;
+ sub->state = PJSIP_EVSUB_STATE_NULL;
+ sub->state_str = evsub_state_names[sub->state];
+ sub->expires = pjsip_expires_hdr_create(sub->pool, pkg->pkg_expires);
+ sub->accept = (pjsip_accept_hdr*)
+ pjsip_hdr_clone(sub->pool, pkg->pkg_accept);
+ pj_list_init(&sub->sub_hdr_list);
+
+ sub->timer.user_data = sub;
+ sub->timer.cb = &on_timer;
+
+ /* Set name. */
+ pj_ansi_snprintf(sub->obj_name, PJ_ARRAY_SIZE(sub->obj_name),
+ "evsub%p", sub);
+
+
+ /* Copy callback, if any: */
+ if (user_cb)
+ pj_memcpy(&sub->user, user_cb, sizeof(pjsip_evsub_user));
+
+
+ /* Create Event header: */
+ sub->event = pjsip_event_hdr_create(sub->pool);
+ pj_strdup(sub->pool, &sub->event->event_type, event);
+
+
+ /* Check if another subscription has been registered to the dialog. In
+ * that case, just add ourselves to the subscription list, otherwise
+ * create and register a new subscription list.
+ */
+ if (pjsip_dlg_has_usage(dlg, &mod_evsub.mod)) {
+ dlgsub_head = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id];
+ dlgsub = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
+ dlgsub->sub = sub;
+ pj_list_push_back(dlgsub_head, dlgsub);
+ } else {
+ dlgsub_head = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
+ dlgsub = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
+ dlgsub->sub = sub;
+
+ pj_list_init(dlgsub_head);
+ pj_list_push_back(dlgsub_head, dlgsub);
+
+
+ /* Register as dialog usage: */
+
+ status = pjsip_dlg_add_usage(dlg, &mod_evsub.mod, dlgsub_head);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+ }
+ }
+
+ PJ_LOG(5,(sub->obj_name, "%s subscription created, using dialog %s",
+ (role==PJSIP_ROLE_UAC ? "UAC" : "UAS"),
+ dlg->obj_name));
+
+ *p_evsub = sub;
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Create client subscription session.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg,
+ const pjsip_evsub_user *user_cb,
+ const pj_str_t *event,
+ unsigned option,
+ pjsip_evsub **p_evsub)
+{
+ pjsip_evsub *sub;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(dlg && event && p_evsub, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+ status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, option, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Add unique Id to Event header, only when PJSIP_EVSUB_NO_EVENT_ID
+ * is not specified.
+ */
+ if ((option & PJSIP_EVSUB_NO_EVENT_ID) == 0) {
+ pj_create_unique_string(sub->pool, &sub->event->id_param);
+ }
+
+ /* Increment dlg session. */
+ pjsip_dlg_inc_session(sub->dlg, &mod_evsub.mod);
+
+ /* Done */
+ *p_evsub = sub;
+
+on_return:
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+}
+
+
+/*
+ * Create server subscription session from incoming request.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg,
+ const pjsip_evsub_user *user_cb,
+ pjsip_rx_data *rdata,
+ unsigned option,
+ pjsip_evsub **p_evsub)
+{
+ pjsip_evsub *sub;
+ pjsip_transaction *tsx;
+ pjsip_accept_hdr *accept_hdr;
+ pjsip_event_hdr *event_hdr;
+ pjsip_expires_hdr *expires_hdr;
+ pj_status_t status;
+
+ /* Check arguments: */
+ PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
+
+ /* MUST be request message: */
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Transaction MUST have been created (in the dialog) */
+ tsx = pjsip_rdata_get_tsx(rdata);
+ PJ_ASSERT_RETURN(tsx != NULL, PJSIP_ENOTSX);
+
+ /* No subscription must have been attached to transaction */
+ PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] == NULL,
+ PJSIP_ETYPEEXISTS);
+
+ /* Package MUST implement on_rx_refresh */
+ PJ_ASSERT_RETURN(user_cb->on_rx_refresh, PJ_EINVALIDOP);
+
+ /* Request MUST have "Event" header. We need the Event header to get
+ * the package name (don't want to add more arguments in the function).
+ */
+ event_hdr = (pjsip_event_hdr*)
+ pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &STR_EVENT,
+ &STR_EVENT_S, NULL);
+ if (event_hdr == NULL) {
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
+ }
+
+ /* Start locking the mutex: */
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Create the session: */
+
+ status = evsub_create(dlg, PJSIP_UAS_ROLE, user_cb,
+ &event_hdr->event_type, option, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Just duplicate Event header from the request */
+ sub->event = (pjsip_event_hdr*) pjsip_hdr_clone(sub->pool, event_hdr);
+
+ /* Set the method: */
+ pjsip_method_copy(sub->pool, &sub->method,
+ &rdata->msg_info.msg->line.req.method);
+
+ /* Update expiration time according to client request: */
+
+ expires_hdr = (pjsip_expires_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
+ if (expires_hdr) {
+ sub->expires->ivalue = expires_hdr->ivalue;
+ }
+
+ /* Update time. */
+ update_expires(sub, sub->expires->ivalue);
+
+ /* Update Accept header: */
+
+ accept_hdr = (pjsip_accept_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
+ if (accept_hdr)
+ sub->accept = (pjsip_accept_hdr*)pjsip_hdr_clone(sub->pool,accept_hdr);
+
+ /* We can start the session: */
+
+ pjsip_dlg_inc_session(dlg, &mod_evsub.mod);
+ sub->pending_tsx++;
+ tsx->mod_data[mod_evsub.mod.id] = sub;
+
+
+ /* Done. */
+ *p_evsub = sub;
+
+
+on_return:
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+}
+
+
+/*
+ * Forcefully destroy subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_terminate( pjsip_evsub *sub,
+ pj_bool_t notify )
+{
+ PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(sub->dlg);
+
+ /* I think it's pretty safe to disable this check.
+
+ if (sub->pending_tsx) {
+ pj_assert(!"Unable to terminate when there's pending tsx");
+ pjsip_dlg_dec_lock(sub->dlg);
+ return PJ_EINVALIDOP;
+ }
+ */
+
+ sub->call_cb = notify;
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, NULL);
+
+ pjsip_dlg_dec_lock(sub->dlg);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get subscription state.
+ */
+PJ_DEF(pjsip_evsub_state) pjsip_evsub_get_state(pjsip_evsub *sub)
+{
+ return sub->state;
+}
+
+/*
+ * Get state name.
+ */
+PJ_DEF(const char*) pjsip_evsub_get_state_name(pjsip_evsub *sub)
+{
+ return sub->state_str.ptr;
+}
+
+/*
+ * Get termination reason.
+ */
+PJ_DEF(const pj_str_t*) pjsip_evsub_get_termination_reason(pjsip_evsub *sub)
+{
+ return &sub->term_reason;
+}
+
+/*
+ * Initiate client subscription
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_initiate( pjsip_evsub *sub,
+ const pjsip_method *method,
+ pj_int32_t expires,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL);
+
+ /* Use SUBSCRIBE if method is not specified */
+ if (method == NULL)
+ method = &pjsip_subscribe_method;
+
+ pjsip_dlg_inc_lock(sub->dlg);
+
+ /* Update method: */
+ if (sub->state == PJSIP_EVSUB_STATE_NULL)
+ pjsip_method_copy(sub->pool, &sub->method, method);
+
+ status = pjsip_dlg_create_request( sub->dlg, method, -1, &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+
+ /* Add Event header: */
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, sub->event));
+
+ /* Update and add expires header: */
+ if (expires >= 0)
+ sub->expires->ivalue = expires;
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, sub->expires));
+
+ /* Add Supported header (it's optional in RFC 3265, but some event package
+ * RFC may bring this requirement to SHOULD strength - e.g. RFC 5373)
+ */
+ {
+ const pjsip_hdr *hdr = pjsip_endpt_get_capability(sub->endpt,
+ PJSIP_H_SUPPORTED,
+ NULL);
+ if (hdr) {
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr));
+ }
+ }
+
+ /* Add Accept header: */
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, sub->accept));
+
+
+ /* Add Allow-Events header: */
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool,
+ mod_evsub.allow_events_hdr));
+
+
+ /* Add custom headers */
+ {
+ const pjsip_hdr *hdr = sub->sub_hdr_list.next;
+ while (hdr != &sub->sub_hdr_list) {
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+ }
+
+
+ *p_tdata = tdata;
+
+
+on_return:
+
+ pjsip_dlg_dec_lock(sub->dlg);
+ return status;
+}
+
+
+/*
+ * Add custom headers.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_add_header( pjsip_evsub *sub,
+ const pjsip_hdr *hdr_list )
+{
+ const pjsip_hdr *hdr;
+
+ PJ_ASSERT_RETURN(sub && hdr_list, PJ_EINVAL);
+
+ hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pj_list_push_back(&sub->sub_hdr_list, (pjsip_hdr*)
+ pjsip_hdr_clone(sub->pool, hdr));
+ hdr = hdr->next;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Accept incoming subscription request.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_accept( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pjsip_hdr *hdr_list )
+{
+ pjsip_tx_data *tdata;
+ pjsip_transaction *tsx;
+ pj_status_t status;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(sub && rdata, PJ_EINVAL);
+
+ /* Can only be for server subscription: */
+ PJ_ASSERT_RETURN(sub->role == PJSIP_ROLE_UAS, PJ_EINVALIDOP);
+
+ /* Only expect 2xx status code (for now) */
+ PJ_ASSERT_RETURN(st_code/100 == 2, PJ_EINVALIDOP);
+
+ /* Subscription MUST have been attached to the transaction.
+ * Initial subscription request will be attached on evsub_create_uas(),
+ * while subsequent requests will be attached in tsx_state()
+ */
+ tsx = pjsip_rdata_get_tsx(rdata);
+ PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] != NULL,
+ PJ_EINVALIDOP);
+
+ /* Lock dialog */
+ pjsip_dlg_inc_lock(sub->dlg);
+
+ /* Create response: */
+ status = pjsip_dlg_create_response( sub->dlg, rdata, st_code, NULL,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+
+ /* Add expires header: */
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, sub->expires));
+
+ /* Add additional header, if any. */
+ if (hdr_list) {
+ const pjsip_hdr *hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+ }
+
+ /* Send the response: */
+ status = pjsip_dlg_send_response( sub->dlg, tsx, tdata );
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+
+on_return:
+
+ pjsip_dlg_dec_lock(sub->dlg);
+ return status;
+}
+
+
+/*
+ * Create Subscription-State header based on current server subscription
+ * state.
+ */
+static pjsip_sub_state_hdr* sub_state_create( pj_pool_t *pool,
+ pjsip_evsub *sub,
+ pjsip_evsub_state state,
+ const pj_str_t *state_str,
+ const pj_str_t *reason )
+{
+ pjsip_sub_state_hdr *sub_state;
+ pj_time_val now, delay;
+
+ /* Get the remaining time before refresh is required */
+ pj_gettimeofday(&now);
+ delay = sub->refresh_time;
+ PJ_TIME_VAL_SUB(delay, now);
+
+ /* Create the Subscription-State header */
+ sub_state = pjsip_sub_state_hdr_create(pool);
+
+ /* Fill up the header */
+ switch (state) {
+ case PJSIP_EVSUB_STATE_NULL:
+ case PJSIP_EVSUB_STATE_SENT:
+ pj_assert(!"Invalid state!");
+ /* Treat as pending */
+
+ case PJSIP_EVSUB_STATE_ACCEPTED:
+ case PJSIP_EVSUB_STATE_PENDING:
+ sub_state->sub_state = STR_PENDING;
+ sub_state->expires_param = delay.sec;
+ break;
+
+ case PJSIP_EVSUB_STATE_ACTIVE:
+ sub_state->sub_state = STR_ACTIVE;
+ sub_state->expires_param = delay.sec;
+ break;
+
+ case PJSIP_EVSUB_STATE_TERMINATED:
+ sub_state->sub_state = STR_TERMINATED;
+ if (reason != NULL)
+ pj_strdup(pool, &sub_state->reason_param, reason);
+ break;
+
+ case PJSIP_EVSUB_STATE_UNKNOWN:
+ pj_assert(state_str != NULL);
+ pj_strdup(pool, &sub_state->sub_state, state_str);
+ break;
+ }
+
+ return sub_state;
+}
+
+/*
+ * Create and send NOTIFY request.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_notify( pjsip_evsub *sub,
+ pjsip_evsub_state state,
+ const pj_str_t *state_str,
+ const pj_str_t *reason,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_sub_state_hdr *sub_state;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL);
+
+ /* Lock dialog. */
+ pjsip_dlg_inc_lock(sub->dlg);
+
+ /* Create NOTIFY request */
+ status = pjsip_dlg_create_request( sub->dlg, pjsip_get_notify_method(),
+ -1, &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Add Event header */
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, sub->event));
+
+ /* Add Subscription-State header */
+ sub_state = sub_state_create(tdata->pool, sub, state, state_str,
+ reason);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sub_state);
+
+ /* Add Allow-Events header */
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, mod_evsub.allow_events_hdr));
+
+ /* Add Authentication headers. */
+ pjsip_auth_clt_init_req( &sub->dlg->auth_sess, tdata );
+
+ /* Update reason */
+ if (reason)
+ pj_strdup(sub->dlg->pool, &sub->term_reason, reason);
+
+ /* Save destination state. */
+ sub->dst_state = state;
+ if (state_str)
+ pj_strdup(sub->pool, &sub->dst_state_str, state_str);
+ else
+ sub->dst_state_str.slen = 0;
+
+
+ *p_tdata = tdata;
+
+on_return:
+ /* Unlock dialog */
+ pjsip_dlg_dec_lock(sub->dlg);
+ return status;
+}
+
+
+/*
+ * Create NOTIFY to reflect current status.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_current_notify( pjsip_evsub *sub,
+ pjsip_tx_data **p_tdata )
+{
+ return pjsip_evsub_notify( sub, sub->state, &sub->state_str,
+ NULL, p_tdata );
+}
+
+
+/*
+ * Send request.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_send_request( pjsip_evsub *sub,
+ pjsip_tx_data *tdata)
+{
+ pj_status_t status;
+
+ /* Must be request message. */
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Lock */
+ pjsip_dlg_inc_lock(sub->dlg);
+
+ /* Send the request. */
+ status = pjsip_dlg_send_request(sub->dlg, tdata, -1, NULL);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+
+ /* Special case for NOTIFY:
+ * The new state was set in pjsip_evsub_notify(), but we apply the
+ * new state now, when the request was actually sent.
+ */
+ if (pjsip_method_cmp(&tdata->msg->line.req.method,
+ &pjsip_notify_method)==0)
+ {
+ PJ_ASSERT_ON_FAIL( sub->dst_state!=PJSIP_EVSUB_STATE_NULL,
+ {goto on_return;});
+
+ set_state(sub, sub->dst_state,
+ (sub->dst_state_str.slen ? &sub->dst_state_str : NULL),
+ NULL, NULL);
+
+ sub->dst_state = PJSIP_EVSUB_STATE_NULL;
+ sub->dst_state_str.slen = 0;
+
+ }
+
+
+on_return:
+ pjsip_dlg_dec_lock(sub->dlg);
+ return status;
+}
+
+
+/* Callback to be called to terminate transaction. */
+static void terminate_timer_cb(pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pj_str_t *key;
+ pjsip_transaction *tsx;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ key = (pj_str_t*)entry->user_data;
+ tsx = pjsip_tsx_layer_find_tsx(key, PJ_FALSE);
+ /* Chance of race condition here */
+ if (tsx) {
+ pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_UPDATED);
+ }
+}
+
+
+/*
+ * Attach subscription session to newly created transaction, if appropriate.
+ */
+static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ /*
+ * Newly created transaction will not have subscription session
+ * attached to it. Find the subscription session from the dialog,
+ * by matching the Event header.
+ */
+ pjsip_dialog *dlg;
+ pjsip_event_hdr *event_hdr;
+ pjsip_msg *msg;
+ struct dlgsub *dlgsub_head, *dlgsub;
+ pjsip_evsub *sub;
+
+ dlg = pjsip_tsx_get_dlg(tsx);
+ if (!dlg) {
+ pj_assert(!"Transaction should have a dialog instance!");
+ return NULL;
+ }
+
+
+ switch (event->body.tsx_state.type) {
+ case PJSIP_EVENT_RX_MSG:
+ msg = event->body.tsx_state.src.rdata->msg_info.msg;
+ break;
+ case PJSIP_EVENT_TX_MSG:
+ msg = event->body.tsx_state.src.tdata->msg;
+ break;
+ default:
+ if (tsx->role == PJSIP_ROLE_UAC)
+ msg = tsx->last_tx->msg;
+ else
+ msg = NULL;
+ break;
+ }
+
+ if (!msg) {
+ //Note:
+ // this transaction can be other transaction in the dialog.
+ // The assertion below probably only valid for dialog that
+ // only has one event subscription usage.
+ //pj_assert(!"First transaction event is not TX or RX!");
+ return NULL;
+ }
+
+ event_hdr = (pjsip_event_hdr*)
+ pjsip_msg_find_hdr_by_names(msg, &STR_EVENT,
+ &STR_EVENT_S, NULL);
+ if (!event_hdr) {
+ /* Not subscription related message */
+ return NULL;
+ }
+
+ /* Find the subscription in the dialog, based on the content
+ * of Event header:
+ */
+
+ dlgsub_head = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id];
+ if (dlgsub_head == NULL) {
+ dlgsub_head = PJ_POOL_ALLOC_T(dlg->pool, struct dlgsub);
+ pj_list_init(dlgsub_head);
+ dlg->mod_data[mod_evsub.mod.id] = dlgsub_head;
+ }
+ dlgsub = dlgsub_head->next;
+
+ while (dlgsub != dlgsub_head) {
+
+ if (pj_stricmp(&dlgsub->sub->event->event_type,
+ &event_hdr->event_type)==0)
+ {
+ /* Event type matched.
+ * Check if event ID matched too.
+ */
+ if (pj_strcmp(&dlgsub->sub->event->id_param,
+ &event_hdr->id_param)==0)
+ {
+
+ break;
+
+ }
+ /*
+ * Otherwise if it is an UAC subscription, AND
+ * PJSIP_EVSUB_NO_EVENT_ID flag is set, AND
+ * the session's event id is NULL, AND
+ * the incoming request is NOTIFY with event ID, then
+ * we consider it as a match, and update the
+ * session's event id.
+ */
+ else if (dlgsub->sub->role == PJSIP_ROLE_UAC &&
+ (dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID)!=0 &&
+ dlgsub->sub->event->id_param.slen==0 &&
+ !pjsip_method_cmp(&tsx->method, &pjsip_notify_method))
+ {
+ /* Update session's event id. */
+ pj_strdup(dlgsub->sub->pool,
+ &dlgsub->sub->event->id_param,
+ &event_hdr->id_param);
+
+ break;
+ }
+ }
+
+
+
+ dlgsub = dlgsub->next;
+ }
+
+ /* Note:
+ * the second condition is for http://trac.pjsip.org/repos/ticket/911
+ */
+ if (dlgsub == dlgsub_head ||
+ (dlgsub->sub &&
+ pjsip_evsub_get_state(dlgsub->sub)==PJSIP_EVSUB_STATE_TERMINATED))
+ {
+ const char *reason_msg =
+ (dlgsub == dlgsub_head ? "Subscription Does Not Exist" :
+ "Subscription already terminated");
+
+ /* This could be incoming request to create new subscription */
+ PJ_LOG(4,(THIS_FILE,
+ "%s for %.*s, event=%.*s;id=%.*s",
+ reason_msg,
+ (int)tsx->method.name.slen,
+ tsx->method.name.ptr,
+ (int)event_hdr->event_type.slen,
+ event_hdr->event_type.ptr,
+ (int)event_hdr->id_param.slen,
+ event_hdr->id_param.ptr));
+
+ /* If this is an incoming NOTIFY, reject with 481 */
+ if (tsx->state == PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0)
+ {
+ pj_str_t reason;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ pj_cstr(&reason, reason_msg);
+ status = pjsip_dlg_create_response(dlg,
+ event->body.tsx_state.src.rdata,
+ 481, &reason,
+ &tdata);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_dlg_send_response(dlg, tsx, tdata);
+ }
+ }
+ return NULL;
+ }
+
+ /* Found! */
+ sub = dlgsub->sub;
+
+ /* Attach session to the transaction */
+ tsx->mod_data[mod_evsub.mod.id] = sub;
+ sub->pending_tsx++;
+
+ /* Special case for outgoing/UAC SUBSCRIBE/REFER transaction.
+ * We can only have one pending UAC SUBSCRIBE/REFER, so if another
+ * transaction is started while previous one still alive, terminate
+ * the older one.
+ *
+ * Sample scenario:
+ * - subscribe sent to destination that doesn't exist, transaction
+ * is still retransmitting request, then unsubscribe is sent.
+ */
+ if (tsx->role == PJSIP_ROLE_UAC &&
+ tsx->state == PJSIP_TSX_STATE_CALLING &&
+ (pjsip_method_cmp(&tsx->method, &sub->method) == 0 ||
+ pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0))
+ {
+
+ if (sub->pending_sub &&
+ sub->pending_sub->state < PJSIP_TSX_STATE_COMPLETED)
+ {
+ pj_timer_entry *timer;
+ pj_str_t *key;
+ pj_time_val timeout = {0, 0};
+
+ PJ_LOG(4,(sub->obj_name,
+ "Cancelling pending subscription request"));
+
+ /* By convention, we use 490 (Request Updated) status code.
+ * When transaction handler (below) see this status code, it
+ * will ignore the transaction.
+ */
+ /* This unfortunately may cause deadlock, because at the moment
+ * we are holding dialog's mutex. If a response to this
+ * transaction is in progress in another thread, that thread
+ * will deadlock when trying to acquire dialog mutex, because
+ * it is holding the transaction mutex.
+ *
+ * So the solution is to register timer to kill this transaction.
+ */
+ //pjsip_tsx_terminate(sub->pending_sub, PJSIP_SC_REQUEST_UPDATED);
+ timer = PJ_POOL_ZALLOC_T(dlg->pool, pj_timer_entry);
+ key = PJ_POOL_ALLOC_T(dlg->pool, pj_str_t);
+ pj_strdup(dlg->pool, key, &sub->pending_sub->transaction_key);
+ timer->cb = &terminate_timer_cb;
+ timer->user_data = key;
+
+ pjsip_endpt_schedule_timer(dlg->endpt, timer, &timeout);
+ }
+
+ sub->pending_sub = tsx;
+
+ }
+
+ return sub;
+}
+
+
+/*
+ * Create response, adding custome headers and msg body.
+ */
+static pj_status_t create_response( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ const pjsip_hdr *res_hdr,
+ const pjsip_msg_body *body,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_hdr *hdr;
+ pj_status_t status;
+
+ status = pjsip_dlg_create_response(sub->dlg, rdata,
+ st_code, st_text, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ *p_tdata = tdata;
+
+ /* Add response headers. */
+ hdr = res_hdr->next;
+ while (hdr != res_hdr) {
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+
+ /* Add msg body, if any */
+ if (body) {
+ tdata->msg->body = pjsip_msg_body_clone(tdata->pool, body);
+ if (tdata->msg->body == NULL) {
+
+ PJ_LOG(4,(THIS_FILE, "Error: unable to clone msg body"));
+
+ /* Ignore */
+ return PJ_SUCCESS;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get subscription state from the value of Subscription-State header.
+ */
+static void get_hdr_state( pjsip_sub_state_hdr *sub_state,
+ pjsip_evsub_state *state,
+ pj_str_t **state_str )
+{
+ if (pj_stricmp(&sub_state->sub_state, &STR_TERMINATED)==0) {
+
+ *state = PJSIP_EVSUB_STATE_TERMINATED;
+ *state_str = NULL;
+
+ } else if (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0) {
+
+ *state = PJSIP_EVSUB_STATE_ACTIVE;
+ *state_str = NULL;
+
+ } else if (pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0) {
+
+ *state = PJSIP_EVSUB_STATE_PENDING;
+ *state_str = NULL;
+
+ } else {
+
+ *state = PJSIP_EVSUB_STATE_UNKNOWN;
+ *state_str = &sub_state->sub_state;
+
+ }
+}
+
+/*
+ * Transaction event processing by UAC, after subscription is sent.
+ */
+static void on_tsx_state_uac( pjsip_evsub *sub, pjsip_transaction *tsx,
+ pjsip_event *event )
+{
+
+ if (pjsip_method_cmp(&tsx->method, &sub->method)==0 ||
+ pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method)==0)
+ {
+
+ /* Received response to outgoing request that establishes/refresh
+ * subscription.
+ */
+
+ /* First time initial request is sent. */
+ if (sub->state == PJSIP_EVSUB_STATE_NULL &&
+ tsx->state == PJSIP_TSX_STATE_CALLING)
+ {
+ set_state(sub, PJSIP_EVSUB_STATE_SENT, NULL, event, NULL);
+ return;
+ }
+
+ /* Only interested in final response */
+ if (tsx->state != PJSIP_TSX_STATE_COMPLETED &&
+ tsx->state != PJSIP_TSX_STATE_TERMINATED)
+ {
+ return;
+ }
+
+ /* Clear pending subscription */
+ if (tsx == sub->pending_sub) {
+ sub->pending_sub = NULL;
+ } else if (sub->pending_sub != NULL) {
+ /* This SUBSCRIBE transaction has been "renewed" with another
+ * SUBSCRIBE, so we can just ignore this. For example, user
+ * sent SUBSCRIBE followed immediately with UN-SUBSCRIBE.
+ */
+ return;
+ }
+
+ /* Handle authentication. */
+ if (tsx->status_code==401 || tsx->status_code==407) {
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+ /* Previously failed transaction has terminated */
+ return;
+ }
+
+ status = pjsip_auth_clt_reinit_req(&sub->dlg->auth_sess,
+ event->body.tsx_state.src.rdata,
+ tsx->last_tx, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_dlg_send_request(sub->dlg, tdata, -1, NULL);
+
+ if (status != PJ_SUCCESS) {
+ /* Authentication failed! */
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, event, &tsx->status_text);
+ return;
+ }
+
+ return;
+ }
+
+ if (tsx->status_code/100 == 2) {
+
+ /* Successfull SUBSCRIBE request!
+ * This could be:
+ * - response to initial SUBSCRIBE request
+ * - response to subsequent refresh
+ * - response to unsubscription
+ */
+
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+ /* Ignore; this transaction has been processed before */
+ return;
+ }
+
+ /* Update UAC refresh time, if response contains Expires header,
+ * only when we're not unsubscribing.
+ */
+ if (sub->expires->ivalue != 0) {
+ pjsip_msg *msg;
+ pjsip_expires_hdr *expires;
+
+ msg = event->body.tsx_state.src.rdata->msg_info.msg;
+ expires = (pjsip_expires_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+ if (expires) {
+ sub->expires->ivalue = expires->ivalue;
+ }
+ }
+
+ /* Update time */
+ update_expires(sub, sub->expires->ivalue);
+
+ /* Start UAC refresh timer, only when we're not unsubscribing */
+ if (sub->expires->ivalue != 0) {
+ unsigned timeout = (sub->expires->ivalue > TIME_UAC_REFRESH) ?
+ sub->expires->ivalue - TIME_UAC_REFRESH : sub->expires->ivalue;
+
+ PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds",
+ timeout));
+ set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout);
+
+ } else {
+ /* Otherwise set timer to terminate client subscription when
+ * NOTIFY to end subscription is not received.
+ */
+ set_timer(sub, TIMER_TYPE_UAC_TERMINATE, TIME_UAC_TERMINATE);
+ }
+
+ /* Set state, if necessary */
+ pj_assert(sub->state != PJSIP_EVSUB_STATE_NULL);
+ if (sub->state == PJSIP_EVSUB_STATE_SENT) {
+ set_state(sub, PJSIP_EVSUB_STATE_ACCEPTED, NULL, event, NULL);
+ }
+
+ } else {
+
+ /* Failed SUBSCRIBE request!
+ *
+ * The RFC 3265 says that if outgoing SUBSCRIBE fails with status
+ * other than 481, the subscription is still considered valid for
+ * the duration of the last Expires.
+ *
+ * Since we send refresh about 5 seconds (TIME_UAC_REFRESH) before
+ * expiration, theoritically the expiration is still valid for the
+ * next 5 seconds even when we receive non-481 failed response.
+ *
+ * Ah, what the heck!
+ *
+ * Just terminate now!
+ *
+ */
+
+ if (sub->state == PJSIP_EVSUB_STATE_TERMINATED) {
+ /* Ignore, has been handled before */
+ return;
+ }
+
+ /* Ignore 490 (Request Updated) status.
+ * This happens when application sends SUBSCRIBE/REFER while
+ * another one is still in progress.
+ */
+ if (tsx->status_code == PJSIP_SC_REQUEST_UPDATED) {
+ return;
+ }
+
+ /* Kill any timer. */
+ set_timer(sub, TIMER_TYPE_NONE, 0);
+
+ /* Set state to TERMINATED */
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, event, &tsx->status_text);
+
+ }
+
+ } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) {
+
+ /* Incoming NOTIFY.
+ * This can be the result of:
+ * - Initial subscription response
+ * - UAS updating the resource info.
+ * - Unsubscription response.
+ */
+ int st_code = 200;
+ pj_str_t *st_text = NULL;
+ pjsip_hdr res_hdr;
+ pjsip_msg_body *body = NULL;
+
+ pjsip_rx_data *rdata;
+ pjsip_msg *msg;
+ pjsip_sub_state_hdr *sub_state;
+
+ pjsip_evsub_state new_state;
+ pj_str_t *new_state_str;
+
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Only want to handle initial NOTIFY receive event. */
+ if (tsx->state != PJSIP_TSX_STATE_TRYING)
+ return;
+
+
+ rdata = event->body.tsx_state.src.rdata;
+ msg = rdata->msg_info.msg;
+
+ pj_list_init(&res_hdr);
+
+ /* Get subscription state header. */
+ sub_state = (pjsip_sub_state_hdr*)
+ pjsip_msg_find_hdr_by_name(msg, &STR_SUB_STATE, NULL);
+ if (sub_state == NULL) {
+
+ pjsip_warning_hdr *warn_hdr;
+ pj_str_t warn_text = { "Missing Subscription-State header", 33};
+
+ /* Bad request! Add warning header. */
+ st_code = PJSIP_SC_BAD_REQUEST;
+ warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399,
+ pjsip_endpt_name(sub->endpt),
+ &warn_text);
+ pj_list_push_back(&res_hdr, warn_hdr);
+ }
+
+ /* Call application registered callback to handle incoming NOTIFY,
+ * if any.
+ */
+ if (st_code==200 && sub->user.on_rx_notify && sub->call_cb) {
+ (*sub->user.on_rx_notify)(sub, rdata, &st_code, &st_text,
+ &res_hdr, &body);
+
+ /* Application MUST specify final response! */
+ PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; });
+
+ /* Must be a valid status code */
+ PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; });
+ }
+
+
+ /* If non-2xx should be returned, then send the response.
+ * No need to update server subscription state.
+ */
+ if (st_code >= 300) {
+ status = create_response(sub, rdata, st_code, st_text, &res_hdr,
+ body, &tdata);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);
+ }
+
+ /* Start timer to terminate subscription, just in case server
+ * is not able to generate NOTIFY to our response.
+ */
+ if (status == PJ_SUCCESS) {
+ unsigned timeout = TIME_UAC_WAIT_NOTIFY;
+ set_timer(sub, TIMER_TYPE_UAC_WAIT_NOTIFY, timeout);
+ } else {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t reason;
+
+ reason = pj_strerror(status, errmsg, sizeof(errmsg));
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL,
+ &reason);
+ }
+
+ return;
+ }
+
+ /* Update expiration from the value of expires param in
+ * Subscription-State header, but ONLY when subscription state
+ * is "active" or "pending", AND the header contains expires param.
+ */
+ if (sub->expires->ivalue != 0 &&
+ sub_state->expires_param >= 0 &&
+ (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0 ||
+ pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0))
+ {
+ int next_refresh = sub_state->expires_param;
+ unsigned timeout;
+
+ update_expires(sub, next_refresh);
+
+ /* Start UAC refresh timer, only when we're not unsubscribing */
+ timeout = (next_refresh > TIME_UAC_REFRESH) ?
+ next_refresh - TIME_UAC_REFRESH : next_refresh;
+
+ PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", timeout));
+ set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout);
+ }
+
+ /* Find out the state */
+ get_hdr_state(sub_state, &new_state, &new_state_str);
+
+ /* Send response. */
+ status = create_response(sub, rdata, st_code, st_text, &res_hdr,
+ body, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);
+
+ /* Set the state */
+ if (status == PJ_SUCCESS) {
+ set_state(sub, new_state, new_state_str, event,
+ &sub_state->reason_param);
+ } else {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t reason;
+
+ reason = pj_strerror(status, errmsg, sizeof(errmsg));
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event,
+ &reason);
+ }
+
+
+ } else {
+
+ /*
+ * Unexpected method!
+ */
+ PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s",
+ (int)tsx->method.name.slen, tsx->method.name.ptr));
+ }
+}
+
+
+/*
+ * Transaction event processing by UAS, after subscription is accepted.
+ */
+static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+
+ if (pjsip_method_cmp(&tsx->method, &sub->method) == 0 ||
+ pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0)
+ {
+
+ /*
+ * Incoming request (e.g. SUBSCRIBE or REFER) to refresh subsciption.
+ *
+ */
+ pjsip_rx_data *rdata;
+ pjsip_event_hdr *event_hdr;
+ pjsip_expires_hdr *expires;
+ pjsip_msg *msg;
+ pjsip_tx_data *tdata;
+ int st_code = 200;
+ pj_str_t *st_text = NULL;
+ pjsip_hdr res_hdr;
+ pjsip_msg_body *body = NULL;
+ pjsip_evsub_state old_state;
+ pj_str_t old_state_str;
+ pj_str_t reason = { NULL, 0 };
+ pj_status_t status;
+
+
+ /* Only wants to handle the first event when the request is
+ * received.
+ */
+ if (tsx->state != PJSIP_TSX_STATE_TRYING)
+ return;
+
+ rdata = event->body.tsx_state.src.rdata;
+ msg = rdata->msg_info.msg;
+
+ /* Set expiration time based on client request (in Expires header),
+ * or package default expiration time.
+ */
+ event_hdr = (pjsip_event_hdr*)
+ pjsip_msg_find_hdr_by_names(msg, &STR_EVENT,
+ &STR_EVENT, NULL);
+ expires = (pjsip_expires_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+ if (event_hdr && expires) {
+ struct evpkg *evpkg;
+
+ evpkg = find_pkg(&event_hdr->event_type);
+ if (evpkg) {
+ if (expires->ivalue < (pj_int32_t)evpkg->pkg_expires)
+ sub->expires->ivalue = expires->ivalue;
+ else
+ sub->expires->ivalue = evpkg->pkg_expires;
+ }
+ }
+
+ /* Update time (before calling on_rx_refresh, since application
+ * will send NOTIFY.
+ */
+ update_expires(sub, sub->expires->ivalue);
+
+
+ /* Save old state.
+ * If application respond with non-2xx, revert to old state.
+ */
+ old_state = sub->state;
+ old_state_str = sub->state_str;
+
+ if (sub->expires->ivalue == 0) {
+ sub->state = PJSIP_EVSUB_STATE_TERMINATED;
+ sub->state_str = evsub_state_names[sub->state];
+ } else if (sub->state == PJSIP_EVSUB_STATE_NULL) {
+ sub->state = PJSIP_EVSUB_STATE_ACCEPTED;
+ sub->state_str = evsub_state_names[sub->state];
+ }
+
+ /* Call application's on_rx_refresh, just in case it wants to send
+ * response other than 200 (OK)
+ */
+ pj_list_init(&res_hdr);
+
+ if (sub->user.on_rx_refresh && sub->call_cb) {
+ (*sub->user.on_rx_refresh)(sub, rdata, &st_code, &st_text,
+ &res_hdr, &body);
+ }
+
+ /* Application MUST specify final response! */
+ PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; });
+
+ /* Must be a valid status code */
+ PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; });
+
+
+ /* Create and send response */
+ status = create_response(sub, rdata, st_code, st_text, &res_hdr,
+ body, &tdata);
+ if (status == PJ_SUCCESS) {
+ /* Add expires header: */
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool,
+ sub->expires));
+
+ /* Send */
+ status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);
+ }
+
+ /* Update state or revert state */
+ if (st_code/100==2) {
+
+ if (sub->expires->ivalue == 0) {
+ set_state(sub, sub->state, NULL, event, &reason);
+ } else if (sub->state == PJSIP_EVSUB_STATE_NULL) {
+ set_state(sub, sub->state, NULL, event, &reason);
+ }
+
+ /* Set UAS timeout timer, when state is not terminated. */
+ if (sub->state != PJSIP_EVSUB_STATE_TERMINATED) {
+ PJ_LOG(5,(sub->obj_name, "UAS timeout in %d seconds",
+ sub->expires->ivalue));
+ set_timer(sub, TIMER_TYPE_UAS_TIMEOUT,
+ sub->expires->ivalue);
+ }
+
+ } else {
+ sub->state = old_state;
+ sub->state_str = old_state_str;
+ }
+
+
+ } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0) {
+
+ /* Handle authentication */
+ if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+ (tsx->status_code==401 || tsx->status_code==407))
+ {
+ pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_auth_clt_reinit_req( &sub->dlg->auth_sess, rdata,
+ tsx->last_tx, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_dlg_send_request( sub->dlg, tdata, -1, NULL );
+
+ if (status != PJ_SUCCESS) {
+ /* Can't authenticate. Terminate session (?) */
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL,
+ &tsx->status_text);
+ return;
+ }
+
+ }
+ /*
+ * Terminate event usage if we receive 481, 408, and 7 class
+ * responses.
+ */
+ if (sub->state != PJSIP_EVSUB_STATE_TERMINATED &&
+ (tsx->status_code==481 || tsx->status_code==408 ||
+ tsx->status_code/100 == 7))
+ {
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event,
+ &tsx->status_text);
+ return;
+ }
+
+ } else {
+
+ /*
+ * Unexpected method!
+ */
+ PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s",
+ (int)tsx->method.name.slen, tsx->method.name.ptr));
+
+ }
+}
+
+
+/*
+ * Notification when transaction state has changed!
+ */
+static void mod_evsub_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
+{
+ pjsip_evsub *sub = pjsip_tsx_get_evsub(tsx);
+
+ if (sub == NULL) {
+ sub = on_new_transaction(tsx, event);
+ if (sub == NULL)
+ return;
+ }
+
+
+ /* Call on_tsx_state callback, if any. */
+ if (sub->user.on_tsx_state && sub->call_cb)
+ (*sub->user.on_tsx_state)(sub, tsx, event);
+
+
+ /* Process the event: */
+
+ if (sub->role == PJSIP_ROLE_UAC) {
+ on_tsx_state_uac(sub, tsx, event);
+ } else {
+ on_tsx_state_uas(sub, tsx, event);
+ }
+
+
+ /* Check transaction TERMINATE event */
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ --sub->pending_tsx;
+
+ if (sub->state == PJSIP_EVSUB_STATE_TERMINATED &&
+ sub->pending_tsx == 0)
+ {
+ evsub_destroy(sub);
+ }
+
+ }
+}
+
+
diff --git a/pjsip/src/pjsip-simple/evsub_msg.c b/pjsip/src/pjsip-simple/evsub_msg.c
new file mode 100644
index 0000000..d91afa9
--- /dev/null
+++ b/pjsip/src/pjsip-simple/evsub_msg.c
@@ -0,0 +1,304 @@
+/* $Id: evsub_msg.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip/print_util.h>
+#include <pjsip/sip_parser.h>
+#include <pjlib-util/string.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/except.h>
+
+/*
+ * Event header.
+ */
+static int pjsip_event_hdr_print( pjsip_event_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool,
+ const pjsip_event_hdr *hdr);
+static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_event_hdr*);
+
+static pjsip_hdr_vptr event_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_event_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_event_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_event_hdr_print,
+};
+
+
+PJ_DEF(pjsip_event_hdr*) pjsip_event_hdr_create(pj_pool_t *pool)
+{
+ pjsip_event_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_event_hdr);
+ hdr->type = PJSIP_H_OTHER;
+ hdr->name.ptr = "Event";
+ hdr->name.slen = 5;
+ hdr->sname.ptr = "o";
+ hdr->sname.slen = 1;
+ hdr->vptr = &event_hdr_vptr;
+ pj_list_init(hdr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+static int pjsip_event_hdr_print( pjsip_event_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf+size;
+ int printed;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ copy_advance(p, hdr->name);
+ *p++ = ':';
+ *p++ = ' ';
+
+ copy_advance(p, hdr->event_type);
+ copy_advance_pair(p, ";id=", 4, hdr->id_param);
+
+ printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return printed;
+
+ p += printed;
+ return p - buf;
+}
+
+static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool,
+ const pjsip_event_hdr *rhs)
+{
+ pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool);
+ pj_strdup(pool, &hdr->event_type, &rhs->event_type);
+ pj_strdup(pool, &hdr->id_param, &rhs->id_param);
+ pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_event_hdr*
+pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_event_hdr *rhs )
+{
+ pjsip_event_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_event_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+
+/*
+ * Allow-Events header.
+ */
+PJ_DEF(pjsip_allow_events_hdr*) pjsip_allow_events_hdr_create(pj_pool_t *pool)
+{
+ const pj_str_t STR_ALLOW_EVENTS = { "Allow-Events", 12};
+ pjsip_allow_events_hdr *hdr;
+
+ hdr = pjsip_generic_array_hdr_create(pool, &STR_ALLOW_EVENTS);
+
+ if (hdr) {
+ hdr->sname.ptr = "u";
+ hdr->sname.slen = 1;
+ }
+
+ return hdr;
+}
+
+
+/*
+ * Subscription-State header.
+ */
+static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr *hdr);
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr*);
+
+static pjsip_hdr_vptr sub_state_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_sub_state_hdr_print,
+};
+
+
+PJ_DEF(pjsip_sub_state_hdr*) pjsip_sub_state_hdr_create(pj_pool_t *pool)
+{
+ pj_str_t sub_state = { "Subscription-State", 18 };
+ pjsip_sub_state_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_sub_state_hdr);
+ hdr->type = PJSIP_H_OTHER;
+ hdr->name = hdr->sname = sub_state;
+ hdr->vptr = &sub_state_hdr_vptr;
+ hdr->expires_param = -1;
+ hdr->retry_after = -1;
+ pj_list_init(hdr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf+size;
+ int printed;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ copy_advance(p, hdr->name);
+ *p++ = ':';
+ *p++ = ' ';
+
+ copy_advance_escape(p, hdr->sub_state, pc->pjsip_TOKEN_SPEC);
+ copy_advance_pair_escape(p, ";reason=", 8, hdr->reason_param,
+ pc->pjsip_TOKEN_SPEC);
+ if (hdr->expires_param >= 0) {
+ pj_memcpy(p, ";expires=", 9);
+ p += 9;
+ printed = pj_utoa(hdr->expires_param, p);
+ p += printed;
+ }
+ if (hdr->retry_after >= 0) {
+ pj_memcpy(p, ";retry-after=", 13);
+ p += 9;
+ printed = pj_utoa(hdr->retry_after, p);
+ p += printed;
+ }
+
+ printed = pjsip_param_print_on( &hdr->other_param, p, endbuf-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC,
+ ';');
+ if (printed < 0)
+ return printed;
+
+ p += printed;
+
+ return p - buf;
+}
+
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr *rhs)
+{
+ pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool);
+ pj_strdup(pool, &hdr->sub_state, &rhs->sub_state);
+ pj_strdup(pool, &hdr->reason_param, &rhs->reason_param);
+ hdr->retry_after = rhs->retry_after;
+ hdr->expires_param = rhs->expires_param;
+ pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr *rhs)
+{
+ pjsip_sub_state_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_sub_state_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+
+/*
+ * Parse Event header.
+ */
+static pjsip_hdr *parse_hdr_event(pjsip_parse_ctx *ctx)
+{
+ pjsip_event_hdr *hdr = pjsip_event_hdr_create(ctx->pool);
+ const pj_str_t id_param = { "id", 2 };
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ pj_scan_get(ctx->scanner, &pc->pjsip_TOKEN_SPEC, &hdr->event_type);
+
+ while (*ctx->scanner->curptr == ';') {
+ pj_str_t pname, pvalue;
+
+ pj_scan_get_char(ctx->scanner);
+ pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
+
+ if (pj_stricmp(&pname, &id_param)==0) {
+ hdr->id_param = pvalue;
+ } else {
+ pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+ param->name = pname;
+ param->value = pvalue;
+ pj_list_push_back(&hdr->other_param, param);
+ }
+ }
+ pjsip_parse_end_hdr_imp( ctx->scanner );
+ return (pjsip_hdr*)hdr;
+}
+
+/*
+ * Parse Subscription-State header.
+ */
+static pjsip_hdr* parse_hdr_sub_state( pjsip_parse_ctx *ctx )
+{
+ pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(ctx->pool);
+ const pj_str_t reason = { "reason", 6 },
+ expires = { "expires", 7 },
+ retry_after = { "retry-after", 11 };
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ pj_scan_get(ctx->scanner, &pc->pjsip_TOKEN_SPEC, &hdr->sub_state);
+
+ while (*ctx->scanner->curptr == ';') {
+ pj_str_t pname, pvalue;
+
+ pj_scan_get_char(ctx->scanner);
+ pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
+
+ if (pj_stricmp(&pname, &reason) == 0) {
+ hdr->reason_param = pvalue;
+
+ } else if (pj_stricmp(&pname, &expires) == 0) {
+ hdr->expires_param = pj_strtoul(&pvalue);
+
+ } else if (pj_stricmp(&pname, &retry_after) == 0) {
+ hdr->retry_after = pj_strtoul(&pvalue);
+
+ } else {
+ pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+ param->name = pname;
+ param->value = pvalue;
+ pj_list_push_back(&hdr->other_param, param);
+ }
+ }
+
+ pjsip_parse_end_hdr_imp( ctx->scanner );
+ return (pjsip_hdr*)hdr;
+}
+
+/*
+ * Register header parsers.
+ */
+PJ_DEF(void) pjsip_evsub_init_parser(void)
+{
+ pjsip_register_hdr_parser( "Event", "o",
+ &parse_hdr_event);
+
+ pjsip_register_hdr_parser( "Subscription-State", NULL,
+ &parse_hdr_sub_state);
+}
+
diff --git a/pjsip/src/pjsip-simple/iscomposing.c b/pjsip/src/pjsip-simple/iscomposing.c
new file mode 100644
index 0000000..7053539
--- /dev/null
+++ b/pjsip/src/pjsip-simple/iscomposing.c
@@ -0,0 +1,218 @@
+/* $Id: iscomposing.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/iscomposing.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip/sip_msg.h>
+#include <pjlib-util/errno.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+/* MIME */
+static const pj_str_t STR_MIME_TYPE = { "application", 11 };
+static const pj_str_t STR_MIME_SUBTYPE = { "im-iscomposing+xml", 18 };
+
+
+/* XML node constants. */
+static const pj_str_t STR_ISCOMPOSING = { "isComposing", 11 };
+static const pj_str_t STR_STATE = { "state", 5 };
+static const pj_str_t STR_ACTIVE = { "active", 6 };
+static const pj_str_t STR_IDLE = { "idle", 4 };
+static const pj_str_t STR_LASTACTIVE = { "lastactive", 10 };
+static const pj_str_t STR_CONTENTTYPE = { "contenttype", 11 };
+static const pj_str_t STR_REFRESH = { "refresh", 7 };
+
+
+/* XML attributes constants */
+static const pj_str_t STR_XMLNS_NAME = { "xmlns", 5 };
+static const pj_str_t STR_XMLNS_VAL = { "urn:ietf:params:xml:ns:im-iscomposing", 37 };
+static const pj_str_t STR_XMLNS_XSI_NAME = { "xmlns:xsi", 9 };
+static const pj_str_t STR_XMLNS_XSI_VAL = { "http://www.w3.org/2001/XMLSchema-instance", 41 };
+static const pj_str_t STR_XSI_SLOC_NAME = { "xsi:schemaLocation", 18 };
+static const pj_str_t STR_XSI_SLOC_VAL = { "urn:ietf:params:xml:ns:im-composing iscomposing.xsd", 51 };
+
+
+PJ_DEF(pj_xml_node*) pjsip_iscomposing_create_xml( pj_pool_t *pool,
+ pj_bool_t is_composing,
+ const pj_time_val *lst_actv,
+ const pj_str_t *content_tp,
+ int refresh)
+{
+ pj_xml_node *doc, *node;
+ pj_xml_attr *attr;
+
+ /* Root document. */
+ doc = pj_xml_node_new(pool, &STR_ISCOMPOSING);
+
+ /* Add attributes */
+ attr = pj_xml_attr_new(pool, &STR_XMLNS_NAME, &STR_XMLNS_VAL);
+ pj_xml_add_attr(doc, attr);
+
+ attr = pj_xml_attr_new(pool, &STR_XMLNS_XSI_NAME, &STR_XMLNS_XSI_VAL);
+ pj_xml_add_attr(doc, attr);
+
+ attr = pj_xml_attr_new(pool, &STR_XSI_SLOC_NAME, &STR_XSI_SLOC_VAL);
+ pj_xml_add_attr(doc, attr);
+
+
+ /* Add state. */
+ node = pj_xml_node_new(pool, &STR_STATE);
+ if (is_composing)
+ node->content = STR_ACTIVE;
+ else
+ node->content = STR_IDLE;
+ pj_xml_add_node(doc, node);
+
+ /* Add lastactive, if any. */
+ PJ_UNUSED_ARG(lst_actv);
+ //if (!is_composing && lst_actv) {
+ // PJ_TODO(IMPLEMENT_LAST_ACTIVE_ATTRIBUTE);
+ //}
+
+ /* Add contenttype, if any. */
+ if (content_tp) {
+ node = pj_xml_node_new(pool, &STR_CONTENTTYPE);
+ pj_strdup(pool, &node->content, content_tp);
+ pj_xml_add_node(doc, node);
+ }
+
+ /* Add refresh, if any. */
+ if (is_composing && refresh > 1 && refresh < 3601) {
+ node = pj_xml_node_new(pool, &STR_REFRESH);
+ node->content.ptr = (char*) pj_pool_alloc(pool, 10);
+ node->content.slen = pj_utoa(refresh, node->content.ptr);
+ pj_xml_add_node(doc, node);
+ }
+
+ /* Done! */
+
+ return doc;
+}
+
+
+
+/*
+ * Function to print XML message body.
+ */
+static int xml_print_body( struct pjsip_msg_body *msg_body,
+ char *buf, pj_size_t size)
+{
+ return pj_xml_print((const pj_xml_node*)msg_body->data, buf, size,
+ PJ_TRUE);
+}
+
+
+/*
+ * Function to clone XML document.
+ */
+static void* xml_clone_data(pj_pool_t *pool, const void *data, unsigned len)
+{
+ PJ_UNUSED_ARG(len);
+ return pj_xml_clone( pool, (const pj_xml_node*)data);
+}
+
+
+
+PJ_DEF(pjsip_msg_body*) pjsip_iscomposing_create_body( pj_pool_t *pool,
+ pj_bool_t is_composing,
+ const pj_time_val *lst_actv,
+ const pj_str_t *content_tp,
+ int refresh)
+{
+ pj_xml_node *doc;
+ pjsip_msg_body *body;
+
+ doc = pjsip_iscomposing_create_xml( pool, is_composing, lst_actv,
+ content_tp, refresh);
+ if (doc == NULL)
+ return NULL;
+
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ body->content_type.type = STR_MIME_TYPE;
+ body->content_type.subtype = STR_MIME_SUBTYPE;
+
+ body->data = doc;
+ body->len = 0;
+
+ body->print_body = &xml_print_body;
+ body->clone_data = &xml_clone_data;
+
+ return body;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_iscomposing_parse( pj_pool_t *pool,
+ char *msg,
+ pj_size_t len,
+ pj_bool_t *p_is_composing,
+ pj_str_t **p_last_active,
+ pj_str_t **p_content_type,
+ int *p_refresh )
+{
+ pj_xml_node *doc, *node;
+
+ /* Set defaults: */
+ if (p_is_composing) *p_is_composing = PJ_FALSE;
+ if (p_last_active) *p_last_active = NULL;
+ if (p_content_type) *p_content_type = NULL;
+
+ /* Parse XML */
+ doc = pj_xml_parse( pool, msg, len);
+ if (!doc)
+ return PJLIB_UTIL_EINXML;
+
+ /* Root document must be "isComposing" */
+ if (pj_stricmp(&doc->name, &STR_ISCOMPOSING) != 0)
+ return PJSIP_SIMPLE_EBADISCOMPOSE;
+
+ /* Get the status. */
+ if (p_is_composing) {
+ node = pj_xml_find_node(doc, &STR_STATE);
+ if (node == NULL)
+ return PJSIP_SIMPLE_EBADISCOMPOSE;
+ *p_is_composing = (pj_stricmp(&node->content, &STR_ACTIVE)==0);
+ }
+
+ /* Get last active. */
+ if (p_last_active) {
+ node = pj_xml_find_node(doc, &STR_LASTACTIVE);
+ if (node)
+ *p_last_active = &node->content;
+ }
+
+ /* Get content type */
+ if (p_content_type) {
+ node = pj_xml_find_node(doc, &STR_CONTENTTYPE);
+ if (node)
+ *p_content_type = &node->content;
+ }
+
+ /* Get refresh */
+ if (p_refresh) {
+ node = pj_xml_find_node(doc, &STR_REFRESH);
+ if (node)
+ *p_refresh = pj_strtoul(&node->content);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjsip/src/pjsip-simple/mwi.c b/pjsip/src/pjsip-simple/mwi.c
new file mode 100644
index 0000000..da86131
--- /dev/null
+++ b/pjsip/src/pjsip-simple/mwi.c
@@ -0,0 +1,598 @@
+/* $Id: mwi.c 4172 2012-06-19 14:35:18Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/mwi.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_dialog.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "mwi.c"
+
+ /*
+ * MWI module (mod-mdi)
+ */
+static struct pjsip_module mod_mwi =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-mwi", 7 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_DIALOG_USAGE,/* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ NULL, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+/*
+ * This structure describe an mwi agent (both client and server)
+ */
+typedef struct pjsip_mwi
+{
+ pjsip_evsub *sub; /**< Event subscribtion record. */
+ pjsip_dialog *dlg; /**< The dialog. */
+ pjsip_evsub_user user_cb; /**< The user callback. */
+
+ /* These are for server subscriptions */
+ pj_pool_t *body_pool; /**< Pool to save message body */
+ pjsip_media_type mime_type; /**< MIME type of last msg body */
+ pj_str_t body; /**< Last sent message body */
+} pjsip_mwi;
+
+
+/*
+ * Forward decl for evsub callbacks.
+ */
+static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
+static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+ pjsip_event *event);
+static void mwi_on_evsub_rx_refresh( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body);
+static void mwi_on_evsub_rx_notify( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body);
+static void mwi_on_evsub_client_refresh(pjsip_evsub *sub);
+static void mwi_on_evsub_server_timeout(pjsip_evsub *sub);
+
+
+/*
+ * Event subscription callback for mwi.
+ */
+static pjsip_evsub_user mwi_user =
+{
+ &mwi_on_evsub_state,
+ &mwi_on_evsub_tsx_state,
+ &mwi_on_evsub_rx_refresh,
+ &mwi_on_evsub_rx_notify,
+ &mwi_on_evsub_client_refresh,
+ &mwi_on_evsub_server_timeout,
+};
+
+
+/*
+ * Some static constants.
+ */
+static const pj_str_t STR_EVENT = { "Event", 5 };
+static const pj_str_t STR_MWI = { "message-summary", 15 };
+static const pj_str_t STR_APP_SIMPLE_SMS = { "application/simple-message-summary", 34};
+
+/*
+ * Init mwi module.
+ */
+PJ_DEF(pj_status_t) pjsip_mwi_init_module( pjsip_endpoint *endpt,
+ pjsip_module *mod_evsub)
+{
+ pj_status_t status;
+ pj_str_t accept[1];
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL);
+
+ /* Must have not been registered */
+ PJ_ASSERT_RETURN(mod_mwi.id == -1, PJ_EINVALIDOP);
+
+ /* Register to endpoint */
+ status = pjsip_endpt_register_module(endpt, &mod_mwi);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ accept[0] = STR_APP_SIMPLE_SMS;
+
+ /* Register event package to event module. */
+ status = pjsip_evsub_register_pkg( &mod_mwi, &STR_MWI,
+ PJSIP_MWI_DEFAULT_EXPIRES,
+ PJ_ARRAY_SIZE(accept), accept);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_unregister_module(endpt, &mod_mwi);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get mwi module instance.
+ */
+PJ_DEF(pjsip_module*) pjsip_mwi_instance(void)
+{
+ return &mod_mwi;
+}
+
+
+/*
+ * Create client subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_mwi_create_uac( pjsip_dialog *dlg,
+ const pjsip_evsub_user *user_cb,
+ unsigned options,
+ pjsip_evsub **p_evsub )
+{
+ pj_status_t status;
+ pjsip_mwi *mwi;
+ pjsip_evsub *sub;
+
+ PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
+
+ PJ_UNUSED_ARG(options);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Create event subscription */
+ status = pjsip_evsub_create_uac( dlg, &mwi_user, &STR_MWI,
+ options, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create mwi */
+ mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi);
+ mwi->dlg = dlg;
+ mwi->sub = sub;
+ if (user_cb)
+ pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user));
+
+ /* Attach to evsub */
+ pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi);
+
+ *p_evsub = sub;
+
+on_return:
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+}
+
+
+/*
+ * Create server subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_mwi_create_uas( pjsip_dialog *dlg,
+ const pjsip_evsub_user *user_cb,
+ pjsip_rx_data *rdata,
+ pjsip_evsub **p_evsub )
+{
+ pjsip_accept_hdr *accept;
+ pjsip_event_hdr *event;
+ pjsip_evsub *sub;
+ pjsip_mwi *mwi;
+ char obj_name[PJ_MAX_OBJ_NAME];
+ pj_status_t status;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
+
+ /* Must be request message */
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Check that request is SUBSCRIBE */
+ PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
+ &pjsip_subscribe_method)==0,
+ PJSIP_SIMPLE_ENOTSUBSCRIBE);
+
+ /* Check that Event header contains "mwi" */
+ event = (pjsip_event_hdr*)
+ pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL);
+ if (!event) {
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
+ }
+ if (pj_stricmp(&event->event_type, &STR_MWI) != 0) {
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT);
+ }
+
+ /* Check that request contains compatible Accept header. */
+ accept = (pjsip_accept_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
+ if (accept) {
+ unsigned i;
+ for (i=0; i<accept->count; ++i) {
+ if (pj_stricmp(&accept->values[i], &STR_APP_SIMPLE_SMS)==0) {
+ break;
+ }
+ }
+
+ if (i==accept->count) {
+ /* Nothing is acceptable */
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
+ }
+
+ } else {
+ /* No Accept header.
+ * Assume client supports "application/simple-message-summary"
+ */
+ }
+
+ /* Lock dialog */
+ pjsip_dlg_inc_lock(dlg);
+
+
+ /* Create server subscription */
+ status = pjsip_evsub_create_uas( dlg, &mwi_user, rdata, 0, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create server mwi subscription */
+ mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi);
+ mwi->dlg = dlg;
+ mwi->sub = sub;
+ if (user_cb)
+ pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user));
+
+ pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "mwibd%p", dlg->pool);
+ mwi->body_pool = pj_pool_create(dlg->pool->factory, obj_name,
+ 512, 512, NULL);
+
+ /* Attach to evsub */
+ pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi);
+
+ /* Done: */
+ *p_evsub = sub;
+
+on_return:
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+}
+
+
+/*
+ * Forcefully terminate mwi.
+ */
+PJ_DEF(pj_status_t) pjsip_mwi_terminate( pjsip_evsub *sub,
+ pj_bool_t notify )
+{
+ return pjsip_evsub_terminate(sub, notify);
+}
+
+/*
+ * Create SUBSCRIBE
+ */
+PJ_DEF(pj_status_t) pjsip_mwi_initiate( pjsip_evsub *sub,
+ pj_int32_t expires,
+ pjsip_tx_data **p_tdata)
+{
+ return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires,
+ p_tdata);
+}
+
+
+/*
+ * Accept incoming subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_mwi_accept( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pjsip_hdr *hdr_list )
+{
+ return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
+}
+
+/*
+ * Create message body and attach it to the (NOTIFY) request.
+ */
+static pj_status_t mwi_create_msg_body( pjsip_mwi *mwi,
+ pjsip_tx_data *tdata)
+{
+ pjsip_msg_body *body;
+ pj_str_t dup_text;
+
+ PJ_ASSERT_RETURN(mwi->mime_type.type.slen && mwi->body.slen, PJ_EINVALIDOP);
+
+ /* Clone the message body and mime type */
+ pj_strdup(tdata->pool, &dup_text, &mwi->body);
+
+ /* Create the message body */
+ body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body);
+ pjsip_media_type_cp(tdata->pool, &body->content_type, &mwi->mime_type);
+ body->data = dup_text.ptr;
+ body->len = (unsigned)dup_text.slen;
+ body->print_body = &pjsip_print_text_body;
+ body->clone_data = &pjsip_clone_text_data;
+
+ /* Attach to tdata */
+ tdata->msg->body = body;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create NOTIFY
+ */
+PJ_DEF(pj_status_t) pjsip_mwi_notify( pjsip_evsub *sub,
+ pjsip_evsub_state state,
+ const pj_str_t *state_str,
+ const pj_str_t *reason,
+ const pjsip_media_type *mime_type,
+ const pj_str_t *body,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_mwi *mwi;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(sub && mime_type && body && p_tdata, PJ_EINVAL);
+
+ /* Get the mwi object. */
+ mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
+ PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP);
+
+ /* Lock object. */
+ pjsip_dlg_inc_lock(mwi->dlg);
+
+ /* Create the NOTIFY request. */
+ status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Update the cached message body */
+ if (mime_type || body)
+ pj_pool_reset(mwi->body_pool);
+ if (mime_type)
+ pjsip_media_type_cp(mwi->body_pool, &mwi->mime_type, mime_type);
+ if (body)
+ pj_strdup(mwi->body_pool, &mwi->body, body);
+
+ /* Create message body */
+ status = mwi_create_msg_body( mwi, tdata );
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Done. */
+ *p_tdata = tdata;
+
+on_return:
+ pjsip_dlg_dec_lock(mwi->dlg);
+ return status;
+}
+
+
+/*
+ * Create NOTIFY that reflect current state.
+ */
+PJ_DEF(pj_status_t) pjsip_mwi_current_notify( pjsip_evsub *sub,
+ pjsip_tx_data **p_tdata )
+{
+ pjsip_mwi *mwi;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL);
+
+ /* Get the mwi object. */
+ mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
+ PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP);
+
+ /* Lock object. */
+ pjsip_dlg_inc_lock(mwi->dlg);
+
+ /* Create the NOTIFY request. */
+ status = pjsip_evsub_current_notify( sub, &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+
+ /* Create message body to reflect the mwi status. */
+ status = mwi_create_msg_body( mwi, tdata );
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Done. */
+ *p_tdata = tdata;
+
+on_return:
+ pjsip_dlg_dec_lock(mwi->dlg);
+ return status;
+}
+
+
+/*
+ * Send request.
+ */
+PJ_DEF(pj_status_t) pjsip_mwi_send_request( pjsip_evsub *sub,
+ pjsip_tx_data *tdata )
+{
+ return pjsip_evsub_send_request(sub, tdata);
+}
+
+/*
+ * This callback is called by event subscription when subscription
+ * state has changed.
+ */
+static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
+{
+ pjsip_mwi *mwi;
+
+ mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
+ PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
+
+ if (mwi->user_cb.on_evsub_state)
+ (*mwi->user_cb.on_evsub_state)(sub, event);
+
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ if (mwi->body_pool) {
+ pj_pool_release(mwi->body_pool);
+ mwi->body_pool = NULL;
+ }
+ }
+}
+
+/*
+ * Called when transaction state has changed.
+ */
+static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pjsip_mwi *mwi;
+
+ mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
+ PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
+
+ if (mwi->user_cb.on_tsx_state)
+ (*mwi->user_cb.on_tsx_state)(sub, tsx, event);
+}
+
+
+/*
+ * Called when SUBSCRIBE is received.
+ */
+static void mwi_on_evsub_rx_refresh( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsip_mwi *mwi;
+
+ mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
+ PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
+
+ if (mwi->user_cb.on_rx_refresh) {
+ (*mwi->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
+ res_hdr, p_body);
+
+ } else {
+ /* Implementors MUST send NOTIFY if it implements on_rx_refresh */
+ pjsip_tx_data *tdata;
+ pj_str_t timeout = { "timeout", 7};
+ pj_status_t status;
+
+ if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
+ status = pjsip_mwi_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, &timeout, NULL, NULL, &tdata);
+ } else {
+ status = pjsip_mwi_current_notify(sub, &tdata);
+ }
+
+ if (status == PJ_SUCCESS)
+ pjsip_mwi_send_request(sub, tdata);
+ }
+}
+
+
+/*
+ * Called when NOTIFY is received.
+ */
+static void mwi_on_evsub_rx_notify( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsip_mwi *mwi;
+
+ mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
+ PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
+
+ /* Just notify application. */
+ if (mwi->user_cb.on_rx_notify) {
+ (*mwi->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text,
+ res_hdr, p_body);
+ }
+}
+
+/*
+ * Called when it's time to send SUBSCRIBE.
+ */
+static void mwi_on_evsub_client_refresh(pjsip_evsub *sub)
+{
+ pjsip_mwi *mwi;
+
+ mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
+ PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
+
+ if (mwi->user_cb.on_client_refresh) {
+ (*mwi->user_cb.on_client_refresh)(sub);
+ } else {
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ status = pjsip_mwi_initiate(sub, -1, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_mwi_send_request(sub, tdata);
+ }
+}
+
+/*
+ * Called when no refresh is received after the interval.
+ */
+static void mwi_on_evsub_server_timeout(pjsip_evsub *sub)
+{
+ pjsip_mwi *mwi;
+
+ mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
+ PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
+
+ if (mwi->user_cb.on_server_timeout) {
+ (*mwi->user_cb.on_server_timeout)(sub);
+ } else {
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+ pj_str_t reason = { "timeout", 7 };
+
+ status = pjsip_mwi_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, &reason, NULL, NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_mwi_send_request(sub, tdata);
+ }
+}
+
diff --git a/pjsip/src/pjsip-simple/pidf.c b/pjsip/src/pjsip-simple/pidf.c
new file mode 100644
index 0000000..0b68d1a
--- /dev/null
+++ b/pjsip/src/pjsip-simple/pidf.c
@@ -0,0 +1,365 @@
+/* $Id: pidf.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/pidf.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+
+
+struct pjpidf_op_desc pjpidf_op =
+{
+ {
+ &pjpidf_pres_construct,
+ &pjpidf_pres_add_tuple,
+ &pjpidf_pres_get_first_tuple,
+ &pjpidf_pres_get_next_tuple,
+ &pjpidf_pres_find_tuple,
+ &pjpidf_pres_remove_tuple,
+ &pjpidf_pres_add_note,
+ &pjpidf_pres_get_first_note,
+ &pjpidf_pres_get_next_note
+ },
+ {
+ &pjpidf_tuple_construct,
+ &pjpidf_tuple_get_id,
+ &pjpidf_tuple_set_id,
+ &pjpidf_tuple_get_status,
+ &pjpidf_tuple_get_contact,
+ &pjpidf_tuple_set_contact,
+ &pjpidf_tuple_set_contact_prio,
+ &pjpidf_tuple_get_contact_prio,
+ &pjpidf_tuple_add_note,
+ &pjpidf_tuple_get_first_note,
+ &pjpidf_tuple_get_next_note,
+ &pjpidf_tuple_get_timestamp,
+ &pjpidf_tuple_set_timestamp,
+ &pjpidf_tuple_set_timestamp_np
+ },
+ {
+ &pjpidf_status_construct,
+ &pjpidf_status_is_basic_open,
+ &pjpidf_status_set_basic_open
+ }
+};
+
+static pj_str_t PRESENCE = { "presence", 8 };
+static pj_str_t ENTITY = { "entity", 6};
+static pj_str_t TUPLE = { "tuple", 5 };
+static pj_str_t ID = { "id", 2 };
+static pj_str_t NOTE = { "note", 4 };
+static pj_str_t STATUS = { "status", 6 };
+static pj_str_t CONTACT = { "contact", 7 };
+static pj_str_t PRIORITY = { "priority", 8 };
+static pj_str_t TIMESTAMP = { "timestamp", 9 };
+static pj_str_t BASIC = { "basic", 5 };
+static pj_str_t OPEN = { "open", 4 };
+static pj_str_t CLOSED = { "closed", 6 };
+static pj_str_t EMPTY_STRING = { NULL, 0 };
+
+static pj_str_t XMLNS = { "xmlns", 5 };
+static pj_str_t PIDF_XMLNS = { "urn:ietf:params:xml:ns:pidf", 27 };
+
+static void xml_init_node(pj_pool_t *pool, pj_xml_node *node,
+ pj_str_t *name, const pj_str_t *value)
+{
+ pj_list_init(&node->attr_head);
+ pj_list_init(&node->node_head);
+ node->name = *name;
+ if (value) pj_strdup(pool, &node->content, value);
+ else node->content.ptr=NULL, node->content.slen=0;
+}
+
+static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name,
+ const pj_str_t *value)
+{
+ pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
+ attr->name = *name;
+ pj_strdup(pool, &attr->value, value);
+ return attr;
+}
+
+/* Presence */
+PJ_DEF(void) pjpidf_pres_construct(pj_pool_t *pool, pjpidf_pres *pres,
+ const pj_str_t *entity)
+{
+ pj_xml_attr *attr;
+
+ xml_init_node(pool, pres, &PRESENCE, NULL);
+ attr = xml_create_attr(pool, &ENTITY, entity);
+ pj_xml_add_attr(pres, attr);
+ attr = xml_create_attr(pool, &XMLNS, &PIDF_XMLNS);
+ pj_xml_add_attr(pres, attr);
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_add_tuple(pj_pool_t *pool, pjpidf_pres *pres,
+ const pj_str_t *id)
+{
+ pjpidf_tuple *t = PJ_POOL_ALLOC_T(pool, pjpidf_tuple);
+ pjpidf_tuple_construct(pool, t, id);
+ pj_xml_add_node(pres, t);
+ return t;
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_get_first_tuple(pjpidf_pres *pres)
+{
+ return pj_xml_find_node(pres, &TUPLE);
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_get_next_tuple(pjpidf_pres *pres,
+ pjpidf_tuple *tuple)
+{
+ return pj_xml_find_next_node(pres, tuple, &TUPLE);
+}
+
+static pj_bool_t find_tuple_by_id(const pj_xml_node *node, const void *id)
+{
+ return pj_xml_find_attr(node, &ID, (const pj_str_t*)id) != NULL;
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_find_tuple(pjpidf_pres *pres, const pj_str_t *id)
+{
+ return pj_xml_find(pres, &TUPLE, id, &find_tuple_by_id);
+}
+
+PJ_DEF(void) pjpidf_pres_remove_tuple(pjpidf_pres *pres, pjpidf_tuple *t)
+{
+ PJ_UNUSED_ARG(pres);
+ pj_list_erase(t);
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_pres_add_note(pj_pool_t *pool, pjpidf_pres *pres,
+ const pj_str_t *text)
+{
+ pjpidf_note *note = PJ_POOL_ALLOC_T(pool, pjpidf_note);
+ xml_init_node(pool, note, &NOTE, text);
+ pj_xml_add_node(pres, note);
+ return note;
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_pres_get_first_note(pjpidf_pres *pres)
+{
+ return pj_xml_find_node( pres, &NOTE);
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_pres_get_next_note(pjpidf_pres *t, pjpidf_note *note)
+{
+ return pj_xml_find_next_node(t, note, &NOTE);
+}
+
+
+/* Tuple */
+PJ_DEF(void) pjpidf_tuple_construct(pj_pool_t *pool, pjpidf_tuple *t,
+ const pj_str_t *id)
+{
+ pj_xml_attr *attr;
+ pjpidf_status *st;
+
+ xml_init_node(pool, t, &TUPLE, NULL);
+ attr = xml_create_attr(pool, &ID, id);
+ pj_xml_add_attr(t, attr);
+ st = PJ_POOL_ALLOC_T(pool, pjpidf_status);
+ pjpidf_status_construct(pool, st);
+ pj_xml_add_node(t, st);
+}
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_id(const pjpidf_tuple *t)
+{
+ const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)t, &ID, NULL);
+ pj_assert(attr);
+ return &attr->value;
+}
+
+PJ_DEF(void) pjpidf_tuple_set_id(pj_pool_t *pool, pjpidf_tuple *t, const pj_str_t *id)
+{
+ pj_xml_attr *attr = pj_xml_find_attr(t, &ID, NULL);
+ pj_assert(attr);
+ pj_strdup(pool, &attr->value, id);
+}
+
+
+PJ_DEF(pjpidf_status*) pjpidf_tuple_get_status(pjpidf_tuple *t)
+{
+ pjpidf_status *st = (pjpidf_status*)pj_xml_find_node(t, &STATUS);
+ pj_assert(st);
+ return st;
+}
+
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_contact(const pjpidf_tuple *t)
+{
+ pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &CONTACT);
+ if (!node)
+ return &EMPTY_STRING;
+ return &node->content;
+}
+
+PJ_DEF(void) pjpidf_tuple_set_contact(pj_pool_t *pool, pjpidf_tuple *t,
+ const pj_str_t *contact)
+{
+ pj_xml_node *node = pj_xml_find_node(t, &CONTACT);
+ if (!node) {
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ xml_init_node(pool, node, &CONTACT, contact);
+ pj_xml_add_node(t, node);
+ } else {
+ pj_strdup(pool, &node->content, contact);
+ }
+}
+
+PJ_DEF(void) pjpidf_tuple_set_contact_prio(pj_pool_t *pool, pjpidf_tuple *t,
+ const pj_str_t *prio)
+{
+ pj_xml_node *node = pj_xml_find_node(t, &CONTACT);
+ pj_xml_attr *attr;
+
+ if (!node) {
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ xml_init_node(pool, node, &CONTACT, NULL);
+ pj_xml_add_node(t, node);
+ }
+ attr = pj_xml_find_attr(node, &PRIORITY, NULL);
+ if (!attr) {
+ attr = xml_create_attr(pool, &PRIORITY, prio);
+ pj_xml_add_attr(node, attr);
+ } else {
+ pj_strdup(pool, &attr->value, prio);
+ }
+}
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_contact_prio(const pjpidf_tuple *t)
+{
+ pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &CONTACT);
+ pj_xml_attr *attr;
+
+ if (!node)
+ return &EMPTY_STRING;
+ attr = pj_xml_find_attr(node, &PRIORITY, NULL);
+ if (!attr)
+ return &EMPTY_STRING;
+ return &attr->value;
+}
+
+
+PJ_DEF(pjpidf_note*) pjpidf_tuple_add_note(pj_pool_t *pool, pjpidf_tuple *t,
+ const pj_str_t *text)
+{
+ pjpidf_note *note = PJ_POOL_ALLOC_T(pool, pjpidf_note);
+ xml_init_node(pool, note, &NOTE, text);
+ pj_xml_add_node(t, note);
+ return note;
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_tuple_get_first_note(pjpidf_tuple *t)
+{
+ return pj_xml_find_node(t, &NOTE);
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_tuple_get_next_note(pjpidf_tuple *t, pjpidf_note *n)
+{
+ return pj_xml_find_next_node(t, n, &NOTE);
+}
+
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_timestamp(const pjpidf_tuple *t)
+{
+ pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &TIMESTAMP);
+ return node ? &node->content : &EMPTY_STRING;
+}
+
+PJ_DEF(void) pjpidf_tuple_set_timestamp(pj_pool_t *pool, pjpidf_tuple *t,
+ const pj_str_t *ts)
+{
+ pj_xml_node *node = pj_xml_find_node(t, &TIMESTAMP);
+ if (!node) {
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ xml_init_node(pool, node, &TIMESTAMP, ts);
+ pj_xml_add_node(t, node);
+ } else {
+ pj_strdup(pool, &node->content, ts);
+ }
+}
+
+
+PJ_DEF(void) pjpidf_tuple_set_timestamp_np(pj_pool_t *pool, pjpidf_tuple *t,
+ pj_str_t *ts)
+{
+ pj_xml_node *node = pj_xml_find_node(t, &TIMESTAMP);
+ if (!node) {
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ xml_init_node(pool, node, &TIMESTAMP, ts);
+ } else {
+ node->content = *ts;
+ }
+}
+
+
+/* Status */
+PJ_DEF(void) pjpidf_status_construct(pj_pool_t *pool, pjpidf_status *st)
+{
+ pj_xml_node *node;
+
+ xml_init_node(pool, st, &STATUS, NULL);
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ xml_init_node(pool, node, &BASIC, &CLOSED);
+ pj_xml_add_node(st, node);
+}
+
+PJ_DEF(pj_bool_t) pjpidf_status_is_basic_open(const pjpidf_status *st)
+{
+ pj_xml_node *node = pj_xml_find_node((pj_xml_node*)st, &BASIC);
+ if (!node)
+ return PJ_FALSE;
+ return pj_stricmp(&node->content, &OPEN)==0;
+}
+
+PJ_DEF(void) pjpidf_status_set_basic_open(pjpidf_status *st, pj_bool_t open)
+{
+ pj_xml_node *node = pj_xml_find_node(st, &BASIC);
+ if (node)
+ node->content = open ? OPEN : CLOSED;
+}
+
+PJ_DEF(pjpidf_pres*) pjpidf_create(pj_pool_t *pool, const pj_str_t *entity)
+{
+ pjpidf_pres *pres = PJ_POOL_ALLOC_T(pool, pjpidf_pres);
+ pjpidf_pres_construct(pool, pres, entity);
+ return pres;
+}
+
+PJ_DEF(pjpidf_pres*) pjpidf_parse(pj_pool_t *pool, char *text, int len)
+{
+ pjpidf_pres *pres = pj_xml_parse(pool, text, len);
+ if (pres && pres->name.slen >= 8) {
+ pj_str_t name;
+
+ name.ptr = pres->name.ptr + (pres->name.slen - 8);
+ name.slen = 8;
+
+ if (pj_stricmp(&name, &PRESENCE) == 0)
+ return pres;
+ }
+ return NULL;
+}
+
+PJ_DEF(int) pjpidf_print(const pjpidf_pres* pres, char *buf, int len)
+{
+ return pj_xml_print(pres, buf, len, PJ_TRUE);
+}
+
diff --git a/pjsip/src/pjsip-simple/presence.c b/pjsip/src/pjsip-simple/presence.c
new file mode 100644
index 0000000..5b96ec7
--- /dev/null
+++ b/pjsip/src/pjsip-simple/presence.c
@@ -0,0 +1,941 @@
+/* $Id: presence.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/presence.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_multipart.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_dialog.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "presence.c"
+#define PRES_DEFAULT_EXPIRES PJSIP_PRES_DEFAULT_EXPIRES
+
+#if PJSIP_PRES_BAD_CONTENT_RESPONSE < 200 || \
+ PJSIP_PRES_BAD_CONTENT_RESPONSE > 699 || \
+ PJSIP_PRES_BAD_CONTENT_RESPONSE/100 == 3
+# error Invalid PJSIP_PRES_BAD_CONTENT_RESPONSE value
+#endif
+
+/*
+ * Presence module (mod-presence)
+ */
+static struct pjsip_module mod_presence =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-presence", 12 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_DIALOG_USAGE,/* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ NULL, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+/*
+ * Presence message body type.
+ */
+typedef enum content_type_e
+{
+ CONTENT_TYPE_NONE,
+ CONTENT_TYPE_PIDF,
+ CONTENT_TYPE_XPIDF,
+} content_type_e;
+
+/*
+ * This structure describe a presentity, for both subscriber and notifier.
+ */
+struct pjsip_pres
+{
+ pjsip_evsub *sub; /**< Event subscribtion record. */
+ pjsip_dialog *dlg; /**< The dialog. */
+ content_type_e content_type; /**< Content-Type. */
+ pj_pool_t *status_pool; /**< Pool for pres_status */
+ pjsip_pres_status status; /**< Presence status. */
+ pj_pool_t *tmp_pool; /**< Pool for tmp_status */
+ pjsip_pres_status tmp_status; /**< Temp, before NOTIFY is answred.*/
+ pjsip_evsub_user user_cb; /**< The user callback. */
+};
+
+
+typedef struct pjsip_pres pjsip_pres;
+
+
+/*
+ * Forward decl for evsub callback.
+ */
+static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
+static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+ pjsip_event *event);
+static void pres_on_evsub_rx_refresh( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body);
+static void pres_on_evsub_rx_notify( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body);
+static void pres_on_evsub_client_refresh(pjsip_evsub *sub);
+static void pres_on_evsub_server_timeout(pjsip_evsub *sub);
+
+
+/*
+ * Event subscription callback for presence.
+ */
+static pjsip_evsub_user pres_user =
+{
+ &pres_on_evsub_state,
+ &pres_on_evsub_tsx_state,
+ &pres_on_evsub_rx_refresh,
+ &pres_on_evsub_rx_notify,
+ &pres_on_evsub_client_refresh,
+ &pres_on_evsub_server_timeout,
+};
+
+
+/*
+ * Some static constants.
+ */
+const pj_str_t STR_EVENT = { "Event", 5 };
+const pj_str_t STR_PRESENCE = { "presence", 8 };
+const pj_str_t STR_APPLICATION = { "application", 11 };
+const pj_str_t STR_PIDF_XML = { "pidf+xml", 8};
+const pj_str_t STR_XPIDF_XML = { "xpidf+xml", 9};
+const pj_str_t STR_APP_PIDF_XML = { "application/pidf+xml", 20 };
+const pj_str_t STR_APP_XPIDF_XML = { "application/xpidf+xml", 21 };
+
+
+/*
+ * Init presence module.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_init_module( pjsip_endpoint *endpt,
+ pjsip_module *mod_evsub)
+{
+ pj_status_t status;
+ pj_str_t accept[2];
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL);
+
+ /* Must have not been registered */
+ PJ_ASSERT_RETURN(mod_presence.id == -1, PJ_EINVALIDOP);
+
+ /* Register to endpoint */
+ status = pjsip_endpt_register_module(endpt, &mod_presence);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ accept[0] = STR_APP_PIDF_XML;
+ accept[1] = STR_APP_XPIDF_XML;
+
+ /* Register event package to event module. */
+ status = pjsip_evsub_register_pkg( &mod_presence, &STR_PRESENCE,
+ PRES_DEFAULT_EXPIRES,
+ PJ_ARRAY_SIZE(accept), accept);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_unregister_module(endpt, &mod_presence);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get presence module instance.
+ */
+PJ_DEF(pjsip_module*) pjsip_pres_instance(void)
+{
+ return &mod_presence;
+}
+
+
+/*
+ * Create client subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg,
+ const pjsip_evsub_user *user_cb,
+ unsigned options,
+ pjsip_evsub **p_evsub )
+{
+ pj_status_t status;
+ pjsip_pres *pres;
+ char obj_name[PJ_MAX_OBJ_NAME];
+ pjsip_evsub *sub;
+
+ PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Create event subscription */
+ status = pjsip_evsub_create_uac( dlg, &pres_user, &STR_PRESENCE,
+ options, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create presence */
+ pres = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_pres);
+ pres->dlg = dlg;
+ pres->sub = sub;
+ if (user_cb)
+ pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user));
+
+ pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "pres%p", dlg->pool);
+ pres->status_pool = pj_pool_create(dlg->pool->factory, obj_name,
+ 512, 512, NULL);
+ pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "tmpres%p", dlg->pool);
+ pres->tmp_pool = pj_pool_create(dlg->pool->factory, obj_name,
+ 512, 512, NULL);
+
+ /* Attach to evsub */
+ pjsip_evsub_set_mod_data(sub, mod_presence.id, pres);
+
+ *p_evsub = sub;
+
+on_return:
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+}
+
+
+/*
+ * Create server subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_create_uas( pjsip_dialog *dlg,
+ const pjsip_evsub_user *user_cb,
+ pjsip_rx_data *rdata,
+ pjsip_evsub **p_evsub )
+{
+ pjsip_accept_hdr *accept;
+ pjsip_event_hdr *event;
+ content_type_e content_type = CONTENT_TYPE_NONE;
+ pjsip_evsub *sub;
+ pjsip_pres *pres;
+ char obj_name[PJ_MAX_OBJ_NAME];
+ pj_status_t status;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
+
+ /* Must be request message */
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Check that request is SUBSCRIBE */
+ PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
+ &pjsip_subscribe_method)==0,
+ PJSIP_SIMPLE_ENOTSUBSCRIBE);
+
+ /* Check that Event header contains "presence" */
+ event = (pjsip_event_hdr*)
+ pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL);
+ if (!event) {
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
+ }
+ if (pj_stricmp(&event->event_type, &STR_PRESENCE) != 0) {
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT);
+ }
+
+ /* Check that request contains compatible Accept header. */
+ accept = (pjsip_accept_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
+ if (accept) {
+ unsigned i;
+ for (i=0; i<accept->count; ++i) {
+ if (pj_stricmp(&accept->values[i], &STR_APP_PIDF_XML)==0) {
+ content_type = CONTENT_TYPE_PIDF;
+ break;
+ } else
+ if (pj_stricmp(&accept->values[i], &STR_APP_XPIDF_XML)==0) {
+ content_type = CONTENT_TYPE_XPIDF;
+ break;
+ }
+ }
+
+ if (i==accept->count) {
+ /* Nothing is acceptable */
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
+ }
+
+ } else {
+ /* No Accept header.
+ * Treat as "application/pidf+xml"
+ */
+ content_type = CONTENT_TYPE_PIDF;
+ }
+
+ /* Lock dialog */
+ pjsip_dlg_inc_lock(dlg);
+
+
+ /* Create server subscription */
+ status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, 0, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create server presence subscription */
+ pres = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_pres);
+ pres->dlg = dlg;
+ pres->sub = sub;
+ pres->content_type = content_type;
+ if (user_cb)
+ pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user));
+
+ pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "pres%p", dlg->pool);
+ pres->status_pool = pj_pool_create(dlg->pool->factory, obj_name,
+ 512, 512, NULL);
+ pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "tmpres%p", dlg->pool);
+ pres->tmp_pool = pj_pool_create(dlg->pool->factory, obj_name,
+ 512, 512, NULL);
+
+ /* Attach to evsub */
+ pjsip_evsub_set_mod_data(sub, mod_presence.id, pres);
+
+ /* Done: */
+ *p_evsub = sub;
+
+on_return:
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+}
+
+
+/*
+ * Forcefully terminate presence.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_terminate( pjsip_evsub *sub,
+ pj_bool_t notify )
+{
+ return pjsip_evsub_terminate(sub, notify);
+}
+
+/*
+ * Create SUBSCRIBE
+ */
+PJ_DEF(pj_status_t) pjsip_pres_initiate( pjsip_evsub *sub,
+ pj_int32_t expires,
+ pjsip_tx_data **p_tdata)
+{
+ return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires,
+ p_tdata);
+}
+
+
+/*
+ * Add custom headers.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_add_header( pjsip_evsub *sub,
+ const pjsip_hdr *hdr_list )
+{
+ return pjsip_evsub_add_header( sub, hdr_list );
+}
+
+
+/*
+ * Accept incoming subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_accept( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pjsip_hdr *hdr_list )
+{
+ return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
+}
+
+
+/*
+ * Get presence status.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_get_status( pjsip_evsub *sub,
+ pjsip_pres_status *status )
+{
+ pjsip_pres *pres;
+
+ PJ_ASSERT_RETURN(sub && status, PJ_EINVAL);
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
+
+ if (pres->tmp_status._is_valid) {
+ PJ_ASSERT_RETURN(pres->tmp_pool!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
+ pj_memcpy(status, &pres->tmp_status, sizeof(pjsip_pres_status));
+ } else {
+ PJ_ASSERT_RETURN(pres->status_pool!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
+ pj_memcpy(status, &pres->status, sizeof(pjsip_pres_status));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set presence status.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_set_status( pjsip_evsub *sub,
+ const pjsip_pres_status *status )
+{
+ unsigned i;
+ pj_pool_t *tmp;
+ pjsip_pres *pres;
+
+ PJ_ASSERT_RETURN(sub && status, PJ_EINVAL);
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
+
+ for (i=0; i<status->info_cnt; ++i) {
+ pres->status.info[i].basic_open = status->info[i].basic_open;
+ if (pres->status.info[i].id.slen) {
+ /* Id already set */
+ } else if (status->info[i].id.slen == 0) {
+ pj_create_unique_string(pres->dlg->pool,
+ &pres->status.info[i].id);
+ } else {
+ pj_strdup(pres->dlg->pool,
+ &pres->status.info[i].id,
+ &status->info[i].id);
+ }
+ pj_strdup(pres->tmp_pool,
+ &pres->status.info[i].contact,
+ &status->info[i].contact);
+
+ /* Duplicate <person> */
+ pres->status.info[i].rpid.activity =
+ status->info[i].rpid.activity;
+ pj_strdup(pres->tmp_pool,
+ &pres->status.info[i].rpid.id,
+ &status->info[i].rpid.id);
+ pj_strdup(pres->tmp_pool,
+ &pres->status.info[i].rpid.note,
+ &status->info[i].rpid.note);
+
+ }
+
+ pres->status.info_cnt = status->info_cnt;
+
+ /* Swap pools */
+ tmp = pres->tmp_pool;
+ pres->tmp_pool = pres->status_pool;
+ pres->status_pool = tmp;
+ pj_pool_reset(pres->tmp_pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create message body.
+ */
+static pj_status_t pres_create_msg_body( pjsip_pres *pres,
+ pjsip_tx_data *tdata)
+{
+ pj_str_t entity;
+
+ /* Get publisher URI */
+ entity.ptr = (char*) pj_pool_alloc(tdata->pool, PJSIP_MAX_URL_SIZE);
+ entity.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
+ pres->dlg->local.info->uri,
+ entity.ptr, PJSIP_MAX_URL_SIZE);
+ if (entity.slen < 1)
+ return PJ_ENOMEM;
+
+ if (pres->content_type == CONTENT_TYPE_PIDF) {
+
+ return pjsip_pres_create_pidf(tdata->pool, &pres->status,
+ &entity, &tdata->msg->body);
+
+ } else if (pres->content_type == CONTENT_TYPE_XPIDF) {
+
+ return pjsip_pres_create_xpidf(tdata->pool, &pres->status,
+ &entity, &tdata->msg->body);
+
+ } else {
+ return PJSIP_SIMPLE_EBADCONTENT;
+ }
+}
+
+
+/*
+ * Create NOTIFY
+ */
+PJ_DEF(pj_status_t) pjsip_pres_notify( pjsip_evsub *sub,
+ pjsip_evsub_state state,
+ const pj_str_t *state_str,
+ const pj_str_t *reason,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_pres *pres;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+ /* Get the presence object. */
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE);
+
+ /* Must have at least one presence info, unless state is
+ * PJSIP_EVSUB_STATE_TERMINATED. This could happen if subscription
+ * has not been active (e.g. we're waiting for user authorization)
+ * and remote cancels the subscription.
+ */
+ PJ_ASSERT_RETURN(state==PJSIP_EVSUB_STATE_TERMINATED ||
+ pres->status.info_cnt > 0, PJSIP_SIMPLE_ENOPRESENCEINFO);
+
+
+ /* Lock object. */
+ pjsip_dlg_inc_lock(pres->dlg);
+
+ /* Create the NOTIFY request. */
+ status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+
+ /* Create message body to reflect the presence status.
+ * Only do this if we have presence status info to send (see above).
+ */
+ if (pres->status.info_cnt > 0) {
+ status = pres_create_msg_body( pres, tdata );
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ }
+
+ /* Done. */
+ *p_tdata = tdata;
+
+
+on_return:
+ pjsip_dlg_dec_lock(pres->dlg);
+ return status;
+}
+
+
+/*
+ * Create NOTIFY that reflect current state.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_current_notify( pjsip_evsub *sub,
+ pjsip_tx_data **p_tdata )
+{
+ pjsip_pres *pres;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+ /* Get the presence object. */
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE);
+
+ /* We may not have a presence info yet, e.g. when we receive SUBSCRIBE
+ * to refresh subscription while we're waiting for user authorization.
+ */
+ //PJ_ASSERT_RETURN(pres->status.info_cnt > 0,
+ // PJSIP_SIMPLE_ENOPRESENCEINFO);
+
+
+ /* Lock object. */
+ pjsip_dlg_inc_lock(pres->dlg);
+
+ /* Create the NOTIFY request. */
+ status = pjsip_evsub_current_notify( sub, &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+
+ /* Create message body to reflect the presence status. */
+ if (pres->status.info_cnt > 0) {
+ status = pres_create_msg_body( pres, tdata );
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ }
+
+ /* Done. */
+ *p_tdata = tdata;
+
+
+on_return:
+ pjsip_dlg_dec_lock(pres->dlg);
+ return status;
+}
+
+
+/*
+ * Send request.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_send_request( pjsip_evsub *sub,
+ pjsip_tx_data *tdata )
+{
+ return pjsip_evsub_send_request(sub, tdata);
+}
+
+
+/*
+ * This callback is called by event subscription when subscription
+ * state has changed.
+ */
+static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
+{
+ pjsip_pres *pres;
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ if (pres->user_cb.on_evsub_state)
+ (*pres->user_cb.on_evsub_state)(sub, event);
+
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ if (pres->status_pool) {
+ pj_pool_release(pres->status_pool);
+ pres->status_pool = NULL;
+ }
+ if (pres->tmp_pool) {
+ pj_pool_release(pres->tmp_pool);
+ pres->tmp_pool = NULL;
+ }
+ }
+}
+
+/*
+ * Called when transaction state has changed.
+ */
+static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pjsip_pres *pres;
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ if (pres->user_cb.on_tsx_state)
+ (*pres->user_cb.on_tsx_state)(sub, tsx, event);
+}
+
+
+/*
+ * Called when SUBSCRIBE is received.
+ */
+static void pres_on_evsub_rx_refresh( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsip_pres *pres;
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ if (pres->user_cb.on_rx_refresh) {
+ (*pres->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
+ res_hdr, p_body);
+
+ } else {
+ /* Implementors MUST send NOTIFY if it implements on_rx_refresh */
+ pjsip_tx_data *tdata;
+ pj_str_t timeout = { "timeout", 7};
+ pj_status_t status;
+
+ if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
+ status = pjsip_pres_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, &timeout, &tdata);
+ } else {
+ status = pjsip_pres_current_notify(sub, &tdata);
+ }
+
+ if (status == PJ_SUCCESS)
+ pjsip_pres_send_request(sub, tdata);
+ }
+}
+
+
+/*
+ * Process the content of incoming NOTIFY request and update temporary
+ * status.
+ *
+ * return PJ_SUCCESS if incoming request is acceptable. If return value
+ * is not PJ_SUCCESS, res_hdr may be added with Warning header.
+ */
+static pj_status_t pres_process_rx_notify( pjsip_pres *pres,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr)
+{
+ const pj_str_t STR_MULTIPART = { "multipart", 9 };
+ pjsip_ctype_hdr *ctype_hdr;
+ pj_status_t status = PJ_SUCCESS;
+
+ *p_st_text = NULL;
+
+ /* Check Content-Type and msg body are present. */
+ ctype_hdr = rdata->msg_info.ctype;
+
+ if (ctype_hdr==NULL || rdata->msg_info.msg->body==NULL) {
+
+ pjsip_warning_hdr *warn_hdr;
+ pj_str_t warn_text;
+
+ *p_st_code = PJSIP_SC_BAD_REQUEST;
+
+ warn_text = pj_str("Message body is not present");
+ warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399,
+ pjsip_endpt_name(pres->dlg->endpt),
+ &warn_text);
+ pj_list_push_back(res_hdr, warn_hdr);
+
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
+ }
+
+ /* Parse content. */
+ if (pj_stricmp(&ctype_hdr->media.type, &STR_MULTIPART)==0) {
+ pjsip_multipart_part *mpart;
+ pjsip_media_type ctype;
+
+ pjsip_media_type_init(&ctype, (pj_str_t*)&STR_APPLICATION,
+ (pj_str_t*)&STR_PIDF_XML);
+ mpart = pjsip_multipart_find_part(rdata->msg_info.msg->body,
+ &ctype, NULL);
+ if (mpart) {
+ status = pjsip_pres_parse_pidf2((char*)mpart->body->data,
+ mpart->body->len, pres->tmp_pool,
+ &pres->tmp_status);
+ }
+
+ if (mpart==NULL) {
+ pjsip_media_type_init(&ctype, (pj_str_t*)&STR_APPLICATION,
+ (pj_str_t*)&STR_XPIDF_XML);
+ mpart = pjsip_multipart_find_part(rdata->msg_info.msg->body,
+ &ctype, NULL);
+ if (mpart) {
+ status = pjsip_pres_parse_xpidf2((char*)mpart->body->data,
+ mpart->body->len,
+ pres->tmp_pool,
+ &pres->tmp_status);
+ } else {
+ status = PJSIP_SIMPLE_EBADCONTENT;
+ }
+ }
+ }
+ else
+ if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
+ pj_stricmp(&ctype_hdr->media.subtype, &STR_PIDF_XML)==0)
+ {
+ status = pjsip_pres_parse_pidf( rdata, pres->tmp_pool,
+ &pres->tmp_status);
+ }
+ else
+ if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
+ pj_stricmp(&ctype_hdr->media.subtype, &STR_XPIDF_XML)==0)
+ {
+ status = pjsip_pres_parse_xpidf( rdata, pres->tmp_pool,
+ &pres->tmp_status);
+ }
+ else
+ {
+ status = PJSIP_SIMPLE_EBADCONTENT;
+ }
+
+ if (status != PJ_SUCCESS) {
+ /* Unsupported or bad Content-Type */
+ if (PJSIP_PRES_BAD_CONTENT_RESPONSE >= 300) {
+ pjsip_accept_hdr *accept_hdr;
+ pjsip_warning_hdr *warn_hdr;
+
+ *p_st_code = PJSIP_PRES_BAD_CONTENT_RESPONSE;
+
+ /* Add Accept header */
+ accept_hdr = pjsip_accept_hdr_create(rdata->tp_info.pool);
+ accept_hdr->values[accept_hdr->count++] = STR_APP_PIDF_XML;
+ accept_hdr->values[accept_hdr->count++] = STR_APP_XPIDF_XML;
+ pj_list_push_back(res_hdr, accept_hdr);
+
+ /* Add Warning header */
+ warn_hdr = pjsip_warning_hdr_create_from_status(
+ rdata->tp_info.pool,
+ pjsip_endpt_name(pres->dlg->endpt),
+ status);
+ pj_list_push_back(res_hdr, warn_hdr);
+
+ return status;
+ } else {
+ pj_assert(PJSIP_PRES_BAD_CONTENT_RESPONSE/100 == 2);
+ PJ_PERROR(4,(THIS_FILE, status,
+ "Ignoring presence error due to "
+ "PJSIP_PRES_BAD_CONTENT_RESPONSE setting [%d]",
+ PJSIP_PRES_BAD_CONTENT_RESPONSE));
+ *p_st_code = PJSIP_PRES_BAD_CONTENT_RESPONSE;
+ status = PJ_SUCCESS;
+ }
+ }
+
+ /* If application calls pres_get_status(), redirect the call to
+ * retrieve the temporary status.
+ */
+ pres->tmp_status._is_valid = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Called when NOTIFY is received.
+ */
+static void pres_on_evsub_rx_notify( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsip_pres *pres;
+ pj_status_t status;
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ if (rdata->msg_info.msg->body) {
+ status = pres_process_rx_notify( pres, rdata, p_st_code, p_st_text,
+ res_hdr );
+ if (status != PJ_SUCCESS)
+ return;
+
+ } else {
+#if 1
+ /* This is the newest change, http://trac.pjsip.org/repos/ticket/873
+ * Some app want to be notified about the empty NOTIFY, e.g. to
+ * decide whether it should consider the buddy as offline.
+ * In this case, leave the buddy state unchanged, but set the
+ * "tuple_node" in pjsip_pres_status to NULL.
+ */
+ unsigned i;
+ for (i=0; i<pres->status.info_cnt; ++i) {
+ pres->status.info[i].tuple_node = NULL;
+ }
+
+#elif 0
+ /* This has just been changed. Previously, we treat incoming NOTIFY
+ * with no message body as having the presence subscription closed.
+ * Now we treat it as no change in presence status (ref: EyeBeam).
+ */
+ *p_st_code = 200;
+ return;
+#else
+ unsigned i;
+ /* Subscription is terminated. Consider contact is offline */
+ pres->tmp_status._is_valid = PJ_TRUE;
+ for (i=0; i<pres->tmp_status.info_cnt; ++i)
+ pres->tmp_status.info[i].basic_open = PJ_FALSE;
+#endif
+ }
+
+ /* Notify application. */
+ if (pres->user_cb.on_rx_notify) {
+ (*pres->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text,
+ res_hdr, p_body);
+ }
+
+
+ /* If application responded NOTIFY with 2xx, copy temporary status
+ * to main status, and mark the temporary status as invalid.
+ */
+ if ((*p_st_code)/100 == 2) {
+ pj_pool_t *tmp;
+
+ pj_memcpy(&pres->status, &pres->tmp_status, sizeof(pjsip_pres_status));
+
+ /* Swap the pool */
+ tmp = pres->tmp_pool;
+ pres->tmp_pool = pres->status_pool;
+ pres->status_pool = tmp;
+ }
+
+ pres->tmp_status._is_valid = PJ_FALSE;
+ pj_pool_reset(pres->tmp_pool);
+
+ /* Done */
+}
+
+/*
+ * Called when it's time to send SUBSCRIBE.
+ */
+static void pres_on_evsub_client_refresh(pjsip_evsub *sub)
+{
+ pjsip_pres *pres;
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ if (pres->user_cb.on_client_refresh) {
+ (*pres->user_cb.on_client_refresh)(sub);
+ } else {
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ status = pjsip_pres_initiate(sub, -1, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_pres_send_request(sub, tdata);
+ }
+}
+
+/*
+ * Called when no refresh is received after the interval.
+ */
+static void pres_on_evsub_server_timeout(pjsip_evsub *sub)
+{
+ pjsip_pres *pres;
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ if (pres->user_cb.on_server_timeout) {
+ (*pres->user_cb.on_server_timeout)(sub);
+ } else {
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+ pj_str_t reason = { "timeout", 7 };
+
+ status = pjsip_pres_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, &reason, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_pres_send_request(sub, tdata);
+ }
+}
+
diff --git a/pjsip/src/pjsip-simple/presence_body.c b/pjsip/src/pjsip-simple/presence_body.c
new file mode 100644
index 0000000..e692f47
--- /dev/null
+++ b/pjsip/src/pjsip-simple/presence_body.c
@@ -0,0 +1,288 @@
+/* $Id: presence_body.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/presence.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_transport.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "presence_body.c"
+
+
+static const pj_str_t STR_APPLICATION = { "application", 11 };
+static const pj_str_t STR_PIDF_XML = { "pidf+xml", 8 };
+static const pj_str_t STR_XPIDF_XML = { "xpidf+xml", 9 };
+
+
+
+
+/*
+ * Function to print XML message body.
+ */
+static int pres_print_body(struct pjsip_msg_body *msg_body,
+ char *buf, pj_size_t size)
+{
+ return pj_xml_print((const pj_xml_node*)msg_body->data, buf, size,
+ PJ_TRUE);
+}
+
+
+/*
+ * Function to clone XML document.
+ */
+static void* xml_clone_data(pj_pool_t *pool, const void *data, unsigned len)
+{
+ PJ_UNUSED_ARG(len);
+ return pj_xml_clone( pool, (const pj_xml_node*) data);
+}
+
+
+/*
+ * This is a utility function to create PIDF message body from PJSIP
+ * presence status (pjsip_pres_status).
+ */
+PJ_DEF(pj_status_t) pjsip_pres_create_pidf( pj_pool_t *pool,
+ const pjsip_pres_status *status,
+ const pj_str_t *entity,
+ pjsip_msg_body **p_body )
+{
+ pjpidf_pres *pidf;
+ pjsip_msg_body *body;
+ unsigned i;
+
+ /* Create <presence>. */
+ pidf = pjpidf_create(pool, entity);
+
+ /* Create <tuple> */
+ for (i=0; i<status->info_cnt; ++i) {
+
+ pjpidf_tuple *pidf_tuple;
+ pjpidf_status *pidf_status;
+ pj_str_t id;
+
+ /* Add tuple id. */
+ if (status->info[i].id.slen == 0) {
+ /* xs:ID must start with letter */
+ //pj_create_unique_string(pool, &id);
+ id.ptr = (char*)pj_pool_alloc(pool, PJ_GUID_STRING_LENGTH+2);
+ id.ptr += 2;
+ pj_generate_unique_string(&id);
+ id.ptr -= 2;
+ id.ptr[0] = 'p';
+ id.ptr[1] = 'j';
+ id.slen += 2;
+ } else {
+ id = status->info[i].id;
+ }
+
+ pidf_tuple = pjpidf_pres_add_tuple(pool, pidf, &id);
+
+ /* Set <contact> */
+ if (status->info[i].contact.slen)
+ pjpidf_tuple_set_contact(pool, pidf_tuple,
+ &status->info[i].contact);
+
+
+ /* Set basic status */
+ pidf_status = pjpidf_tuple_get_status(pidf_tuple);
+ pjpidf_status_set_basic_open(pidf_status,
+ status->info[i].basic_open);
+
+ /* Add <timestamp> if configured */
+#if defined(PJSIP_PRES_PIDF_ADD_TIMESTAMP) && PJSIP_PRES_PIDF_ADD_TIMESTAMP
+ if (PJSIP_PRES_PIDF_ADD_TIMESTAMP) {
+ char buf[50];
+ int tslen = 0;
+ pj_time_val tv;
+ pj_parsed_time pt;
+
+ pj_gettimeofday(&tv);
+ /* TODO: convert time to GMT! (unsupported by pjlib) */
+ pj_time_decode( &tv, &pt);
+
+ tslen = pj_ansi_snprintf(buf, sizeof(buf),
+ "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
+ pt.year, pt.mon+1, pt.day,
+ pt.hour, pt.min, pt.sec, pt.msec);
+ if (tslen > 0 && tslen < (int)sizeof(buf)) {
+ pj_str_t time = pj_str(buf);
+ pjpidf_tuple_set_timestamp(pool, pidf_tuple, &time);
+ }
+ }
+#endif
+ }
+
+ /* Create <person> (RPID) */
+ if (status->info_cnt) {
+ pjrpid_add_element(pidf, pool, 0, &status->info[0].rpid);
+ }
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ body->data = pidf;
+ body->content_type.type = STR_APPLICATION;
+ body->content_type.subtype = STR_PIDF_XML;
+ body->print_body = &pres_print_body;
+ body->clone_data = &xml_clone_data;
+
+ *p_body = body;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This is a utility function to create X-PIDF message body from PJSIP
+ * presence status (pjsip_pres_status).
+ */
+PJ_DEF(pj_status_t) pjsip_pres_create_xpidf( pj_pool_t *pool,
+ const pjsip_pres_status *status,
+ const pj_str_t *entity,
+ pjsip_msg_body **p_body )
+{
+ /* Note: PJSIP implementation of XPIDF is not complete!
+ */
+ pjxpidf_pres *xpidf;
+ pjsip_msg_body *body;
+
+ PJ_LOG(4,(THIS_FILE, "Warning: XPIDF format is not fully supported "
+ "by PJSIP"));
+
+ /* Create XPIDF document. */
+ xpidf = pjxpidf_create(pool, entity);
+
+ /* Set basic status. */
+ if (status->info_cnt > 0)
+ pjxpidf_set_status( xpidf, status->info[0].basic_open);
+ else
+ pjxpidf_set_status( xpidf, PJ_FALSE);
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ body->data = xpidf;
+ body->content_type.type = STR_APPLICATION;
+ body->content_type.subtype = STR_XPIDF_XML;
+ body->print_body = &pres_print_body;
+ body->clone_data = &xml_clone_data;
+
+ *p_body = body;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * This is a utility function to parse PIDF body into PJSIP presence status.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_parse_pidf( pjsip_rx_data *rdata,
+ pj_pool_t *pool,
+ pjsip_pres_status *pres_status)
+{
+ return pjsip_pres_parse_pidf2((char*)rdata->msg_info.msg->body->data,
+ rdata->msg_info.msg->body->len,
+ pool, pres_status);
+}
+
+PJ_DEF(pj_status_t) pjsip_pres_parse_pidf2(char *body, unsigned body_len,
+ pj_pool_t *pool,
+ pjsip_pres_status *pres_status)
+{
+ pjpidf_pres *pidf;
+ pjpidf_tuple *pidf_tuple;
+
+ pidf = pjpidf_parse(pool, body, body_len);
+ if (pidf == NULL)
+ return PJSIP_SIMPLE_EBADPIDF;
+
+ pres_status->info_cnt = 0;
+
+ pidf_tuple = pjpidf_pres_get_first_tuple(pidf);
+ while (pidf_tuple && pres_status->info_cnt < PJSIP_PRES_STATUS_MAX_INFO) {
+ pjpidf_status *pidf_status;
+
+ pres_status->info[pres_status->info_cnt].tuple_node =
+ pj_xml_clone(pool, pidf_tuple);
+
+ pj_strdup(pool,
+ &pres_status->info[pres_status->info_cnt].id,
+ pjpidf_tuple_get_id(pidf_tuple));
+
+ pj_strdup(pool,
+ &pres_status->info[pres_status->info_cnt].contact,
+ pjpidf_tuple_get_contact(pidf_tuple));
+
+ pidf_status = pjpidf_tuple_get_status(pidf_tuple);
+ if (pidf_status) {
+ pres_status->info[pres_status->info_cnt].basic_open =
+ pjpidf_status_is_basic_open(pidf_status);
+ } else {
+ pres_status->info[pres_status->info_cnt].basic_open = PJ_FALSE;
+ }
+
+ pidf_tuple = pjpidf_pres_get_next_tuple( pidf, pidf_tuple );
+ pres_status->info_cnt++;
+ }
+
+ /* Parse <person> (RPID) */
+ pjrpid_get_element(pidf, pool, &pres_status->info[0].rpid);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This is a utility function to parse X-PIDF body into PJSIP presence status.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_parse_xpidf(pjsip_rx_data *rdata,
+ pj_pool_t *pool,
+ pjsip_pres_status *pres_status)
+{
+ return pjsip_pres_parse_xpidf2((char*)rdata->msg_info.msg->body->data,
+ rdata->msg_info.msg->body->len,
+ pool, pres_status);
+}
+
+PJ_DEF(pj_status_t) pjsip_pres_parse_xpidf2(char *body, unsigned body_len,
+ pj_pool_t *pool,
+ pjsip_pres_status *pres_status)
+{
+ pjxpidf_pres *xpidf;
+
+ xpidf = pjxpidf_parse(pool, body, body_len);
+ if (xpidf == NULL)
+ return PJSIP_SIMPLE_EBADXPIDF;
+
+ pres_status->info_cnt = 1;
+
+ pj_strdup(pool,
+ &pres_status->info[0].contact,
+ pjxpidf_get_uri(xpidf));
+ pres_status->info[0].basic_open = pjxpidf_get_status(xpidf);
+ pres_status->info[0].id.slen = 0;
+ pres_status->info[0].tuple_node = NULL;
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjsip/src/pjsip-simple/publishc.c b/pjsip/src/pjsip-simple/publishc.c
new file mode 100644
index 0000000..efee6fb
--- /dev/null
+++ b/pjsip/src/pjsip-simple/publishc.c
@@ -0,0 +1,790 @@
+/* $Id: publishc.c 4173 2012-06-20 10:39:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/publish.h>
+#include <pjsip/sip_auth.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_uri.h>
+#include <pjsip/sip_util.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+#include <pj/timer.h>
+
+
+#define REFRESH_TIMER 1
+#define DELAY_BEFORE_REFRESH PJSIP_PUBLISHC_DELAY_BEFORE_REFRESH
+#define THIS_FILE "publishc.c"
+
+
+/* Let's define this enum, so that it'll trigger compilation error
+ * when somebody define the same enum in sip_msg.h
+ */
+enum
+{
+ PJSIP_PUBLISH_METHOD = PJSIP_OTHER_METHOD,
+};
+
+const pjsip_method pjsip_publish_method =
+{
+ (pjsip_method_e)PJSIP_PUBLISH_METHOD,
+ { "PUBLISH", 7 }
+};
+
+
+/**
+ * Pending request list.
+ */
+typedef struct pending_publish
+{
+ PJ_DECL_LIST_MEMBER(pjsip_tx_data);
+} pending_publish;
+
+
+/**
+ * SIP client publication structure.
+ */
+struct pjsip_publishc
+{
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pj_bool_t _delete_flag;
+ int pending_tsx;
+ pj_bool_t in_callback;
+ pj_mutex_t *mutex;
+
+ pjsip_publishc_opt opt;
+ void *token;
+ pjsip_publishc_cb *cb;
+
+ pj_str_t event;
+ pj_str_t str_target_uri;
+ pjsip_uri *target_uri;
+ pjsip_cid_hdr *cid_hdr;
+ pjsip_cseq_hdr *cseq_hdr;
+ pj_str_t from_uri;
+ pjsip_from_hdr *from_hdr;
+ pjsip_to_hdr *to_hdr;
+ pj_str_t etag;
+ pjsip_expires_hdr *expires_hdr;
+ pj_uint32_t expires;
+ pjsip_route_hdr route_set;
+ pjsip_hdr usr_hdr;
+ pjsip_host_port via_addr;
+ const void *via_tp;
+
+ /* Authorization sessions. */
+ pjsip_auth_clt_sess auth_sess;
+
+ /* Auto refresh publication. */
+ pj_bool_t auto_refresh;
+ pj_time_val last_refresh;
+ pj_time_val next_refresh;
+ pj_timer_entry timer;
+
+ /* Pending PUBLISH request */
+ pending_publish pending_reqs;
+};
+
+
+PJ_DEF(void) pjsip_publishc_opt_default(pjsip_publishc_opt *opt)
+{
+ pj_bzero(opt, sizeof(*opt));
+ opt->queue_request = PJSIP_PUBLISHC_QUEUE_REQUEST;
+}
+
+
+/*
+ * Initialize client publication module.
+ */
+PJ_DEF(pj_status_t) pjsip_publishc_init_module(pjsip_endpoint *endpt)
+{
+ /* Note:
+ Commented out the capability registration below, since it's
+ wrong to include PUBLISH in Allow header of INVITE requests/
+ responses.
+
+ 13.2.1 Creating the Initial INVITE
+ An Allow header field (Section 20.5) SHOULD be present in the
+ INVITE. It indicates what methods can be invoked within a dialog
+
+ 20.5 Allow
+ The Allow header field lists the set of methods supported by the
+ UA generating the message.
+
+ While the semantic of Allow header in non-dialog requests is unclear,
+ it's probably best not to include PUBLISH in Allow header for now
+ until we can find out how to customize the inclusion of methods in
+ Allow header for in-dialog vs out-dialog requests.
+
+ return pjsip_endpt_add_capability( endpt, NULL, PJSIP_H_ALLOW, NULL,
+ 1, &pjsip_publish_method.name);
+ */
+ PJ_UNUSED_ARG(endpt);
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_create( pjsip_endpoint *endpt,
+ const pjsip_publishc_opt *opt,
+ void *token,
+ pjsip_publishc_cb *cb,
+ pjsip_publishc **p_pubc)
+{
+ pj_pool_t *pool;
+ pjsip_publishc *pubc;
+ pjsip_publishc_opt default_opt;
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(endpt && cb && p_pubc, PJ_EINVAL);
+
+ pool = pjsip_endpt_create_pool(endpt, "pubc%p", 1024, 1024);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ pubc = PJ_POOL_ZALLOC_T(pool, pjsip_publishc);
+
+ pubc->pool = pool;
+ pubc->endpt = endpt;
+ pubc->token = token;
+ pubc->cb = cb;
+ pubc->expires = PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED;
+
+ if (!opt) {
+ pjsip_publishc_opt_default(&default_opt);
+ opt = &default_opt;
+ }
+ pj_memcpy(&pubc->opt, opt, sizeof(*opt));
+ pj_list_init(&pubc->pending_reqs);
+
+ status = pj_mutex_create_recursive(pubc->pool, "pubc%p", &pubc->mutex);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ status = pjsip_auth_clt_init(&pubc->auth_sess, endpt, pubc->pool, 0);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_destroy(pubc->mutex);
+ pj_pool_release(pool);
+ return status;
+ }
+
+ pj_list_init(&pubc->route_set);
+ pj_list_init(&pubc->usr_hdr);
+
+ /* Done */
+ *p_pubc = pubc;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_destroy(pjsip_publishc *pubc)
+{
+ PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
+
+ if (pubc->pending_tsx || pubc->in_callback) {
+ pubc->_delete_flag = 1;
+ pubc->cb = NULL;
+ } else {
+ /* Cancel existing timer, if any */
+ if (pubc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
+ pubc->timer.id = 0;
+ }
+
+ if (pubc->mutex)
+ pj_mutex_destroy(pubc->mutex);
+ pjsip_endpt_release_pool(pubc->endpt, pubc->pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_pool_t*) pjsip_publishc_get_pool(pjsip_publishc *pubc)
+{
+ return pubc->pool;
+}
+
+static void set_expires( pjsip_publishc *pubc, pj_uint32_t expires)
+{
+ if (expires != pubc->expires &&
+ expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED)
+ {
+ pubc->expires_hdr = pjsip_expires_hdr_create(pubc->pool, expires);
+ } else {
+ pubc->expires_hdr = NULL;
+ }
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_init(pjsip_publishc *pubc,
+ const pj_str_t *event,
+ const pj_str_t *target_uri,
+ const pj_str_t *from_uri,
+ const pj_str_t *to_uri,
+ pj_uint32_t expires)
+{
+ pj_str_t tmp;
+
+ PJ_ASSERT_RETURN(pubc && event && target_uri && from_uri && to_uri &&
+ expires, PJ_EINVAL);
+
+ /* Copy event type */
+ pj_strdup_with_null(pubc->pool, &pubc->event, event);
+
+ /* Copy server URL. */
+ pj_strdup_with_null(pubc->pool, &pubc->str_target_uri, target_uri);
+
+ /* Set server URL. */
+ tmp = pubc->str_target_uri;
+ pubc->target_uri = pjsip_parse_uri( pubc->pool, tmp.ptr, tmp.slen, 0);
+ if (pubc->target_uri == NULL) {
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Set "From" header. */
+ pj_strdup_with_null(pubc->pool, &pubc->from_uri, from_uri);
+ tmp = pubc->from_uri;
+ pubc->from_hdr = pjsip_from_hdr_create(pubc->pool);
+ pubc->from_hdr->uri = pjsip_parse_uri(pubc->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (!pubc->from_hdr->uri) {
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Set "To" header. */
+ pj_strdup_with_null(pubc->pool, &tmp, to_uri);
+ pubc->to_hdr = pjsip_to_hdr_create(pubc->pool);
+ pubc->to_hdr->uri = pjsip_parse_uri(pubc->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (!pubc->to_hdr->uri) {
+ return PJSIP_EINVALIDURI;
+ }
+
+
+ /* Set "Expires" header, if required. */
+ set_expires( pubc, expires);
+
+ /* Set "Call-ID" header. */
+ pubc->cid_hdr = pjsip_cid_hdr_create(pubc->pool);
+ pj_create_unique_string(pubc->pool, &pubc->cid_hdr->id);
+
+ /* Set "CSeq" header. */
+ pubc->cseq_hdr = pjsip_cseq_hdr_create(pubc->pool);
+ pubc->cseq_hdr->cseq = pj_rand() % 0xFFFF;
+ pjsip_method_set( &pubc->cseq_hdr->method, PJSIP_REGISTER_METHOD);
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_publishc_set_credentials( pjsip_publishc *pubc,
+ int count,
+ const pjsip_cred_info cred[] )
+{
+ PJ_ASSERT_RETURN(pubc && count && cred, PJ_EINVAL);
+ return pjsip_auth_clt_set_credentials(&pubc->auth_sess, count, cred);
+}
+
+PJ_DEF(pj_status_t) pjsip_publishc_set_route_set( pjsip_publishc *pubc,
+ const pjsip_route_hdr *route_set)
+{
+ const pjsip_route_hdr *chdr;
+
+ PJ_ASSERT_RETURN(pubc && route_set, PJ_EINVAL);
+
+ pj_list_init(&pubc->route_set);
+
+ chdr = route_set->next;
+ while (chdr != route_set) {
+ pj_list_push_back(&pubc->route_set, pjsip_hdr_clone(pubc->pool, chdr));
+ chdr = chdr->next;
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_publishc_set_headers( pjsip_publishc *pubc,
+ const pjsip_hdr *hdr_list)
+{
+ const pjsip_hdr *h;
+
+ PJ_ASSERT_RETURN(pubc && hdr_list, PJ_EINVAL);
+
+ pj_list_init(&pubc->usr_hdr);
+ h = hdr_list->next;
+ while (h != hdr_list) {
+ pj_list_push_back(&pubc->usr_hdr, pjsip_hdr_clone(pubc->pool, h));
+ h = h->next;
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_publishc_set_via_sent_by(pjsip_publishc *pubc,
+ pjsip_host_port *via_addr,
+ pjsip_transport *via_tp)
+{
+ PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
+
+ if (!via_addr)
+ pj_bzero(&pubc->via_addr, sizeof(pubc->via_addr));
+ else
+ pubc->via_addr = *via_addr;
+ pubc->via_tp = via_tp;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t create_request(pjsip_publishc *pubc,
+ pjsip_tx_data **p_tdata)
+{
+ const pj_str_t STR_EVENT = { "Event", 5 };
+ pj_status_t status;
+ pjsip_generic_string_hdr *hdr;
+ pjsip_tx_data *tdata;
+
+ PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
+
+ /* Create the request. */
+ status = pjsip_endpt_create_request_from_hdr( pubc->endpt,
+ &pjsip_publish_method,
+ pubc->target_uri,
+ pubc->from_hdr,
+ pubc->to_hdr,
+ NULL,
+ pubc->cid_hdr,
+ pubc->cseq_hdr->cseq,
+ NULL,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add cached authorization headers. */
+ pjsip_auth_clt_init_req( &pubc->auth_sess, tdata );
+
+ /* Add Route headers from route set, ideally after Via header */
+ if (!pj_list_empty(&pubc->route_set)) {
+ pjsip_hdr *route_pos;
+ const pjsip_route_hdr *route;
+
+ route_pos = (pjsip_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ if (!route_pos)
+ route_pos = &tdata->msg->hdr;
+
+ route = pubc->route_set.next;
+ while (route != &pubc->route_set) {
+ pjsip_hdr *new_hdr = (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, route);
+ pj_list_insert_after(route_pos, new_hdr);
+ route_pos = new_hdr;
+ route = route->next;
+ }
+ }
+
+ /* Add Event header */
+ hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_EVENT,
+ &pubc->event);
+ if (hdr)
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+
+
+ /* Add SIP-If-Match if we have etag */
+ if (pubc->etag.slen) {
+ const pj_str_t STR_HNAME = { "SIP-If-Match", 12 };
+
+ hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_HNAME,
+ &pubc->etag);
+ if (hdr)
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+ }
+
+ /* Add user headers */
+ if (!pj_list_empty(&pubc->usr_hdr)) {
+ const pjsip_hdr *hdr;
+
+ hdr = pubc->usr_hdr.next;
+ while (hdr != &pubc->usr_hdr) {
+ pjsip_hdr *new_hdr = (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr);
+ pjsip_msg_add_hdr(tdata->msg, new_hdr);
+ hdr = hdr->next;
+ }
+ }
+
+
+ /* Done. */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_publish(pjsip_publishc *pubc,
+ pj_bool_t auto_refresh,
+ pjsip_tx_data **p_tdata)
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
+
+ status = create_request(pubc, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add Expires header */
+ if (pubc->expires_hdr) {
+ pjsip_hdr *dup;
+
+ dup = (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, pubc->expires_hdr);
+ if (dup)
+ pjsip_msg_add_hdr(tdata->msg, dup);
+ }
+
+ /* Cancel existing timer */
+ if (pubc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
+ pubc->timer.id = 0;
+ }
+
+ pubc->auto_refresh = auto_refresh;
+
+ /* Done */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_unpublish(pjsip_publishc *pubc,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_msg *msg;
+ pjsip_expires_hdr *expires;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
+
+ if (pubc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
+ pubc->timer.id = 0;
+ }
+
+ status = create_request(pubc, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ msg = tdata->msg;
+
+ /* Add Expires:0 header */
+ expires = pjsip_expires_hdr_create(tdata->pool, 0);
+ pjsip_msg_add_hdr( msg, (pjsip_hdr*)expires);
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_update_expires( pjsip_publishc *pubc,
+ pj_uint32_t expires )
+{
+ PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
+ set_expires( pubc, expires );
+ return PJ_SUCCESS;
+}
+
+
+static void call_callback(pjsip_publishc *pubc, pj_status_t status,
+ int st_code, const pj_str_t *reason,
+ pjsip_rx_data *rdata, pj_int32_t expiration)
+{
+ struct pjsip_publishc_cbparam cbparam;
+
+
+ cbparam.pubc = pubc;
+ cbparam.token = pubc->token;
+ cbparam.status = status;
+ cbparam.code = st_code;
+ cbparam.reason = *reason;
+ cbparam.rdata = rdata;
+ cbparam.expiration = expiration;
+
+ (*pubc->cb)(&cbparam);
+}
+
+static void pubc_refresh_timer_cb( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pjsip_publishc *pubc = (pjsip_publishc*) entry->user_data;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ entry->id = 0;
+ status = pjsip_publishc_publish(pubc, 1, &tdata);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg));
+ call_callback(pubc, status, 400, &reason, NULL, -1);
+ return;
+ }
+
+ status = pjsip_publishc_send(pubc, tdata);
+ /* No need to call callback as it should have been called */
+}
+
+static void tsx_callback(void *token, pjsip_event *event)
+{
+ pj_status_t status;
+ pjsip_publishc *pubc = (pjsip_publishc*) token;
+ pjsip_transaction *tsx = event->body.tsx_state.tsx;
+
+ /* Decrement pending transaction counter. */
+ pj_assert(pubc->pending_tsx > 0);
+ --pubc->pending_tsx;
+
+ /* Mark that we're in callback to prevent deletion (#1164) */
+ ++pubc->in_callback;
+
+ /* If publication data has been deleted by user then remove publication
+ * data from transaction's callback, and don't call callback.
+ */
+ if (pubc->_delete_flag) {
+
+ /* Nothing to do */
+ ;
+
+ } else if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED ||
+ tsx->status_code == PJSIP_SC_UNAUTHORIZED)
+ {
+ pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+
+ status = pjsip_auth_clt_reinit_req( &pubc->auth_sess,
+ rdata,
+ tsx->last_tx,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ call_callback(pubc, status, tsx->status_code,
+ &rdata->msg_info.msg->line.status.reason,
+ rdata, -1);
+ } else {
+ status = pjsip_publishc_send(pubc, tdata);
+ }
+
+ } else {
+ pjsip_rx_data *rdata;
+ pj_int32_t expiration = 0xFFFF;
+
+ if (tsx->status_code/100 == 2) {
+ pjsip_msg *msg;
+ pjsip_expires_hdr *expires;
+ pjsip_generic_string_hdr *etag_hdr;
+ const pj_str_t STR_ETAG = { "SIP-ETag", 8 };
+
+ rdata = event->body.tsx_state.src.rdata;
+ msg = rdata->msg_info.msg;
+
+ /* Save ETag value */
+ etag_hdr = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(msg, &STR_ETAG, NULL);
+ if (etag_hdr) {
+ pj_strdup(pubc->pool, &pubc->etag, &etag_hdr->hvalue);
+ } else {
+ pubc->etag.slen = 0;
+ }
+
+ /* Update expires value */
+ expires = (pjsip_expires_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+
+ if (pubc->auto_refresh && expires)
+ expiration = expires->ivalue;
+
+ if (pubc->auto_refresh && expiration!=0 && expiration!=0xFFFF) {
+ pj_time_val delay = { 0, 0};
+
+ /* Cancel existing timer, if any */
+ if (pubc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
+ pubc->timer.id = 0;
+ }
+
+ delay.sec = expiration - DELAY_BEFORE_REFRESH;
+ if (pubc->expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED &&
+ delay.sec > (pj_int32_t)pubc->expires)
+ {
+ delay.sec = pubc->expires;
+ }
+ if (delay.sec < DELAY_BEFORE_REFRESH)
+ delay.sec = DELAY_BEFORE_REFRESH;
+ pubc->timer.cb = &pubc_refresh_timer_cb;
+ pubc->timer.id = REFRESH_TIMER;
+ pubc->timer.user_data = pubc;
+ pjsip_endpt_schedule_timer( pubc->endpt, &pubc->timer, &delay);
+ pj_gettimeofday(&pubc->last_refresh);
+ pubc->next_refresh = pubc->last_refresh;
+ pubc->next_refresh.sec += delay.sec;
+ }
+
+ } else {
+ rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ?
+ event->body.tsx_state.src.rdata : NULL;
+ }
+
+
+ /* Call callback. */
+ if (expiration == 0xFFFF) expiration = -1;
+
+ /* Temporarily increment pending_tsx to prevent callback from
+ * destroying pubc.
+ */
+ ++pubc->pending_tsx;
+
+ call_callback(pubc, PJ_SUCCESS, tsx->status_code,
+ (rdata ? &rdata->msg_info.msg->line.status.reason
+ : pjsip_get_status_text(tsx->status_code)),
+ rdata, expiration);
+
+ --pubc->pending_tsx;
+
+ /* If we have pending request(s), send them now */
+ pj_mutex_lock(pubc->mutex);
+ while (!pj_list_empty(&pubc->pending_reqs)) {
+ pjsip_tx_data *tdata = pubc->pending_reqs.next;
+ pj_list_erase(tdata);
+
+ /* Add SIP-If-Match if we have etag and the request doesn't have
+ * one (http://trac.pjsip.org/repos/ticket/996)
+ */
+ if (pubc->etag.slen) {
+ const pj_str_t STR_HNAME = { "SIP-If-Match", 12 };
+ pjsip_generic_string_hdr *sim_hdr;
+
+ sim_hdr = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(tdata->msg, &STR_HNAME, NULL);
+ if (!sim_hdr) {
+ /* Create the header */
+ sim_hdr = pjsip_generic_string_hdr_create(tdata->pool,
+ &STR_HNAME,
+ &pubc->etag);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sim_hdr);
+
+ } else {
+ /* Update */
+ if (pj_strcmp(&pubc->etag, &sim_hdr->hvalue))
+ pj_strdup(tdata->pool, &sim_hdr->hvalue, &pubc->etag);
+ }
+ }
+
+ status = pjsip_publishc_send(pubc, tdata);
+ if (status == PJ_EPENDING) {
+ pj_assert(!"Not expected");
+ pj_list_erase(tdata);
+ pjsip_tx_data_dec_ref(tdata);
+ } else if (status == PJ_SUCCESS) {
+ break;
+ }
+ }
+ pj_mutex_unlock(pubc->mutex);
+ }
+
+ /* No longer in callback. */
+ --pubc->in_callback;
+
+ /* Delete the record if user destroy pubc during the callback. */
+ if (pubc->_delete_flag && pubc->pending_tsx==0) {
+ pjsip_publishc_destroy(pubc);
+ }
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_send(pjsip_publishc *pubc,
+ pjsip_tx_data *tdata)
+{
+ pj_status_t status;
+ pjsip_cseq_hdr *cseq_hdr;
+ pj_uint32_t cseq;
+
+ PJ_ASSERT_RETURN(pubc && tdata, PJ_EINVAL);
+
+ /* Make sure we don't have pending transaction. */
+ pj_mutex_lock(pubc->mutex);
+ if (pubc->pending_tsx) {
+ if (pubc->opt.queue_request) {
+ pj_list_push_back(&pubc->pending_reqs, tdata);
+ pj_mutex_unlock(pubc->mutex);
+ PJ_LOG(4,(THIS_FILE, "Request is queued, pubc has another "
+ "transaction pending"));
+ return PJ_EPENDING;
+ } else {
+ pjsip_tx_data_dec_ref(tdata);
+ pj_mutex_unlock(pubc->mutex);
+ PJ_LOG(4,(THIS_FILE, "Unable to send request, pubc has another "
+ "transaction pending"));
+ return PJ_EBUSY;
+ }
+ }
+ pj_mutex_unlock(pubc->mutex);
+
+ /* If via_addr is set, use this address for the Via header. */
+ if (pubc->via_addr.host.slen > 0) {
+ tdata->via_addr = pubc->via_addr;
+ tdata->via_tp = pubc->via_tp;
+ }
+
+ /* Invalidate message buffer. */
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Increment CSeq */
+ cseq = ++pubc->cseq_hdr->cseq;
+ cseq_hdr = (pjsip_cseq_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
+ cseq_hdr->cseq = cseq;
+
+ /* Increment pending transaction first, since transaction callback
+ * may be called even before send_request() returns!
+ */
+ ++pubc->pending_tsx;
+ status = pjsip_endpt_send_request(pubc->endpt, tdata, -1, pubc,
+ &tsx_callback);
+ if (status!=PJ_SUCCESS) {
+ // no need to decrement, callback has been called and it should
+ // already decremented pending_tsx. Decrementing this here may
+ // cause accessing freed memory location.
+ //--pubc->pending_tsx;
+ PJ_LOG(4,(THIS_FILE, "Error sending request, status=%d", status));
+ }
+
+ return status;
+}
+
diff --git a/pjsip/src/pjsip-simple/rpid.c b/pjsip/src/pjsip-simple/rpid.c
new file mode 100644
index 0000000..9ec3bb0
--- /dev/null
+++ b/pjsip/src/pjsip-simple/rpid.c
@@ -0,0 +1,279 @@
+/* $Id: rpid.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/rpid.h>
+#include <pjsip-simple/errno.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+static const pj_str_t DM_NAME = {"xmlns:dm", 8};
+static const pj_str_t DM_VAL = {"urn:ietf:params:xml:ns:pidf:data-model", 38};
+static const pj_str_t RPID_NAME = {"xmlns:rpid", 10};
+static const pj_str_t RPID_VAL = {"urn:ietf:params:xml:ns:pidf:rpid", 32};
+
+static const pj_str_t DM_NOTE = {"dm:note", 7};
+static const pj_str_t DM_PERSON = {"dm:person", 9};
+static const pj_str_t ID = {"id", 2};
+static const pj_str_t NOTE = {"note", 4};
+static const pj_str_t RPID_ACTIVITIES = {"rpid:activities", 15};
+static const pj_str_t RPID_AWAY = {"rpid:away", 9};
+static const pj_str_t RPID_BUSY = {"rpid:busy", 9};
+static const pj_str_t RPID_UNKNOWN = {"rpid:unknown", 12};
+
+
+/* Duplicate RPID element */
+PJ_DEF(void) pjrpid_element_dup(pj_pool_t *pool, pjrpid_element *dst,
+ const pjrpid_element *src)
+{
+ pj_memcpy(dst, src, sizeof(pjrpid_element));
+ pj_strdup(pool, &dst->id, &src->id);
+ pj_strdup(pool, &dst->note, &src->note);
+}
+
+
+/* Update RPID namespaces. */
+static void update_namespaces(pjpidf_pres *pres,
+ pj_pool_t *pool)
+{
+ /* Check if namespace is already present. */
+ if (pj_xml_find_attr(pres, &DM_NAME, NULL) != NULL)
+ return;
+
+ pj_xml_add_attr(pres, pj_xml_attr_new(pool, &DM_NAME, &DM_VAL));
+ pj_xml_add_attr(pres, pj_xml_attr_new(pool, &RPID_NAME, &RPID_VAL));
+}
+
+
+/* Comparison function to find node name substring */
+static pj_bool_t substring_match(const pj_xml_node *node,
+ const char *part_name,
+ int part_len)
+{
+ pj_str_t end_name;
+
+ if (part_len < 1)
+ part_len = pj_ansi_strlen(part_name);
+
+ if (node->name.slen < part_len)
+ return PJ_FALSE;
+
+ end_name.ptr = node->name.ptr + (node->name.slen - part_len);
+ end_name.slen = part_len;
+
+ return pj_strnicmp2(&end_name, part_name, part_len)==0;
+}
+
+/* Util to find child node with the specified substring */
+static pj_xml_node *find_node(const pj_xml_node *parent,
+ const char *part_name)
+{
+ const pj_xml_node *node = parent->node_head.next,
+ *head = (pj_xml_node*) &parent->node_head;
+ int part_len = pj_ansi_strlen(part_name);
+
+ while (node != head) {
+ if (substring_match(node, part_name, part_len))
+ return (pj_xml_node*) node;
+
+ node = node->next;
+ }
+
+ return NULL;
+}
+
+/*
+ * Add RPID element into existing PIDF document.
+ */
+PJ_DEF(pj_status_t) pjrpid_add_element(pjpidf_pres *pres,
+ pj_pool_t *pool,
+ unsigned options,
+ const pjrpid_element *elem)
+{
+ pj_xml_node *nd_person, *nd_activities, *nd_activity, *nd_note;
+ pj_xml_attr *attr;
+
+ PJ_ASSERT_RETURN(pres && pool && options==0 && elem, PJ_EINVAL);
+
+ PJ_UNUSED_ARG(options);
+
+ /* Check if we need to add RPID information into the PIDF document. */
+ if (elem->id.slen==0 &&
+ elem->activity==PJRPID_ACTIVITY_UNKNOWN &&
+ elem->note.slen==0)
+ {
+ /* No RPID information to be added. */
+ return PJ_SUCCESS;
+ }
+
+ /* Add <note> to <tuple> */
+ if (elem->note.slen != 0) {
+ pj_xml_node *nd_tuple;
+
+ nd_tuple = find_node(pres, "tuple");
+
+ if (nd_tuple) {
+ nd_note = pj_xml_node_new(pool, &NOTE);
+ pj_strdup(pool, &nd_note->content, &elem->note);
+ pj_xml_add_node(nd_tuple, nd_note);
+ nd_note = NULL;
+ }
+ }
+
+ /* Update namespace */
+ update_namespaces(pres, pool);
+
+ /* Add <person> */
+ nd_person = pj_xml_node_new(pool, &DM_PERSON);
+ if (elem->id.slen != 0) {
+ attr = pj_xml_attr_new(pool, &ID, &elem->id);
+ } else {
+ pj_str_t person_id;
+ /* xs:ID must start with letter */
+ //pj_create_unique_string(pool, &person_id);
+ person_id.ptr = (char*)pj_pool_alloc(pool, PJ_GUID_STRING_LENGTH+2);
+ person_id.ptr += 2;
+ pj_generate_unique_string(&person_id);
+ person_id.ptr -= 2;
+ person_id.ptr[0] = 'p';
+ person_id.ptr[1] = 'j';
+ person_id.slen += 2;
+
+ attr = pj_xml_attr_new(pool, &ID, &person_id);
+ }
+ pj_xml_add_attr(nd_person, attr);
+ pj_xml_add_node(pres, nd_person);
+
+ /* Add <activities> */
+ nd_activities = pj_xml_node_new(pool, &RPID_ACTIVITIES);
+ pj_xml_add_node(nd_person, nd_activities);
+
+ /* Add the activity */
+ switch (elem->activity) {
+ case PJRPID_ACTIVITY_AWAY:
+ nd_activity = pj_xml_node_new(pool, &RPID_AWAY);
+ break;
+ case PJRPID_ACTIVITY_BUSY:
+ nd_activity = pj_xml_node_new(pool, &RPID_BUSY);
+ break;
+ case PJRPID_ACTIVITY_UNKNOWN:
+ default:
+ nd_activity = pj_xml_node_new(pool, &RPID_UNKNOWN);
+ break;
+ }
+ pj_xml_add_node(nd_activities, nd_activity);
+
+ /* Add custom text if required. */
+ if (elem->note.slen != 0) {
+ nd_note = pj_xml_node_new(pool, &DM_NOTE);
+ pj_strdup(pool, &nd_note->content, &elem->note);
+ pj_xml_add_node(nd_person, nd_note);
+ }
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+
+/* Get <note> element from PIDF <tuple> element */
+static pj_status_t get_tuple_note(const pjpidf_pres *pres,
+ pj_pool_t *pool,
+ pjrpid_element *elem)
+{
+ const pj_xml_node *nd_tuple, *nd_note;
+
+ nd_tuple = find_node(pres, "tuple");
+ if (!nd_tuple)
+ return PJSIP_SIMPLE_EBADRPID;
+
+ nd_note = find_node(pres, "note");
+ if (nd_note) {
+ pj_strdup(pool, &elem->note, &nd_note->content);
+ return PJ_SUCCESS;
+ }
+
+ return PJSIP_SIMPLE_EBADRPID;
+}
+
+/*
+ * Get RPID element from PIDF document, if any.
+ */
+PJ_DEF(pj_status_t) pjrpid_get_element(const pjpidf_pres *pres,
+ pj_pool_t *pool,
+ pjrpid_element *elem)
+{
+ const pj_xml_node *nd_person, *nd_activities, *nd_note = NULL;
+ const pj_xml_attr *attr;
+
+ /* Reset */
+ pj_bzero(elem, sizeof(*elem));
+ elem->activity = PJRPID_ACTIVITY_UNKNOWN;
+
+ /* Find <person> */
+ nd_person = find_node(pres, "person");
+ if (!nd_person) {
+ /* <person> not found, try to get <note> from <tuple> */
+ return get_tuple_note(pres, pool, elem);
+ }
+
+ /* Get element id attribute */
+ attr = pj_xml_find_attr((pj_xml_node*)nd_person, &ID, NULL);
+ if (attr)
+ pj_strdup(pool, &elem->id, &attr->value);
+
+ /* Get <activities> */
+ nd_activities = find_node(nd_person, "activities");
+ if (nd_activities) {
+ const pj_xml_node *nd_activity;
+
+ /* Try to get <note> from <activities> */
+ nd_note = find_node(nd_activities, "note");
+
+ /* Get the activity */
+ nd_activity = nd_activities->node_head.next;
+ if (nd_activity == nd_note)
+ nd_activity = nd_activity->next;
+
+ if (nd_activity != (pj_xml_node*) &nd_activities->node_head) {
+ if (substring_match(nd_activity, "busy", -1))
+ elem->activity = PJRPID_ACTIVITY_BUSY;
+ else if (substring_match(nd_activity, "away", -1))
+ elem->activity = PJRPID_ACTIVITY_AWAY;
+ else
+ elem->activity = PJRPID_ACTIVITY_UNKNOWN;
+
+ }
+ }
+
+ /* If <note> is not found, get <note> from <person> */
+ if (nd_note == NULL)
+ nd_note = find_node(nd_person, "note");
+
+ if (nd_note) {
+ pj_strdup(pool, &elem->note, &nd_note->content);
+ } else {
+ get_tuple_note(pres, pool, elem);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjsip/src/pjsip-simple/xpidf.c b/pjsip/src/pjsip-simple/xpidf.c
new file mode 100644
index 0000000..22801d7
--- /dev/null
+++ b/pjsip/src/pjsip-simple/xpidf.c
@@ -0,0 +1,301 @@
+/* $Id: xpidf.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-simple/xpidf.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+static pj_str_t STR_PRESENCE = { "presence", 8 };
+static pj_str_t STR_STATUS = { "status", 6 };
+static pj_str_t STR_OPEN = { "open", 4 };
+static pj_str_t STR_CLOSED = { "closed", 6 };
+static pj_str_t STR_URI = { "uri", 3 };
+static pj_str_t STR_ATOM = { "atom", 4 };
+static pj_str_t STR_ATOMID = { "atomid", 6 };
+static pj_str_t STR_ID = { "id", 2 };
+static pj_str_t STR_ADDRESS = { "address", 7 };
+static pj_str_t STR_SUBSCRIBE_PARAM = { ";method=SUBSCRIBE", 17 };
+static pj_str_t STR_PRESENTITY = { "presentity", 10 };
+static pj_str_t STR_EMPTY_STRING = { NULL, 0 };
+
+static pj_xml_node* xml_create_node(pj_pool_t *pool,
+ pj_str_t *name, const pj_str_t *value)
+{
+ pj_xml_node *node;
+
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ pj_list_init(&node->attr_head);
+ pj_list_init(&node->node_head);
+ node->name = *name;
+ if (value) pj_strdup(pool, &node->content, value);
+ else node->content.ptr=NULL, node->content.slen=0;
+
+ return node;
+}
+
+static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name,
+ const pj_str_t *value)
+{
+ pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
+ attr->name = *name;
+ pj_strdup(pool, &attr->value, value);
+ return attr;
+}
+
+
+PJ_DEF(pjxpidf_pres*) pjxpidf_create(pj_pool_t *pool, const pj_str_t *uri_cstr)
+{
+ pjxpidf_pres *pres;
+ pj_xml_node *presentity;
+ pj_xml_node *atom;
+ pj_xml_node *addr;
+ pj_xml_node *status;
+ pj_xml_attr *attr;
+ pj_str_t uri;
+ pj_str_t tmp;
+
+ /* <presence> */
+ pres = xml_create_node(pool, &STR_PRESENCE, NULL);
+
+ /* <presentity> */
+ presentity = xml_create_node(pool, &STR_PRESENTITY, NULL);
+ pj_xml_add_node(pres, presentity);
+
+ /* uri attribute */
+ uri.ptr = (char*) pj_pool_alloc(pool, uri_cstr->slen +
+ STR_SUBSCRIBE_PARAM.slen);
+ pj_strcpy( &uri, uri_cstr);
+ pj_strcat( &uri, &STR_SUBSCRIBE_PARAM);
+ attr = xml_create_attr(pool, &STR_URI, &uri);
+ pj_xml_add_attr(presentity, attr);
+
+ /* <atom> */
+ atom = xml_create_node(pool, &STR_ATOM, NULL);
+ pj_xml_add_node(pres, atom);
+
+ /* atom id */
+ pj_create_unique_string(pool, &tmp);
+ attr = xml_create_attr(pool, &STR_ATOMID, &tmp);
+ pj_xml_add_attr(atom, attr);
+
+ /* address */
+ addr = xml_create_node(pool, &STR_ADDRESS, NULL);
+ pj_xml_add_node(atom, addr);
+
+ /* address'es uri */
+ attr = xml_create_attr(pool, &STR_URI, uri_cstr);
+ pj_xml_add_attr(addr, attr);
+
+ /* status */
+ status = xml_create_node(pool, &STR_STATUS, NULL);
+ pj_xml_add_node(addr, status);
+
+ /* status attr */
+ attr = xml_create_attr(pool, &STR_STATUS, &STR_OPEN);
+ pj_xml_add_attr(status, attr);
+
+ return pres;
+}
+
+
+
+PJ_DEF(pjxpidf_pres*) pjxpidf_parse(pj_pool_t *pool, char *text, pj_size_t len)
+{
+ pjxpidf_pres *pres;
+ pj_xml_node *node;
+
+ pres = pj_xml_parse(pool, text, len);
+ if (!pres)
+ return NULL;
+
+ /* Validate <presence> */
+ if (pj_stricmp(&pres->name, &STR_PRESENCE) != 0)
+ return NULL;
+
+ /* Validate <presentity> */
+ node = pj_xml_find_node(pres, &STR_PRESENTITY);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &STR_URI, NULL) == NULL)
+ return NULL;
+
+ /* Validate <atom> */
+ node = pj_xml_find_node(pres, &STR_ATOM);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &STR_ATOMID, NULL) == NULL &&
+ pj_xml_find_attr(node, &STR_ID, NULL) == NULL)
+ {
+ return NULL;
+ }
+
+ /* Address */
+ node = pj_xml_find_node(node, &STR_ADDRESS);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &STR_URI, NULL) == NULL)
+ return NULL;
+
+
+ /* Status */
+ node = pj_xml_find_node(node, &STR_STATUS);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &STR_STATUS, NULL) == NULL)
+ return NULL;
+
+ return pres;
+}
+
+
+PJ_DEF(int) pjxpidf_print( pjxpidf_pres *pres, char *text, pj_size_t len)
+{
+ return pj_xml_print(pres, text, len, PJ_TRUE);
+}
+
+
+PJ_DEF(pj_str_t*) pjxpidf_get_uri(pjxpidf_pres *pres)
+{
+ pj_xml_node *presentity;
+ pj_xml_attr *attr;
+
+ presentity = pj_xml_find_node(pres, &STR_PRESENTITY);
+ if (!presentity)
+ return &STR_EMPTY_STRING;
+
+ attr = pj_xml_find_attr(presentity, &STR_URI, NULL);
+ if (!attr)
+ return &STR_EMPTY_STRING;
+
+ return &attr->value;
+}
+
+
+PJ_DEF(pj_status_t) pjxpidf_set_uri(pj_pool_t *pool, pjxpidf_pres *pres,
+ const pj_str_t *uri)
+{
+ pj_xml_node *presentity;
+ pj_xml_node *atom;
+ pj_xml_node *addr;
+ pj_xml_attr *attr;
+ pj_str_t dup_uri;
+
+ presentity = pj_xml_find_node(pres, &STR_PRESENTITY);
+ if (!presentity) {
+ pj_assert(0);
+ return -1;
+ }
+ atom = pj_xml_find_node(pres, &STR_ATOM);
+ if (!atom) {
+ pj_assert(0);
+ return -1;
+ }
+ addr = pj_xml_find_node(atom, &STR_ADDRESS);
+ if (!addr) {
+ pj_assert(0);
+ return -1;
+ }
+
+ /* Set uri in presentity */
+ attr = pj_xml_find_attr(presentity, &STR_URI, NULL);
+ if (!attr) {
+ pj_assert(0);
+ return -1;
+ }
+ pj_strdup(pool, &dup_uri, uri);
+ attr->value = dup_uri;
+
+ /* Set uri in address. */
+ attr = pj_xml_find_attr(addr, &STR_URI, NULL);
+ if (!attr) {
+ pj_assert(0);
+ return -1;
+ }
+ attr->value = dup_uri;
+
+ return 0;
+}
+
+
+PJ_DEF(pj_bool_t) pjxpidf_get_status(pjxpidf_pres *pres)
+{
+ pj_xml_node *atom;
+ pj_xml_node *addr;
+ pj_xml_node *status;
+ pj_xml_attr *attr;
+
+ atom = pj_xml_find_node(pres, &STR_ATOM);
+ if (!atom) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+ addr = pj_xml_find_node(atom, &STR_ADDRESS);
+ if (!addr) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+ status = pj_xml_find_node(addr, &STR_STATUS);
+ if (!status) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+ attr = pj_xml_find_attr(status, &STR_STATUS, NULL);
+ if (!attr) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+
+ return pj_stricmp(&attr->value, &STR_OPEN)==0 ? PJ_TRUE : PJ_FALSE;
+}
+
+
+PJ_DEF(pj_status_t) pjxpidf_set_status(pjxpidf_pres *pres, pj_bool_t online_status)
+{
+ pj_xml_node *atom;
+ pj_xml_node *addr;
+ pj_xml_node *status;
+ pj_xml_attr *attr;
+
+ atom = pj_xml_find_node(pres, &STR_ATOM);
+ if (!atom) {
+ pj_assert(0);
+ return -1;
+ }
+ addr = pj_xml_find_node(atom, &STR_ADDRESS);
+ if (!addr) {
+ pj_assert(0);
+ return -1;
+ }
+ status = pj_xml_find_node(addr, &STR_STATUS);
+ if (!status) {
+ pj_assert(0);
+ return -1;
+ }
+ attr = pj_xml_find_attr(status, &STR_STATUS, NULL);
+ if (!attr) {
+ pj_assert(0);
+ return -1;
+ }
+
+ attr->value = ( online_status ? STR_OPEN : STR_CLOSED );
+ return 0;
+}
+
diff --git a/pjsip/src/pjsip-ua/sip_100rel.c b/pjsip/src/pjsip-ua/sip_100rel.c
new file mode 100644
index 0000000..07122c4
--- /dev/null
+++ b/pjsip/src/pjsip-ua/sip_100rel.c
@@ -0,0 +1,905 @@
+/* $Id: sip_100rel.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-ua/sip_100rel.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_transaction.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+
+#define THIS_FILE "sip_100rel.c"
+
+/* PRACK method */
+PJ_DEF_DATA(const pjsip_method) pjsip_prack_method =
+{
+ PJSIP_OTHER_METHOD,
+ { "PRACK", 5 }
+};
+
+typedef struct dlg_data dlg_data;
+
+/*
+ * Static prototypes.
+ */
+static pj_status_t mod_100rel_load(pjsip_endpoint *endpt);
+
+static void on_retransmit(pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry);
+
+
+const pj_str_t tag_100rel = { "100rel", 6 };
+const pj_str_t RSEQ = { "RSeq", 4 };
+const pj_str_t RACK = { "RAck", 4 };
+
+
+/* 100rel module */
+static struct mod_100rel
+{
+ pjsip_module mod;
+ pjsip_endpoint *endpt;
+} mod_100rel =
+{
+ {
+ NULL, NULL, /* prev, next. */
+ { "mod-100rel", 10 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */
+ &mod_100rel_load, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ NULL, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+ }
+
+};
+
+/* List of pending transmission (may include the final response as well) */
+typedef struct tx_data_list_t
+{
+ PJ_DECL_LIST_MEMBER(struct tx_data_list_t);
+ pj_uint32_t rseq;
+ pjsip_tx_data *tdata;
+} tx_data_list_t;
+
+
+/* Below, UAS and UAC roles are of the INVITE transaction */
+
+/* UAS state. */
+typedef struct uas_state_t
+{
+ pj_int32_t cseq;
+ pj_uint32_t rseq; /* Initialized to -1 */
+ tx_data_list_t tx_data_list;
+ unsigned retransmit_count;
+ pj_timer_entry retransmit_timer;
+} uas_state_t;
+
+
+/* UAC state */
+typedef struct uac_state_t
+{
+ pj_str_t tag; /* To tag */
+ pj_int32_t cseq;
+ pj_uint32_t rseq; /* Initialized to -1 */
+ struct uac_state_t *next; /* next call leg */
+} uac_state_t;
+
+
+/* State attached to each dialog. */
+struct dlg_data
+{
+ pjsip_inv_session *inv;
+ uas_state_t *uas_state;
+ uac_state_t *uac_state_list;
+};
+
+
+/*****************************************************************************
+ **
+ ** Module
+ **
+ *****************************************************************************
+ */
+static pj_status_t mod_100rel_load(pjsip_endpoint *endpt)
+{
+ mod_100rel.endpt = endpt;
+ pjsip_endpt_add_capability(endpt, &mod_100rel.mod,
+ PJSIP_H_ALLOW, NULL,
+ 1, &pjsip_prack_method.name);
+ pjsip_endpt_add_capability(endpt, &mod_100rel.mod,
+ PJSIP_H_SUPPORTED, NULL,
+ 1, &tag_100rel);
+
+ return PJ_SUCCESS;
+}
+
+static pjsip_require_hdr *find_req_hdr(pjsip_msg *msg)
+{
+ pjsip_require_hdr *hreq;
+
+ hreq = (pjsip_require_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL);
+
+ while (hreq) {
+ unsigned i;
+ for (i=0; i<hreq->count; ++i) {
+ if (!pj_stricmp(&hreq->values[i], &tag_100rel)) {
+ return hreq;
+ }
+ }
+
+ if ((void*)hreq->next == (void*)&msg->hdr)
+ return NULL;
+
+ hreq = (pjsip_require_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, hreq->next);
+
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Get PRACK method constant.
+ */
+PJ_DEF(const pjsip_method*) pjsip_get_prack_method(void)
+{
+ return &pjsip_prack_method;
+}
+
+
+/*
+ * init module
+ */
+PJ_DEF(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt)
+{
+ if (mod_100rel.mod.id != -1)
+ return PJ_SUCCESS;
+
+ return pjsip_endpt_register_module(endpt, &mod_100rel.mod);
+}
+
+
+/*
+ * API: attach 100rel support in invite session. Called by
+ * sip_inv.c
+ */
+PJ_DEF(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv)
+{
+ dlg_data *dd;
+
+ /* Check that 100rel module has been initialized */
+ PJ_ASSERT_RETURN(mod_100rel.mod.id >= 0, PJ_EINVALIDOP);
+
+ /* Create and attach as dialog usage */
+ dd = PJ_POOL_ZALLOC_T(inv->dlg->pool, dlg_data);
+ dd->inv = inv;
+ pjsip_dlg_add_usage(inv->dlg, &mod_100rel.mod, (void*)dd);
+
+ PJ_LOG(5,(dd->inv->dlg->obj_name, "100rel module attached"));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Check if incoming response has reliable provisional response feature.
+ */
+PJ_DEF(pj_bool_t) pjsip_100rel_is_reliable(pjsip_rx_data *rdata)
+{
+ pjsip_msg *msg = rdata->msg_info.msg;
+
+ PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG, PJ_FALSE);
+
+ return msg->line.status.code > 100 && msg->line.status.code < 200 &&
+ rdata->msg_info.require != NULL &&
+ find_req_hdr(msg) != NULL;
+}
+
+
+/*
+ * Create PRACK request for the incoming reliable provisional response.
+ */
+PJ_DEF(pj_status_t) pjsip_100rel_create_prack( pjsip_inv_session *inv,
+ pjsip_rx_data *rdata,
+ pjsip_tx_data **p_tdata)
+{
+ dlg_data *dd;
+ uac_state_t *uac_state = NULL;
+ const pj_str_t *to_tag = &rdata->msg_info.to->tag;
+ pjsip_transaction *tsx;
+ pjsip_msg *msg;
+ pjsip_generic_string_hdr *rseq_hdr;
+ pjsip_generic_string_hdr *rack_hdr;
+ unsigned rseq;
+ pj_str_t rack;
+ char rack_buf[80];
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ *p_tdata = NULL;
+
+ dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
+ PJ_ASSERT_RETURN(dd != NULL, PJSIP_ENOTINITIALIZED);
+
+ tsx = pjsip_rdata_get_tsx(rdata);
+ msg = rdata->msg_info.msg;
+
+ /* Check our assumptions */
+ pj_assert( tsx->role == PJSIP_ROLE_UAC &&
+ tsx->method.id == PJSIP_INVITE_METHOD &&
+ msg->line.status.code > 100 &&
+ msg->line.status.code < 200);
+
+
+ /* Get the RSeq header */
+ rseq_hdr = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(msg, &RSEQ, NULL);
+ if (rseq_hdr == NULL) {
+ PJ_LOG(4,(dd->inv->dlg->obj_name,
+ "Ignoring 100rel response with no RSeq header"));
+ return PJSIP_EMISSINGHDR;
+ }
+ rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue);
+
+ /* Find UAC state for the specified call leg */
+ uac_state = dd->uac_state_list;
+ while (uac_state) {
+ if (pj_strcmp(&uac_state->tag, to_tag)==0)
+ break;
+ uac_state = uac_state->next;
+ }
+
+ /* Create new UAC state if we don't have one */
+ if (uac_state == NULL) {
+ uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool, uac_state_t);
+ uac_state->cseq = rdata->msg_info.cseq->cseq;
+ uac_state->rseq = rseq - 1;
+ pj_strdup(dd->inv->dlg->pool, &uac_state->tag, to_tag);
+ uac_state->next = dd->uac_state_list;
+ dd->uac_state_list = uac_state;
+ }
+
+ /* If this is from new INVITE transaction, reset UAC state. */
+ if (rdata->msg_info.cseq->cseq != uac_state->cseq) {
+ uac_state->cseq = rdata->msg_info.cseq->cseq;
+ uac_state->rseq = rseq - 1;
+ }
+
+ /* Ignore provisional response retransmission */
+ if (rseq <= uac_state->rseq) {
+ /* This should have been handled before */
+ return PJ_EIGNORED;
+
+ /* Ignore provisional response with out-of-order RSeq */
+ } else if (rseq != uac_state->rseq + 1) {
+ PJ_LOG(4,(dd->inv->dlg->obj_name,
+ "Ignoring 100rel response because RSeq jump "
+ "(expecting %u, got %u)",
+ uac_state->rseq+1, rseq));
+ return PJ_EIGNORED;
+ }
+
+ /* Update our RSeq */
+ uac_state->rseq = rseq;
+
+ /* Create PRACK */
+ status = pjsip_dlg_create_request(dd->inv->dlg, &pjsip_prack_method,
+ -1, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* If this response is a forked response from a different call-leg,
+ * update the req URI (https://trac.pjsip.org/repos/ticket/1364)
+ */
+ if (pj_strcmp(&uac_state->tag, &dd->inv->dlg->remote.info->tag)) {
+ const pjsip_contact_hdr *mhdr;
+
+ mhdr = (const pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg,
+ PJSIP_H_CONTACT, NULL);
+ if (!mhdr || !mhdr->uri) {
+ PJ_LOG(4,(dd->inv->dlg->obj_name,
+ "Ignoring 100rel response with no or "
+ "invalid Contact header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_EIGNORED;
+ }
+ tdata->msg->line.req.uri = (pjsip_uri*)
+ pjsip_uri_clone(tdata->pool, mhdr->uri);
+ }
+
+ /* Create RAck header */
+ rack.ptr = rack_buf;
+ rack.slen = pj_ansi_snprintf(rack.ptr, sizeof(rack_buf),
+ "%u %u %.*s",
+ rseq, rdata->msg_info.cseq->cseq,
+ (int)tsx->method.name.slen,
+ tsx->method.name.ptr);
+ rack_hdr = pjsip_generic_string_hdr_create(tdata->pool, &RACK, &rack);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) rack_hdr);
+
+ /* Done */
+ *p_tdata = tdata;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Send PRACK request.
+ */
+PJ_DEF(pj_status_t) pjsip_100rel_send_prack( pjsip_inv_session *inv,
+ pjsip_tx_data *tdata)
+{
+ dlg_data *dd;
+
+ dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
+ PJ_ASSERT_ON_FAIL(dd != NULL,
+ {pjsip_tx_data_dec_ref(tdata); return PJSIP_ENOTINITIALIZED; });
+
+ return pjsip_dlg_send_request(inv->dlg, tdata,
+ mod_100rel.mod.id, (void*) dd);
+
+}
+
+
+/*
+ * Notify 100rel module that the invite session has been disconnected.
+ */
+PJ_DEF(pj_status_t) pjsip_100rel_end_session(pjsip_inv_session *inv)
+{
+ dlg_data *dd;
+
+ dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
+ if (!dd)
+ return PJ_SUCCESS;
+
+ /* Make sure we don't have pending transmission */
+ if (dd->uas_state) {
+ pj_assert(!dd->uas_state->retransmit_timer.id);
+ pj_assert(pj_list_empty(&dd->uas_state->tx_data_list));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static void parse_rack(const pj_str_t *rack,
+ pj_uint32_t *p_rseq, pj_int32_t *p_seq,
+ pj_str_t *p_method)
+{
+ const char *p = rack->ptr, *end = p + rack->slen;
+ pj_str_t token;
+
+ token.ptr = (char*)p;
+ while (p < end && pj_isdigit(*p))
+ ++p;
+ token.slen = p - token.ptr;
+ *p_rseq = pj_strtoul(&token);
+
+ ++p;
+ token.ptr = (char*)p;
+ while (p < end && pj_isdigit(*p))
+ ++p;
+ token.slen = p - token.ptr;
+ *p_seq = pj_strtoul(&token);
+
+ ++p;
+ if (p < end) {
+ p_method->ptr = (char*)p;
+ p_method->slen = end - p;
+ } else {
+ p_method->ptr = NULL;
+ p_method->slen = 0;
+ }
+}
+
+/* Clear all responses in the transmission list */
+static void clear_all_responses(dlg_data *dd)
+{
+ tx_data_list_t *tl;
+
+ tl = dd->uas_state->tx_data_list.next;
+ while (tl != &dd->uas_state->tx_data_list) {
+ pjsip_tx_data_dec_ref(tl->tdata);
+ tl = tl->next;
+ }
+ pj_list_init(&dd->uas_state->tx_data_list);
+}
+
+
+/*
+ * Handle incoming PRACK request.
+ */
+PJ_DEF(pj_status_t) pjsip_100rel_on_rx_prack( pjsip_inv_session *inv,
+ pjsip_rx_data *rdata)
+{
+ dlg_data *dd;
+ pjsip_transaction *tsx;
+ pjsip_msg *msg;
+ pjsip_generic_string_hdr *rack_hdr;
+ pjsip_tx_data *tdata;
+ pj_uint32_t rseq;
+ pj_int32_t cseq;
+ pj_str_t method;
+ pj_status_t status;
+
+ tsx = pjsip_rdata_get_tsx(rdata);
+ pj_assert(tsx != NULL);
+
+ msg = rdata->msg_info.msg;
+
+ dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
+ if (dd == NULL) {
+ /* UAC sends us PRACK while we didn't send reliable provisional
+ * response. Respond with 400 (?)
+ */
+ const pj_str_t reason = pj_str("Unexpected PRACK");
+
+ status = pjsip_dlg_create_response(inv->dlg, rdata, 400,
+ &reason, &tdata);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_dlg_send_response(inv->dlg, tsx, tdata);
+ }
+ return PJSIP_ENOTINITIALIZED;
+ }
+
+ /* Always reply with 200/OK for PRACK */
+ status = pjsip_dlg_create_response(inv->dlg, rdata, 200, NULL, &tdata);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_dlg_send_response(inv->dlg, tsx, tdata);
+ }
+
+ /* Ignore if we don't have pending transmission */
+ if (dd->uas_state == NULL || pj_list_empty(&dd->uas_state->tx_data_list)) {
+ PJ_LOG(4,(dd->inv->dlg->obj_name,
+ "PRACK ignored - no pending response"));
+ return PJ_EIGNORED;
+ }
+
+ /* Find RAck header */
+ rack_hdr = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(msg, &RACK, NULL);
+ if (!rack_hdr) {
+ /* RAck header not found */
+ PJ_LOG(4,(dd->inv->dlg->obj_name, "No RAck header"));
+ return PJSIP_EMISSINGHDR;
+ }
+
+ /* Parse RAck header */
+ parse_rack(&rack_hdr->hvalue, &rseq, &cseq, &method);
+
+
+ /* Match RAck against outgoing transmission */
+ if (rseq == dd->uas_state->tx_data_list.next->rseq &&
+ cseq == dd->uas_state->cseq)
+ {
+ /*
+ * Yes this PRACK matches outgoing transmission.
+ */
+ tx_data_list_t *tl = dd->uas_state->tx_data_list.next;
+
+ if (dd->uas_state->retransmit_timer.id) {
+ pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
+ &dd->uas_state->retransmit_timer);
+ dd->uas_state->retransmit_timer.id = PJ_FALSE;
+ }
+
+ /* Remove from the list */
+ if (tl != &dd->uas_state->tx_data_list) {
+ pj_list_erase(tl);
+
+ /* Destroy the response */
+ pjsip_tx_data_dec_ref(tl->tdata);
+ }
+
+ /* Schedule next packet */
+ dd->uas_state->retransmit_count = 0;
+ if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
+ on_retransmit(NULL, &dd->uas_state->retransmit_timer);
+ }
+
+ } else {
+ /* No it doesn't match */
+ PJ_LOG(4,(dd->inv->dlg->obj_name,
+ "Rx PRACK with no matching reliable response"));
+ return PJ_EIGNORED;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This is retransmit timer callback, called initially to send the response,
+ * and subsequently when the retransmission time elapses.
+ */
+static void on_retransmit(pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ dlg_data *dd;
+ tx_data_list_t *tl;
+ pjsip_tx_data *tdata;
+ pj_bool_t final;
+ pj_time_val delay;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ dd = (dlg_data*) entry->user_data;
+
+ entry->id = PJ_FALSE;
+
+ ++dd->uas_state->retransmit_count;
+ if (dd->uas_state->retransmit_count >= 7) {
+ /* If a reliable provisional response is retransmitted for
+ 64*T1 seconds without reception of a corresponding PRACK,
+ the UAS SHOULD reject the original request with a 5xx
+ response.
+ */
+ pj_str_t reason = pj_str("Reliable response timed out");
+ pj_status_t status;
+
+ /* Clear all pending responses */
+ clear_all_responses(dd);
+
+ /* Send 500 response */
+ status = pjsip_inv_end_session(dd->inv, 500, &reason, &tdata);
+ if (status == PJ_SUCCESS) {
+ pjsip_dlg_send_response(dd->inv->dlg,
+ dd->inv->invite_tsx,
+ tdata);
+ }
+ return;
+ }
+
+ pj_assert(!pj_list_empty(&dd->uas_state->tx_data_list));
+ tl = dd->uas_state->tx_data_list.next;
+ tdata = tl->tdata;
+
+ pjsip_tx_data_add_ref(tdata);
+ final = tdata->msg->line.status.code >= 200;
+
+ if (dd->uas_state->retransmit_count == 1) {
+ pjsip_tsx_send_msg(dd->inv->invite_tsx, tdata);
+ } else {
+ pjsip_tsx_retransmit_no_state(dd->inv->invite_tsx, tdata);
+ }
+
+ if (final) {
+ /* This is final response, which will be retransmitted by
+ * UA layer. There's no more task to do, so clear the
+ * transmission list and bail out.
+ */
+ clear_all_responses(dd);
+ return;
+ }
+
+ /* Schedule next retransmission */
+ if (dd->uas_state->retransmit_count < 6) {
+ delay.sec = 0;
+ delay.msec = (1 << dd->uas_state->retransmit_count) *
+ pjsip_cfg()->tsx.t1;
+ pj_time_val_normalize(&delay);
+ } else {
+ delay.sec = 1;
+ delay.msec = 500;
+ }
+
+
+ pjsip_endpt_schedule_timer(dd->inv->dlg->endpt,
+ &dd->uas_state->retransmit_timer,
+ &delay);
+
+ entry->id = PJ_TRUE;
+}
+
+
+/* Clone response. */
+static pjsip_tx_data *clone_tdata(dlg_data *dd,
+ const pjsip_tx_data *src)
+{
+ pjsip_tx_data *dst;
+ const pjsip_hdr *hsrc;
+ pjsip_msg *msg;
+ pj_status_t status;
+
+ status = pjsip_endpt_create_tdata(dd->inv->dlg->endpt, &dst);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ msg = pjsip_msg_create(dst->pool, PJSIP_RESPONSE_MSG);
+ dst->msg = msg;
+ pjsip_tx_data_add_ref(dst);
+
+ /* Duplicate status line */
+ msg->line.status.code = src->msg->line.status.code;
+ pj_strdup(dst->pool, &msg->line.status.reason,
+ &src->msg->line.status.reason);
+
+ /* Duplicate all headers */
+ hsrc = src->msg->hdr.next;
+ while (hsrc != &src->msg->hdr) {
+ pjsip_hdr *h = (pjsip_hdr*) pjsip_hdr_clone(dst->pool, hsrc);
+ pjsip_msg_add_hdr(msg, h);
+ hsrc = hsrc->next;
+ }
+
+ /* Duplicate message body */
+ if (src->msg->body)
+ msg->body = pjsip_msg_body_clone(dst->pool, src->msg->body);
+
+ PJ_LOG(5,(dd->inv->dlg->obj_name,
+ "Reliable response %s created",
+ pjsip_tx_data_get_info(dst)));
+
+ return dst;
+}
+
+
+/* Check if any pending response in transmission list has SDP */
+static pj_bool_t has_sdp(dlg_data *dd)
+{
+ tx_data_list_t *tl;
+
+ tl = dd->uas_state->tx_data_list.next;
+ while (tl != &dd->uas_state->tx_data_list) {
+ if (tl->tdata->msg->body)
+ return PJ_TRUE;
+ tl = tl->next;
+ }
+
+ return PJ_FALSE;
+}
+
+
+/* Send response reliably */
+PJ_DEF(pj_status_t) pjsip_100rel_tx_response(pjsip_inv_session *inv,
+ pjsip_tx_data *tdata)
+{
+ pjsip_cseq_hdr *cseq_hdr;
+ pjsip_generic_string_hdr *rseq_hdr;
+ pjsip_require_hdr *req_hdr;
+ int status_code;
+ dlg_data *dd;
+ pjsip_tx_data *old_tdata;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG,
+ PJSIP_ENOTRESPONSEMSG);
+
+ status_code = tdata->msg->line.status.code;
+
+ /* 100 response doesn't need PRACK */
+ if (status_code == 100)
+ return pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
+
+
+ /* Get the 100rel data attached to this dialog */
+ dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
+ PJ_ASSERT_RETURN(dd != NULL, PJ_EINVALIDOP);
+
+
+ /* Clone tdata.
+ * We need to clone tdata because we may need to keep it in our
+ * retransmission list, while the original dialog may modify it
+ * if it wants to send another response.
+ */
+ old_tdata = tdata;
+ tdata = clone_tdata(dd, old_tdata);
+ pjsip_tx_data_dec_ref(old_tdata);
+
+
+ /* Get CSeq header, and make sure this is INVITE response */
+ cseq_hdr = (pjsip_cseq_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
+ PJ_ASSERT_RETURN(cseq_hdr != NULL, PJ_EBUG);
+ PJ_ASSERT_RETURN(cseq_hdr->method.id == PJSIP_INVITE_METHOD,
+ PJ_EINVALIDOP);
+
+ /* Remove existing Require header */
+ req_hdr = find_req_hdr(tdata->msg);
+ if (req_hdr) {
+ pj_list_erase(req_hdr);
+ }
+
+ /* Remove existing RSeq header */
+ rseq_hdr = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(tdata->msg, &RSEQ, NULL);
+ if (rseq_hdr)
+ pj_list_erase(rseq_hdr);
+
+ /* Different treatment for provisional and final response */
+ if (status_code/100 == 2) {
+
+ /* RFC 3262 Section 3: UAS Behavior:
+
+ The UAS MAY send a final response to the initial request
+ before having received PRACKs for all unacknowledged
+ reliable provisional responses, unless the final response
+ is 2xx and any of the unacknowledged reliable provisional
+ responses contained a session description. In that case,
+ it MUST NOT send a final response until those provisional
+ responses are acknowledged.
+ */
+
+ if (dd->uas_state && has_sdp(dd)) {
+ /* Yes we have transmitted 1xx with SDP reliably.
+ * In this case, must queue the 2xx response.
+ */
+ tx_data_list_t *tl;
+
+ tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t);
+ tl->tdata = tdata;
+ tl->rseq = (pj_uint32_t)-1;
+ pj_list_push_back(&dd->uas_state->tx_data_list, tl);
+
+ /* Will send later */
+ status = PJ_SUCCESS;
+
+ PJ_LOG(4,(dd->inv->dlg->obj_name,
+ "2xx response will be sent after PRACK"));
+
+ } else if (dd->uas_state) {
+ /*
+ RFC 3262 Section 3: UAS Behavior:
+
+ If the UAS does send a final response when reliable
+ responses are still unacknowledged, it SHOULD NOT
+ continue to retransmit the unacknowledged reliable
+ provisional responses, but it MUST be prepared to
+ process PRACK requests for those outstanding
+ responses.
+ */
+
+ PJ_LOG(4,(dd->inv->dlg->obj_name,
+ "No SDP sent so far, sending 2xx now"));
+
+ /* Cancel the retransmit timer */
+ if (dd->uas_state->retransmit_timer.id) {
+ pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
+ &dd->uas_state->retransmit_timer);
+ dd->uas_state->retransmit_timer.id = PJ_FALSE;
+ }
+
+ /* Clear all pending responses (drop 'em) */
+ clear_all_responses(dd);
+
+ /* And transmit the 2xx response */
+ status=pjsip_dlg_send_response(inv->dlg,
+ inv->invite_tsx, tdata);
+
+ } else {
+ /* We didn't send any reliable provisional response */
+
+ /* Transmit the 2xx response */
+ status=pjsip_dlg_send_response(inv->dlg,
+ inv->invite_tsx, tdata);
+ }
+
+ } else if (status_code >= 300) {
+
+ /*
+ RFC 3262 Section 3: UAS Behavior:
+
+ If the UAS does send a final response when reliable
+ responses are still unacknowledged, it SHOULD NOT
+ continue to retransmit the unacknowledged reliable
+ provisional responses, but it MUST be prepared to
+ process PRACK requests for those outstanding
+ responses.
+ */
+
+ /* Cancel the retransmit timer */
+ if (dd->uas_state && dd->uas_state->retransmit_timer.id) {
+ pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
+ &dd->uas_state->retransmit_timer);
+ dd->uas_state->retransmit_timer.id = PJ_FALSE;
+
+ /* Clear all pending responses (drop 'em) */
+ clear_all_responses(dd);
+ }
+
+ /* And transmit the 2xx response */
+ status=pjsip_dlg_send_response(inv->dlg,
+ inv->invite_tsx, tdata);
+
+ } else {
+ /*
+ * This is provisional response.
+ */
+ char rseq_str[32];
+ pj_str_t rseq;
+ tx_data_list_t *tl;
+
+ /* Create UAS state if we don't have one */
+ if (dd->uas_state == NULL) {
+ dd->uas_state = PJ_POOL_ZALLOC_T(inv->dlg->pool,
+ uas_state_t);
+ dd->uas_state->cseq = cseq_hdr->cseq;
+ dd->uas_state->rseq = pj_rand() % 0x7FFF;
+ pj_list_init(&dd->uas_state->tx_data_list);
+ dd->uas_state->retransmit_timer.user_data = dd;
+ dd->uas_state->retransmit_timer.cb = &on_retransmit;
+ }
+
+ /* Check that CSeq match */
+ PJ_ASSERT_RETURN(cseq_hdr->cseq == dd->uas_state->cseq,
+ PJ_EINVALIDOP);
+
+ /* Add Require header */
+ req_hdr = pjsip_require_hdr_create(tdata->pool);
+ req_hdr->count = 1;
+ req_hdr->values[0] = tag_100rel;
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)req_hdr);
+
+ /* Add RSeq header */
+ pj_ansi_snprintf(rseq_str, sizeof(rseq_str), "%u",
+ dd->uas_state->rseq);
+ rseq = pj_str(rseq_str);
+ rseq_hdr = pjsip_generic_string_hdr_create(tdata->pool,
+ &RSEQ, &rseq);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rseq_hdr);
+
+ /* Create list entry for this response */
+ tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t);
+ tl->tdata = tdata;
+ tl->rseq = dd->uas_state->rseq++;
+
+ /* Add to queue if there's pending response, otherwise
+ * transmit immediately.
+ */
+ if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
+
+ int code = tdata->msg->line.status.code;
+
+ /* Will send later */
+ pj_list_push_back(&dd->uas_state->tx_data_list, tl);
+ status = PJ_SUCCESS;
+
+ PJ_LOG(4,(dd->inv->dlg->obj_name,
+ "Reliable %d response enqueued (%d pending)",
+ code, pj_list_size(&dd->uas_state->tx_data_list)));
+
+ } else {
+ pj_list_push_back(&dd->uas_state->tx_data_list, tl);
+
+ dd->uas_state->retransmit_count = 0;
+ on_retransmit(NULL, &dd->uas_state->retransmit_timer);
+ status = PJ_SUCCESS;
+ }
+
+ }
+
+ return status;
+}
+
+
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
new file mode 100644
index 0000000..8db5308
--- /dev/null
+++ b/pjsip/src/pjsip-ua/sip_inv.c
@@ -0,0 +1,4491 @@
+/* $Id: sip_inv.c 4156 2012-06-06 07:24:08Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-ua/sip_inv.h>
+#include <pjsip-ua/sip_100rel.h>
+#include <pjsip-ua/sip_timer.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_multipart.h>
+#include <pjsip/sip_transaction.h>
+#include <pjmedia/sdp.h>
+#include <pjmedia/sdp_neg.h>
+#include <pjmedia/errno.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pj/os.h>
+#include <pj/log.h>
+#include <pj/rand.h>
+
+/*
+ * Note on offer/answer:
+ *
+ * The offer/answer framework in this implementation assumes the occurence
+ * of SDP in a particular request/response according to this table:
+
+ offer answer Note:
+ ========================================================================
+ INVITE X INVITE may contain offer
+ 18x/INVITE X X Response may contain offer or answer
+ 2xx/INVITE X X Response may contain offer or answer
+ ACK X ACK may contain answer
+
+ PRACK X PRACK can only contain answer
+ 2xx/PRACK Response may not have offer nor answer
+
+ UPDATE X UPDATE may only contain offer
+ 2xx/UPDATE X Response may only contain answer
+ ========================================================================
+
+ *
+ */
+
+#define THIS_FILE "sip_inv.c"
+
+static const char *inv_state_names[] =
+{
+ "NULL",
+ "CALLING",
+ "INCOMING",
+ "EARLY",
+ "CONNECTING",
+ "CONFIRMED",
+ "DISCONNCTD",
+ "TERMINATED",
+};
+
+/* UPDATE method */
+static const pjsip_method pjsip_update_method =
+{
+ PJSIP_OTHER_METHOD,
+ { "UPDATE", 6 }
+};
+
+#define POOL_INIT_SIZE 256
+#define POOL_INC_SIZE 256
+
+/*
+ * Static prototypes.
+ */
+static pj_status_t mod_inv_load(pjsip_endpoint *endpt);
+static pj_status_t mod_inv_unload(void);
+static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata);
+static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata);
+static void mod_inv_on_tsx_state(pjsip_transaction*, pjsip_event*);
+
+static void inv_on_state_null( pjsip_inv_session *inv, pjsip_event *e);
+static void inv_on_state_calling( pjsip_inv_session *inv, pjsip_event *e);
+static void inv_on_state_incoming( pjsip_inv_session *inv, pjsip_event *e);
+static void inv_on_state_early( pjsip_inv_session *inv, pjsip_event *e);
+static void inv_on_state_connecting( pjsip_inv_session *inv, pjsip_event *e);
+static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e);
+static void inv_on_state_disconnected( pjsip_inv_session *inv, pjsip_event *e);
+
+static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
+ pjsip_transaction *tsx,
+ pjsip_rx_data *rdata);
+static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv );
+static pjsip_msg_body *create_sdp_body(pj_pool_t *pool,
+ const pjmedia_sdp_session *c_sdp);
+static pj_status_t process_answer( pjsip_inv_session *inv,
+ int st_code,
+ pjsip_tx_data *tdata,
+ const pjmedia_sdp_session *local_sdp);
+
+static pj_status_t handle_timer_response(pjsip_inv_session *inv,
+ const pjsip_rx_data *rdata,
+ pj_bool_t end_sess_on_failure);
+
+static void (*inv_state_handler[])( pjsip_inv_session *inv, pjsip_event *e) =
+{
+ &inv_on_state_null,
+ &inv_on_state_calling,
+ &inv_on_state_incoming,
+ &inv_on_state_early,
+ &inv_on_state_connecting,
+ &inv_on_state_confirmed,
+ &inv_on_state_disconnected,
+};
+
+static struct mod_inv
+{
+ pjsip_module mod;
+ pjsip_endpoint *endpt;
+ pjsip_inv_callback cb;
+} mod_inv =
+{
+ {
+ NULL, NULL, /* prev, next. */
+ { "mod-invite", 10 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */
+ &mod_inv_load, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ &mod_inv_unload, /* unload() */
+ &mod_inv_on_rx_request, /* on_rx_request() */
+ &mod_inv_on_rx_response, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ &mod_inv_on_tsx_state, /* on_tsx_state() */
+ }
+};
+
+
+/* Invite session data to be attached to transaction. */
+struct tsx_inv_data
+{
+ pjsip_inv_session *inv; /* The invite session */
+ pj_bool_t sdp_done; /* SDP negotiation done for this tsx? */
+ pj_bool_t retrying; /* Resend (e.g. due to 401/407) */
+ pj_str_t done_tag; /* To tag in RX response with answer */
+ pj_bool_t done_early;/* Negotiation was done for early med? */
+};
+
+/*
+ * Module load()
+ */
+static pj_status_t mod_inv_load(pjsip_endpoint *endpt)
+{
+ pj_str_t allowed[] = {{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6},
+ { "UPDATE", 6}};
+ pj_str_t accepted = { "application/sdp", 15 };
+
+ /* Register supported methods: INVITE, ACK, BYE, CANCEL, UPDATE */
+ pjsip_endpt_add_capability(endpt, &mod_inv.mod, PJSIP_H_ALLOW, NULL,
+ PJ_ARRAY_SIZE(allowed), allowed);
+
+ /* Register "application/sdp" in Accept header */
+ pjsip_endpt_add_capability(endpt, &mod_inv.mod, PJSIP_H_ACCEPT, NULL,
+ 1, &accepted);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Module unload()
+ */
+static pj_status_t mod_inv_unload(void)
+{
+ /* Should remove capability here */
+ return PJ_SUCCESS;
+}
+
+/*
+ * Set session state.
+ */
+void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state,
+ pjsip_event *e)
+{
+ pjsip_inv_state prev_state = inv->state;
+ pj_bool_t dont_notify = PJ_FALSE;
+ pj_status_t status;
+
+ /* Prevent STATE_CALLING from being reported more than once because
+ * of authentication
+ * https://trac.pjsip.org/repos/ticket/1318
+ */
+ if (state==PJSIP_INV_STATE_CALLING &&
+ (inv->cb_called & (1 << PJSIP_INV_STATE_CALLING)) != 0)
+ {
+ dont_notify = PJ_TRUE;
+ }
+
+ /* If state is confirmed, check that SDP negotiation is done,
+ * otherwise disconnect the session.
+ */
+ if (state == PJSIP_INV_STATE_CONFIRMED) {
+ struct tsx_inv_data *tsx_inv_data = NULL;
+
+ if (inv->invite_tsx) {
+ tsx_inv_data = (struct tsx_inv_data*)
+ inv->invite_tsx->mod_data[mod_inv.mod.id];
+ }
+
+ if (pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE &&
+ (tsx_inv_data && !tsx_inv_data->sdp_done) )
+ {
+ pjsip_tx_data *bye;
+
+ PJ_LOG(4,(inv->obj_name, "SDP offer/answer incomplete, ending the "
+ "session"));
+
+ status = pjsip_inv_end_session(inv, PJSIP_SC_NOT_ACCEPTABLE,
+ NULL, &bye);
+ if (status == PJ_SUCCESS && bye)
+ status = pjsip_inv_send_msg(inv, bye);
+
+ return;
+ }
+ }
+
+ /* Set state. */
+ inv->state = state;
+
+ /* If state is DISCONNECTED, cause code MUST have been set. */
+ pj_assert(inv->state != PJSIP_INV_STATE_DISCONNECTED ||
+ inv->cause != 0);
+
+ /* Mark the callback as called for this state */
+ inv->cb_called |= (1 << state);
+
+ /* Call on_state_changed() callback. */
+ if (mod_inv.cb.on_state_changed && inv->notify && !dont_notify)
+ (*mod_inv.cb.on_state_changed)(inv, e);
+
+ /* Only decrement when previous state is not already DISCONNECTED */
+ if (inv->state == PJSIP_INV_STATE_DISCONNECTED &&
+ prev_state != PJSIP_INV_STATE_DISCONNECTED)
+ {
+ if (inv->last_ack) {
+ pjsip_tx_data_dec_ref(inv->last_ack);
+ inv->last_ack = NULL;
+ }
+ if (inv->invite_req) {
+ pjsip_tx_data_dec_ref(inv->invite_req);
+ inv->invite_req = NULL;
+ }
+ pjsip_100rel_end_session(inv);
+ pjsip_timer_end_session(inv);
+ pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
+
+ /* Release the flip-flop pools */
+ pj_pool_release(inv->pool_prov);
+ inv->pool_prov = NULL;
+ pj_pool_release(inv->pool_active);
+ inv->pool_active = NULL;
+ }
+}
+
+
+/*
+ * Set cause code.
+ */
+void inv_set_cause(pjsip_inv_session *inv, int cause_code,
+ const pj_str_t *cause_text)
+{
+ if (cause_code > inv->cause) {
+ inv->cause = (pjsip_status_code) cause_code;
+ if (cause_text)
+ pj_strdup(inv->pool, &inv->cause_text, cause_text);
+ else if (cause_code/100 == 2)
+ inv->cause_text = pj_str("Normal call clearing");
+ else
+ inv->cause_text = *pjsip_get_status_text(cause_code);
+ }
+}
+
+
+/*
+ * Check if outgoing request needs to have SDP answer.
+ * This applies for both ACK and PRACK requests.
+ */
+static const pjmedia_sdp_session *inv_has_pending_answer(pjsip_inv_session *inv,
+ pjsip_transaction *tsx)
+{
+ pjmedia_sdp_neg_state neg_state;
+ const pjmedia_sdp_session *sdp = NULL;
+ pj_status_t status;
+
+ /* If SDP negotiator is ready, start negotiation. */
+
+ /* Start nego when appropriate. */
+ neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) :
+ PJMEDIA_SDP_NEG_STATE_NULL;
+
+ if (neg_state == PJMEDIA_SDP_NEG_STATE_DONE) {
+
+ /* Nothing to do */
+
+ } else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
+ pjmedia_sdp_neg_has_local_answer(inv->neg) )
+ {
+ struct tsx_inv_data *tsx_inv_data;
+ struct tsx_inv_data dummy;
+
+ /* Get invite session's transaction data.
+ * Note that tsx may be NULL, for example when application sends
+ * delayed ACK request (at this time, the original INVITE
+ * transaction may have been destroyed.
+ */
+ if (tsx) {
+ tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
+ } else {
+ tsx_inv_data = &dummy;
+ pj_bzero(&dummy, sizeof(dummy));
+ dummy.inv = inv;
+ }
+
+ status = inv_negotiate_sdp(inv);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ /* Mark this transaction has having SDP offer/answer done. */
+ tsx_inv_data->sdp_done = 1;
+
+ status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp);
+
+ } else {
+ /* This remark is only valid for ACK.
+ PJ_LOG(4,(inv->dlg->obj_name,
+ "FYI, the SDP negotiator state (%s) is in a mess "
+ "when sending this ACK/PRACK request",
+ pjmedia_sdp_neg_state_str(neg_state)));
+ */
+ }
+
+ return sdp;
+}
+
+
+/*
+ * Send ACK for 2xx response.
+ */
+static pj_status_t inv_send_ack(pjsip_inv_session *inv, pjsip_event *e)
+{
+ pjsip_rx_data *rdata;
+ pjsip_event ack_e;
+ pj_status_t status;
+
+ if (e->type == PJSIP_EVENT_TSX_STATE)
+ rdata = e->body.tsx_state.src.rdata;
+ else if (e->type == PJSIP_EVENT_RX_MSG)
+ rdata = e->body.rx_msg.rdata;
+ else {
+ pj_assert(!"Unsupported event type");
+ return PJ_EBUG;
+ }
+
+ PJ_LOG(5,(inv->obj_name, "Received %s, sending ACK",
+ pjsip_rx_data_get_info(rdata)));
+
+ /* Check if we have cached ACK request. Must not use the cached ACK
+ * if it's still marked as pending by transport (#1011)
+ */
+ if (inv->last_ack && rdata->msg_info.cseq->cseq == inv->last_ack_cseq &&
+ !inv->last_ack->is_pending)
+ {
+ pjsip_tx_data_add_ref(inv->last_ack);
+
+ } else if (mod_inv.cb.on_send_ack) {
+ /* If application handles ACK transmission manually, just notify the
+ * callback
+ */
+ PJ_LOG(5,(inv->obj_name, "Received %s, notifying application callback",
+ pjsip_rx_data_get_info(rdata)));
+
+ (*mod_inv.cb.on_send_ack)(inv, rdata);
+ return PJ_SUCCESS;
+
+ } else {
+ status = pjsip_inv_create_ack(inv, rdata->msg_info.cseq->cseq,
+ &inv->last_ack);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ PJSIP_EVENT_INIT_TX_MSG(ack_e, inv->last_ack);
+
+ /* Send ACK */
+ status = pjsip_dlg_send_request(inv->dlg, inv->last_ack, -1, NULL);
+ if (status != PJ_SUCCESS) {
+ /* Better luck next time */
+ pj_assert(!"Unable to send ACK!");
+ return status;
+ }
+
+
+ /* Set state to CONFIRMED (if we're not in CONFIRMED yet).
+ * But don't set it to CONFIRMED if we're already DISCONNECTED
+ * (this may have been a late 200/OK response.
+ */
+ if (inv->state < PJSIP_INV_STATE_CONFIRMED) {
+ inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &ack_e);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Module on_rx_request()
+ *
+ * This callback is called for these events:
+ * - endpoint receives request which was unhandled by higher priority
+ * modules (e.g. transaction layer, dialog layer).
+ * - dialog distributes incoming request to its usages.
+ */
+static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata)
+{
+ pjsip_method *method;
+ pjsip_dialog *dlg;
+ pjsip_inv_session *inv;
+
+ /* Only wants to receive request from a dialog. */
+ dlg = pjsip_rdata_get_dlg(rdata);
+ if (dlg == NULL)
+ return PJ_FALSE;
+
+ inv = (pjsip_inv_session*) dlg->mod_data[mod_inv.mod.id];
+
+ /* Report to dialog that we handle INVITE, CANCEL, BYE, ACK.
+ * If we need to send response, it will be sent in the state
+ * handlers.
+ */
+ method = &rdata->msg_info.msg->line.req.method;
+
+ if (method->id == PJSIP_INVITE_METHOD) {
+ return PJ_TRUE;
+ }
+
+ /* BYE and CANCEL must have existing invite session */
+ if (method->id == PJSIP_BYE_METHOD ||
+ method->id == PJSIP_CANCEL_METHOD)
+ {
+ if (inv == NULL)
+ return PJ_FALSE;
+
+ return PJ_TRUE;
+ }
+
+ /* On receipt ACK request, when state is CONNECTING,
+ * move state to CONFIRMED.
+ */
+ if (method->id == PJSIP_ACK_METHOD && inv) {
+
+ /* Ignore if we don't have INVITE in progress */
+ if (!inv->invite_tsx) {
+ return PJ_TRUE;
+ }
+
+ /* Ignore ACK if pending INVITE transaction has not finished. */
+ if (inv->invite_tsx->state < PJSIP_TSX_STATE_COMPLETED) {
+ return PJ_TRUE;
+ }
+
+ /* Ignore ACK with different CSeq
+ * https://trac.pjsip.org/repos/ticket/1391
+ */
+ if (rdata->msg_info.cseq->cseq != inv->invite_tsx->cseq) {
+ return PJ_TRUE;
+ }
+
+ /* Terminate INVITE transaction, if it's still present. */
+ if (inv->invite_tsx->state <= PJSIP_TSX_STATE_COMPLETED) {
+ /* Before we terminate INVITE transaction, process the SDP
+ * in the ACK request, if any.
+ * Only do this when invite state is not already disconnected
+ * (http://trac.pjsip.org/repos/ticket/640).
+ */
+ if (inv->state < PJSIP_INV_STATE_DISCONNECTED) {
+ inv_check_sdp_in_incoming_msg(inv, inv->invite_tsx, rdata);
+
+ /* Check if local offer got no SDP answer and INVITE session
+ * is in CONFIRMED state.
+ */
+ if (pjmedia_sdp_neg_get_state(inv->neg)==
+ PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER &&
+ inv->state==PJSIP_INV_STATE_CONFIRMED)
+ {
+ pjmedia_sdp_neg_cancel_offer(inv->neg);
+ }
+ }
+
+ /* Now we can terminate the INVITE transaction */
+ pj_assert(inv->invite_tsx->status_code >= 200);
+ pjsip_tsx_terminate(inv->invite_tsx,
+ inv->invite_tsx->status_code);
+ inv->invite_tsx = NULL;
+ if (inv->last_answer) {
+ pjsip_tx_data_dec_ref(inv->last_answer);
+ inv->last_answer = NULL;
+ }
+ }
+
+ /* On receipt of ACK, only set state to confirmed when state
+ * is CONNECTING (e.g. we don't want to set the state to confirmed
+ * when we receive ACK retransmission after sending non-2xx!)
+ */
+ if (inv->state == PJSIP_INV_STATE_CONNECTING) {
+ pjsip_event event;
+
+ PJSIP_EVENT_INIT_RX_MSG(event, rdata);
+ inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &event);
+ }
+ }
+
+ return PJ_FALSE;
+}
+
+/* This function will process Session Timer headers in received
+ * 2xx or 422 response of INVITE/UPDATE request.
+ */
+static pj_status_t handle_timer_response(pjsip_inv_session *inv,
+ const pjsip_rx_data *rdata,
+ pj_bool_t end_sess_on_failure)
+{
+ pjsip_status_code st_code;
+ pj_status_t status;
+
+ status = pjsip_timer_process_resp(inv, rdata, &st_code);
+ if (status != PJ_SUCCESS && end_sess_on_failure) {
+ pjsip_tx_data *tdata;
+ pj_status_t status2;
+
+ status2 = pjsip_inv_end_session(inv, st_code, NULL, &tdata);
+ if (tdata && status2 == PJ_SUCCESS)
+ pjsip_inv_send_msg(inv, tdata);
+ }
+
+ return status;
+}
+
+/*
+ * Module on_rx_response().
+ *
+ * This callback is called for these events:
+ * - dialog distributes incoming 2xx response to INVITE (outside
+ * transaction) to its usages.
+ * - endpoint distributes strayed responses.
+ */
+static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata)
+{
+ pjsip_dialog *dlg;
+ pjsip_inv_session *inv;
+ pjsip_msg *msg = rdata->msg_info.msg;
+
+ dlg = pjsip_rdata_get_dlg(rdata);
+
+ /* Ignore responses outside dialog */
+ if (dlg == NULL)
+ return PJ_FALSE;
+
+ /* Ignore responses not belonging to invite session */
+ inv = pjsip_dlg_get_inv_session(dlg);
+ if (inv == NULL)
+ return PJ_FALSE;
+
+ /* This MAY be retransmission of 2xx response to INVITE.
+ * If it is, we need to send ACK.
+ */
+ if (msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/100==2 &&
+ rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD &&
+ inv->invite_tsx == NULL)
+ {
+ pjsip_event e;
+
+ PJSIP_EVENT_INIT_RX_MSG(e, rdata);
+ inv_send_ack(inv, &e);
+ return PJ_TRUE;
+
+ }
+
+ /* No other processing needs to be done here. */
+ return PJ_FALSE;
+}
+
+/*
+ * Module on_tsx_state()
+ *
+ * This callback is called by dialog framework for all transactions
+ * inside the dialog for all its dialog usages.
+ */
+static void mod_inv_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e)
+{
+ pjsip_dialog *dlg;
+ pjsip_inv_session *inv;
+
+ dlg = pjsip_tsx_get_dlg(tsx);
+ if (dlg == NULL)
+ return;
+
+ inv = pjsip_dlg_get_inv_session(dlg);
+ if (inv == NULL)
+ return;
+
+ /* Call state handler for the invite session. */
+ (*inv_state_handler[inv->state])(inv, e);
+
+ /* Call on_tsx_state */
+ if (mod_inv.cb.on_tsx_state_changed && inv->notify)
+ (*mod_inv.cb.on_tsx_state_changed)(inv, tsx, e);
+
+ /* Clear invite transaction when tsx is confirmed.
+ * Previously we set invite_tsx to NULL only when transaction has
+ * terminated, but this didn't work when ACK has the same Via branch
+ * value as the INVITE (see http://www.pjsip.org/trac/ticket/113)
+ */
+ if (tsx->state>=PJSIP_TSX_STATE_CONFIRMED && tsx == inv->invite_tsx) {
+ inv->invite_tsx = NULL;
+ if (inv->last_answer) {
+ pjsip_tx_data_dec_ref(inv->last_answer);
+ inv->last_answer = NULL;
+ }
+ }
+}
+
+
+/*
+ * Initialize the invite module.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_usage_init( pjsip_endpoint *endpt,
+ const pjsip_inv_callback *cb)
+{
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt && cb, PJ_EINVAL);
+
+ /* Some callbacks are mandatory */
+ PJ_ASSERT_RETURN(cb->on_state_changed && cb->on_new_session, PJ_EINVAL);
+
+ /* Check if module already registered. */
+ PJ_ASSERT_RETURN(mod_inv.mod.id == -1, PJ_EINVALIDOP);
+
+ /* Copy param. */
+ pj_memcpy(&mod_inv.cb, cb, sizeof(pjsip_inv_callback));
+
+ mod_inv.endpt = endpt;
+
+ /* Register the module. */
+ status = pjsip_endpt_register_module(endpt, &mod_inv.mod);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get the instance of invite module.
+ */
+PJ_DEF(pjsip_module*) pjsip_inv_usage_instance(void)
+{
+ return &mod_inv.mod;
+}
+
+
+
+/*
+ * Return the invite session for the specified dialog.
+ */
+PJ_DEF(pjsip_inv_session*) pjsip_dlg_get_inv_session(pjsip_dialog *dlg)
+{
+ return (pjsip_inv_session*) dlg->mod_data[mod_inv.mod.id];
+}
+
+
+/*
+ * Get INVITE state name.
+ */
+PJ_DEF(const char *) pjsip_inv_state_name(pjsip_inv_state state)
+{
+ PJ_ASSERT_RETURN(state >= PJSIP_INV_STATE_NULL &&
+ state <= PJSIP_INV_STATE_DISCONNECTED,
+ "??");
+
+ return inv_state_names[state];
+}
+
+/*
+ * Create UAC invite session.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg,
+ const pjmedia_sdp_session *local_sdp,
+ unsigned options,
+ pjsip_inv_session **p_inv)
+{
+ pjsip_inv_session *inv;
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(dlg && p_inv, PJ_EINVAL);
+
+ /* Must lock dialog first */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Normalize options */
+ if (options & PJSIP_INV_REQUIRE_100REL)
+ options |= PJSIP_INV_SUPPORT_100REL;
+ if (options & PJSIP_INV_REQUIRE_TIMER)
+ options |= PJSIP_INV_SUPPORT_TIMER;
+
+ /* Create the session */
+ inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session);
+ pj_assert(inv != NULL);
+
+ inv->pool = dlg->pool;
+ inv->role = PJSIP_ROLE_UAC;
+ inv->state = PJSIP_INV_STATE_NULL;
+ inv->dlg = dlg;
+ inv->options = options;
+ inv->notify = PJ_TRUE;
+ inv->cause = (pjsip_status_code) 0;
+
+ /* Create flip-flop pool (see ticket #877) */
+ /* (using inv->obj_name as temporary variable for pool names */
+ pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg->pool);
+ inv->pool_prov = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name,
+ POOL_INIT_SIZE, POOL_INC_SIZE);
+ inv->pool_active = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name,
+ POOL_INIT_SIZE, POOL_INC_SIZE);
+
+ /* Object name will use the same dialog pointer. */
+ pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg);
+
+ /* Create negotiator if local_sdp is specified. */
+ if (local_sdp) {
+ status = pjmedia_sdp_neg_create_w_local_offer(inv->pool,
+ local_sdp, &inv->neg);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+ }
+ }
+
+ /* Register invite as dialog usage. */
+ status = pjsip_dlg_add_usage(dlg, &mod_inv.mod, inv);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+ }
+
+ /* Increment dialog session */
+ pjsip_dlg_inc_session(dlg, &mod_inv.mod);
+
+ /* Create 100rel handler */
+ pjsip_100rel_attach(inv);
+
+ /* Done */
+ *p_inv = inv;
+
+ pjsip_dlg_dec_lock(dlg);
+
+ PJ_LOG(5,(inv->obj_name, "UAC invite session created for dialog %s",
+ dlg->obj_name));
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata)
+{
+ pjsip_rdata_sdp_info *sdp_info;
+ pjsip_msg_body *body = rdata->msg_info.msg->body;
+ pjsip_ctype_hdr *ctype_hdr = rdata->msg_info.ctype;
+ pjsip_media_type app_sdp;
+
+ sdp_info = (pjsip_rdata_sdp_info*)
+ rdata->endpt_info.mod_data[mod_inv.mod.id];
+ if (sdp_info)
+ return sdp_info;
+
+ sdp_info = PJ_POOL_ZALLOC_T(rdata->tp_info.pool,
+ pjsip_rdata_sdp_info);
+ PJ_ASSERT_RETURN(mod_inv.mod.id >= 0, sdp_info);
+ rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info;
+
+ pjsip_media_type_init2(&app_sdp, "application", "sdp");
+
+ if (body && ctype_hdr &&
+ pj_stricmp(&ctype_hdr->media.type, &app_sdp.type)==0 &&
+ pj_stricmp(&ctype_hdr->media.subtype, &app_sdp.subtype)==0)
+ {
+ sdp_info->body.ptr = (char*)body->data;
+ sdp_info->body.slen = body->len;
+ } else if (body && ctype_hdr &&
+ pj_stricmp2(&ctype_hdr->media.type, "multipart")==0 &&
+ (pj_stricmp2(&ctype_hdr->media.subtype, "mixed")==0 ||
+ pj_stricmp2(&ctype_hdr->media.subtype, "alternative")==0))
+ {
+ pjsip_multipart_part *part;
+
+ part = pjsip_multipart_find_part(body, &app_sdp, NULL);
+ if (part) {
+ sdp_info->body.ptr = (char*)part->body->data;
+ sdp_info->body.slen = part->body->len;
+ }
+ }
+
+ if (sdp_info->body.ptr) {
+ pj_status_t status;
+ status = pjmedia_sdp_parse(rdata->tp_info.pool,
+ sdp_info->body.ptr,
+ sdp_info->body.slen,
+ &sdp_info->sdp);
+ if (status == PJ_SUCCESS)
+ status = pjmedia_sdp_validate(sdp_info->sdp);
+
+ if (status != PJ_SUCCESS) {
+ sdp_info->sdp = NULL;
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error parsing/validating SDP body"));
+ }
+
+ sdp_info->sdp_err = status;
+ }
+
+ return sdp_info;
+}
+
+
+/*
+ * Verify incoming INVITE request.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
+ pj_pool_t *tmp_pool,
+ unsigned *options,
+ const pjmedia_sdp_session *r_sdp,
+ const pjmedia_sdp_session *l_sdp,
+ pjsip_dialog *dlg,
+ pjsip_endpoint *endpt,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_msg *msg = NULL;
+ pjsip_allow_hdr *allow = NULL;
+ pjsip_supported_hdr *sup_hdr = NULL;
+ pjsip_require_hdr *req_hdr = NULL;
+ pjsip_contact_hdr *c_hdr = NULL;
+ int code = 200;
+ unsigned rem_option = 0;
+ pj_status_t status = PJ_SUCCESS;
+ pjsip_hdr res_hdr_list;
+ pjsip_rdata_sdp_info *sdp_info;
+
+ /* Init return arguments. */
+ if (p_tdata) *p_tdata = NULL;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(tmp_pool != NULL && options != NULL, PJ_EINVAL);
+
+ /* Normalize options */
+ if (*options & PJSIP_INV_REQUIRE_100REL)
+ *options |= PJSIP_INV_SUPPORT_100REL;
+ if (*options & PJSIP_INV_REQUIRE_TIMER)
+ *options |= PJSIP_INV_SUPPORT_TIMER;
+ if (*options & PJSIP_INV_REQUIRE_ICE)
+ *options |= PJSIP_INV_SUPPORT_ICE;
+
+ if (rdata) {
+ /* Get the message in rdata */
+ msg = rdata->msg_info.msg;
+
+ /* Must be INVITE request. */
+ PJ_ASSERT_RETURN(msg && msg->type == PJSIP_REQUEST_MSG &&
+ msg->line.req.method.id == PJSIP_INVITE_METHOD,
+ PJ_EINVAL);
+ }
+
+ /* If tdata is specified, then either dlg or endpt must be specified */
+ PJ_ASSERT_RETURN((!p_tdata) || (endpt || dlg), PJ_EINVAL);
+
+ /* Get the endpoint */
+ endpt = endpt ? endpt : dlg->endpt;
+
+ /* Init response header list */
+ pj_list_init(&res_hdr_list);
+
+ /* Check the Contact header */
+ if (msg) {
+ c_hdr = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL);
+ }
+ if (msg && (!c_hdr || !c_hdr->uri)) {
+ /* Missing Contact header or Contact contains "*" */
+ pjsip_warning_hdr *w;
+ pj_str_t warn_text;
+
+ warn_text = pj_str("Bad/missing Contact header");
+ w = pjsip_warning_hdr_create(tmp_pool, 399,
+ pjsip_endpt_name(endpt),
+ &warn_text);
+ if (w) {
+ pj_list_push_back(&res_hdr_list, w);
+ }
+
+ code = PJSIP_SC_BAD_REQUEST;
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
+ goto on_return;
+ }
+
+ /* Check the request body, see if it's something that we support,
+ * only when the body hasn't been parsed before.
+ */
+ if (r_sdp == NULL && rdata) {
+ sdp_info = pjsip_rdata_get_sdp_info(rdata);
+ } else {
+ sdp_info = NULL;
+ }
+
+ if (r_sdp==NULL && msg && msg->body) {
+
+ /* Check if body really contains SDP. */
+ if (sdp_info->body.ptr == NULL) {
+ /* Couldn't find "application/sdp" */
+ code = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE;
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
+
+ if (p_tdata) {
+ /* Add Accept header to response */
+ pjsip_accept_hdr *acc;
+
+ acc = pjsip_accept_hdr_create(tmp_pool);
+ PJ_ASSERT_RETURN(acc, PJ_ENOMEM);
+ acc->values[acc->count++] = pj_str("application/sdp");
+ pj_list_push_back(&res_hdr_list, acc);
+ }
+
+ goto on_return;
+ }
+
+ if (sdp_info->sdp_err != PJ_SUCCESS) {
+ /* Unparseable or invalid SDP */
+ code = PJSIP_SC_BAD_REQUEST;
+
+ if (p_tdata) {
+ /* Add Warning header. */
+ pjsip_warning_hdr *w;
+
+ w = pjsip_warning_hdr_create_from_status(tmp_pool,
+ pjsip_endpt_name(endpt),
+ sdp_info->sdp_err);
+ PJ_ASSERT_RETURN(w, PJ_ENOMEM);
+
+ pj_list_push_back(&res_hdr_list, w);
+ }
+
+ goto on_return;
+ }
+
+ r_sdp = sdp_info->sdp;
+ }
+
+ if (r_sdp) {
+ /* Negotiate with local SDP */
+ if (l_sdp) {
+ pjmedia_sdp_neg *neg;
+
+ /* Local SDP must be valid! */
+ PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(l_sdp))==PJ_SUCCESS,
+ status);
+
+ /* Create SDP negotiator */
+ status = pjmedia_sdp_neg_create_w_remote_offer(
+ tmp_pool, l_sdp, r_sdp, &neg);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Negotiate SDP */
+ status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0);
+ if (status != PJ_SUCCESS) {
+
+ /* Incompatible media */
+ code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+
+ if (p_tdata) {
+ pjsip_accept_hdr *acc;
+ pjsip_warning_hdr *w;
+
+ /* Add Warning header. */
+ w = pjsip_warning_hdr_create_from_status(
+ tmp_pool,
+ pjsip_endpt_name(endpt), status);
+ PJ_ASSERT_RETURN(w, PJ_ENOMEM);
+
+ pj_list_push_back(&res_hdr_list, w);
+
+ /* Add Accept header to response */
+ acc = pjsip_accept_hdr_create(tmp_pool);
+ PJ_ASSERT_RETURN(acc, PJ_ENOMEM);
+ acc->values[acc->count++] = pj_str("application/sdp");
+ pj_list_push_back(&res_hdr_list, acc);
+
+ }
+
+ goto on_return;
+ }
+ }
+ }
+
+ /* Check supported methods, see if peer supports UPDATE.
+ * We just assume that peer supports standard INVITE, ACK, CANCEL, and BYE
+ * implicitly by sending this INVITE.
+ */
+ if (msg) {
+ allow = (pjsip_allow_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_ALLOW,
+ NULL);
+ }
+ if (allow) {
+ unsigned i;
+ const pj_str_t STR_UPDATE = { "UPDATE", 6 };
+
+ for (i=0; i<allow->count; ++i) {
+ if (pj_stricmp(&allow->values[i], &STR_UPDATE)==0)
+ break;
+ }
+
+ if (i != allow->count) {
+ /* UPDATE is present in Allow */
+ rem_option |= PJSIP_INV_SUPPORT_UPDATE;
+ }
+
+ }
+
+ /* Check Supported header */
+ if (msg) {
+ sup_hdr = (pjsip_supported_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_SUPPORTED, NULL);
+ }
+ if (sup_hdr) {
+ unsigned i;
+ const pj_str_t STR_100REL = { "100rel", 6};
+ const pj_str_t STR_TIMER = { "timer", 5};
+ const pj_str_t STR_ICE = { "ice", 3 };
+
+ for (i=0; i<sup_hdr->count; ++i) {
+ if (pj_stricmp(&sup_hdr->values[i], &STR_100REL)==0)
+ rem_option |= PJSIP_INV_SUPPORT_100REL;
+ else if (pj_stricmp(&sup_hdr->values[i], &STR_TIMER)==0)
+ rem_option |= PJSIP_INV_SUPPORT_TIMER;
+ else if (pj_stricmp(&sup_hdr->values[i], &STR_ICE)==0)
+ rem_option |= PJSIP_INV_SUPPORT_ICE;
+ }
+ }
+
+ /* Check Require header */
+ if (msg) {
+ req_hdr = (pjsip_require_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL);
+ }
+ if (req_hdr) {
+ unsigned i;
+ const pj_str_t STR_100REL = { "100rel", 6};
+ const pj_str_t STR_REPLACES = { "replaces", 8 };
+ const pj_str_t STR_TIMER = { "timer", 5 };
+ const pj_str_t STR_ICE = { "ice", 3 };
+ unsigned unsupp_cnt = 0;
+ pj_str_t unsupp_tags[PJSIP_GENERIC_ARRAY_MAX_COUNT];
+
+ for (i=0; i<req_hdr->count; ++i) {
+ if ((*options & PJSIP_INV_SUPPORT_100REL) &&
+ pj_stricmp(&req_hdr->values[i], &STR_100REL)==0)
+ {
+ rem_option |= PJSIP_INV_REQUIRE_100REL;
+
+ } else if ((*options & PJSIP_INV_SUPPORT_TIMER) &&
+ pj_stricmp(&req_hdr->values[i], &STR_TIMER)==0)
+ {
+ rem_option |= PJSIP_INV_REQUIRE_TIMER;
+
+ } else if (pj_stricmp(&req_hdr->values[i], &STR_REPLACES)==0) {
+ pj_bool_t supp;
+
+ supp = pjsip_endpt_has_capability(endpt, PJSIP_H_SUPPORTED,
+ NULL, &STR_REPLACES);
+ if (!supp)
+ unsupp_tags[unsupp_cnt++] = req_hdr->values[i];
+ } else if ((*options & PJSIP_INV_SUPPORT_ICE) &&
+ pj_stricmp(&req_hdr->values[i], &STR_ICE)==0)
+ {
+ rem_option |= PJSIP_INV_REQUIRE_ICE;
+
+ } else if (!pjsip_endpt_has_capability(endpt, PJSIP_H_SUPPORTED,
+ NULL, &req_hdr->values[i]))
+ {
+ /* Unknown/unsupported extension tag! */
+ unsupp_tags[unsupp_cnt++] = req_hdr->values[i];
+ }
+ }
+
+ /* Check if there are required tags that we don't support */
+ if (unsupp_cnt) {
+
+ code = PJSIP_SC_BAD_EXTENSION;
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
+
+ if (p_tdata) {
+ pjsip_unsupported_hdr *unsupp_hdr;
+ const pjsip_hdr *h;
+
+ /* Add Unsupported header. */
+ unsupp_hdr = pjsip_unsupported_hdr_create(tmp_pool);
+ PJ_ASSERT_RETURN(unsupp_hdr != NULL, PJ_ENOMEM);
+
+ unsupp_hdr->count = unsupp_cnt;
+ for (i=0; i<unsupp_cnt; ++i)
+ unsupp_hdr->values[i] = unsupp_tags[i];
+
+ pj_list_push_back(&res_hdr_list, unsupp_hdr);
+
+ /* Add Supported header. */
+ h = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED,
+ NULL);
+ pj_assert(h);
+ if (h) {
+ sup_hdr = (pjsip_supported_hdr*)
+ pjsip_hdr_clone(tmp_pool, h);
+ pj_list_push_back(&res_hdr_list, sup_hdr);
+ }
+ }
+
+ goto on_return;
+ }
+ }
+
+ /* Check if there are local requirements that are not supported
+ * by peer.
+ */
+ if ( msg && (((*options & PJSIP_INV_REQUIRE_100REL)!=0 &&
+ (rem_option & PJSIP_INV_SUPPORT_100REL)==0) ||
+ ((*options & PJSIP_INV_REQUIRE_TIMER)!=0 &&
+ (rem_option & PJSIP_INV_SUPPORT_TIMER)==0)))
+ {
+ code = PJSIP_SC_EXTENSION_REQUIRED;
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
+
+ if (p_tdata) {
+ const pjsip_hdr *h;
+
+ /* Add Require header. */
+ req_hdr = pjsip_require_hdr_create(tmp_pool);
+ PJ_ASSERT_RETURN(req_hdr != NULL, PJ_ENOMEM);
+
+ if (*options & PJSIP_INV_REQUIRE_100REL)
+ req_hdr->values[req_hdr->count++] = pj_str("100rel");
+ if (*options & PJSIP_INV_REQUIRE_TIMER)
+ req_hdr->values[req_hdr->count++] = pj_str("timer");
+
+ pj_list_push_back(&res_hdr_list, req_hdr);
+
+ /* Add Supported header. */
+ h = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED,
+ NULL);
+ pj_assert(h);
+ if (h) {
+ sup_hdr = (pjsip_supported_hdr*)
+ pjsip_hdr_clone(tmp_pool, h);
+ pj_list_push_back(&res_hdr_list, sup_hdr);
+ }
+
+ }
+
+ goto on_return;
+ }
+
+ /* If remote Require something that we support, make us Require
+ * that feature too.
+ */
+ if (rem_option & PJSIP_INV_REQUIRE_100REL) {
+ pj_assert(*options & PJSIP_INV_SUPPORT_100REL);
+ *options |= PJSIP_INV_REQUIRE_100REL;
+ }
+ if (rem_option & PJSIP_INV_REQUIRE_TIMER) {
+ pj_assert(*options & PJSIP_INV_SUPPORT_TIMER);
+ *options |= PJSIP_INV_REQUIRE_TIMER;
+ }
+
+on_return:
+
+ /* Create response if necessary */
+ if (code != 200 && p_tdata) {
+ pjsip_tx_data *tdata;
+ const pjsip_hdr *h;
+
+ if (!rdata) {
+ return PJSIP_ERRNO_FROM_SIP_STATUS(code);
+ }
+
+ if (dlg) {
+ status = pjsip_dlg_create_response(dlg, rdata, code, NULL,
+ &tdata);
+ } else {
+ status = pjsip_endpt_create_response(endpt, rdata, code, NULL,
+ &tdata);
+ }
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add response headers. */
+ h = res_hdr_list.next;
+ while (h != &res_hdr_list) {
+ pjsip_hdr *cloned;
+
+ cloned = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, h);
+ PJ_ASSERT_RETURN(cloned, PJ_ENOMEM);
+
+ pjsip_msg_add_hdr(tdata->msg, cloned);
+
+ h = h->next;
+ }
+
+ *p_tdata = tdata;
+
+ /* Can not return PJ_SUCCESS when response message is produced.
+ * Ref: PROTOS test ~#2490
+ */
+ if (status == PJ_SUCCESS)
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
+
+ }
+
+ return status;
+}
+
+
+/*
+ * Verify incoming INVITE request.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_verify_request2(pjsip_rx_data *rdata,
+ unsigned *options,
+ const pjmedia_sdp_session *r_sdp,
+ const pjmedia_sdp_session *l_sdp,
+ pjsip_dialog *dlg,
+ pjsip_endpoint *endpt,
+ pjsip_tx_data **p_tdata)
+{
+ return pjsip_inv_verify_request3(rdata, rdata->tp_info.pool,
+ options, r_sdp, l_sdp, dlg,
+ endpt, p_tdata);
+}
+
+
+/*
+ * Verify incoming INVITE request.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_verify_request( pjsip_rx_data *rdata,
+ unsigned *options,
+ const pjmedia_sdp_session *l_sdp,
+ pjsip_dialog *dlg,
+ pjsip_endpoint *endpt,
+ pjsip_tx_data **p_tdata)
+{
+ return pjsip_inv_verify_request3(rdata, rdata->tp_info.pool,
+ options, NULL, l_sdp, dlg,
+ endpt, p_tdata);
+}
+
+/*
+ * Create UAS invite session.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_create_uas( pjsip_dialog *dlg,
+ pjsip_rx_data *rdata,
+ const pjmedia_sdp_session *local_sdp,
+ unsigned options,
+ pjsip_inv_session **p_inv)
+{
+ pjsip_inv_session *inv;
+ struct tsx_inv_data *tsx_inv_data;
+ pjsip_msg *msg;
+ pjsip_rdata_sdp_info *sdp_info;
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(dlg && rdata && p_inv, PJ_EINVAL);
+
+ /* Dialog MUST have been initialised. */
+ PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata) != NULL, PJ_EINVALIDOP);
+
+ msg = rdata->msg_info.msg;
+
+ /* rdata MUST contain INVITE request */
+ PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG &&
+ msg->line.req.method.id == PJSIP_INVITE_METHOD,
+ PJ_EINVALIDOP);
+
+ /* Lock dialog */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Normalize options */
+ if (options & PJSIP_INV_REQUIRE_100REL)
+ options |= PJSIP_INV_SUPPORT_100REL;
+ if (options & PJSIP_INV_REQUIRE_TIMER)
+ options |= PJSIP_INV_SUPPORT_TIMER;
+
+ /* Create the session */
+ inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session);
+ pj_assert(inv != NULL);
+
+ inv->pool = dlg->pool;
+ inv->role = PJSIP_ROLE_UAS;
+ inv->state = PJSIP_INV_STATE_NULL;
+ inv->dlg = dlg;
+ inv->options = options;
+ inv->notify = PJ_TRUE;
+ inv->cause = (pjsip_status_code) 0;
+
+ /* Create flip-flop pool (see ticket #877) */
+ /* (using inv->obj_name as temporary variable for pool names */
+ pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg->pool);
+ inv->pool_prov = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name,
+ POOL_INIT_SIZE, POOL_INC_SIZE);
+ inv->pool_active = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name,
+ POOL_INIT_SIZE, POOL_INC_SIZE);
+
+ /* Object name will use the same dialog pointer. */
+ pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg);
+
+ /* Process SDP in message body, if present. */
+ sdp_info = pjsip_rdata_get_sdp_info(rdata);
+ if (sdp_info->sdp_err) {
+ pjsip_dlg_dec_lock(dlg);
+ return sdp_info->sdp_err;
+ }
+
+ /* Create negotiator. */
+ if (sdp_info->sdp) {
+ status = pjmedia_sdp_neg_create_w_remote_offer(inv->pool, local_sdp,
+ sdp_info->sdp,
+ &inv->neg);
+
+ } else if (local_sdp) {
+ status = pjmedia_sdp_neg_create_w_local_offer(inv->pool,
+ local_sdp, &inv->neg);
+ } else {
+ status = PJ_SUCCESS;
+ }
+
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+ }
+
+ /* Register invite as dialog usage. */
+ status = pjsip_dlg_add_usage(dlg, &mod_inv.mod, inv);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+ }
+
+ /* Increment session in the dialog. */
+ pjsip_dlg_inc_session(dlg, &mod_inv.mod);
+
+ /* Save the invite transaction. */
+ inv->invite_tsx = pjsip_rdata_get_tsx(rdata);
+
+ /* Attach our data to the transaction. */
+ tsx_inv_data = PJ_POOL_ZALLOC_T(inv->invite_tsx->pool, struct tsx_inv_data);
+ tsx_inv_data->inv = inv;
+ inv->invite_tsx->mod_data[mod_inv.mod.id] = tsx_inv_data;
+
+ /* Create 100rel handler */
+ if (inv->options & PJSIP_INV_REQUIRE_100REL) {
+ pjsip_100rel_attach(inv);
+ }
+
+ /* Done */
+ pjsip_dlg_dec_lock(dlg);
+ *p_inv = inv;
+
+ PJ_LOG(5,(inv->obj_name, "UAS invite session created for dialog %s",
+ dlg->obj_name));
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Forcefully terminate the session.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_terminate( pjsip_inv_session *inv,
+ int st_code,
+ pj_bool_t notify)
+{
+ PJ_ASSERT_RETURN(inv, PJ_EINVAL);
+
+ /* Lock dialog. */
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ /* Set callback notify flag. */
+ inv->notify = notify;
+
+ /* If there's pending transaction, terminate the transaction.
+ * This may subsequently set the INVITE session state to
+ * disconnected.
+ */
+ if (inv->invite_tsx &&
+ inv->invite_tsx->state <= PJSIP_TSX_STATE_COMPLETED)
+ {
+ pjsip_tsx_terminate(inv->invite_tsx, st_code);
+
+ }
+
+ /* Set cause. */
+ inv_set_cause(inv, st_code, NULL);
+
+ /* Forcefully terminate the session if state is not DISCONNECTED */
+ if (inv->state != PJSIP_INV_STATE_DISCONNECTED) {
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, NULL);
+ }
+
+ /* Done.
+ * The dec_lock() below will actually destroys the dialog if it
+ * has no other session.
+ */
+ pjsip_dlg_dec_lock(inv->dlg);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Restart UAC session, possibly because app or us wants to re-send the
+ * INVITE request due to 401/407 challenge or 3xx response.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_uac_restart(pjsip_inv_session *inv,
+ pj_bool_t new_offer)
+{
+ PJ_ASSERT_RETURN(inv, PJ_EINVAL);
+
+ inv->state = PJSIP_INV_STATE_NULL;
+ inv->invite_tsx = NULL;
+ if (inv->last_answer) {
+ pjsip_tx_data_dec_ref(inv->last_answer);
+ inv->last_answer = NULL;
+ }
+
+ if (new_offer && inv->neg) {
+ pjmedia_sdp_neg_state neg_state;
+
+ neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+ if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
+ pjmedia_sdp_neg_cancel_offer(inv->neg);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static void *clone_sdp(pj_pool_t *pool, const void *data, unsigned len)
+{
+ PJ_UNUSED_ARG(len);
+ return pjmedia_sdp_session_clone(pool, (const pjmedia_sdp_session*)data);
+}
+
+static int print_sdp(pjsip_msg_body *body, char *buf, pj_size_t len)
+{
+ return pjmedia_sdp_print((const pjmedia_sdp_session*)body->data, buf, len);
+}
+
+
+PJ_DEF(pj_status_t) pjsip_create_sdp_body( pj_pool_t *pool,
+ pjmedia_sdp_session *sdp,
+ pjsip_msg_body **p_body)
+{
+ const pj_str_t STR_APPLICATION = { "application", 11};
+ const pj_str_t STR_SDP = { "sdp", 3 };
+ pjsip_msg_body *body;
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ PJ_ASSERT_RETURN(body != NULL, PJ_ENOMEM);
+
+ pjsip_media_type_init(&body->content_type, (pj_str_t*)&STR_APPLICATION,
+ (pj_str_t*)&STR_SDP);
+ body->data = sdp;
+ body->len = 0;
+ body->clone_data = &clone_sdp;
+ body->print_body = &print_sdp;
+
+ *p_body = body;
+
+ return PJ_SUCCESS;
+}
+
+static pjsip_msg_body *create_sdp_body(pj_pool_t *pool,
+ const pjmedia_sdp_session *c_sdp)
+{
+ pjsip_msg_body *body;
+ pj_status_t status;
+
+ status = pjsip_create_sdp_body(pool,
+ pjmedia_sdp_session_clone(pool, c_sdp),
+ &body);
+
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ return body;
+}
+
+/*
+ * Create initial INVITE request.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv,
+ pjsip_tx_data **p_tdata )
+{
+ pjsip_tx_data *tdata;
+ const pjsip_hdr *hdr;
+ pj_bool_t has_sdp;
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+ /* State MUST be NULL or CONFIRMED. */
+ PJ_ASSERT_RETURN(inv->state == PJSIP_INV_STATE_NULL ||
+ inv->state == PJSIP_INV_STATE_CONFIRMED,
+ PJ_EINVALIDOP);
+
+ /* Lock dialog. */
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ /* Create the INVITE request. */
+ status = pjsip_dlg_create_request(inv->dlg, pjsip_get_invite_method(), -1,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+
+ /* If this is the first INVITE, then copy the headers from inv_hdr.
+ * These are the headers parsed from the request URI when the
+ * dialog was created.
+ */
+ if (inv->state == PJSIP_INV_STATE_NULL) {
+ hdr = inv->dlg->inv_hdr.next;
+
+ while (hdr != &inv->dlg->inv_hdr) {
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+ }
+
+ /* See if we have SDP to send. */
+ if (inv->neg) {
+ pjmedia_sdp_neg_state neg_state;
+
+ neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+
+ has_sdp = (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER ||
+ (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
+ pjmedia_sdp_neg_has_local_answer(inv->neg)));
+
+
+ } else {
+ has_sdp = PJ_FALSE;
+ }
+
+ /* Add SDP, if any. */
+ if (has_sdp) {
+ const pjmedia_sdp_session *offer;
+
+ status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ goto on_return;
+ }
+
+ tdata->msg->body = create_sdp_body(tdata->pool, offer);
+ }
+
+ /* Add Allow header. */
+ if (inv->dlg->add_allow) {
+ hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_ALLOW, NULL);
+ if (hdr) {
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr));
+ }
+ }
+
+ /* Add Supported header */
+ hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_SUPPORTED, NULL);
+ if (hdr) {
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr));
+ }
+
+ /* Add Require header. */
+ if ((inv->options & PJSIP_INV_REQUIRE_100REL) ||
+ (inv->options & PJSIP_INV_REQUIRE_TIMER))
+ {
+ pjsip_require_hdr *hreq;
+
+ hreq = pjsip_require_hdr_create(tdata->pool);
+
+ if (inv->options & PJSIP_INV_REQUIRE_100REL)
+ hreq->values[hreq->count++] = pj_str("100rel");
+ if (inv->options & PJSIP_INV_REQUIRE_TIMER)
+ hreq->values[hreq->count++] = pj_str("timer");
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hreq);
+ }
+
+ status = pjsip_timer_update_req(inv, tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Done. */
+ *p_tdata = tdata;
+
+
+on_return:
+ pjsip_dlg_dec_lock(inv->dlg);
+ return status;
+}
+
+
+/* Util: swap pool */
+static void swap_pool(pj_pool_t **p1, pj_pool_t **p2)
+{
+ pj_pool_t *tmp = *p1;
+ *p1 = *p2;
+ *p2 = tmp;
+}
+
+
+/*
+ * Initiate SDP negotiation in the SDP negotiator.
+ */
+static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv )
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pjmedia_sdp_neg_get_state(inv->neg) ==
+ PJMEDIA_SDP_NEG_STATE_WAIT_NEGO,
+ PJMEDIA_SDPNEG_EINSTATE);
+
+ status = pjmedia_sdp_neg_negotiate(inv->pool_prov, inv->neg, 0);
+
+ PJ_LOG(5,(inv->obj_name, "SDP negotiation done, status=%d", status));
+
+ if (mod_inv.cb.on_media_update && inv->notify)
+ (*mod_inv.cb.on_media_update)(inv, status);
+
+ /* Invite session may have been terminated by the application even
+ * after a successful SDP negotiation, for example when no audio
+ * codec is present in the offer (see ticket #1034).
+ */
+ if (inv->state != PJSIP_INV_STATE_DISCONNECTED) {
+
+ /* Swap the flip-flop pool when SDP negotiation success. */
+ if (status == PJ_SUCCESS) {
+ swap_pool(&inv->pool_prov, &inv->pool_active);
+ }
+
+ /* Reset the provisional pool regardless SDP negotiation result. */
+ pj_pool_reset(inv->pool_prov);
+
+ } else {
+
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(inv->cause);
+ }
+
+ return status;
+}
+
+/*
+ * Check in incoming message for SDP offer/answer.
+ */
+static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv,
+ pjsip_transaction *tsx,
+ pjsip_rx_data *rdata)
+{
+ struct tsx_inv_data *tsx_inv_data;
+ pj_status_t status;
+ pjsip_msg *msg;
+ pjsip_rdata_sdp_info *sdp_info;
+
+ /* Check if SDP is present in the message. */
+
+ msg = rdata->msg_info.msg;
+ if (msg->body == NULL) {
+ /* Message doesn't have body. */
+ return PJ_SUCCESS;
+ }
+
+ sdp_info = pjsip_rdata_get_sdp_info(rdata);
+ if (sdp_info->body.ptr == NULL) {
+ /* Message body is not "application/sdp" */
+ return PJMEDIA_SDP_EINSDP;
+ }
+
+ /* Get/attach invite session's transaction data */
+ tsx_inv_data = (struct tsx_inv_data*) tsx->mod_data[mod_inv.mod.id];
+ if (tsx_inv_data == NULL) {
+ tsx_inv_data = PJ_POOL_ZALLOC_T(tsx->pool, struct tsx_inv_data);
+ tsx_inv_data->inv = inv;
+ tsx->mod_data[mod_inv.mod.id] = tsx_inv_data;
+ }
+
+ /* MUST NOT do multiple SDP offer/answer in a single transaction,
+ * EXCEPT if:
+ * - this is an initial UAC INVITE transaction (i.e. not re-INVITE), and
+ * - the previous negotiation was done on an early media (18x) and
+ * this response is a final/2xx response, and
+ * - the 2xx response has different To tag than the 18x response
+ * (i.e. the request has forked).
+ *
+ * The exception above is to add a rudimentary support for early media
+ * forking (sample case: custom ringback). See this ticket for more
+ * info: http://trac.pjsip.org/repos/ticket/657
+ */
+ if (tsx_inv_data->sdp_done) {
+ pj_str_t res_tag;
+
+ res_tag = rdata->msg_info.to->tag;
+
+ /* Allow final response after SDP has been negotiated in early
+ * media, IF this response is a final response with different
+ * tag.
+ */
+ if (tsx->role == PJSIP_ROLE_UAC &&
+ rdata->msg_info.msg->line.status.code/100 == 2 &&
+ tsx_inv_data->done_early &&
+ pj_strcmp(&tsx_inv_data->done_tag, &res_tag))
+ {
+ const pjmedia_sdp_session *reoffer_sdp = NULL;
+
+ PJ_LOG(4,(inv->obj_name, "Received forked final response "
+ "after SDP negotiation has been done in early "
+ "media. Renegotiating SDP.."));
+
+ /* Retrieve original SDP offer from INVITE request */
+ reoffer_sdp = (const pjmedia_sdp_session*)
+ tsx->last_tx->msg->body->data;
+
+ /* Feed the original offer to negotiator */
+ status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov,
+ inv->neg,
+ reoffer_sdp);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(1,(inv->obj_name, "Error updating local offer for "
+ "forked 2xx response (err=%d)", status));
+ return status;
+ }
+
+ } else {
+
+ if (rdata->msg_info.msg->body) {
+ PJ_LOG(4,(inv->obj_name, "SDP negotiation done, message "
+ "body is ignored"));
+ }
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Process the SDP body. */
+ if (sdp_info->sdp_err) {
+ PJ_PERROR(4,(THIS_FILE, sdp_info->sdp_err,
+ "Error parsing SDP in %s",
+ pjsip_rx_data_get_info(rdata)));
+ return PJMEDIA_SDP_EINSDP;
+ }
+
+ pj_assert(sdp_info->sdp != NULL);
+
+ /* The SDP can be an offer or answer, depending on negotiator's state */
+
+ if (inv->neg == NULL ||
+ pjmedia_sdp_neg_get_state(inv->neg) == PJMEDIA_SDP_NEG_STATE_DONE)
+ {
+
+ /* This is an offer. */
+
+ PJ_LOG(5,(inv->obj_name, "Got SDP offer in %s",
+ pjsip_rx_data_get_info(rdata)));
+
+ if (inv->neg == NULL) {
+ status=pjmedia_sdp_neg_create_w_remote_offer(inv->pool, NULL,
+ sdp_info->sdp,
+ &inv->neg);
+ } else {
+ status=pjmedia_sdp_neg_set_remote_offer(inv->pool_prov, inv->neg,
+ sdp_info->sdp);
+ }
+
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(THIS_FILE, status, "Error processing SDP offer in %",
+ pjsip_rx_data_get_info(rdata)));
+ return PJMEDIA_SDP_EINSDP;
+ }
+
+ /* Inform application about remote offer. */
+ if (mod_inv.cb.on_rx_offer && inv->notify) {
+
+ (*mod_inv.cb.on_rx_offer)(inv, sdp_info->sdp);
+
+ }
+
+ /* application must have supplied an answer at this point. */
+ if (pjmedia_sdp_neg_get_state(inv->neg) !=
+ PJMEDIA_SDP_NEG_STATE_WAIT_NEGO)
+ {
+ return PJ_EINVALIDOP;
+ }
+
+ } else if (pjmedia_sdp_neg_get_state(inv->neg) ==
+ PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
+ {
+ int status_code;
+
+ /* This is an answer.
+ * Process and negotiate remote answer.
+ */
+
+ PJ_LOG(5,(inv->obj_name, "Got SDP answer in %s",
+ pjsip_rx_data_get_info(rdata)));
+
+ status = pjmedia_sdp_neg_set_remote_answer(inv->pool_prov, inv->neg,
+ sdp_info->sdp);
+
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(THIS_FILE, status, "Error processing SDP answer in %s",
+ pjsip_rx_data_get_info(rdata)));
+ return PJMEDIA_SDP_EINSDP;
+ }
+
+ /* Negotiate SDP */
+
+ inv_negotiate_sdp(inv);
+
+ /* Mark this transaction has having SDP offer/answer done, and
+ * save the reference to the To tag
+ */
+
+ tsx_inv_data->sdp_done = 1;
+ status_code = rdata->msg_info.msg->line.status.code;
+ tsx_inv_data->done_early = (status_code/100==1);
+ pj_strdup(tsx->pool, &tsx_inv_data->done_tag,
+ &rdata->msg_info.to->tag);
+
+ } else {
+
+ PJ_LOG(5,(THIS_FILE, "Ignored SDP in %s: negotiator state is %s",
+ pjsip_rx_data_get_info(rdata),
+ pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_get_state(inv->neg))));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Process INVITE answer, for both initial and subsequent re-INVITE
+ */
+static pj_status_t process_answer( pjsip_inv_session *inv,
+ int st_code,
+ pjsip_tx_data *tdata,
+ const pjmedia_sdp_session *local_sdp)
+{
+ pj_status_t status;
+ const pjmedia_sdp_session *sdp = NULL;
+
+ /* If local_sdp is specified, then we MUST NOT have answered the
+ * offer before.
+ */
+ if (local_sdp && (st_code/100==1 || st_code/100==2)) {
+
+ if (inv->neg == NULL) {
+ status = pjmedia_sdp_neg_create_w_local_offer(inv->pool,
+ local_sdp,
+ &inv->neg);
+ } else if (pjmedia_sdp_neg_get_state(inv->neg)==
+ PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER)
+ {
+ status = pjmedia_sdp_neg_set_local_answer(inv->pool_prov, inv->neg,
+ local_sdp);
+ } else {
+
+ /* Can not specify local SDP at this state. */
+ pj_assert(0);
+ status = PJMEDIA_SDPNEG_EINSTATE;
+ }
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ }
+
+
+ /* If SDP negotiator is ready, start negotiation. */
+ if (st_code/100==2 || (st_code/10==18 && st_code!=180)) {
+
+ pjmedia_sdp_neg_state neg_state;
+
+ /* Start nego when appropriate. */
+ neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) :
+ PJMEDIA_SDP_NEG_STATE_NULL;
+
+ if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
+
+ status = pjmedia_sdp_neg_get_neg_local(inv->neg, &sdp);
+
+ } else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO &&
+ pjmedia_sdp_neg_has_local_answer(inv->neg) )
+ {
+ struct tsx_inv_data *tsx_inv_data;
+
+ /* Get invite session's transaction data */
+ tsx_inv_data = (struct tsx_inv_data*)
+ inv->invite_tsx->mod_data[mod_inv.mod.id];
+
+ status = inv_negotiate_sdp(inv);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Mark this transaction has having SDP offer/answer done. */
+ tsx_inv_data->sdp_done = 1;
+
+ status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp);
+ }
+ }
+
+ /* Include SDP when it's available for 2xx and 18x (but not 180) response.
+ * Subsequent response will include this SDP.
+ *
+ * Note note:
+ * - When offer/answer has been completed in reliable 183, we MUST NOT
+ * send SDP in 2xx response. So if we don't have SDP to send, clear
+ * the SDP in the message body ONLY if 100rel is active in this
+ * session.
+ */
+ if (sdp) {
+ tdata->msg->body = create_sdp_body(tdata->pool, sdp);
+ } else {
+ if (inv->options & PJSIP_INV_REQUIRE_100REL) {
+ tdata->msg->body = NULL;
+ }
+ }
+
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create first response to INVITE
+ */
+PJ_DEF(pj_status_t) pjsip_inv_initial_answer( pjsip_inv_session *inv,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ const pjmedia_sdp_session *sdp,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+ pjsip_status_code st_code2;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+ /* Must have INVITE transaction. */
+ PJ_ASSERT_RETURN(inv->invite_tsx, PJ_EBUG);
+
+ pj_log_push_indent();
+
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ /* Create response */
+ status = pjsip_dlg_create_response(inv->dlg, rdata, st_code, st_text,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Invoke Session Timers module */
+ status = pjsip_timer_process_req(inv, rdata, &st_code2);
+ if (status != PJ_SUCCESS) {
+ pj_status_t status2;
+
+ status2 = pjsip_dlg_modify_response(inv->dlg, tdata, st_code2, NULL);
+ if (status2 != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ goto on_return;
+ }
+ status2 = pjsip_timer_update_resp(inv, tdata);
+ if (status2 == PJ_SUCCESS)
+ *p_tdata = tdata;
+ else
+ pjsip_tx_data_dec_ref(tdata);
+
+ goto on_return;
+ }
+
+ /* Process SDP in answer */
+ status = process_answer(inv, st_code, tdata, sdp);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ goto on_return;
+ }
+
+ /* Save this answer */
+ inv->last_answer = tdata;
+ pjsip_tx_data_add_ref(inv->last_answer);
+ PJ_LOG(5,(inv->dlg->obj_name, "Initial answer %s",
+ pjsip_tx_data_get_info(inv->last_answer)));
+
+ /* Invoke Session Timers */
+ pjsip_timer_update_resp(inv, tdata);
+
+ *p_tdata = tdata;
+
+on_return:
+ pjsip_dlg_dec_lock(inv->dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Answer initial INVITE
+ * Re-INVITE will be answered automatically, and will not use this function.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv,
+ int st_code,
+ const pj_str_t *st_text,
+ const pjmedia_sdp_session *local_sdp,
+ pjsip_tx_data **p_tdata )
+{
+ pjsip_tx_data *last_res;
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+ /* Must have INVITE transaction. */
+ PJ_ASSERT_RETURN(inv->invite_tsx, PJ_EBUG);
+
+ /* Must have created an answer before */
+ PJ_ASSERT_RETURN(inv->last_answer, PJ_EINVALIDOP);
+
+ pj_log_push_indent();
+
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ /* Modify last response. */
+ last_res = inv->last_answer;
+ status = pjsip_dlg_modify_response(inv->dlg, last_res, st_code, st_text);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* For non-2xx final response, strip message body */
+ if (st_code >= 300) {
+ last_res->msg->body = NULL;
+ }
+
+ /* Process SDP in answer */
+ status = process_answer(inv, st_code, last_res, local_sdp);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(last_res);
+ goto on_return;
+ }
+
+ /* Invoke Session Timers */
+ pjsip_timer_update_resp(inv, last_res);
+
+ *p_tdata = last_res;
+
+on_return:
+ pjsip_dlg_dec_lock(inv->dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Set local SDP offer/answer.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_set_local_sdp(pjsip_inv_session *inv,
+ const pjmedia_sdp_session *sdp )
+{
+ const pjmedia_sdp_session *offer;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(inv && sdp, PJ_EINVAL);
+
+ /* If we have remote SDP offer, set local answer to respond to the offer,
+ * otherwise we set/modify our local offer (and create an SDP negotiator
+ * if we don't have one yet).
+ */
+ if (inv->neg) {
+ pjmedia_sdp_neg_state neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+
+ if ((neg_state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER ||
+ neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) &&
+ pjmedia_sdp_neg_get_neg_remote(inv->neg, &offer) == PJ_SUCCESS)
+ {
+ status = pjsip_inv_set_sdp_answer(inv, sdp);
+ } else if (neg_state == PJMEDIA_SDP_NEG_STATE_DONE) {
+ status = pjmedia_sdp_neg_modify_local_offer(inv->pool,
+ inv->neg, sdp);
+ } else
+ return PJMEDIA_SDPNEG_EINSTATE;
+ } else {
+ status = pjmedia_sdp_neg_create_w_local_offer(inv->pool,
+ sdp, &inv->neg);
+ }
+
+ return status;
+}
+
+
+/*
+ * Set SDP answer.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_set_sdp_answer( pjsip_inv_session *inv,
+ const pjmedia_sdp_session *sdp )
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(inv && sdp, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(inv->dlg);
+ status = pjmedia_sdp_neg_set_local_answer( inv->pool_prov, inv->neg, sdp);
+ pjsip_dlg_dec_lock(inv->dlg);
+
+ return status;
+}
+
+
+/*
+ * End session.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv,
+ int st_code,
+ const pj_str_t *st_text,
+ pjsip_tx_data **p_tdata )
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+ pj_log_push_indent();
+
+ /* Set cause code. */
+ inv_set_cause(inv, st_code, st_text);
+
+ /* Create appropriate message. */
+ switch (inv->state) {
+ case PJSIP_INV_STATE_CALLING:
+ case PJSIP_INV_STATE_EARLY:
+ case PJSIP_INV_STATE_INCOMING:
+
+ if (inv->role == PJSIP_ROLE_UAC) {
+
+ /* For UAC when session has not been confirmed, create CANCEL. */
+
+ /* MUST have the original UAC INVITE transaction. */
+ PJ_ASSERT_RETURN(inv->invite_tsx != NULL, PJ_EBUG);
+
+ /* But CANCEL should only be called when we have received a
+ * provisional response. If we haven't received any responses,
+ * just destroy the transaction.
+ */
+ if (inv->invite_tsx->status_code < 100) {
+
+ /* Do not stop INVITE retransmission, see ticket #506 */
+ //pjsip_tsx_stop_retransmit(inv->invite_tsx);
+ inv->cancelling = PJ_TRUE;
+ inv->pending_cancel = PJ_TRUE;
+ *p_tdata = NULL;
+ PJ_LOG(4, (inv->obj_name, "Delaying CANCEL since no "
+ "provisional response is received yet"));
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+ }
+
+ /* The CSeq here assumes that the dialog is started with an
+ * INVITE session. This may not be correct; dialog can be
+ * started as SUBSCRIBE session.
+ * So fix this!
+ */
+ status = pjsip_endpt_create_cancel(inv->dlg->endpt,
+ inv->invite_tsx->last_tx,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ return status;
+ }
+
+ /* Set timeout for the INVITE transaction, in case UAS is not
+ * able to respond the INVITE with 487 final response. The
+ * timeout value is 64*T1.
+ */
+ pjsip_tsx_set_timeout(inv->invite_tsx, 64 * pjsip_cfg()->tsx.t1);
+
+ } else {
+
+ /* For UAS, send a final response. */
+ tdata = inv->invite_tsx->last_tx;
+ PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP);
+
+ //status = pjsip_dlg_modify_response(inv->dlg, tdata, st_code,
+ // st_text);
+ status = pjsip_inv_answer(inv, st_code, st_text, NULL, &tdata);
+ }
+ break;
+
+ case PJSIP_INV_STATE_CONNECTING:
+ case PJSIP_INV_STATE_CONFIRMED:
+ /* End Session Timer */
+ pjsip_timer_end_session(inv);
+
+ /* For established dialog, send BYE */
+ status = pjsip_dlg_create_request(inv->dlg, pjsip_get_bye_method(),
+ -1, &tdata);
+ break;
+
+ case PJSIP_INV_STATE_DISCONNECTED:
+ /* No need to do anything. */
+ pj_log_pop_indent();
+ return PJSIP_ESESSIONTERMINATED;
+
+ default:
+ pj_assert(!"Invalid operation!");
+ pj_log_pop_indent();
+ return PJ_EINVALIDOP;
+ }
+
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ return status;
+ }
+
+
+ /* Done */
+
+ inv->cancelling = PJ_TRUE;
+ *p_tdata = tdata;
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+/* Following redirection recursion, get next target from the target set and
+ * notify user.
+ *
+ * Returns PJ_FALSE if recursion fails (either because there's no more target
+ * or user rejects the recursion). If we return PJ_FALSE, caller should
+ * disconnect the session.
+ *
+ * Note:
+ * the event 'e' argument may be NULL.
+ */
+static pj_bool_t inv_uac_recurse(pjsip_inv_session *inv, int code,
+ const pj_str_t *reason, pjsip_event *e)
+{
+ pjsip_redirect_op op;
+ pjsip_target *target;
+
+ /* Won't redirect if the callback is not implemented. */
+ if (mod_inv.cb.on_redirected == NULL)
+ return PJ_FALSE;
+
+ if (reason == NULL)
+ reason = pjsip_get_status_text(code);
+
+ /* Set status of current target */
+ pjsip_target_assign_status(inv->dlg->target_set.current, inv->dlg->pool,
+ code, reason);
+
+ /* Fetch next target from the target set. We only want to
+ * process SIP/SIPS URI for now.
+ */
+ for (;;) {
+ target = pjsip_target_set_get_next(&inv->dlg->target_set);
+ if (target == NULL) {
+ /* No more target. */
+ return PJ_FALSE;
+ }
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(target->uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(target->uri))
+ {
+ code = PJSIP_SC_UNSUPPORTED_URI_SCHEME;
+ reason = pjsip_get_status_text(code);
+
+ /* Mark this target as unusable and fetch next target. */
+ pjsip_target_assign_status(target, inv->dlg->pool, code, reason);
+ } else {
+ /* Found a target */
+ break;
+ }
+ }
+
+ /* We have target in 'target'. Set this target as current target
+ * and notify callback.
+ */
+ pjsip_target_set_set_current(&inv->dlg->target_set, target);
+
+ op = (*mod_inv.cb.on_redirected)(inv, target->uri, e);
+
+
+ /* Check what the application wants to do now */
+ switch (op) {
+ case PJSIP_REDIRECT_ACCEPT:
+ case PJSIP_REDIRECT_STOP:
+ /* Must increment session counter, that's the convention of the
+ * pjsip_inv_process_redirect().
+ */
+ pjsip_dlg_inc_session(inv->dlg, &mod_inv.mod);
+
+ /* Act on the recursion */
+ pjsip_inv_process_redirect(inv, op, e);
+ return PJ_TRUE;
+
+ case PJSIP_REDIRECT_PENDING:
+ /* Increment session so that the dialog/session is not destroyed
+ * while we're waiting for user confirmation.
+ */
+ pjsip_dlg_inc_session(inv->dlg, &mod_inv.mod);
+
+ /* Also clear the invite_tsx variable, otherwise when this tsx is
+ * terminated, it will also terminate the session.
+ */
+ inv->invite_tsx = NULL;
+
+ /* Done. The processing will continue once the application calls
+ * pjsip_inv_process_redirect().
+ */
+ return PJ_TRUE;
+
+ case PJSIP_REDIRECT_REJECT:
+ /* Recursively call this function again to fetch next target, if any.
+ */
+ return inv_uac_recurse(inv, PJSIP_SC_REQUEST_TERMINATED, NULL, e);
+
+ }
+
+ pj_assert(!"Should not reach here");
+ return PJ_FALSE;
+}
+
+
+/* Process redirection/recursion */
+PJ_DEF(pj_status_t) pjsip_inv_process_redirect( pjsip_inv_session *inv,
+ pjsip_redirect_op op,
+ pjsip_event *e)
+{
+ const pjsip_status_code cancel_code = PJSIP_SC_REQUEST_TERMINATED;
+ pjsip_event usr_event;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(inv && op != PJSIP_REDIRECT_PENDING, PJ_EINVAL);
+
+ if (e == NULL) {
+ PJSIP_EVENT_INIT_USER(usr_event, NULL, NULL, NULL, NULL);
+ e = &usr_event;
+ }
+
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ /* Decrement session. That's the convention here to prevent the dialog
+ * or session from being destroyed while we're waiting for user
+ * confirmation.
+ */
+ pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
+
+ /* See what the application wants to do now */
+ switch (op) {
+ case PJSIP_REDIRECT_ACCEPT:
+ /* User accept the redirection. Reset the session and resend the
+ * INVITE request.
+ */
+ {
+ pjsip_tx_data *tdata;
+ pjsip_via_hdr *via;
+
+ /* Get the original INVITE request. */
+ tdata = inv->invite_req;
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Restore strict route set.
+ * See http://trac.pjsip.org/repos/ticket/492
+ */
+ pjsip_restore_strict_route_set(tdata);
+
+ /* Set target */
+ tdata->msg->line.req.uri = (pjsip_uri*)
+ pjsip_uri_clone(tdata->pool, inv->dlg->target_set.current->uri);
+
+ /* Remove branch param in Via header. */
+ via = (pjsip_via_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ via->branch_param.slen = 0;
+
+ /* Reset message destination info (see #1248). */
+ pj_bzero(&tdata->dest_info, sizeof(tdata->dest_info));
+
+ /* Must invalidate the message! */
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Reset the session */
+ pjsip_inv_uac_restart(inv, PJ_FALSE);
+
+ /* (re)Send the INVITE request */
+ status = pjsip_inv_send_msg(inv, tdata);
+ }
+ break;
+
+ case PJSIP_REDIRECT_STOP:
+ /* User doesn't want the redirection. Disconnect the session now. */
+ inv_set_cause(inv, cancel_code, pjsip_get_status_text(cancel_code));
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+
+ /* Caller should expect that the invite session is gone now, so
+ * we don't need to set status to PJSIP_ESESSIONTERMINATED here.
+ */
+ break;
+
+ case PJSIP_REDIRECT_REJECT:
+ /* Current target is rejected. Fetch next target if any. */
+ if (inv_uac_recurse(inv, cancel_code, NULL, NULL) == PJ_FALSE) {
+ inv_set_cause(inv, cancel_code,
+ pjsip_get_status_text(cancel_code));
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+
+ /* Tell caller that the invite session is gone now */
+ status = PJSIP_ESESSIONTERMINATED;
+ }
+ break;
+
+
+ case PJSIP_REDIRECT_PENDING:
+ pj_assert(!"Should not happen");
+ break;
+ }
+
+
+ pjsip_dlg_dec_lock(inv->dlg);
+
+ return status;
+}
+
+
+/*
+ * Create re-INVITE.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_reinvite( pjsip_inv_session *inv,
+ const pj_str_t *new_contact,
+ const pjmedia_sdp_session *new_offer,
+ pjsip_tx_data **p_tdata )
+{
+ pj_status_t status;
+ pjsip_contact_hdr *contact_hdr = NULL;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+ /* Must NOT have a pending INVITE transaction */
+ if (inv->invite_tsx!=NULL)
+ return PJ_EINVALIDOP;
+
+ pj_log_push_indent();
+
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ if (new_contact) {
+ pj_str_t tmp;
+ const pj_str_t STR_CONTACT = { "Contact", 7 };
+
+ pj_strdup_with_null(inv->dlg->pool, &tmp, new_contact);
+ contact_hdr = (pjsip_contact_hdr*)
+ pjsip_parse_hdr(inv->dlg->pool, &STR_CONTACT,
+ tmp.ptr, tmp.slen, NULL);
+ if (!contact_hdr) {
+ status = PJSIP_EINVALIDURI;
+ goto on_return;
+ }
+ }
+
+
+ if (new_offer) {
+ if (!inv->neg) {
+ status = pjmedia_sdp_neg_create_w_local_offer(inv->pool,
+ new_offer,
+ &inv->neg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ } else switch (pjmedia_sdp_neg_get_state(inv->neg)) {
+
+ case PJMEDIA_SDP_NEG_STATE_NULL:
+ pj_assert(!"Unexpected SDP neg state NULL");
+ status = PJ_EBUG;
+ goto on_return;
+
+ case PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER:
+ PJ_LOG(4,(inv->obj_name,
+ "pjsip_inv_reinvite: already have an offer, new "
+ "offer is ignored"));
+ break;
+
+ case PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER:
+ status = pjmedia_sdp_neg_set_local_answer(inv->pool_prov,
+ inv->neg,
+ new_offer);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ break;
+
+ case PJMEDIA_SDP_NEG_STATE_WAIT_NEGO:
+ PJ_LOG(4,(inv->obj_name,
+ "pjsip_inv_reinvite: SDP in WAIT_NEGO state, new "
+ "offer is ignored"));
+ break;
+
+ case PJMEDIA_SDP_NEG_STATE_DONE:
+ status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov,
+ inv->neg,
+ new_offer);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ break;
+ }
+ }
+
+ if (contact_hdr)
+ inv->dlg->local.contact = contact_hdr;
+
+ status = pjsip_inv_invite(inv, p_tdata);
+
+on_return:
+ pjsip_dlg_dec_lock(inv->dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+/*
+ * Create UPDATE.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_update ( pjsip_inv_session *inv,
+ const pj_str_t *new_contact,
+ const pjmedia_sdp_session *offer,
+ pjsip_tx_data **p_tdata )
+{
+ pjsip_contact_hdr *contact_hdr = NULL;
+ pjsip_tx_data *tdata = NULL;
+ pjmedia_sdp_session *sdp_copy;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+ /* Dialog must have been established */
+ PJ_ASSERT_RETURN(inv->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED,
+ PJ_EINVALIDOP);
+
+ /* Invite session must not have been disconnected */
+ PJ_ASSERT_RETURN(inv->state < PJSIP_INV_STATE_DISCONNECTED,
+ PJ_EINVALIDOP);
+
+ pj_log_push_indent();
+
+ /* Lock dialog. */
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ /* Process offer, if any */
+ if (offer) {
+ if (pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) {
+ PJ_LOG(4,(inv->dlg->obj_name,
+ "Invalid SDP offer/answer state for UPDATE"));
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Notify negotiator about the new offer. This will fix the offer
+ * with correct SDP origin.
+ */
+ status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov, inv->neg,
+ offer);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Retrieve the "fixed" offer from negotiator */
+ pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
+ }
+
+ /* Update Contact if required */
+ if (new_contact) {
+ pj_str_t tmp;
+ const pj_str_t STR_CONTACT = { "Contact", 7 };
+
+ pj_strdup_with_null(inv->dlg->pool, &tmp, new_contact);
+ contact_hdr = (pjsip_contact_hdr*)
+ pjsip_parse_hdr(inv->dlg->pool, &STR_CONTACT,
+ tmp.ptr, tmp.slen, NULL);
+ if (!contact_hdr) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ inv->dlg->local.contact = contact_hdr;
+ }
+
+ /* Create request */
+ status = pjsip_dlg_create_request(inv->dlg, &pjsip_update_method,
+ -1, &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Attach SDP body */
+ if (offer) {
+ sdp_copy = pjmedia_sdp_session_clone(tdata->pool, offer);
+ pjsip_create_sdp_body(tdata->pool, sdp_copy, &tdata->msg->body);
+ }
+
+ /* Unlock dialog. */
+ pjsip_dlg_dec_lock(inv->dlg);
+
+ status = pjsip_timer_update_req(inv, tdata);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ *p_tdata = tdata;
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ if (tdata)
+ pjsip_tx_data_dec_ref(tdata);
+
+ /* Unlock dialog. */
+ pjsip_dlg_dec_lock(inv->dlg);
+
+ pj_log_pop_indent();
+ return status;
+}
+
+/*
+ * Create an ACK request.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_create_ack(pjsip_inv_session *inv,
+ int cseq,
+ pjsip_tx_data **p_tdata)
+{
+ const pjmedia_sdp_session *sdp = NULL;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+ /* Lock dialog. */
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ /* Destroy last_ack */
+ if (inv->last_ack) {
+ pjsip_tx_data_dec_ref(inv->last_ack);
+ inv->last_ack = NULL;
+ }
+
+ /* Create new ACK request */
+ status = pjsip_dlg_create_request(inv->dlg, pjsip_get_ack_method(),
+ cseq, &inv->last_ack);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_dec_lock(inv->dlg);
+ return status;
+ }
+
+ /* See if we have pending SDP answer to send */
+ sdp = inv_has_pending_answer(inv, inv->invite_tsx);
+ if (sdp) {
+ inv->last_ack->msg->body = create_sdp_body(inv->last_ack->pool, sdp);
+ }
+
+ /* Keep this for subsequent response retransmission */
+ inv->last_ack_cseq = cseq;
+ pjsip_tx_data_add_ref(inv->last_ack);
+
+ /* Done */
+ *p_tdata = inv->last_ack;
+
+ /* Unlock dialog. */
+ pjsip_dlg_dec_lock(inv->dlg);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Send a request or response message.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_send_msg( pjsip_inv_session *inv,
+ pjsip_tx_data *tdata)
+{
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
+
+ pj_log_push_indent();
+
+ PJ_LOG(5,(inv->obj_name, "Sending %s",
+ pjsip_tx_data_get_info(tdata)));
+
+ if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+ struct tsx_inv_data *tsx_inv_data;
+
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ /* Check again that we didn't receive incoming re-INVITE */
+ if (tdata->msg->line.req.method.id==PJSIP_INVITE_METHOD &&
+ inv->invite_tsx)
+ {
+ pjsip_tx_data_dec_ref(tdata);
+ pjsip_dlg_dec_lock(inv->dlg);
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Associate our data in outgoing invite transaction */
+ tsx_inv_data = PJ_POOL_ZALLOC_T(inv->pool, struct tsx_inv_data);
+ tsx_inv_data->inv = inv;
+
+ pjsip_dlg_dec_lock(inv->dlg);
+
+ status = pjsip_dlg_send_request(inv->dlg, tdata, mod_inv.mod.id,
+ tsx_inv_data);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ } else {
+ pjsip_cseq_hdr *cseq;
+
+ /* Can only do this to send response to original INVITE
+ * request.
+ */
+ PJ_ASSERT_RETURN((cseq=(pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL))!=NULL
+ && (cseq->cseq == inv->invite_tsx->cseq),
+ PJ_EINVALIDOP);
+
+ if (inv->options & PJSIP_INV_REQUIRE_100REL) {
+ status = pjsip_100rel_tx_response(inv, tdata);
+ } else
+ {
+ status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
+ }
+
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+ }
+
+ /* Done */
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Respond to incoming CANCEL request.
+ */
+static void inv_respond_incoming_cancel(pjsip_inv_session *inv,
+ pjsip_transaction *cancel_tsx,
+ pjsip_rx_data *rdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_transaction *invite_tsx;
+ pj_str_t key;
+ pj_status_t status;
+
+ /* See if we have matching INVITE server transaction: */
+
+ pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAS,
+ pjsip_get_invite_method(), rdata);
+ invite_tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE);
+
+ if (invite_tsx == NULL) {
+
+ /* Invite transaction not found!
+ * Respond CANCEL with 481 (RFC 3261 Section 9.2 page 55)
+ */
+ status = pjsip_dlg_create_response( inv->dlg, rdata, 481, NULL,
+ &tdata);
+
+ } else {
+ /* Always answer CANCEL will 200 (OK) regardless of
+ * the state of the INVITE transaction.
+ */
+ status = pjsip_dlg_create_response( inv->dlg, rdata, 200, NULL,
+ &tdata);
+ }
+
+ /* See if we have created the response successfully. */
+ if (status != PJ_SUCCESS) return;
+
+ /* Send the CANCEL response */
+ status = pjsip_dlg_send_response(inv->dlg, cancel_tsx, tdata);
+ if (status != PJ_SUCCESS) return;
+
+
+ /* See if we need to terminate the UAS INVITE transaction
+ * with 487 (Request Terminated) response.
+ */
+ if (invite_tsx && invite_tsx->status_code < 200) {
+
+ pj_assert(invite_tsx->last_tx != NULL);
+
+ tdata = invite_tsx->last_tx;
+
+ status = pjsip_dlg_modify_response(inv->dlg, tdata, 487, NULL);
+ if (status == PJ_SUCCESS) {
+ /* Remove the message body */
+ tdata->msg->body = NULL;
+ if (inv->options & PJSIP_INV_REQUIRE_100REL) {
+ status = pjsip_100rel_tx_response(inv, tdata);
+ } else {
+ status = pjsip_dlg_send_response(inv->dlg, invite_tsx,
+ tdata);
+ }
+ }
+ }
+
+ if (invite_tsx)
+ pj_mutex_unlock(invite_tsx->mutex);
+}
+
+
+/*
+ * Respond to incoming BYE request.
+ */
+static void inv_respond_incoming_bye( pjsip_inv_session *inv,
+ pjsip_transaction *bye_tsx,
+ pjsip_rx_data *rdata,
+ pjsip_event *e )
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ /* Respond BYE with 200: */
+
+ status = pjsip_dlg_create_response(inv->dlg, rdata, 200, NULL, &tdata);
+ if (status != PJ_SUCCESS) return;
+
+ status = pjsip_dlg_send_response(inv->dlg, bye_tsx, tdata);
+ if (status != PJ_SUCCESS) return;
+
+ /* Terminate session: */
+
+ if (inv->state != PJSIP_INV_STATE_DISCONNECTED) {
+ inv_set_cause(inv, PJSIP_SC_OK, NULL);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ }
+}
+
+/*
+ * Respond to BYE request.
+ */
+static void inv_handle_bye_response( pjsip_inv_session *inv,
+ pjsip_transaction *tsx,
+ pjsip_rx_data *rdata,
+ pjsip_event *e )
+{
+ pj_status_t status;
+
+ if (e->body.tsx_state.type != PJSIP_EVENT_RX_MSG) {
+ inv_set_cause(inv, PJSIP_SC_OK, NULL);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ return;
+ }
+
+ /* Handle 401/407 challenge. */
+ if (tsx->status_code == 401 || tsx->status_code == 407) {
+
+ pjsip_tx_data *tdata;
+
+ status = pjsip_auth_clt_reinit_req( &inv->dlg->auth_sess,
+ rdata,
+ tsx->last_tx,
+ &tdata);
+
+ if (status != PJ_SUCCESS) {
+
+ /* Does not have proper credentials.
+ * End the session anyway.
+ */
+ inv_set_cause(inv, PJSIP_SC_OK, NULL);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+
+ } else {
+ struct tsx_inv_data *tsx_inv_data;
+
+ tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
+ if (tsx_inv_data)
+ tsx_inv_data->retrying = PJ_TRUE;
+
+ /* Re-send BYE. */
+ status = pjsip_inv_send_msg(inv, tdata);
+ }
+
+ } else {
+
+ /* End the session. */
+ inv_set_cause(inv, PJSIP_SC_OK, NULL);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ }
+
+}
+
+/*
+ * Respond to incoming UPDATE request.
+ */
+static void inv_respond_incoming_update(pjsip_inv_session *inv,
+ pjsip_rx_data *rdata)
+{
+ pjmedia_sdp_neg_state neg_state;
+ pj_status_t status;
+ pjsip_tx_data *tdata = NULL;
+ pjsip_status_code st_code;
+
+ /* Invoke Session Timers module */
+ status = pjsip_timer_process_req(inv, rdata, &st_code);
+ if (status != PJ_SUCCESS) {
+ status = pjsip_dlg_create_response(inv->dlg, rdata, st_code,
+ NULL, &tdata);
+ goto on_return;
+ }
+
+ neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+
+ /* Send 491 if we receive UPDATE while we're waiting for an answer */
+ if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
+ status = pjsip_dlg_create_response(inv->dlg, rdata,
+ PJSIP_SC_REQUEST_PENDING, NULL,
+ &tdata);
+ }
+ /* Send 500 with Retry-After header set randomly between 0 and 10 if we
+ * receive UPDATE while we haven't sent answer.
+ */
+ else if (neg_state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER ||
+ neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) {
+ status = pjsip_dlg_create_response(inv->dlg, rdata,
+ PJSIP_SC_INTERNAL_SERVER_ERROR,
+ NULL, &tdata);
+
+ /* If UPDATE doesn't contain SDP, just respond with 200/OK.
+ * This is a valid scenario according to session-timer draft.
+ */
+ } else if (rdata->msg_info.msg->body == NULL) {
+
+ status = pjsip_dlg_create_response(inv->dlg, rdata,
+ 200, NULL, &tdata);
+
+ } else {
+ /* We receive new offer from remote */
+ inv_check_sdp_in_incoming_msg(inv, pjsip_rdata_get_tsx(rdata), rdata);
+
+ /* Application MUST have supplied the answer by now.
+ * If so, negotiate the SDP.
+ */
+ neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+ if (neg_state != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO ||
+ (status=inv_negotiate_sdp(inv)) != PJ_SUCCESS)
+ {
+ /* Negotiation has failed. If negotiator is still
+ * stuck at non-DONE state, cancel any ongoing offer.
+ */
+ neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+ if (neg_state != PJMEDIA_SDP_NEG_STATE_DONE) {
+ pjmedia_sdp_neg_cancel_offer(inv->neg);
+ }
+
+ status = pjsip_dlg_create_response(inv->dlg, rdata,
+ PJSIP_SC_NOT_ACCEPTABLE_HERE,
+ NULL, &tdata);
+ } else {
+ /* New media has been negotiated successfully, send 200/OK */
+ status = pjsip_dlg_create_response(inv->dlg, rdata,
+ PJSIP_SC_OK, NULL, &tdata);
+ if (status == PJ_SUCCESS) {
+ const pjmedia_sdp_session *sdp;
+ status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp);
+ if (status == PJ_SUCCESS)
+ tdata->msg->body = create_sdp_body(tdata->pool, sdp);
+ }
+ }
+ }
+
+on_return:
+ /* Invoke Session Timers */
+ if (status == PJ_SUCCESS)
+ status = pjsip_timer_update_resp(inv, tdata);
+
+ if (status != PJ_SUCCESS) {
+ if (tdata != NULL) {
+ pjsip_tx_data_dec_ref(tdata);
+ tdata = NULL;
+ }
+ return;
+ }
+
+ pjsip_dlg_send_response(inv->dlg, pjsip_rdata_get_tsx(rdata), tdata);
+}
+
+
+/*
+ * Handle incoming response to UAC UPDATE request.
+ */
+static pj_bool_t inv_handle_update_response( pjsip_inv_session *inv,
+ pjsip_event *e)
+{
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ struct tsx_inv_data *tsx_inv_data;
+ pj_bool_t handled = PJ_FALSE;
+ pj_status_t status = -1;
+
+ tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
+ pj_assert(tsx_inv_data);
+
+ /* Handle 401/407 challenge. */
+ if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+ (tsx->status_code == 401 || tsx->status_code == 407))
+ {
+ pjsip_tx_data *tdata;
+
+ status = pjsip_auth_clt_reinit_req( &inv->dlg->auth_sess,
+ e->body.tsx_state.src.rdata,
+ tsx->last_tx,
+ &tdata);
+
+ if (status != PJ_SUCCESS) {
+
+ /* Somehow failed. Probably it's not a good idea to terminate
+ * the session since this is just a request within dialog. And
+ * even if we terminate we should send BYE.
+ */
+ /*
+ inv_set_cause(inv, PJSIP_SC_OK, NULL);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ */
+
+ } else {
+ if (tsx_inv_data)
+ tsx_inv_data->retrying = PJ_TRUE;
+
+ /* Re-send request. */
+ status = pjsip_inv_send_msg(inv, tdata);
+ }
+
+ handled = PJ_TRUE;
+ }
+
+ /* Process 422 response */
+ else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+ tsx->status_code == 422)
+ {
+ status = handle_timer_response(inv, e->body.tsx_state.src.rdata,
+ PJ_FALSE);
+ handled = PJ_TRUE;
+ }
+
+ /* Process 2xx response */
+ else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+ tsx->status_code/100 == 2 &&
+ e->body.tsx_state.src.rdata->msg_info.msg->body)
+ {
+ status = handle_timer_response(inv, e->body.tsx_state.src.rdata,
+ PJ_FALSE);
+ status = inv_check_sdp_in_incoming_msg(inv, tsx,
+ e->body.tsx_state.src.rdata);
+ handled = PJ_TRUE;
+ }
+
+ /* Get/attach invite session's transaction data */
+ else
+ {
+ /* Session-Timer needs to see any error responses, to determine
+ * whether peer supports UPDATE with empty body.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+ tsx->role == PJSIP_ROLE_UAC)
+ {
+ status = handle_timer_response(inv, e->body.tsx_state.src.rdata,
+ PJ_FALSE);
+ handled = PJ_TRUE;
+ }
+ }
+
+ /* Cancel the negotiation if we don't get successful negotiation by now,
+ * unless it's authentication challenge and the request is being retried.
+ */
+ if (pjmedia_sdp_neg_get_state(inv->neg) ==
+ PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER &&
+ tsx_inv_data && tsx_inv_data->sdp_done == PJ_FALSE &&
+ !tsx_inv_data->retrying)
+ {
+ pjmedia_sdp_neg_cancel_offer(inv->neg);
+
+ /* Prevent from us cancelling different offer! */
+ tsx_inv_data->sdp_done = PJ_TRUE;
+ }
+
+ return handled;
+}
+
+
+/*
+ * Handle incoming reliable response.
+ */
+static void inv_handle_incoming_reliable_response(pjsip_inv_session *inv,
+ pjsip_rx_data *rdata)
+{
+ pjsip_tx_data *tdata;
+ const pjmedia_sdp_session *sdp;
+ pj_status_t status;
+
+ /* Create PRACK */
+ status = pjsip_100rel_create_prack(inv, rdata, &tdata);
+ if (status != PJ_SUCCESS)
+ return;
+
+ /* See if we need to attach SDP answer on the PRACK request */
+ sdp = inv_has_pending_answer(inv, pjsip_rdata_get_tsx(rdata));
+ if (sdp) {
+ tdata->msg->body = create_sdp_body(tdata->pool, sdp);
+ }
+
+ /* Send PRACK (must be using 100rel module!) */
+ pjsip_100rel_send_prack(inv, tdata);
+}
+
+
+/*
+ * Handle incoming PRACK.
+ */
+static void inv_respond_incoming_prack(pjsip_inv_session *inv,
+ pjsip_rx_data *rdata)
+{
+ pj_status_t status;
+
+ /* Run through 100rel module to see if we can accept this
+ * PRACK request. The 100rel will send 200/OK to PRACK request.
+ */
+ status = pjsip_100rel_on_rx_prack(inv, rdata);
+ if (status != PJ_SUCCESS)
+ return;
+
+ /* Now check for SDP answer in the PRACK request */
+ if (rdata->msg_info.msg->body) {
+ status = inv_check_sdp_in_incoming_msg(inv,
+ pjsip_rdata_get_tsx(rdata), rdata);
+ } else {
+ /* No SDP body */
+ status = -1;
+ }
+
+ /* If SDP negotiation has been successful, also mark the
+ * SDP negotiation flag in the invite transaction to be
+ * done too.
+ */
+ if (status == PJ_SUCCESS && inv->invite_tsx) {
+ struct tsx_inv_data *tsx_inv_data;
+
+ /* Get/attach invite session's transaction data */
+ tsx_inv_data = (struct tsx_inv_data*)
+ inv->invite_tsx->mod_data[mod_inv.mod.id];
+ if (tsx_inv_data == NULL) {
+ tsx_inv_data = PJ_POOL_ZALLOC_T(inv->invite_tsx->pool,
+ struct tsx_inv_data);
+ tsx_inv_data->inv = inv;
+ inv->invite_tsx->mod_data[mod_inv.mod.id] = tsx_inv_data;
+ }
+
+ tsx_inv_data->sdp_done = PJ_TRUE;
+ }
+}
+
+
+/*
+ * State NULL is before anything is sent/received.
+ */
+static void inv_on_state_null( pjsip_inv_session *inv, pjsip_event *e)
+{
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+ PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+ /* Keep the initial INVITE transaction. */
+ if (inv->invite_tsx == NULL)
+ inv->invite_tsx = tsx;
+
+ if (dlg->role == PJSIP_ROLE_UAC) {
+
+ /* Save the original INVITE request, if on_redirected() callback
+ * is implemented. We may need to resend the INVITE if we receive
+ * redirection response.
+ */
+ if (mod_inv.cb.on_redirected) {
+ if (inv->invite_req) {
+ pjsip_tx_data_dec_ref(inv->invite_req);
+ inv->invite_req = NULL;
+ }
+ inv->invite_req = tsx->last_tx;
+ pjsip_tx_data_add_ref(inv->invite_req);
+ }
+
+ switch (tsx->state) {
+ case PJSIP_TSX_STATE_CALLING:
+ inv_set_state(inv, PJSIP_INV_STATE_CALLING, e);
+ break;
+ default:
+ inv_on_state_calling(inv, e);
+ break;
+ }
+
+ } else {
+ switch (tsx->state) {
+ case PJSIP_TSX_STATE_TRYING:
+ inv_set_state(inv, PJSIP_INV_STATE_INCOMING, e);
+ break;
+ case PJSIP_TSX_STATE_PROCEEDING:
+ inv_set_state(inv, PJSIP_INV_STATE_INCOMING, e);
+ if (tsx->status_code > 100)
+ inv_set_state(inv, PJSIP_INV_STATE_EARLY, e);
+ break;
+ case PJSIP_TSX_STATE_TERMINATED:
+ /* there is a failure in sending response. */
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ break;
+ default:
+ inv_on_state_incoming(inv, e);
+ break;
+ }
+ }
+
+ } else {
+ pj_assert(!"Unexpected transaction type");
+ }
+}
+
+/*
+ * Generic UAC transaction handler:
+ * - resend request on 401 or 407 response.
+ * - terminate dialog on 408 and 481 response.
+ * - resend request on 422 response.
+ */
+static pj_bool_t handle_uac_tsx_response(pjsip_inv_session *inv,
+ pjsip_event *e)
+{
+ /* RFC 3261 Section 12.2.1.2:
+ * If the response for a request within a dialog is a 481
+ * (Call/Transaction Does Not Exist) or a 408 (Request Timeout), the UAC
+ * SHOULD terminate the dialog. A UAC SHOULD also terminate a dialog if
+ * no response at all is received for the request (the client
+ * transaction would inform the TU about the timeout.)
+ *
+ * For INVITE initiated dialogs, terminating the dialog consists of
+ * sending a BYE.
+ *
+ * Note:
+ * according to X, this should terminate dialog usage only, not the
+ * dialog.
+ */
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+
+ pj_assert(tsx->role == PJSIP_UAC_ROLE);
+
+ /* Note that 481 response to CANCEL does not terminate dialog usage,
+ * but only the transaction.
+ */
+ if (inv->state != PJSIP_INV_STATE_DISCONNECTED &&
+ ((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST &&
+ tsx->method.id != PJSIP_CANCEL_METHOD) ||
+ tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT ||
+ tsx->status_code == PJSIP_SC_TSX_TIMEOUT ||
+ tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR))
+ {
+ pjsip_tx_data *bye;
+ pj_status_t status;
+
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+
+ /* Send BYE */
+ status = pjsip_dlg_create_request(inv->dlg, pjsip_get_bye_method(),
+ -1, &bye);
+ if (status == PJ_SUCCESS) {
+ pjsip_inv_send_msg(inv, bye);
+ }
+
+ return PJ_TRUE; /* Handled */
+
+ }
+ /* Handle 401/407 challenge. */
+ else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+ (tsx->status_code == PJSIP_SC_UNAUTHORIZED ||
+ tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED))
+ {
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ if (tsx->method.id == PJSIP_INVITE_METHOD)
+ inv->invite_tsx = NULL;
+
+ status = pjsip_auth_clt_reinit_req( &inv->dlg->auth_sess,
+ e->body.tsx_state.src.rdata,
+ tsx->last_tx, &tdata);
+
+ if (status != PJ_SUCCESS) {
+ /* Somehow failed. Probably it's not a good idea to terminate
+ * the session since this is just a request within dialog. And
+ * even if we terminate we should send BYE.
+ */
+ /*
+ inv_set_cause(inv, PJSIP_SC_OK, NULL);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ */
+
+ } else {
+ struct tsx_inv_data *tsx_inv_data;
+
+ tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
+ if (tsx_inv_data)
+ tsx_inv_data->retrying = PJ_TRUE;
+
+ /* Re-send request. */
+ status = pjsip_inv_send_msg(inv, tdata);
+ }
+
+ return PJ_TRUE; /* Handled */
+ }
+
+ /* Handle session timer 422 response. */
+ else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+ tsx->status_code == PJSIP_SC_SESSION_TIMER_TOO_SMALL)
+ {
+ handle_timer_response(inv, e->body.tsx_state.src.rdata,
+ PJ_FALSE);
+
+ return PJ_TRUE; /* Handled */
+
+ } else {
+ return PJ_FALSE; /* Unhandled */
+ }
+}
+
+
+/* Handle call rejection, especially with regard to processing call
+ * redirection. We need to handle the following scenarios:
+ * - 3xx response is received -- see if on_redirected() callback is
+ * implemented. If so, add the Contact URIs in the response to the
+ * target set and notify user.
+ * - 4xx - 6xx resposne is received -- see if we're currently recursing,
+ * if so fetch the next target if any and notify the on_redirected()
+ * callback.
+ * - for other cases -- disconnect the session.
+ */
+static void handle_uac_call_rejection(pjsip_inv_session *inv, pjsip_event *e)
+{
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ pj_status_t status;
+
+ if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 300)) {
+
+ if (mod_inv.cb.on_redirected == NULL) {
+
+ /* Redirection callback is not implemented, disconnect the
+ * call.
+ */
+ goto terminate_session;
+
+ } else {
+ const pjsip_msg *res_msg;
+
+ res_msg = e->body.tsx_state.src.rdata->msg_info.msg;
+
+ /* Gather all Contact URI's in the response and add them
+ * to target set. The function will take care of removing
+ * duplicate URI's.
+ */
+ pjsip_target_set_add_from_msg(&inv->dlg->target_set,
+ inv->dlg->pool, res_msg);
+
+ /* Recurse to alternate targets if application allows us */
+ if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e))
+ {
+ /* Recursion fails, terminate session now */
+ goto terminate_session;
+ }
+
+ /* Done */
+ }
+
+ } else if ((tsx->status_code==401 || tsx->status_code==407) &&
+ !inv->cancelling)
+ {
+
+ /* Handle authentication failure:
+ * Resend the request with Authorization header.
+ */
+ pjsip_tx_data *tdata;
+
+ status = pjsip_auth_clt_reinit_req(&inv->dlg->auth_sess,
+ e->body.tsx_state.src.rdata,
+ tsx->last_tx,
+ &tdata);
+
+ if (status != PJ_SUCCESS) {
+
+ /* Does not have proper credentials. If we are currently
+ * recursing, try the next target. Otherwise end the session.
+ */
+ if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e))
+ {
+ /* Recursion fails, terminate session now */
+ goto terminate_session;
+ }
+
+ } else {
+
+ /* Restart session. */
+ pjsip_inv_uac_restart(inv, PJ_FALSE);
+
+ /* Send the request. */
+ status = pjsip_inv_send_msg(inv, tdata);
+ }
+
+ } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+ tsx->status_code == PJSIP_SC_SESSION_TIMER_TOO_SMALL)
+ {
+ /* Handle session timer 422 response:
+ * Resend the request with requested session timer setting.
+ */
+ status = handle_timer_response(inv, e->body.tsx_state.src.rdata,
+ PJ_TRUE);
+
+ } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 600)) {
+ /* Global error */
+ goto terminate_session;
+
+ } else {
+ /* See if we have alternate target to try */
+ if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e)) {
+ /* Recursion fails, terminate session now */
+ goto terminate_session;
+ }
+ }
+ return;
+
+terminate_session:
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+}
+
+
+/*
+ * State CALLING is after sending initial INVITE request but before
+ * any response (with tag) is received.
+ */
+static void inv_on_state_calling( pjsip_inv_session *inv, pjsip_event *e)
+{
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+ pj_status_t status;
+
+ PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+ if (tsx == inv->invite_tsx) {
+
+ switch (tsx->state) {
+
+ case PJSIP_TSX_STATE_CALLING:
+ inv_set_state(inv, PJSIP_INV_STATE_CALLING, e);
+ break;
+
+ case PJSIP_TSX_STATE_PROCEEDING:
+ if (inv->pending_cancel) {
+ pjsip_tx_data *cancel;
+
+ inv->pending_cancel = PJ_FALSE;
+
+ status = pjsip_inv_end_session(inv, 487, NULL, &cancel);
+ if (status == PJ_SUCCESS && cancel)
+ status = pjsip_inv_send_msg(inv, cancel);
+ }
+
+ if (dlg->remote.info->tag.slen) {
+
+ inv_set_state(inv, PJSIP_INV_STATE_EARLY, e);
+
+ inv_check_sdp_in_incoming_msg(inv, tsx,
+ e->body.tsx_state.src.rdata);
+
+ if (pjsip_100rel_is_reliable(e->body.tsx_state.src.rdata)) {
+ inv_handle_incoming_reliable_response(
+ inv, e->body.tsx_state.src.rdata);
+ }
+
+ } else {
+ /* Ignore 100 (Trying) response, as it doesn't change
+ * session state. It only ceases retransmissions.
+ */
+ }
+ break;
+
+ case PJSIP_TSX_STATE_COMPLETED:
+ if (tsx->status_code/100 == 2) {
+
+ /* This should not happen.
+ * When transaction receives 2xx, it should be terminated
+ */
+ pj_assert(0);
+
+ /* Process session timer response. */
+ status = handle_timer_response(inv,
+ e->body.tsx_state.src.rdata,
+ PJ_TRUE);
+ if (status != PJ_SUCCESS)
+ break;
+
+ inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e);
+
+ inv_check_sdp_in_incoming_msg(inv, tsx,
+ e->body.tsx_state.src.rdata);
+
+ } else {
+ handle_uac_call_rejection(inv, e);
+ }
+ break;
+
+ case PJSIP_TSX_STATE_TERMINATED:
+ /* INVITE transaction can be terminated either because UAC
+ * transaction received 2xx response or because of transport
+ * error.
+ */
+ if (tsx->status_code/100 == 2) {
+ /* This must be receipt of 2xx response */
+
+ /* Process session timer response. */
+ status = handle_timer_response(inv,
+ e->body.tsx_state.src.rdata,
+ PJ_TRUE);
+ if (status != PJ_SUCCESS)
+ break;
+
+ /* Set state to CONNECTING */
+ inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e);
+
+ inv_check_sdp_in_incoming_msg(inv, tsx,
+ e->body.tsx_state.src.rdata);
+
+ /* Send ACK */
+ pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
+
+ inv_send_ack(inv, e);
+
+ } else {
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ } else if (tsx->role == PJSIP_ROLE_UAC) {
+ /*
+ * Handle case when outgoing request is answered with 481 (Call/
+ * Transaction Does Not Exist), 408, or when it's timed out. In these
+ * cases, disconnect session (i.e. dialog usage only).
+ * Note that 481 response to CANCEL does not terminate dialog usage,
+ * but only the transaction.
+ */
+ if ((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST &&
+ tsx->method.id != PJSIP_CANCEL_METHOD) ||
+ tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT ||
+ tsx->status_code == PJSIP_SC_TSX_TIMEOUT ||
+ tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)
+ {
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ }
+ }
+}
+
+/*
+ * State INCOMING is after we received the request, but before
+ * responses with tag are sent.
+ */
+static void inv_on_state_incoming( pjsip_inv_session *inv, pjsip_event *e)
+{
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+ PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+ if (tsx == inv->invite_tsx) {
+
+ /*
+ * Handle the INVITE state transition.
+ */
+
+ switch (tsx->state) {
+
+ case PJSIP_TSX_STATE_TRYING:
+ inv_set_state(inv, PJSIP_INV_STATE_INCOMING, e);
+ break;
+
+ case PJSIP_TSX_STATE_PROCEEDING:
+ /*
+ * Transaction sent provisional response.
+ */
+ if (tsx->status_code > 100)
+ inv_set_state(inv, PJSIP_INV_STATE_EARLY, e);
+ break;
+
+ case PJSIP_TSX_STATE_COMPLETED:
+ /*
+ * Transaction sent final response.
+ */
+ if (tsx->status_code/100 == 2) {
+ inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e);
+ } else {
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ }
+ break;
+
+ case PJSIP_TSX_STATE_TERMINATED:
+ /*
+ * This happens on transport error (e.g. failed to send
+ * response)
+ */
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ break;
+
+ default:
+ pj_assert(!"Unexpected INVITE state");
+ break;
+ }
+
+ } else if (tsx->method.id == PJSIP_CANCEL_METHOD &&
+ tsx->role == PJSIP_ROLE_UAS &&
+ tsx->state < PJSIP_TSX_STATE_COMPLETED &&
+ e->body.tsx_state.type == PJSIP_EVENT_RX_MSG )
+ {
+
+ /*
+ * Handle incoming CANCEL request.
+ */
+
+ inv_respond_incoming_cancel(inv, tsx, e->body.tsx_state.src.rdata);
+
+ }
+}
+
+/*
+ * State EARLY is for both UAS and UAC, after response with To tag
+ * is sent/received.
+ */
+static void inv_on_state_early( pjsip_inv_session *inv, pjsip_event *e)
+{
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+ PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+ if (tsx == inv->invite_tsx) {
+
+ /*
+ * Handle the INVITE state progress.
+ */
+
+ switch (tsx->state) {
+
+ case PJSIP_TSX_STATE_PROCEEDING:
+ /* Send/received another provisional response. */
+ inv_set_state(inv, PJSIP_INV_STATE_EARLY, e);
+
+ if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+ inv_check_sdp_in_incoming_msg(inv, tsx,
+ e->body.tsx_state.src.rdata);
+
+ if (pjsip_100rel_is_reliable(e->body.tsx_state.src.rdata)) {
+ inv_handle_incoming_reliable_response(
+ inv, e->body.tsx_state.src.rdata);
+ }
+ }
+ break;
+
+ case PJSIP_TSX_STATE_COMPLETED:
+ if (tsx->status_code/100 == 2) {
+ inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e);
+ if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+ pj_status_t status;
+
+ /* Process session timer response. */
+ status = handle_timer_response(inv,
+ e->body.tsx_state.src.rdata,
+ PJ_TRUE);
+ if (status != PJ_SUCCESS)
+ break;
+
+ inv_check_sdp_in_incoming_msg(inv, tsx,
+ e->body.tsx_state.src.rdata);
+ }
+
+ } else if (tsx->role == PJSIP_ROLE_UAC) {
+
+ handle_uac_call_rejection(inv, e);
+
+ } else {
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ }
+ break;
+
+ case PJSIP_TSX_STATE_CONFIRMED:
+ /* For some reason can go here (maybe when ACK for 2xx has
+ * the same branch value as the INVITE transaction) */
+
+ case PJSIP_TSX_STATE_TERMINATED:
+ /* INVITE transaction can be terminated either because UAC
+ * transaction received 2xx response or because of transport
+ * error.
+ */
+ if (tsx->status_code/100 == 2) {
+
+ /* This must be receipt of 2xx response */
+
+ /* Set state to CONNECTING */
+ inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e);
+
+ if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+ pj_status_t status;
+
+ /* Process session timer response. */
+ status = handle_timer_response(inv,
+ e->body.tsx_state.src.rdata,
+ PJ_TRUE);
+ if (status != PJ_SUCCESS)
+ break;
+
+ inv_check_sdp_in_incoming_msg(inv, tsx,
+ e->body.tsx_state.src.rdata);
+ }
+
+ /* if UAC, send ACK and move state to confirmed. */
+ if (tsx->role == PJSIP_ROLE_UAC) {
+ pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
+
+ inv_send_ack(inv, e);
+ }
+
+ } else {
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ }
+ break;
+
+ default:
+ pj_assert(!"Unexpected INVITE tsx state");
+ }
+
+ } else if (inv->role == PJSIP_ROLE_UAS &&
+ tsx->role == PJSIP_ROLE_UAS &&
+ tsx->method.id == PJSIP_CANCEL_METHOD &&
+ tsx->state < PJSIP_TSX_STATE_COMPLETED &&
+ e->body.tsx_state.type == PJSIP_EVENT_RX_MSG )
+ {
+
+ /*
+ * Handle incoming CANCEL request.
+ */
+
+ inv_respond_incoming_cancel(inv, tsx, e->body.tsx_state.src.rdata);
+
+ } else if (tsx->role == PJSIP_ROLE_UAS &&
+ tsx->state == PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+ {
+ /*
+ * Handle incoming UPDATE
+ */
+ inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata);
+
+
+ } else if (tsx->role == PJSIP_ROLE_UAC &&
+ (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+ tsx->state == PJSIP_TSX_STATE_TERMINATED) &&
+ pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+ {
+ /*
+ * Handle response to outgoing UPDATE request.
+ */
+ inv_handle_update_response(inv, e);
+
+ } else if (tsx->role == PJSIP_ROLE_UAS &&
+ tsx->state == PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
+ {
+ /*
+ * Handle incoming PRACK
+ */
+ inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata);
+
+ } else if (tsx->role == PJSIP_ROLE_UAC) {
+
+ /* Generic handling for UAC tsx completion */
+ handle_uac_tsx_response(inv, e);
+
+ } else if (tsx->role == PJSIP_ROLE_UAS &&
+ tsx->method.id == PJSIP_BYE_METHOD &&
+ tsx->status_code < 200 &&
+ e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
+ {
+ /* Received BYE before the 2xx/OK response to INVITE.
+ * Assume that the 2xx/OK response is lost and the BYE
+ * arrives earlier.
+ */
+ inv_respond_incoming_bye(inv, tsx, e->body.tsx_state.src.rdata, e);
+
+ if (inv->invite_tsx->role == PJSIP_ROLE_UAC) {
+ /* Set timer just in case we will never get the final response
+ * for INVITE.
+ */
+ pjsip_tsx_set_timeout(inv->invite_tsx, 64*pjsip_cfg()->tsx.t1);
+ } else if (inv->invite_tsx->status_code < 200) {
+ pjsip_tx_data *tdata;
+ pjsip_msg *msg;
+
+ /* For UAS, send a final response. */
+ tdata = inv->invite_tsx->last_tx;
+ PJ_ASSERT_ON_FAIL(tdata != NULL, return);
+
+ msg = tdata->msg;
+ msg->line.status.code = PJSIP_SC_REQUEST_TERMINATED;
+ msg->line.status.reason =
+ *pjsip_get_status_text(PJSIP_SC_REQUEST_TERMINATED);
+ msg->body = NULL;
+
+ pjsip_tx_data_invalidate_msg(tdata);
+ pjsip_tx_data_add_ref(tdata);
+
+ pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
+ }
+ }
+}
+
+/*
+ * State CONNECTING is after 2xx response to INVITE is sent/received.
+ */
+static void inv_on_state_connecting( pjsip_inv_session *inv, pjsip_event *e)
+{
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+ PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+ if (tsx == inv->invite_tsx) {
+
+ /*
+ * Handle INVITE state progression.
+ */
+ switch (tsx->state) {
+
+ case PJSIP_TSX_STATE_CONFIRMED:
+ /* It can only go here if incoming ACK request has the same Via
+ * branch parameter as the INVITE transaction.
+ */
+ if (tsx->status_code/100 == 2) {
+ if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+ inv_check_sdp_in_incoming_msg(inv, tsx,
+ e->body.tsx_state.src.rdata);
+ }
+
+ inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, e);
+ }
+ break;
+
+ case PJSIP_TSX_STATE_TERMINATED:
+ /* INVITE transaction can be terminated either because UAC
+ * transaction received 2xx response or because of transport
+ * error.
+ */
+ if (tsx->status_code/100 != 2) {
+ if (tsx->role == PJSIP_ROLE_UAC) {
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+ } else {
+ pjsip_tx_data *bye;
+ pj_status_t status;
+
+ /* Send BYE */
+ status = pjsip_dlg_create_request(inv->dlg,
+ pjsip_get_bye_method(),
+ -1, &bye);
+ if (status == PJ_SUCCESS) {
+ pjsip_inv_send_msg(inv, bye);
+ }
+ }
+ }
+ break;
+
+ case PJSIP_TSX_STATE_DESTROYED:
+ /* Do nothing. */
+ break;
+
+ default:
+ pj_assert(!"Unexpected state");
+ }
+
+ } else if (tsx->role == PJSIP_ROLE_UAS &&
+ tsx->method.id == PJSIP_BYE_METHOD &&
+ tsx->status_code < 200 &&
+ e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
+ {
+
+ /*
+ * Handle incoming BYE.
+ */
+
+ inv_respond_incoming_bye( inv, tsx, e->body.tsx_state.src.rdata, e );
+
+ } else if (tsx->method.id == PJSIP_BYE_METHOD &&
+ tsx->role == PJSIP_ROLE_UAC &&
+ (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+ tsx->state == PJSIP_TSX_STATE_TERMINATED))
+ {
+
+ /*
+ * Outgoing BYE
+ */
+ inv_handle_bye_response( inv, tsx, e->body.tsx_state.src.rdata, e);
+
+ }
+ else if (tsx->method.id == PJSIP_CANCEL_METHOD &&
+ tsx->role == PJSIP_ROLE_UAS &&
+ tsx->status_code < 200 &&
+ e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
+ {
+
+ /*
+ * Handle strandled incoming CANCEL.
+ */
+ pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata);
+ if (status != PJ_SUCCESS) return;
+
+ status = pjsip_dlg_send_response(dlg, tsx, tdata);
+ if (status != PJ_SUCCESS) return;
+
+ } else if (tsx->role == PJSIP_ROLE_UAS &&
+ tsx->state == PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, &pjsip_invite_method)==0)
+ {
+ pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* See https://trac.pjsip.org/repos/ticket/1455
+ * Handle incoming re-INVITE before current INVITE is confirmed.
+ * According to RFC 5407:
+ * - answer with 200 if we don't have pending offer-answer
+ * - answer with 491 if we *have* pending offer-answer
+ *
+ * But unfortunately accepting the re-INVITE would mean we have
+ * two outstanding INVITEs, and we don't support that because
+ * we will get confused when we handle the ACK.
+ */
+ status = pjsip_dlg_create_response(inv->dlg, rdata,
+ PJSIP_SC_REQUEST_PENDING,
+ NULL, &tdata);
+ if (status != PJ_SUCCESS)
+ return;
+ pjsip_timer_update_resp(inv, tdata);
+ status = pjsip_dlg_send_response(dlg, tsx, tdata);
+
+ } else if (tsx->role == PJSIP_ROLE_UAS &&
+ tsx->state == PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+ {
+ /*
+ * Handle incoming UPDATE
+ */
+ inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata);
+
+
+ } else if (tsx->role == PJSIP_ROLE_UAC &&
+ (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+ tsx->state == PJSIP_TSX_STATE_TERMINATED) &&
+ pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+ {
+ /*
+ * Handle response to outgoing UPDATE request.
+ */
+ if (inv_handle_update_response(inv, e) == PJ_FALSE)
+ handle_uac_tsx_response(inv, e);
+
+ } else if (tsx->role == PJSIP_ROLE_UAS &&
+ tsx->state == PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
+ {
+ /*
+ * Handle incoming PRACK
+ */
+ inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata);
+
+ } else if (tsx->role == PJSIP_ROLE_UAC) {
+
+ /* Generic handling for UAC tsx completion */
+ handle_uac_tsx_response(inv, e);
+
+ }
+
+}
+
+/*
+ * State CONFIRMED is after ACK is sent/received.
+ */
+static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e)
+{
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+ PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+
+ if (tsx->method.id == PJSIP_BYE_METHOD &&
+ tsx->role == PJSIP_ROLE_UAC &&
+ (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+ tsx->state == PJSIP_TSX_STATE_TERMINATED))
+ {
+
+ /*
+ * Outgoing BYE
+ */
+
+ inv_handle_bye_response( inv, tsx, e->body.tsx_state.src.rdata, e);
+
+ }
+ else if (tsx->method.id == PJSIP_BYE_METHOD &&
+ tsx->role == PJSIP_ROLE_UAS &&
+ tsx->status_code < 200 &&
+ e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
+ {
+
+ /*
+ * Handle incoming BYE.
+ */
+
+ inv_respond_incoming_bye( inv, tsx, e->body.tsx_state.src.rdata, e );
+
+ }
+ else if (tsx->method.id == PJSIP_CANCEL_METHOD &&
+ tsx->role == PJSIP_ROLE_UAS &&
+ tsx->status_code < 200 &&
+ e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
+ {
+
+ /*
+ * Handle strandled incoming CANCEL.
+ */
+ pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata);
+ if (status != PJ_SUCCESS) return;
+
+ status = pjsip_dlg_send_response(dlg, tsx, tdata);
+ if (status != PJ_SUCCESS) return;
+
+ }
+ else if (tsx->method.id == PJSIP_INVITE_METHOD &&
+ tsx->role == PJSIP_ROLE_UAS)
+ {
+
+ /*
+ * Handle incoming re-INVITE
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TRYING) {
+
+ pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+ pjsip_rdata_sdp_info *sdp_info;
+ pjsip_status_code st_code;
+
+ /* Check if we have INVITE pending. */
+ if (inv->invite_tsx && inv->invite_tsx!=tsx) {
+ int code;
+ pj_str_t reason;
+
+ reason = pj_str("Another INVITE transaction in progress");
+
+ if (inv->invite_tsx->role == PJSIP_ROLE_UAC)
+ code = 491;
+ else
+ code = 500;
+
+ /* Can not receive re-INVITE while another one is pending. */
+ status = pjsip_dlg_create_response( inv->dlg, rdata, code,
+ &reason, &tdata);
+ if (status != PJ_SUCCESS)
+ return;
+
+ if (code == 500) {
+ /* MUST include Retry-After header with random value
+ * between 0-10.
+ */
+ pjsip_retry_after_hdr *ra_hdr;
+ int val = (pj_rand() % 10);
+
+ ra_hdr = pjsip_retry_after_hdr_create(tdata->pool, val);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)ra_hdr);
+ }
+
+ status = pjsip_dlg_send_response( inv->dlg, tsx, tdata);
+
+
+ return;
+ }
+
+ /* Save the invite transaction. */
+ inv->invite_tsx = tsx;
+
+ /* Process session timers headers in the re-INVITE */
+ status = pjsip_timer_process_req(inv, rdata, &st_code);
+ if (status != PJ_SUCCESS) {
+ status = pjsip_dlg_create_response(inv->dlg, rdata, st_code,
+ NULL, &tdata);
+ if (status != PJ_SUCCESS)
+ return;
+
+ pjsip_timer_update_resp(inv, tdata);
+ status = pjsip_dlg_send_response(dlg, tsx, tdata);
+ return;
+ }
+
+ /* Send 491 if we receive re-INVITE while another offer/answer
+ * negotiation is in progress
+ */
+ if (pjmedia_sdp_neg_get_state(inv->neg) !=
+ PJMEDIA_SDP_NEG_STATE_DONE)
+ {
+ status = pjsip_dlg_create_response(inv->dlg, rdata,
+ PJSIP_SC_REQUEST_PENDING,
+ NULL, &tdata);
+ if (status != PJ_SUCCESS)
+ return;
+ pjsip_timer_update_resp(inv, tdata);
+ status = pjsip_dlg_send_response(dlg, tsx, tdata);
+ return;
+ }
+
+ /* Process SDP in incoming message. */
+ status = inv_check_sdp_in_incoming_msg(inv, tsx, rdata);
+
+ if (status != PJ_SUCCESS) {
+
+ /* Not Acceptable */
+ const pjsip_hdr *accept;
+
+ /* The incoming SDP is unacceptable. If the SDP negotiator
+ * state has just been changed, i.e: DONE -> REMOTE_OFFER,
+ * revert it back.
+ */
+ if (pjmedia_sdp_neg_get_state(inv->neg) ==
+ PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER)
+ {
+ pjmedia_sdp_neg_cancel_offer(inv->neg);
+ }
+
+ status = pjsip_dlg_create_response(inv->dlg, rdata,
+ 488, NULL, &tdata);
+ if (status != PJ_SUCCESS)
+ return;
+
+
+ accept = pjsip_endpt_get_capability(dlg->endpt, PJSIP_H_ACCEPT,
+ NULL);
+ if (accept) {
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_clone(tdata->pool, accept));
+ }
+
+ status = pjsip_dlg_send_response(dlg, tsx, tdata);
+
+ return;
+ }
+
+ /* Create 2xx ANSWER */
+ status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata);
+ if (status != PJ_SUCCESS)
+ return;
+
+ /* If the INVITE request has SDP body, send answer.
+ * Otherwise generate offer from local active SDP.
+ */
+ sdp_info = pjsip_rdata_get_sdp_info(rdata);
+ if (sdp_info->sdp != NULL) {
+ status = process_answer(inv, 200, tdata, NULL);
+ } else {
+ /* INVITE does not have SDP.
+ * If on_create_offer() callback is implemented, ask app.
+ * to generate an offer, otherwise just send active local
+ * SDP to signal that nothing gets modified.
+ */
+ pjmedia_sdp_session *sdp = NULL;
+
+ if (mod_inv.cb.on_create_offer) {
+ (*mod_inv.cb.on_create_offer)(inv, &sdp);
+ if (sdp) {
+ /* Notify negotiator about the new offer. This will
+ * fix the offer with correct SDP origin.
+ */
+ status =
+ pjmedia_sdp_neg_modify_local_offer(inv->pool_prov,
+ inv->neg,
+ sdp);
+
+ /* Retrieve the "fixed" offer from negotiator */
+ if (status==PJ_SUCCESS) {
+ const pjmedia_sdp_session *lsdp = NULL;
+ pjmedia_sdp_neg_get_neg_local(inv->neg, &lsdp);
+ sdp = (pjmedia_sdp_session*)lsdp;
+ }
+ }
+ }
+
+ if (sdp == NULL) {
+ const pjmedia_sdp_session *active_sdp = NULL;
+ status = pjmedia_sdp_neg_send_local_offer(inv->pool_prov,
+ inv->neg,
+ &active_sdp);
+ if (status == PJ_SUCCESS)
+ sdp = (pjmedia_sdp_session*) active_sdp;
+ }
+
+ if (sdp) {
+ tdata->msg->body = create_sdp_body(tdata->pool, sdp);
+ }
+ }
+
+ if (status != PJ_SUCCESS) {
+ /*
+ * SDP negotiation has failed.
+ */
+ pj_status_t rc;
+ pj_str_t reason;
+
+ /* Delete the 2xx answer */
+ pjsip_tx_data_dec_ref(tdata);
+
+ /* Create 500 response */
+ reason = pj_str("SDP negotiation failed");
+ rc = pjsip_dlg_create_response(dlg, rdata, 500, &reason,
+ &tdata);
+ if (rc == PJ_SUCCESS) {
+ pjsip_warning_hdr *w;
+ const pj_str_t *endpt_name;
+
+ endpt_name = pjsip_endpt_name(dlg->endpt);
+ w = pjsip_warning_hdr_create_from_status(tdata->pool,
+ endpt_name,
+ status);
+ if (w)
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)w);
+
+ pjsip_inv_send_msg(inv, tdata);
+ }
+ return;
+ }
+
+ /* Invoke Session Timers */
+ pjsip_timer_update_resp(inv, tdata);
+
+ /* Send 2xx regardless of the status of negotiation */
+ status = pjsip_inv_send_msg(inv, tdata);
+
+ } else if (tsx->state == PJSIP_TSX_STATE_CONFIRMED) {
+ /* This is the case where ACK has the same branch as
+ * the INVITE request.
+ */
+ if (tsx->status_code/100 == 2 &&
+ e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
+ {
+ inv_check_sdp_in_incoming_msg(inv, tsx,
+ e->body.tsx_state.src.rdata);
+
+ /* Check if local offer got no SDP answer */
+ if (pjmedia_sdp_neg_get_state(inv->neg)==
+ PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
+ {
+ pjmedia_sdp_neg_cancel_offer(inv->neg);
+ }
+ }
+
+ }
+
+ }
+ else if (tsx->method.id == PJSIP_INVITE_METHOD &&
+ tsx->role == PJSIP_ROLE_UAC)
+ {
+
+ /*
+ * Handle outgoing re-INVITE
+ */
+ if (tsx->state == PJSIP_TSX_STATE_CALLING) {
+
+ /* Must not have other pending INVITE transaction */
+ pj_assert(inv->invite_tsx==NULL || tsx==inv->invite_tsx);
+
+ /* Save pending invite transaction */
+ inv->invite_tsx = tsx;
+
+ } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
+ tsx->status_code/100 == 2)
+ {
+ pj_status_t status;
+
+ /* Re-INVITE was accepted. */
+
+ /* Process session timer response. */
+ status = handle_timer_response(inv,
+ e->body.tsx_state.src.rdata,
+ PJ_TRUE);
+ if (status != PJ_SUCCESS)
+ return;
+
+ /* Process SDP */
+ inv_check_sdp_in_incoming_msg(inv, tsx,
+ e->body.tsx_state.src.rdata);
+
+ /* Check if local offer got no SDP answer */
+ if (pjmedia_sdp_neg_get_state(inv->neg)==
+ PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
+ {
+ pjmedia_sdp_neg_cancel_offer(inv->neg);
+ }
+
+ /* Send ACK */
+ inv_send_ack(inv, e);
+
+ } else if (handle_uac_tsx_response(inv, e)) {
+
+ /* Handle response that terminates dialog */
+ /* Nothing to do (already handled) */
+
+ } else if (tsx->status_code >= 300 && tsx->status_code < 700) {
+
+ pjmedia_sdp_neg_state neg_state;
+ struct tsx_inv_data *tsx_inv_data;
+
+ tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id];
+
+ /* Outgoing INVITE transaction has failed, cancel SDP nego */
+ neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+ if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER &&
+ tsx_inv_data->retrying == PJ_FALSE)
+ {
+ pjmedia_sdp_neg_cancel_offer(inv->neg);
+ }
+
+ if (tsx == inv->invite_tsx)
+ inv->invite_tsx = NULL;
+ }
+
+ } else if (tsx->role == PJSIP_ROLE_UAS &&
+ tsx->state == PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+ {
+ /*
+ * Handle incoming UPDATE
+ */
+ inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata);
+
+ } else if (tsx->role == PJSIP_ROLE_UAC &&
+ (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+ tsx->state == PJSIP_TSX_STATE_TERMINATED) &&
+ pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0)
+ {
+ /*
+ * Handle response to outgoing UPDATE request.
+ */
+ if (inv_handle_update_response(inv, e) == PJ_FALSE)
+ handle_uac_tsx_response(inv, e);
+
+ } else if (tsx->role == PJSIP_ROLE_UAS &&
+ tsx->state == PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0)
+ {
+ /*
+ * Handle strandled incoming PRACK
+ */
+ inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata);
+
+ } else if (tsx->role == PJSIP_ROLE_UAC) {
+ /*
+ * Handle 401/407/408/481/422 response
+ */
+ handle_uac_tsx_response(inv, e);
+ }
+
+}
+
+/*
+ * After session has been terminated, but before dialog is destroyed
+ * (because dialog has other usages, or because dialog is waiting for
+ * the last transaction to terminate).
+ */
+static void inv_on_state_disconnected( pjsip_inv_session *inv, pjsip_event *e)
+{
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+ PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+ if (tsx->role == PJSIP_ROLE_UAS &&
+ tsx->status_code < 200 &&
+ e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
+ {
+ pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+
+ /*
+ * Respond BYE with 200/OK
+ */
+ if (tsx->method.id == PJSIP_BYE_METHOD) {
+ inv_respond_incoming_bye( inv, tsx, rdata, e );
+ } else if (tsx->method.id == PJSIP_CANCEL_METHOD) {
+ /*
+ * Respond CANCEL with 200/OK too.
+ */
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata);
+ if (status != PJ_SUCCESS) return;
+
+ status = pjsip_dlg_send_response(dlg, tsx, tdata);
+ if (status != PJ_SUCCESS) return;
+
+ }
+
+ } else if (tsx->role == PJSIP_ROLE_UAC) {
+ /*
+ * Handle 401/407/408/481/422 response
+ */
+ handle_uac_tsx_response(inv, e);
+ }
+}
+
diff --git a/pjsip/src/pjsip-ua/sip_reg.c b/pjsip/src/pjsip-ua/sip_reg.c
new file mode 100644
index 0000000..bd1fb21
--- /dev/null
+++ b/pjsip/src/pjsip-ua/sip_reg.c
@@ -0,0 +1,1333 @@
+/* $Id: sip_reg.c 4173 2012-06-20 10:39:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-ua/sip_regc.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_auth_msg.h>
+#include <pjsip/sip_errno.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/lock.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/log.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+
+
+#define REFRESH_TIMER 1
+#define DELAY_BEFORE_REFRESH PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH
+#define THIS_FILE "sip_reg.c"
+
+/* Outgoing transaction timeout when server sends 100 but never replies
+ * with final response. Value is in MILISECONDS!
+ */
+#define REGC_TSX_TIMEOUT 33000
+
+enum { NOEXP = 0x1FFFFFFF };
+
+static const pj_str_t XUID_PARAM_NAME = { "x-uid", 5 };
+
+
+/* Current/pending operation */
+enum regc_op
+{
+ REGC_IDLE,
+ REGC_REGISTERING,
+ REGC_UNREGISTERING
+};
+
+/**
+ * SIP client registration structure.
+ */
+struct pjsip_regc
+{
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pj_lock_t *lock;
+ pj_bool_t _delete_flag;
+ pj_bool_t has_tsx;
+ pj_atomic_t *busy_ctr;
+ enum regc_op current_op;
+
+ pj_bool_t add_xuid_param;
+
+ void *token;
+ pjsip_regc_cb *cb;
+
+ pj_str_t str_srv_url;
+ pjsip_uri *srv_url;
+ pjsip_cid_hdr *cid_hdr;
+ pjsip_cseq_hdr *cseq_hdr;
+ pj_str_t from_uri;
+ pjsip_from_hdr *from_hdr;
+ pjsip_to_hdr *to_hdr;
+ pjsip_contact_hdr contact_hdr_list;
+ pjsip_contact_hdr removed_contact_hdr_list;
+ pjsip_expires_hdr *expires_hdr;
+ pj_uint32_t expires;
+ pj_uint32_t delay_before_refresh;
+ pjsip_route_hdr route_set;
+ pjsip_hdr hdr_list;
+ pjsip_host_port via_addr;
+ const void *via_tp;
+
+ /* Authorization sessions. */
+ pjsip_auth_clt_sess auth_sess;
+
+ /* Auto refresh registration. */
+ pj_bool_t auto_reg;
+ pj_time_val last_reg;
+ pj_time_val next_reg;
+ pj_timer_entry timer;
+
+ /* Transport selector */
+ pjsip_tpselector tp_sel;
+
+ /* Last transport used. We acquire the transport to keep
+ * it open.
+ */
+ pjsip_transport *last_transport;
+};
+
+
+PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
+ pjsip_regc_cb *cb,
+ pjsip_regc **p_regc)
+{
+ pj_pool_t *pool;
+ pjsip_regc *regc;
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(endpt && cb && p_regc, PJ_EINVAL);
+
+ pool = pjsip_endpt_create_pool(endpt, "regc%p", 1024, 1024);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ regc = PJ_POOL_ZALLOC_T(pool, pjsip_regc);
+
+ regc->pool = pool;
+ regc->endpt = endpt;
+ regc->token = token;
+ regc->cb = cb;
+ regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED;
+ regc->add_xuid_param = pjsip_cfg()->regc.add_xuid_param;
+
+ status = pj_lock_create_recursive_mutex(pool, pool->obj_name,
+ &regc->lock);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ status = pj_atomic_create(pool, 0, &regc->busy_ctr);
+ if (status != PJ_SUCCESS) {
+ pj_lock_destroy(regc->lock);
+ pj_pool_release(pool);
+ return status;
+ }
+
+ status = pjsip_auth_clt_init(&regc->auth_sess, endpt, regc->pool, 0);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_list_init(&regc->route_set);
+ pj_list_init(&regc->hdr_list);
+ pj_list_init(&regc->contact_hdr_list);
+ pj_list_init(&regc->removed_contact_hdr_list);
+
+ /* Done */
+ *p_regc = regc;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_regc_destroy(pjsip_regc *regc)
+{
+ PJ_ASSERT_RETURN(regc, PJ_EINVAL);
+
+ pj_lock_acquire(regc->lock);
+ if (regc->has_tsx || pj_atomic_get(regc->busy_ctr) != 0) {
+ regc->_delete_flag = 1;
+ regc->cb = NULL;
+ pj_lock_release(regc->lock);
+ } else {
+ pjsip_tpselector_dec_ref(&regc->tp_sel);
+ if (regc->last_transport) {
+ pjsip_transport_dec_ref(regc->last_transport);
+ regc->last_transport = NULL;
+ }
+ if (regc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
+ regc->timer.id = 0;
+ }
+ pj_atomic_destroy(regc->busy_ctr);
+ pj_lock_release(regc->lock);
+ pj_lock_destroy(regc->lock);
+ regc->lock = NULL;
+ pjsip_endpt_release_pool(regc->endpt, regc->pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_regc_get_info( pjsip_regc *regc,
+ pjsip_regc_info *info )
+{
+ PJ_ASSERT_RETURN(regc && info, PJ_EINVAL);
+
+ pj_lock_acquire(regc->lock);
+
+ info->server_uri = regc->str_srv_url;
+ info->client_uri = regc->from_uri;
+ info->is_busy = (pj_atomic_get(regc->busy_ctr) || regc->has_tsx);
+ info->auto_reg = regc->auto_reg;
+ info->interval = regc->expires;
+ info->transport = regc->last_transport;
+
+ if (regc->has_tsx)
+ info->next_reg = 0;
+ else if (regc->auto_reg == 0)
+ info->next_reg = 0;
+ else if (regc->expires < 0)
+ info->next_reg = regc->expires;
+ else {
+ pj_time_val now, next_reg;
+
+ next_reg = regc->next_reg;
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(next_reg, now);
+ info->next_reg = next_reg.sec;
+ }
+
+ pj_lock_release(regc->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_pool_t*) pjsip_regc_get_pool(pjsip_regc *regc)
+{
+ return regc->pool;
+}
+
+static void set_expires( pjsip_regc *regc, pj_uint32_t expires)
+{
+ if (expires != regc->expires) {
+ regc->expires_hdr = pjsip_expires_hdr_create(regc->pool, expires);
+ } else {
+ regc->expires_hdr = NULL;
+ }
+}
+
+
+static pj_status_t set_contact( pjsip_regc *regc,
+ int contact_cnt,
+ const pj_str_t contact[] )
+{
+ const pj_str_t CONTACT = { "Contact", 7 };
+ pjsip_contact_hdr *h;
+ int i;
+
+ /* Save existing contact list to removed_contact_hdr_list and
+ * clear contact_hdr_list.
+ */
+ pj_list_merge_last(&regc->removed_contact_hdr_list,
+ &regc->contact_hdr_list);
+
+ /* Set the expiration of Contacts in to removed_contact_hdr_list
+ * zero.
+ */
+ h = regc->removed_contact_hdr_list.next;
+ while (h != &regc->removed_contact_hdr_list) {
+ h->expires = 0;
+ h = h->next;
+ }
+
+ /* Process new contacts */
+ for (i=0; i<contact_cnt; ++i) {
+ pjsip_contact_hdr *hdr;
+ pj_str_t tmp;
+
+ pj_strdup_with_null(regc->pool, &tmp, &contact[i]);
+ hdr = (pjsip_contact_hdr*)
+ pjsip_parse_hdr(regc->pool, &CONTACT, tmp.ptr, tmp.slen, NULL);
+ if (hdr == NULL) {
+ PJ_LOG(4,(THIS_FILE, "Invalid Contact: \"%.*s\"",
+ (int)tmp.slen, tmp.ptr));
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Find the new contact in old contact list. If found, remove
+ * the old header from the old header list.
+ */
+ h = regc->removed_contact_hdr_list.next;
+ while (h != &regc->removed_contact_hdr_list) {
+ int rc;
+
+ rc = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR,
+ h->uri, hdr->uri);
+ if (rc == 0) {
+ /* Match */
+ pj_list_erase(h);
+ break;
+ }
+
+ h = h->next;
+ }
+
+ /* If add_xuid_param option is enabled and Contact URI is sip/sips,
+ * add xuid parameter to assist matching the Contact URI in the
+ * REGISTER response later.
+ */
+ if (regc->add_xuid_param && (PJSIP_URI_SCHEME_IS_SIP(hdr->uri) ||
+ PJSIP_URI_SCHEME_IS_SIPS(hdr->uri)))
+ {
+ pjsip_param *xuid_param;
+ pjsip_sip_uri *sip_uri;
+
+ xuid_param = PJ_POOL_ZALLOC_T(regc->pool, pjsip_param);
+ xuid_param->name = XUID_PARAM_NAME;
+ pj_create_unique_string(regc->pool, &xuid_param->value);
+
+ sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(hdr->uri);
+ pj_list_push_back(&sip_uri->other_param, xuid_param);
+ }
+
+ pj_list_push_back(&regc->contact_hdr_list, hdr);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_regc_init( pjsip_regc *regc,
+ const pj_str_t *srv_url,
+ const pj_str_t *from_url,
+ const pj_str_t *to_url,
+ int contact_cnt,
+ const pj_str_t contact[],
+ pj_uint32_t expires)
+{
+ pj_str_t tmp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(regc && srv_url && from_url && to_url &&
+ contact_cnt && contact && expires, PJ_EINVAL);
+
+ /* Copy server URL. */
+ pj_strdup_with_null(regc->pool, &regc->str_srv_url, srv_url);
+
+ /* Set server URL. */
+ tmp = regc->str_srv_url;
+ regc->srv_url = pjsip_parse_uri( regc->pool, tmp.ptr, tmp.slen, 0);
+ if (regc->srv_url == NULL) {
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Set "From" header. */
+ pj_strdup_with_null(regc->pool, &regc->from_uri, from_url);
+ tmp = regc->from_uri;
+ regc->from_hdr = pjsip_from_hdr_create(regc->pool);
+ regc->from_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (!regc->from_hdr->uri) {
+ PJ_LOG(4,(THIS_FILE, "regc: invalid source URI %.*s",
+ from_url->slen, from_url->ptr));
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Set "To" header. */
+ pj_strdup_with_null(regc->pool, &tmp, to_url);
+ regc->to_hdr = pjsip_to_hdr_create(regc->pool);
+ regc->to_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (!regc->to_hdr->uri) {
+ PJ_LOG(4,(THIS_FILE, "regc: invalid target URI %.*s", to_url->slen, to_url->ptr));
+ return PJSIP_EINVALIDURI;
+ }
+
+
+ /* Set "Contact" header. */
+ status = set_contact( regc, contact_cnt, contact);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set "Expires" header, if required. */
+ set_expires( regc, expires);
+ regc->delay_before_refresh = DELAY_BEFORE_REFRESH;
+
+ /* Set "Call-ID" header. */
+ regc->cid_hdr = pjsip_cid_hdr_create(regc->pool);
+ pj_create_unique_string(regc->pool, &regc->cid_hdr->id);
+
+ /* Set "CSeq" header. */
+ regc->cseq_hdr = pjsip_cseq_hdr_create(regc->pool);
+ regc->cseq_hdr->cseq = pj_rand() % 0xFFFF;
+ pjsip_method_set( &regc->cseq_hdr->method, PJSIP_REGISTER_METHOD);
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_regc_set_credentials( pjsip_regc *regc,
+ int count,
+ const pjsip_cred_info cred[] )
+{
+ PJ_ASSERT_RETURN(regc && count && cred, PJ_EINVAL);
+ return pjsip_auth_clt_set_credentials(&regc->auth_sess, count, cred);
+}
+
+PJ_DEF(pj_status_t) pjsip_regc_set_prefs( pjsip_regc *regc,
+ const pjsip_auth_clt_pref *pref)
+{
+ PJ_ASSERT_RETURN(regc && pref, PJ_EINVAL);
+ return pjsip_auth_clt_set_prefs(&regc->auth_sess, pref);
+}
+
+PJ_DEF(pj_status_t) pjsip_regc_set_route_set( pjsip_regc *regc,
+ const pjsip_route_hdr *route_set)
+{
+ const pjsip_route_hdr *chdr;
+
+ PJ_ASSERT_RETURN(regc && route_set, PJ_EINVAL);
+
+ pj_list_init(&regc->route_set);
+
+ chdr = route_set->next;
+ while (chdr != route_set) {
+ pj_list_push_back(&regc->route_set, pjsip_hdr_clone(regc->pool, chdr));
+ chdr = chdr->next;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Bind client registration to a specific transport/listener.
+ */
+PJ_DEF(pj_status_t) pjsip_regc_set_transport( pjsip_regc *regc,
+ const pjsip_tpselector *sel)
+{
+ PJ_ASSERT_RETURN(regc && sel, PJ_EINVAL);
+
+ pjsip_tpselector_dec_ref(&regc->tp_sel);
+ pj_memcpy(&regc->tp_sel, sel, sizeof(*sel));
+ pjsip_tpselector_add_ref(&regc->tp_sel);
+
+ return PJ_SUCCESS;
+}
+
+/* Release transport */
+PJ_DEF(pj_status_t) pjsip_regc_release_transport(pjsip_regc *regc)
+{
+ PJ_ASSERT_RETURN(regc, PJ_EINVAL);
+ if (regc->last_transport) {
+ pjsip_transport_dec_ref(regc->last_transport);
+ regc->last_transport = NULL;
+ }
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_regc_add_headers( pjsip_regc *regc,
+ const pjsip_hdr *hdr_list)
+{
+ const pjsip_hdr *hdr;
+
+ PJ_ASSERT_RETURN(regc && hdr_list, PJ_EINVAL);
+
+ //This is "add" operation, so don't remove headers.
+ //pj_list_init(&regc->hdr_list);
+
+ hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pj_list_push_back(&regc->hdr_list, pjsip_hdr_clone(regc->pool, hdr));
+ hdr = hdr->next;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t create_request(pjsip_regc *regc,
+ pjsip_tx_data **p_tdata)
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);
+
+ /* Create the request. */
+ status = pjsip_endpt_create_request_from_hdr( regc->endpt,
+ pjsip_get_register_method(),
+ regc->srv_url,
+ regc->from_hdr,
+ regc->to_hdr,
+ NULL,
+ regc->cid_hdr,
+ regc->cseq_hdr->cseq,
+ NULL,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add cached authorization headers. */
+ pjsip_auth_clt_init_req( &regc->auth_sess, tdata );
+
+ /* Add Route headers from route set, ideally after Via header */
+ if (!pj_list_empty(&regc->route_set)) {
+ pjsip_hdr *route_pos;
+ const pjsip_route_hdr *route;
+
+ route_pos = (pjsip_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ if (!route_pos)
+ route_pos = &tdata->msg->hdr;
+
+ route = regc->route_set.next;
+ while (route != &regc->route_set) {
+ pjsip_hdr *new_hdr = (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, route);
+ pj_list_insert_after(route_pos, new_hdr);
+ route_pos = new_hdr;
+ route = route->next;
+ }
+ }
+
+ /* Add additional request headers */
+ if (!pj_list_empty(&regc->hdr_list)) {
+ const pjsip_hdr *hdr;
+
+ hdr = regc->hdr_list.next;
+ while (hdr != &regc->hdr_list) {
+ pjsip_hdr *new_hdr = (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr);
+ pjsip_msg_add_hdr(tdata->msg, new_hdr);
+ hdr = hdr->next;
+ }
+ }
+
+ /* Done. */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_msg *msg;
+ pjsip_contact_hdr *hdr;
+ const pjsip_hdr *h_allow;
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);
+
+ pj_lock_acquire(regc->lock);
+
+ status = create_request(regc, &tdata);
+ if (status != PJ_SUCCESS) {
+ pj_lock_release(regc->lock);
+ return status;
+ }
+
+ msg = tdata->msg;
+
+ /* Add Contact headers. */
+ hdr = regc->contact_hdr_list.next;
+ while (hdr != &regc->contact_hdr_list) {
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+
+ /* Also add bindings which are to be removed */
+ while (!pj_list_empty(&regc->removed_contact_hdr_list)) {
+ hdr = regc->removed_contact_hdr_list.next;
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)
+ pjsip_hdr_clone(tdata->pool, hdr));
+ pj_list_erase(hdr);
+ }
+
+
+ if (regc->expires_hdr)
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool,
+ regc->expires_hdr));
+
+ if (regc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
+ regc->timer.id = 0;
+ }
+
+ /* Add Allow header (http://trac.pjsip.org/repos/ticket/1039) */
+ h_allow = pjsip_endpt_get_capability(regc->endpt, PJSIP_H_ALLOW, NULL);
+ if (h_allow) {
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, h_allow));
+
+ }
+
+ regc->auto_reg = autoreg;
+
+ pj_lock_release(regc->lock);
+
+ /* Done */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_regc_unregister(pjsip_regc *regc,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_msg *msg;
+ pjsip_hdr *hdr;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);
+
+ pj_lock_acquire(regc->lock);
+
+ if (regc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
+ regc->timer.id = 0;
+ }
+
+ status = create_request(regc, &tdata);
+ if (status != PJ_SUCCESS) {
+ pj_lock_release(regc->lock);
+ return status;
+ }
+
+ msg = tdata->msg;
+
+ /* Add Contact headers. */
+ hdr = (pjsip_hdr*)regc->contact_hdr_list.next;
+ while ((void*)hdr != (void*)&regc->contact_hdr_list) {
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+
+ /* Also add bindings which are to be removed */
+ while (!pj_list_empty(&regc->removed_contact_hdr_list)) {
+ hdr = (pjsip_hdr*)regc->removed_contact_hdr_list.next;
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)
+ pjsip_hdr_clone(tdata->pool, hdr));
+ pj_list_erase(hdr);
+ }
+
+ /* Add Expires:0 header */
+ hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0);
+ pjsip_msg_add_hdr(msg, hdr);
+
+ pj_lock_release(regc->lock);
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_contact_hdr *hcontact;
+ pjsip_hdr *hdr;
+ pjsip_msg *msg;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);
+
+ pj_lock_acquire(regc->lock);
+
+ if (regc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
+ regc->timer.id = 0;
+ }
+
+ status = create_request(regc, &tdata);
+ if (status != PJ_SUCCESS) {
+ pj_lock_release(regc->lock);
+ return status;
+ }
+
+ msg = tdata->msg;
+
+ /* Clear removed_contact_hdr_list */
+ pj_list_init(&regc->removed_contact_hdr_list);
+
+ /* Add Contact:* header */
+ hcontact = pjsip_contact_hdr_create(tdata->pool);
+ hcontact->star = 1;
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)hcontact);
+
+ /* Add Expires:0 header */
+ hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0);
+ pjsip_msg_add_hdr(msg, hdr);
+
+ pj_lock_release(regc->lock);
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_regc_update_contact( pjsip_regc *regc,
+ int contact_cnt,
+ const pj_str_t contact[] )
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(regc, PJ_EINVAL);
+
+ pj_lock_acquire(regc->lock);
+ status = set_contact( regc, contact_cnt, contact );
+ pj_lock_release(regc->lock);
+
+ return status;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_regc_update_expires( pjsip_regc *regc,
+ pj_uint32_t expires )
+{
+ PJ_ASSERT_RETURN(regc, PJ_EINVAL);
+
+ pj_lock_acquire(regc->lock);
+ set_expires( regc, expires );
+ pj_lock_release(regc->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+static void call_callback(pjsip_regc *regc, pj_status_t status, int st_code,
+ const pj_str_t *reason,
+ pjsip_rx_data *rdata, pj_int32_t expiration,
+ int contact_cnt, pjsip_contact_hdr *contact[])
+{
+ struct pjsip_regc_cbparam cbparam;
+
+
+ if (!regc->cb)
+ return;
+
+ cbparam.regc = regc;
+ cbparam.token = regc->token;
+ cbparam.status = status;
+ cbparam.code = st_code;
+ cbparam.reason = *reason;
+ cbparam.rdata = rdata;
+ cbparam.contact_cnt = contact_cnt;
+ cbparam.expiration = expiration;
+ if (contact_cnt) {
+ pj_memcpy( cbparam.contact, contact,
+ contact_cnt*sizeof(pjsip_contact_hdr*));
+ }
+
+ (*regc->cb)(&cbparam);
+}
+
+static void regc_refresh_timer_cb( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pjsip_regc *regc = (pjsip_regc*) entry->user_data;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ /* Temporarily increase busy flag to prevent regc from being deleted
+ * in pjsip_regc_send() or in the callback
+ */
+ pj_atomic_inc(regc->busy_ctr);
+
+ entry->id = 0;
+ status = pjsip_regc_register(regc, 1, &tdata);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_regc_send(regc, tdata);
+ }
+
+ if (status != PJ_SUCCESS && regc->cb) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg));
+ call_callback(regc, status, 400, &reason, NULL, -1, 0, NULL);
+ }
+
+ /* Delete the record if user destroy regc during the callback. */
+ if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) {
+ pjsip_regc_destroy(regc);
+ }
+}
+
+static void schedule_registration ( pjsip_regc *regc, pj_int32_t expiration )
+{
+ if (regc->auto_reg && expiration > 0) {
+ pj_time_val delay = { 0, 0};
+
+ delay.sec = expiration - regc->delay_before_refresh;
+ if (regc->expires != PJSIP_REGC_EXPIRATION_NOT_SPECIFIED &&
+ delay.sec > (pj_int32_t)regc->expires)
+ {
+ delay.sec = regc->expires;
+ }
+ if (delay.sec < DELAY_BEFORE_REFRESH)
+ delay.sec = DELAY_BEFORE_REFRESH;
+ regc->timer.cb = &regc_refresh_timer_cb;
+ regc->timer.id = REFRESH_TIMER;
+ regc->timer.user_data = regc;
+ pjsip_endpt_schedule_timer( regc->endpt, &regc->timer, &delay);
+ pj_gettimeofday(&regc->last_reg);
+ regc->next_reg = regc->last_reg;
+ regc->next_reg.sec += delay.sec;
+ }
+}
+
+PJ_DEF(pj_status_t) pjsip_regc_set_via_sent_by( pjsip_regc *regc,
+ pjsip_host_port *via_addr,
+ pjsip_transport *via_tp)
+{
+ PJ_ASSERT_RETURN(regc, PJ_EINVAL);
+
+ if (!via_addr)
+ pj_bzero(&regc->via_addr, sizeof(regc->via_addr));
+ else
+ regc->via_addr = *via_addr;
+ regc->via_tp = via_tp;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t)
+pjsip_regc_set_delay_before_refresh( pjsip_regc *regc,
+ pj_uint32_t delay )
+{
+ PJ_ASSERT_RETURN(regc, PJ_EINVAL);
+
+ if (delay > regc->expires)
+ return PJ_ETOOBIG;
+
+ if (regc->delay_before_refresh != delay)
+ {
+ regc->delay_before_refresh = delay;
+
+ if (regc->timer.id != 0) {
+ /* Cancel registration timer */
+ pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
+ regc->timer.id = 0;
+
+ /* Schedule next registration */
+ schedule_registration(regc, regc->expires);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_int32_t calculate_response_expiration(const pjsip_regc *regc,
+ const pjsip_rx_data *rdata,
+ unsigned *contact_cnt,
+ unsigned max_contact,
+ pjsip_contact_hdr *contacts[])
+{
+ pj_int32_t expiration = NOEXP;
+ const pjsip_msg *msg = rdata->msg_info.msg;
+ const pjsip_hdr *hdr;
+
+ /* Enumerate all Contact headers in the response */
+ *contact_cnt = 0;
+ for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) {
+ if (hdr->type == PJSIP_H_CONTACT &&
+ *contact_cnt < max_contact)
+ {
+ contacts[*contact_cnt] = (pjsip_contact_hdr*)hdr;
+ ++(*contact_cnt);
+ }
+ }
+
+ if (regc->current_op == REGC_REGISTERING) {
+ pj_bool_t has_our_contact = PJ_FALSE;
+ const pjsip_expires_hdr *expires;
+
+ /* Get Expires header */
+ expires = (const pjsip_expires_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+
+ /* Try to find the Contact URIs that we register, in the response
+ * to get the expires value. We'll try both with comparing the URI
+ * and comparing the extension param only.
+ */
+ if (pjsip_cfg()->regc.check_contact || regc->add_xuid_param) {
+ unsigned i;
+ for (i=0; i<*contact_cnt; ++i) {
+ const pjsip_contact_hdr *our_hdr;
+
+ our_hdr = (const pjsip_contact_hdr*)
+ regc->contact_hdr_list.next;
+
+ /* Match with our Contact header(s) */
+ while ((void*)our_hdr != (void*)&regc->contact_hdr_list) {
+
+ const pjsip_uri *uri1, *uri2;
+ pj_bool_t matched = PJ_FALSE;
+
+ /* Exclude the display name when comparing the URI
+ * since server may not return it.
+ */
+ uri1 = (const pjsip_uri*)
+ pjsip_uri_get_uri(contacts[i]->uri);
+ uri2 = (const pjsip_uri*)
+ pjsip_uri_get_uri(our_hdr->uri);
+
+ /* First try with exact matching, according to RFC 3261
+ * Section 19.1.4 URI Comparison
+ */
+ if (pjsip_cfg()->regc.check_contact) {
+ matched = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR,
+ uri1, uri2)==0;
+ }
+
+ /* If no match is found, try with matching the extension
+ * parameter only if extension parameter was added.
+ */
+ if (!matched && regc->add_xuid_param &&
+ (PJSIP_URI_SCHEME_IS_SIP(uri1) ||
+ PJSIP_URI_SCHEME_IS_SIPS(uri1)) &&
+ (PJSIP_URI_SCHEME_IS_SIP(uri2) ||
+ PJSIP_URI_SCHEME_IS_SIPS(uri2)))
+ {
+ const pjsip_sip_uri *sip_uri1, *sip_uri2;
+ const pjsip_param *p1, *p2;
+
+ sip_uri1 = (const pjsip_sip_uri*)uri1;
+ sip_uri2 = (const pjsip_sip_uri*)uri2;
+
+ p1 = pjsip_param_cfind(&sip_uri1->other_param,
+ &XUID_PARAM_NAME);
+ p2 = pjsip_param_cfind(&sip_uri2->other_param,
+ &XUID_PARAM_NAME);
+ matched = p1 && p2 &&
+ pj_strcmp(&p1->value, &p2->value)==0;
+
+ }
+
+ if (matched) {
+ has_our_contact = PJ_TRUE;
+
+ if (contacts[i]->expires >= 0 &&
+ contacts[i]->expires < expiration)
+ {
+ /* Get the lowest expiration time. */
+ expiration = contacts[i]->expires;
+ }
+
+ break;
+ }
+
+ our_hdr = our_hdr->next;
+
+ } /* while ((void.. */
+
+ } /* for (i=.. */
+
+ /* If matching Contact header(s) are found but the
+ * header doesn't contain expires parameter, get the
+ * expiration value from the Expires header. And
+ * if Expires header is not present, get the expiration
+ * value from the request.
+ */
+ if (has_our_contact && expiration == NOEXP) {
+ if (expires) {
+ expiration = expires->ivalue;
+ } else if (regc->expires_hdr) {
+ expiration = regc->expires_hdr->ivalue;
+ } else {
+ /* We didn't request explicit expiration value,
+ * and server doesn't specify it either. This
+ * shouldn't happen unless we have a broken
+ * registrar.
+ */
+ expiration = 3600;
+ }
+ }
+
+ }
+
+ /* If we still couldn't get matching Contact header(s), it means
+ * there must be something wrong with the registrar (e.g. it may
+ * have modified the URI's in the response, which is prohibited).
+ */
+ if (expiration==NOEXP) {
+ /* If the number of Contact headers in the response matches
+ * ours, they're all probably ours. Get the expiration
+ * from there if this is the case, or from Expires header
+ * if we don't have exact Contact header count, or
+ * from the request as the last resort.
+ */
+ unsigned our_contact_cnt;
+
+ our_contact_cnt = pj_list_size(&regc->contact_hdr_list);
+
+ if (*contact_cnt == our_contact_cnt && *contact_cnt &&
+ contacts[0]->expires >= 0)
+ {
+ expiration = contacts[0]->expires;
+ } else if (expires)
+ expiration = expires->ivalue;
+ else if (regc->expires_hdr)
+ expiration = regc->expires_hdr->ivalue;
+ else
+ expiration = 3600;
+ }
+
+ } else {
+ /* Just assume that the unregistration has been successful. */
+ expiration = 0;
+ }
+
+ /* Must have expiration value by now */
+ pj_assert(expiration != NOEXP);
+
+ return expiration;
+}
+
+static void regc_tsx_callback(void *token, pjsip_event *event)
+{
+ pj_status_t status;
+ pjsip_regc *regc = (pjsip_regc*) token;
+ pjsip_transaction *tsx = event->body.tsx_state.tsx;
+ pj_bool_t handled = PJ_TRUE;
+
+ pj_atomic_inc(regc->busy_ctr);
+ pj_lock_acquire(regc->lock);
+
+ /* Decrement pending transaction counter. */
+ pj_assert(regc->has_tsx);
+ regc->has_tsx = PJ_FALSE;
+
+ /* Add reference to the transport */
+ if (tsx->transport != regc->last_transport) {
+ if (regc->last_transport) {
+ pjsip_transport_dec_ref(regc->last_transport);
+ regc->last_transport = NULL;
+ }
+
+ if (tsx->transport) {
+ regc->last_transport = tsx->transport;
+ pjsip_transport_add_ref(regc->last_transport);
+ }
+ }
+
+ /* Handle 401/407 challenge (even when _delete_flag is set) */
+ if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED ||
+ tsx->status_code == PJSIP_SC_UNAUTHORIZED)
+ {
+ pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+
+ /* reset current op */
+ regc->current_op = REGC_IDLE;
+
+ status = pjsip_auth_clt_reinit_req( &regc->auth_sess,
+ rdata,
+ tsx->last_tx,
+ &tdata);
+
+ if (status == PJ_SUCCESS) {
+ status = pjsip_regc_send(regc, tdata);
+ }
+
+ if (status != PJ_SUCCESS) {
+
+ /* Only call callback if application is still interested
+ * in it.
+ */
+ if (regc->_delete_flag == 0) {
+ /* Should be safe to release the lock temporarily.
+ * We do this to avoid deadlock.
+ */
+ pj_lock_release(regc->lock);
+ call_callback(regc, status, tsx->status_code,
+ &rdata->msg_info.msg->line.status.reason,
+ rdata, -1, 0, NULL);
+ pj_lock_acquire(regc->lock);
+ }
+ }
+
+ } else if (regc->_delete_flag) {
+
+ /* User has called pjsip_regc_destroy(), so don't call callback.
+ * This regc will be destroyed later in this function.
+ */
+
+ /* Just reset current op */
+ regc->current_op = REGC_IDLE;
+
+ } else if (tsx->status_code == PJSIP_SC_INTERVAL_TOO_BRIEF &&
+ regc->current_op == REGC_REGISTERING)
+ {
+ /* Handle 423 response automatically:
+ * - set requested expiration to Min-Expires header, ONLY IF
+ * the original request is a registration (as opposed to
+ * unregistration) and the requested expiration was indeed
+ * lower than Min-Expires)
+ * - resend the request
+ */
+ pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
+ pjsip_min_expires_hdr *me_hdr;
+ pjsip_tx_data *tdata;
+ pj_int32_t min_exp;
+
+ /* reset current op */
+ regc->current_op = REGC_IDLE;
+
+ /* Update requested expiration */
+ me_hdr = (pjsip_min_expires_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg,
+ PJSIP_H_MIN_EXPIRES, NULL);
+ if (me_hdr) {
+ min_exp = me_hdr->ivalue;
+ } else {
+ /* Broken server, Min-Expires doesn't exist.
+ * Just guestimate then, BUT ONLY if if this is the
+ * first time we received such response.
+ */
+ enum {
+ /* Note: changing this value would require changing couple of
+ * Python test scripts.
+ */
+ UNSPECIFIED_MIN_EXPIRES = 3601
+ };
+ if (!regc->expires_hdr ||
+ regc->expires_hdr->ivalue != UNSPECIFIED_MIN_EXPIRES)
+ {
+ min_exp = UNSPECIFIED_MIN_EXPIRES;
+ } else {
+ handled = PJ_FALSE;
+ PJ_LOG(4,(THIS_FILE, "Registration failed: 423 response "
+ "without Min-Expires header is invalid"));
+ goto handle_err;
+ }
+ }
+
+ if (regc->expires_hdr && regc->expires_hdr->ivalue >= min_exp) {
+ /* But we already send with greater expiration time, why does
+ * the server send us with 423? Oh well, just fail the request.
+ */
+ handled = PJ_FALSE;
+ PJ_LOG(4,(THIS_FILE, "Registration failed: invalid "
+ "Min-Expires header value in response"));
+ goto handle_err;
+ }
+
+ set_expires(regc, min_exp);
+
+ status = pjsip_regc_register(regc, regc->auto_reg, &tdata);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_regc_send(regc, tdata);
+ }
+
+ if (status != PJ_SUCCESS) {
+ /* Only call callback if application is still interested
+ * in it.
+ */
+ if (!regc->_delete_flag) {
+ /* Should be safe to release the lock temporarily.
+ * We do this to avoid deadlock.
+ */
+ pj_lock_release(regc->lock);
+ call_callback(regc, status, tsx->status_code,
+ &rdata->msg_info.msg->line.status.reason,
+ rdata, -1, 0, NULL);
+ pj_lock_acquire(regc->lock);
+ }
+ }
+
+ } else {
+ handled = PJ_FALSE;
+ }
+
+handle_err:
+ if (!handled) {
+ pjsip_rx_data *rdata;
+ pj_int32_t expiration = NOEXP;
+ unsigned contact_cnt = 0;
+ pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT];
+
+ if (tsx->status_code/100 == 2) {
+
+ rdata = event->body.tsx_state.src.rdata;
+
+ /* Calculate expiration */
+ expiration = calculate_response_expiration(regc, rdata,
+ &contact_cnt,
+ PJSIP_REGC_MAX_CONTACT,
+ contact);
+
+ /* Schedule next registration */
+ schedule_registration(regc, expiration);
+
+ } else {
+ rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ?
+ event->body.tsx_state.src.rdata : NULL;
+ }
+
+ /* Update registration */
+ if (expiration==NOEXP) expiration=-1;
+ regc->expires = expiration;
+
+ /* Mark operation as complete */
+ regc->current_op = REGC_IDLE;
+
+ /* Call callback. */
+ /* Should be safe to release the lock temporarily.
+ * We do this to avoid deadlock.
+ */
+ pj_lock_release(regc->lock);
+ call_callback(regc, PJ_SUCCESS, tsx->status_code,
+ (rdata ? &rdata->msg_info.msg->line.status.reason
+ : &tsx->status_text),
+ rdata, expiration,
+ contact_cnt, contact);
+ pj_lock_acquire(regc->lock);
+ }
+
+ pj_lock_release(regc->lock);
+
+ /* Delete the record if user destroy regc during the callback. */
+ if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) {
+ pjsip_regc_destroy(regc);
+ }
+}
+
+PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
+{
+ pj_status_t status;
+ pjsip_cseq_hdr *cseq_hdr;
+ pjsip_expires_hdr *expires_hdr;
+ pj_uint32_t cseq;
+
+ pj_atomic_inc(regc->busy_ctr);
+ pj_lock_acquire(regc->lock);
+
+ /* Make sure we don't have pending transaction. */
+ if (regc->has_tsx) {
+ PJ_LOG(4,(THIS_FILE, "Unable to send request, regc has another "
+ "transaction pending"));
+ pjsip_tx_data_dec_ref( tdata );
+ pj_lock_release(regc->lock);
+ pj_atomic_dec(regc->busy_ctr);
+ return PJSIP_EBUSY;
+ }
+
+ pj_assert(regc->current_op == REGC_IDLE);
+
+ /* Invalidate message buffer. */
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Increment CSeq */
+ cseq = ++regc->cseq_hdr->cseq;
+ cseq_hdr = (pjsip_cseq_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
+ cseq_hdr->cseq = cseq;
+
+ /* Find Expires header */
+ expires_hdr = (pjsip_expires_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_EXPIRES, NULL);
+
+ /* Bind to transport selector */
+ pjsip_tx_data_set_transport(tdata, &regc->tp_sel);
+
+ regc->has_tsx = PJ_TRUE;
+
+ /* Set current operation based on the value of Expires header */
+ if (expires_hdr && expires_hdr->ivalue==0)
+ regc->current_op = REGC_UNREGISTERING;
+ else
+ regc->current_op = REGC_REGISTERING;
+
+ /* Prevent deletion of tdata, e.g: when something wrong in sending,
+ * we need tdata to retrieve the transport.
+ */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* If via_addr is set, use this address for the Via header. */
+ if (regc->via_addr.host.slen > 0) {
+ tdata->via_addr = regc->via_addr;
+ tdata->via_tp = regc->via_tp;
+ }
+
+ /* Need to unlock the regc temporarily while sending the message to
+ * prevent deadlock (https://trac.pjsip.org/repos/ticket/1247).
+ * It should be safe to do this since the regc's refcount has been
+ * incremented.
+ */
+ pj_lock_release(regc->lock);
+
+ /* Now send the message */
+ status = pjsip_endpt_send_request(regc->endpt, tdata, REGC_TSX_TIMEOUT,
+ regc, &regc_tsx_callback);
+ if (status!=PJ_SUCCESS) {
+ PJ_LOG(4,(THIS_FILE, "Error sending request, status=%d", status));
+ }
+
+ /* Reacquire the lock */
+ pj_lock_acquire(regc->lock);
+
+ /* Get last transport used and add reference to it */
+ if (tdata->tp_info.transport != regc->last_transport &&
+ status==PJ_SUCCESS)
+ {
+ if (regc->last_transport) {
+ pjsip_transport_dec_ref(regc->last_transport);
+ regc->last_transport = NULL;
+ }
+
+ if (tdata->tp_info.transport) {
+ regc->last_transport = tdata->tp_info.transport;
+ pjsip_transport_add_ref(regc->last_transport);
+ }
+ }
+
+ /* Release tdata */
+ pjsip_tx_data_dec_ref(tdata);
+
+ pj_lock_release(regc->lock);
+
+ /* Delete the record if user destroy regc during the callback. */
+ if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) {
+ pjsip_regc_destroy(regc);
+ }
+
+ return status;
+}
+
+
diff --git a/pjsip/src/pjsip-ua/sip_replaces.c b/pjsip/src/pjsip-ua/sip_replaces.c
new file mode 100644
index 0000000..b961cf9
--- /dev/null
+++ b/pjsip/src/pjsip-ua/sip_replaces.c
@@ -0,0 +1,384 @@
+/* $Id: sip_replaces.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-ua/sip_replaces.h>
+#include <pjsip-ua/sip_inv.h>
+#include <pjsip/print_util.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_ua_layer.h>
+#include <pjsip/sip_util.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE "sip_replaces.c"
+
+
+/*
+ * Replaces header vptr.
+ */
+static int replaces_hdr_print( pjsip_replaces_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_replaces_hdr* replaces_hdr_clone( pj_pool_t *pool,
+ const pjsip_replaces_hdr *hdr);
+static pjsip_replaces_hdr* replaces_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_replaces_hdr*);
+
+static pjsip_hdr_vptr replaces_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &replaces_hdr_clone,
+ (pjsip_hdr_clone_fptr) &replaces_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &replaces_hdr_print,
+};
+
+/* Globals */
+static pjsip_endpoint *the_endpt;
+static pj_bool_t is_initialized;
+
+PJ_DEF(pjsip_replaces_hdr*) pjsip_replaces_hdr_create(pj_pool_t *pool)
+{
+ pjsip_replaces_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_replaces_hdr);
+ hdr->type = PJSIP_H_OTHER;
+ hdr->name.ptr = "Replaces";
+ hdr->name.slen = 8;
+ hdr->vptr = &replaces_hdr_vptr;
+ pj_list_init(hdr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+static int replaces_hdr_print( pjsip_replaces_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf+size;
+ int printed;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ copy_advance(p, hdr->name);
+ *p++ = ':';
+ *p++ = ' ';
+
+ copy_advance(p, hdr->call_id);
+ copy_advance_pair(p, ";to-tag=", 8, hdr->to_tag);
+ copy_advance_pair(p, ";from-tag=", 10, hdr->from_tag);
+
+ if (hdr->early_only) {
+ const pj_str_t str_early_only = { ";early-only", 11 };
+ copy_advance(p, str_early_only);
+ }
+
+ printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return printed;
+
+ p += printed;
+ return p - buf;
+}
+
+static pjsip_replaces_hdr* replaces_hdr_clone( pj_pool_t *pool,
+ const pjsip_replaces_hdr *rhs)
+{
+ pjsip_replaces_hdr *hdr = pjsip_replaces_hdr_create(pool);
+ pj_strdup(pool, &hdr->call_id, &rhs->call_id);
+ pj_strdup(pool, &hdr->to_tag, &rhs->to_tag);
+ pj_strdup(pool, &hdr->from_tag, &rhs->from_tag);
+ hdr->early_only = rhs->early_only;
+ pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_replaces_hdr*
+replaces_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_replaces_hdr *rhs )
+{
+ pjsip_replaces_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_replaces_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+
+/*
+ * Parse Replaces header.
+ */
+static pjsip_hdr *parse_hdr_replaces(pjsip_parse_ctx *ctx)
+{
+ pjsip_replaces_hdr *hdr = pjsip_replaces_hdr_create(ctx->pool);
+ const pj_str_t to_tag = { "to-tag", 6 };
+ const pj_str_t from_tag = { "from-tag", 8 };
+ const pj_str_t early_only_tag = { "early-only", 10 };
+
+ /*pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->call_id);*/
+ /* Get Call-ID (until ';' is found). using pjsip_TOKEN_SPEC doesn't work
+ * because it stops parsing when '@' character is found.
+ */
+ pj_scan_get_until_ch(ctx->scanner, ';', &hdr->call_id);
+
+ while (*ctx->scanner->curptr == ';') {
+ pj_str_t pname, pvalue;
+
+ pj_scan_get_char(ctx->scanner);
+ pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
+
+ if (pj_stricmp(&pname, &to_tag)==0) {
+ hdr->to_tag = pvalue;
+ } else if (pj_stricmp(&pname, &from_tag)==0) {
+ hdr->from_tag = pvalue;
+ } else if (pj_stricmp(&pname, &early_only_tag)==0) {
+ hdr->early_only = PJ_TRUE;
+ } else {
+ pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+ param->name = pname;
+ param->value = pvalue;
+ pj_list_push_back(&hdr->other_param, param);
+ }
+ }
+ pjsip_parse_end_hdr_imp( ctx->scanner );
+ return (pjsip_hdr*)hdr;
+}
+
+
+/* Deinitialize Replaces */
+static void pjsip_replaces_deinit_module(pjsip_endpoint *endpt)
+{
+ PJ_TODO(provide_initialized_flag_for_each_endpoint);
+ PJ_UNUSED_ARG(endpt);
+ is_initialized = PJ_FALSE;
+}
+
+/*
+ * Initialize Replaces support in PJSIP.
+ */
+PJ_DEF(pj_status_t) pjsip_replaces_init_module(pjsip_endpoint *endpt)
+{
+ pj_status_t status;
+ const pj_str_t STR_REPLACES = { "replaces", 8 };
+
+ the_endpt = endpt;
+
+ if (is_initialized)
+ return PJ_SUCCESS;
+
+ /* Register Replaces header parser */
+ status = pjsip_register_hdr_parser( "Replaces", NULL,
+ &parse_hdr_replaces);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Register "replaces" capability */
+ status = pjsip_endpt_add_capability(endpt, NULL, PJSIP_H_SUPPORTED, NULL,
+ 1, &STR_REPLACES);
+
+ /* Register deinit module to be executed when PJLIB shutdown */
+ if (pjsip_endpt_atexit(endpt, &pjsip_replaces_deinit_module) != PJ_SUCCESS)
+ {
+ /* Failure to register this function may cause this module won't
+ * work properly when the stack is restarted (without quitting
+ * application).
+ */
+ pj_assert(!"Failed to register Replaces deinit.");
+ PJ_LOG(1, (THIS_FILE, "Failed to register Replaces deinit."));
+ }
+
+ is_initialized = PJ_TRUE;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Verify that incoming request with Replaces header can be processed.
+ */
+PJ_DEF(pj_status_t) pjsip_replaces_verify_request( pjsip_rx_data *rdata,
+ pjsip_dialog **p_dlg,
+ pj_bool_t lock_dlg,
+ pjsip_tx_data **p_tdata)
+{
+ const pj_str_t STR_REPLACES = { "Replaces", 8 };
+ pjsip_replaces_hdr *rep_hdr;
+ int code = 200;
+ const char *warn_text = NULL;
+ pjsip_hdr res_hdr_list;
+ pjsip_dialog *dlg = NULL;
+ pjsip_inv_session *inv;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(rdata && p_dlg, PJ_EINVAL);
+
+ /* Check that pjsip_replaces_init_module() has been called. */
+ PJ_ASSERT_RETURN(the_endpt != NULL, PJ_EINVALIDOP);
+
+
+ /* Init output arguments */
+ *p_dlg = NULL;
+ if (p_tdata) *p_tdata = NULL;
+
+ pj_list_init(&res_hdr_list);
+
+ /* Find Replaces header */
+ rep_hdr = (pjsip_replaces_hdr*)
+ pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_REPLACES,
+ NULL);
+ if (!rep_hdr) {
+ /* No Replaces header. No further processing is necessary. */
+ return PJ_SUCCESS;
+ }
+
+
+ /* Check that there's no other Replaces header and return 400 Bad Request
+ * if not.
+ */
+ if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_REPLACES,
+ rep_hdr->next)) {
+ code = PJSIP_SC_BAD_REQUEST;
+ warn_text = "Found multiple Replaces headers";
+ goto on_return;
+ }
+
+ /* Find the dialog identified by Replaces header (and always lock the
+ * dialog no matter what application wants).
+ */
+ dlg = pjsip_ua_find_dialog(&rep_hdr->call_id, &rep_hdr->to_tag,
+ &rep_hdr->from_tag, PJ_TRUE);
+
+ /* Respond with 481 "Call/Transaction Does Not Exist" response if
+ * no dialog is found.
+ */
+ if (dlg == NULL) {
+ code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
+ warn_text = "No dialog found for Replaces request";
+ goto on_return;
+ }
+
+ /* Get the invite session within the dialog */
+ inv = pjsip_dlg_get_inv_session(dlg);
+
+ /* Return 481 if no invite session is present. */
+ if (inv == NULL) {
+ code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
+ warn_text = "No INVITE session found for Replaces request";
+ goto on_return;
+ }
+
+ /* Return 603 Declined response if invite session has already
+ * terminated
+ */
+ if (inv->state >= PJSIP_INV_STATE_DISCONNECTED) {
+ code = PJSIP_SC_DECLINE;
+ warn_text = "INVITE session already terminated";
+ goto on_return;
+ }
+
+ /* If "early-only" flag is present, check that the invite session
+ * has not been confirmed yet. If the session has been confirmed,
+ * return 486 "Busy Here" response.
+ */
+ if (rep_hdr->early_only && inv->state >= PJSIP_INV_STATE_CONNECTING) {
+ code = PJSIP_SC_BUSY_HERE;
+ warn_text = "INVITE session already established";
+ goto on_return;
+ }
+
+ /* If the Replaces header field matches an early dialog that was not
+ * initiated by this UA, it returns a 481 (Call/Transaction Does Not
+ * Exist) response to the new INVITE.
+ */
+ if (inv->state <= PJSIP_INV_STATE_EARLY && inv->role != PJSIP_ROLE_UAC) {
+ code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
+ warn_text = "Found early INVITE session but not initiated by this UA";
+ goto on_return;
+ }
+
+
+ /*
+ * Looks like everything is okay!!
+ */
+ *p_dlg = dlg;
+ status = PJ_SUCCESS;
+ code = 200;
+
+on_return:
+
+ /* Create response if necessary */
+ if (code != 200) {
+ /* If we have dialog we must unlock it */
+ if (dlg)
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Create response */
+ if (p_tdata) {
+ pjsip_tx_data *tdata;
+ const pjsip_hdr *h;
+
+ status = pjsip_endpt_create_response(the_endpt, rdata, code,
+ NULL, &tdata);
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add response headers. */
+ h = res_hdr_list.next;
+ while (h != &res_hdr_list) {
+ pjsip_hdr *cloned;
+
+ cloned = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, h);
+ PJ_ASSERT_RETURN(cloned, PJ_ENOMEM);
+
+ pjsip_msg_add_hdr(tdata->msg, cloned);
+
+ h = h->next;
+ }
+
+ /* Add warn text, if any */
+ if (warn_text) {
+ pjsip_warning_hdr *warn_hdr;
+ pj_str_t warn_value = pj_str((char*)warn_text);
+
+ warn_hdr=pjsip_warning_hdr_create(tdata->pool, 399,
+ pjsip_endpt_name(the_endpt),
+ &warn_value);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)warn_hdr);
+ }
+
+ *p_tdata = tdata;
+ }
+
+ /* Can not return PJ_SUCCESS when response message is produced.
+ * Ref: PROTOS test ~#2490
+ */
+ if (status == PJ_SUCCESS)
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
+
+ } else {
+ /* If application doesn't want to lock the dialog, unlock it */
+ if (!lock_dlg)
+ pjsip_dlg_dec_lock(dlg);
+ }
+
+ return status;
+}
+
+
+
diff --git a/pjsip/src/pjsip-ua/sip_timer.c b/pjsip/src/pjsip-ua/sip_timer.c
new file mode 100644
index 0000000..2c70791
--- /dev/null
+++ b/pjsip/src/pjsip-ua/sip_timer.c
@@ -0,0 +1,1062 @@
+/* $Id: sip_timer.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-ua/sip_timer.h>
+#include <pjsip/print_util.h>
+#include <pjsip/sip_endpoint.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+
+#define THIS_FILE "sip_timer.c"
+
+
+/* Constant of Session Timers */
+#define ABS_MIN_SE 90 /* Absolute Min-SE, in seconds */
+
+
+/* String definitions */
+static const pj_str_t STR_SE = {"Session-Expires", 15};
+static const pj_str_t STR_SHORT_SE = {"x", 1};
+static const pj_str_t STR_MIN_SE = {"Min-SE", 6};
+static const pj_str_t STR_REFRESHER = {"refresher", 9};
+static const pj_str_t STR_UAC = {"uac", 3};
+static const pj_str_t STR_UAS = {"uas", 3};
+static const pj_str_t STR_TIMER = {"timer", 5};
+
+
+/* Enumeration of refresher */
+enum timer_refresher {
+ TR_UNKNOWN,
+ TR_UAC,
+ TR_UAS
+};
+
+/* Structure definition of Session Timers */
+struct pjsip_timer
+{
+ pj_bool_t active; /**< Active/inactive flag */
+ pjsip_timer_setting setting; /**< Session Timers setting */
+ enum timer_refresher refresher; /**< Session refresher */
+ pj_time_val last_refresh; /**< Timestamp of last
+ refresh */
+ pj_timer_entry timer; /**< Timer entry */
+ pj_bool_t use_update; /**< Use UPDATE method to
+ refresh the session */
+ pj_bool_t with_sdp; /**< SDP in UPDATE? */
+ pjsip_role_e role; /**< Role in last INVITE/
+ UPDATE transaction. */
+
+};
+
+/* External global vars */
+extern pj_bool_t pjsip_use_compact_form;
+
+/* Local functions & vars */
+static void stop_timer(pjsip_inv_session *inv);
+static void start_timer(pjsip_inv_session *inv);
+static pj_bool_t is_initialized;
+const pjsip_method pjsip_update_method = { PJSIP_OTHER_METHOD, {"UPDATE", 6}};
+/*
+ * Session-Expires header vptr.
+ */
+static int se_hdr_print(pjsip_sess_expires_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_sess_expires_hdr* se_hdr_clone(pj_pool_t *pool,
+ const pjsip_sess_expires_hdr *hdr);
+static pjsip_sess_expires_hdr* se_hdr_shallow_clone(
+ pj_pool_t *pool,
+ const pjsip_sess_expires_hdr* hdr);
+
+static pjsip_hdr_vptr se_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &se_hdr_clone,
+ (pjsip_hdr_clone_fptr) &se_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &se_hdr_print,
+};
+
+/*
+ * Min-SE header vptr.
+ */
+static int min_se_hdr_print(pjsip_min_se_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_min_se_hdr* min_se_hdr_clone(pj_pool_t *pool,
+ const pjsip_min_se_hdr *hdr);
+static pjsip_min_se_hdr* min_se_hdr_shallow_clone(
+ pj_pool_t *pool,
+ const pjsip_min_se_hdr* hdr);
+
+static pjsip_hdr_vptr min_se_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &min_se_hdr_clone,
+ (pjsip_hdr_clone_fptr) &min_se_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &min_se_hdr_print,
+};
+
+/*
+ * Session-Expires header vptr.
+ */
+static int se_hdr_print(pjsip_sess_expires_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf+size;
+ int printed;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ /* Print header name and value */
+ if ((endbuf - p) < (hname->slen + 16))
+ return -1;
+
+ copy_advance(p, (*hname));
+ *p++ = ':';
+ *p++ = ' ';
+
+ printed = pj_utoa(hdr->sess_expires, p);
+ p += printed;
+
+ /* Print 'refresher' param */
+ if (hdr->refresher.slen)
+ {
+ if ((endbuf - p) < (STR_REFRESHER.slen + 2 + hdr->refresher.slen))
+ return -1;
+
+ *p++ = ';';
+ copy_advance(p, STR_REFRESHER);
+ *p++ = '=';
+ copy_advance(p, hdr->refresher);
+ }
+
+ /* Print generic params */
+ printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return printed;
+
+ p += printed;
+ return p - buf;
+}
+
+static pjsip_sess_expires_hdr* se_hdr_clone(pj_pool_t *pool,
+ const pjsip_sess_expires_hdr *hsrc)
+{
+ pjsip_sess_expires_hdr *hdr = pjsip_sess_expires_hdr_create(pool);
+ hdr->sess_expires = hsrc->sess_expires;
+ pj_strdup(pool, &hdr->refresher, &hsrc->refresher);
+ pjsip_param_clone(pool, &hdr->other_param, &hsrc->other_param);
+ return hdr;
+}
+
+static pjsip_sess_expires_hdr* se_hdr_shallow_clone(
+ pj_pool_t *pool,
+ const pjsip_sess_expires_hdr* hsrc)
+{
+ pjsip_sess_expires_hdr *hdr = PJ_POOL_ALLOC_T(pool,pjsip_sess_expires_hdr);
+ pj_memcpy(hdr, hsrc, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &hsrc->other_param);
+ return hdr;
+}
+
+/*
+ * Min-SE header vptr.
+ */
+static int min_se_hdr_print(pjsip_min_se_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf+size;
+ int printed;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ /* Print header name and value */
+ if ((endbuf - p) < (hdr->name.slen + 16))
+ return -1;
+
+ copy_advance(p, hdr->name);
+ *p++ = ':';
+ *p++ = ' ';
+
+ printed = pj_utoa(hdr->min_se, p);
+ p += printed;
+
+ /* Print generic params */
+ printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return printed;
+
+ p += printed;
+ return p - buf;
+}
+
+static pjsip_min_se_hdr* min_se_hdr_clone(pj_pool_t *pool,
+ const pjsip_min_se_hdr *hsrc)
+{
+ pjsip_min_se_hdr *hdr = pjsip_min_se_hdr_create(pool);
+ hdr->min_se = hsrc->min_se;
+ pjsip_param_clone(pool, &hdr->other_param, &hsrc->other_param);
+ return hdr;
+}
+
+static pjsip_min_se_hdr* min_se_hdr_shallow_clone(
+ pj_pool_t *pool,
+ const pjsip_min_se_hdr* hsrc)
+{
+ pjsip_min_se_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_min_se_hdr);
+ pj_memcpy(hdr, hsrc, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &hsrc->other_param);
+ return hdr;
+}
+
+
+/*
+ * Parse Session-Expires header.
+ */
+static pjsip_hdr *parse_hdr_se(pjsip_parse_ctx *ctx)
+{
+ pjsip_sess_expires_hdr *hdr = pjsip_sess_expires_hdr_create(ctx->pool);
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+ pj_str_t token;
+
+ pj_scan_get(ctx->scanner, &pc->pjsip_DIGIT_SPEC, &token);
+ hdr->sess_expires = pj_strtoul(&token);
+
+ while (*ctx->scanner->curptr == ';') {
+ pj_str_t pname, pvalue;
+
+ pj_scan_get_char(ctx->scanner);
+ pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
+
+ if (pj_stricmp(&pname, &STR_REFRESHER)==0) {
+ hdr->refresher = pvalue;
+ } else {
+ pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+ param->name = pname;
+ param->value = pvalue;
+ pj_list_push_back(&hdr->other_param, param);
+ }
+ }
+ pjsip_parse_end_hdr_imp( ctx->scanner );
+ return (pjsip_hdr*)hdr;
+}
+
+/*
+ * Parse Min-SE header.
+ */
+static pjsip_hdr *parse_hdr_min_se(pjsip_parse_ctx *ctx)
+{
+ pjsip_min_se_hdr *hdr = pjsip_min_se_hdr_create(ctx->pool);
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+ pj_str_t token;
+
+ pj_scan_get(ctx->scanner, &pc->pjsip_DIGIT_SPEC, &token);
+ hdr->min_se = pj_strtoul(&token);
+
+ while (*ctx->scanner->curptr == ';') {
+ pj_str_t pname, pvalue;
+ pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+
+ pj_scan_get_char(ctx->scanner);
+ pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
+
+ param->name = pname;
+ param->value = pvalue;
+ pj_list_push_back(&hdr->other_param, param);
+ }
+ pjsip_parse_end_hdr_imp( ctx->scanner );
+ return (pjsip_hdr*)hdr;
+}
+
+
+/* Add "Session-Expires" and "Min-SE" headers. Note that "Min-SE" header
+ * can only be added to INVITE/UPDATE request and 422 response.
+ */
+static void add_timer_headers(pjsip_inv_session *inv, pjsip_tx_data *tdata,
+ pj_bool_t add_se, pj_bool_t add_min_se)
+{
+ pjsip_timer *timer = inv->timer;
+
+ /* Add Session-Expires header */
+ if (add_se) {
+ pjsip_sess_expires_hdr *hdr;
+
+ hdr = pjsip_sess_expires_hdr_create(tdata->pool);
+ hdr->sess_expires = timer->setting.sess_expires;
+ if (timer->refresher != TR_UNKNOWN)
+ hdr->refresher = (timer->refresher == TR_UAC? STR_UAC : STR_UAS);
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr);
+ }
+
+ /* Add Min-SE header */
+ if (add_min_se) {
+ pjsip_min_se_hdr *hdr;
+
+ hdr = pjsip_min_se_hdr_create(tdata->pool);
+ hdr->min_se = timer->setting.min_se;
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr);
+ }
+}
+
+/* Timer callback. When the timer is fired, it can be time to refresh
+ * the session if UA is the refresher, otherwise it is time to end
+ * the session.
+ */
+static void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
+{
+ pjsip_inv_session *inv = (pjsip_inv_session*) entry->user_data;
+ pjsip_tx_data *tdata = NULL;
+ pj_status_t status;
+ pj_bool_t as_refresher;
+
+ pj_assert(inv);
+
+ inv->timer->timer.id = 0;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ /* Lock dialog. */
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ /* Check our role */
+ as_refresher =
+ (inv->timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
+ (inv->timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS);
+
+ /* Do action based on role, refresher or refreshee */
+ if (as_refresher) {
+ pj_time_val now;
+
+ /* As refresher, reshedule the refresh request on the following:
+ * - msut not send re-INVITE if another INVITE or SDP negotiation
+ * is in progress.
+ * - must not send UPDATE with SDP if SDP negotiation is in progress
+ */
+ pjmedia_sdp_neg_state neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+ if ( (!inv->timer->use_update && (
+ inv->invite_tsx != NULL ||
+ neg_state != PJMEDIA_SDP_NEG_STATE_DONE)
+ )
+ ||
+ (inv->timer->use_update && inv->timer->with_sdp &&
+ neg_state != PJMEDIA_SDP_NEG_STATE_DONE
+ )
+ )
+ {
+ pj_time_val delay = {1, 0};
+
+ inv->timer->timer.id = 1;
+ pjsip_endpt_schedule_timer(inv->dlg->endpt, &inv->timer->timer,
+ &delay);
+ pjsip_dlg_dec_lock(inv->dlg);
+ return;
+ }
+
+ /* Refresher, refresh the session */
+ if (inv->timer->use_update) {
+ const pjmedia_sdp_session *offer = NULL;
+
+ if (inv->timer->with_sdp) {
+ pjmedia_sdp_neg_get_active_local(inv->neg, &offer);
+ }
+ status = pjsip_inv_update(inv, NULL, offer, &tdata);
+ } else {
+ /* Create re-INVITE without modifying session */
+ pjsip_msg_body *body;
+ const pjmedia_sdp_session *offer = NULL;
+
+ pj_assert(pjmedia_sdp_neg_get_state(inv->neg) ==
+ PJMEDIA_SDP_NEG_STATE_DONE);
+
+ status = pjsip_inv_invite(inv, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjmedia_sdp_neg_send_local_offer(inv->pool_prov,
+ inv->neg, &offer);
+ if (status == PJ_SUCCESS)
+ status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_create_sdp_body(tdata->pool,
+ (pjmedia_sdp_session*)offer, &body);
+ tdata->msg->body = body;
+ }
+ }
+
+ pj_gettimeofday(&now);
+ PJ_LOG(4, (inv->pool->obj_name,
+ "Refreshing session after %ds (expiration period=%ds)",
+ (now.sec-inv->timer->last_refresh.sec),
+ inv->timer->setting.sess_expires));
+ } else {
+
+ pj_time_val now;
+
+ /* Refreshee, terminate the session */
+ status = pjsip_inv_end_session(inv, PJSIP_SC_REQUEST_TIMEOUT,
+ NULL, &tdata);
+
+ pj_gettimeofday(&now);
+ PJ_LOG(3, (inv->pool->obj_name,
+ "No session refresh received after %ds "
+ "(expiration period=%ds), stopping session now!",
+ (now.sec-inv->timer->last_refresh.sec),
+ inv->timer->setting.sess_expires));
+ }
+
+ /* Unlock dialog. */
+ pjsip_dlg_dec_lock(inv->dlg);
+
+ /* Send message, if any */
+ if (tdata && status == PJ_SUCCESS) {
+ status = pjsip_inv_send_msg(inv, tdata);
+ }
+
+ /* Print error message, if any */
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(2, (inv->pool->obj_name, status,
+ "Error in %s session timer",
+ (as_refresher? "refreshing" : "terminating")));
+ }
+}
+
+/* Start Session Timers */
+static void start_timer(pjsip_inv_session *inv)
+{
+ const pj_str_t UPDATE = { "UPDATE", 6 };
+ pjsip_timer *timer = inv->timer;
+ pj_time_val delay = {0};
+
+ pj_assert(inv->timer->active == PJ_TRUE);
+
+ stop_timer(inv);
+
+ inv->timer->use_update =
+ (pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL,
+ &UPDATE) == PJSIP_DIALOG_CAP_SUPPORTED);
+ if (!inv->timer->use_update) {
+ /* INVITE always needs SDP */
+ inv->timer->with_sdp = PJ_TRUE;
+ }
+
+ pj_timer_entry_init(&timer->timer,
+ 1, /* id */
+ inv, /* user data */
+ timer_cb); /* callback */
+
+ /* Set delay based on role, refresher or refreshee */
+ if ((timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) ||
+ (timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS))
+ {
+ /* Next refresh, the delay is half of session expire */
+ delay.sec = timer->setting.sess_expires / 2;
+ } else {
+ /* Send BYE if no refresh received until this timer fired, delay
+ * is the minimum of 32 seconds and one third of the session interval
+ * before session expiration.
+ */
+ delay.sec = timer->setting.sess_expires -
+ timer->setting.sess_expires/3;
+ delay.sec = PJ_MAX((long)timer->setting.sess_expires-32, delay.sec);
+ }
+
+ /* Schedule the timer */
+ pjsip_endpt_schedule_timer(inv->dlg->endpt, &timer->timer, &delay);
+
+ /* Update last refresh time */
+ pj_gettimeofday(&timer->last_refresh);
+}
+
+/* Stop Session Timers */
+static void stop_timer(pjsip_inv_session *inv)
+{
+ if (inv->timer->timer.id != 0) {
+ pjsip_endpt_cancel_timer(inv->dlg->endpt, &inv->timer->timer);
+ inv->timer->timer.id = 0;
+ }
+}
+
+/* Deinitialize Session Timers */
+static void pjsip_timer_deinit_module(pjsip_endpoint *endpt)
+{
+ PJ_TODO(provide_initialized_flag_for_each_endpoint);
+ PJ_UNUSED_ARG(endpt);
+ is_initialized = PJ_FALSE;
+}
+
+/*
+ * Initialize Session Timers support in PJSIP.
+ */
+PJ_DEF(pj_status_t) pjsip_timer_init_module(pjsip_endpoint *endpt)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
+
+ if (is_initialized)
+ return PJ_SUCCESS;
+
+ /* Register Session-Expires header parser */
+ status = pjsip_register_hdr_parser( STR_SE.ptr, STR_SHORT_SE.ptr,
+ &parse_hdr_se);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Register Min-SE header parser */
+ status = pjsip_register_hdr_parser( STR_MIN_SE.ptr, NULL,
+ &parse_hdr_min_se);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Register 'timer' capability to endpoint */
+ status = pjsip_endpt_add_capability(endpt, NULL, PJSIP_H_SUPPORTED,
+ NULL, 1, &STR_TIMER);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Register deinit module to be executed when PJLIB shutdown */
+ if (pjsip_endpt_atexit(endpt, &pjsip_timer_deinit_module) != PJ_SUCCESS) {
+ /* Failure to register this function may cause this module won't
+ * work properly when the stack is restarted (without quitting
+ * application).
+ */
+ pj_assert(!"Failed to register Session Timer deinit.");
+ PJ_LOG(1, (THIS_FILE, "Failed to register Session Timer deinit."));
+ }
+
+ is_initialized = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Initialize Session Timers setting with default values.
+ */
+PJ_DEF(pj_status_t) pjsip_timer_setting_default(pjsip_timer_setting *setting)
+{
+ pj_bzero(setting, sizeof(pjsip_timer_setting));
+
+ setting->sess_expires = PJSIP_SESS_TIMER_DEF_SE;
+ setting->min_se = ABS_MIN_SE;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Initialize Session Timers in an INVITE session.
+ */
+PJ_DEF(pj_status_t) pjsip_timer_init_session(
+ pjsip_inv_session *inv,
+ const pjsip_timer_setting *setting)
+{
+ pjsip_timer_setting *s;
+
+ pj_assert(is_initialized);
+ PJ_ASSERT_RETURN(inv, PJ_EINVAL);
+
+ /* Allocate and/or reset Session Timers structure */
+ if (!inv->timer)
+ inv->timer = PJ_POOL_ZALLOC_T(inv->pool, pjsip_timer);
+ else
+ pj_bzero(inv->timer, sizeof(pjsip_timer));
+
+ s = &inv->timer->setting;
+
+ /* Init Session Timers setting */
+ if (setting) {
+ PJ_ASSERT_RETURN(setting->min_se >= ABS_MIN_SE,
+ PJ_ETOOSMALL);
+ PJ_ASSERT_RETURN(setting->sess_expires >= setting->min_se,
+ PJ_EINVAL);
+
+ pj_memcpy(s, setting, sizeof(*s));
+ } else {
+ pjsip_timer_setting_default(s);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create Session-Expires header.
+ */
+PJ_DEF(pjsip_sess_expires_hdr*) pjsip_sess_expires_hdr_create(
+ pj_pool_t *pool)
+{
+ pjsip_sess_expires_hdr *hdr = PJ_POOL_ZALLOC_T(pool,
+ pjsip_sess_expires_hdr);
+
+ pj_assert(is_initialized);
+
+ hdr->type = PJSIP_H_OTHER;
+ hdr->name = STR_SE;
+ hdr->sname = STR_SHORT_SE;
+ hdr->vptr = &se_hdr_vptr;
+ pj_list_init(hdr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+
+/*
+ * Create Min-SE header.
+ */
+PJ_DEF(pjsip_min_se_hdr*) pjsip_min_se_hdr_create(pj_pool_t *pool)
+{
+ pjsip_min_se_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_min_se_hdr);
+
+ pj_assert(is_initialized);
+
+ hdr->type = PJSIP_H_OTHER;
+ hdr->name = STR_MIN_SE;
+ hdr->vptr = &min_se_hdr_vptr;
+ pj_list_init(hdr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+
+/*
+ * This function generates headers for Session Timers for intial and
+ * refresh INVITE or UPDATE.
+ */
+PJ_DEF(pj_status_t) pjsip_timer_update_req(pjsip_inv_session *inv,
+ pjsip_tx_data *tdata)
+{
+ PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
+
+ /* Check if Session Timers is supported */
+ if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
+ return PJ_SUCCESS;
+
+ pj_assert(is_initialized);
+
+ /* Make sure Session Timers is initialized */
+ if (inv->timer == NULL)
+ pjsip_timer_init_session(inv, NULL);
+
+ /* If refresher role (i.e: ours or peer) has been set/negotiated,
+ * better to keep it.
+ */
+ if (inv->timer->refresher != TR_UNKNOWN) {
+ pj_bool_t as_refresher;
+
+ /* Check our refresher role */
+ as_refresher =
+ (inv->timer->refresher==TR_UAC && inv->timer->role==PJSIP_ROLE_UAC) ||
+ (inv->timer->refresher==TR_UAS && inv->timer->role==PJSIP_ROLE_UAS);
+
+ /* Update transaction role */
+ inv->timer->role = PJSIP_ROLE_UAC;
+
+ /* Update refresher role */
+ inv->timer->refresher = as_refresher? TR_UAC : TR_UAS;
+ }
+
+ /* Add Session Timers headers */
+ add_timer_headers(inv, tdata, PJ_TRUE, PJ_TRUE);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * This function will handle Session Timers part of INVITE/UPDATE
+ * responses with code:
+ * - 422 (Session Interval Too Small)
+ * - 2xx final response
+ */
+PJ_DEF(pj_status_t) pjsip_timer_process_resp(pjsip_inv_session *inv,
+ const pjsip_rx_data *rdata,
+ pjsip_status_code *st_code)
+{
+ const pjsip_msg *msg;
+
+ PJ_ASSERT_ON_FAIL(inv && rdata,
+ {if(st_code)*st_code=PJSIP_SC_INTERNAL_SERVER_ERROR;return PJ_EINVAL;});
+
+ /* Check if Session Timers is supported */
+ if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
+ return PJ_SUCCESS;
+
+ pj_assert(is_initialized);
+
+ msg = rdata->msg_info.msg;
+ pj_assert(msg->type == PJSIP_RESPONSE_MSG);
+
+ /* Only process response of INVITE or UPDATE */
+ if (rdata->msg_info.cseq->method.id != PJSIP_INVITE_METHOD &&
+ pjsip_method_cmp(&rdata->msg_info.cseq->method, &pjsip_update_method))
+ {
+ return PJ_SUCCESS;
+ }
+
+ if (msg->line.status.code == PJSIP_SC_SESSION_TIMER_TOO_SMALL) {
+ /* Our Session-Expires is too small, let's update it based on
+ * Min-SE header in the response.
+ */
+ pjsip_tx_data *tdata;
+ pjsip_min_se_hdr *min_se_hdr;
+ pjsip_hdr *hdr;
+ pjsip_via_hdr *via;
+
+ /* Get Min-SE value from response */
+ min_se_hdr = (pjsip_min_se_hdr*)
+ pjsip_msg_find_hdr_by_name(msg, &STR_MIN_SE, NULL);
+ if (min_se_hdr == NULL) {
+ /* Response 422 should contain Min-SE header */
+ return PJ_SUCCESS;
+ }
+
+ /* Session Timers should have been initialized here */
+ pj_assert(inv->timer);
+
+ /* Update Min-SE */
+ inv->timer->setting.min_se = PJ_MAX(min_se_hdr->min_se,
+ inv->timer->setting.min_se);
+
+ /* Update Session Timers setting */
+ if (inv->timer->setting.sess_expires < inv->timer->setting.min_se)
+ inv->timer->setting.sess_expires = inv->timer->setting.min_se;
+
+ /* Prepare to restart the request */
+
+ /* Get the original INVITE request. */
+ tdata = inv->invite_req;
+
+ /* Remove branch param in Via header. */
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ pj_assert(via);
+ via->branch_param.slen = 0;
+
+ /* Restore strict route set.
+ * See http://trac.pjsip.org/repos/ticket/492
+ */
+ pjsip_restore_strict_route_set(tdata);
+
+ /* Must invalidate the message! */
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Update Session Timers headers */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_name(tdata->msg,
+ &STR_MIN_SE, NULL);
+ if (hdr != NULL) pj_list_erase(hdr);
+
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_names(tdata->msg, &STR_SE,
+ &STR_SHORT_SE, NULL);
+ if (hdr != NULL) pj_list_erase(hdr);
+
+ add_timer_headers(inv, tdata, PJ_TRUE, PJ_TRUE);
+
+ /* Restart UAC */
+ pjsip_inv_uac_restart(inv, PJ_FALSE);
+ pjsip_inv_send_msg(inv, tdata);
+
+ return PJ_SUCCESS;
+
+ } else if (msg->line.status.code/100 == 2) {
+
+ pjsip_sess_expires_hdr *se_hdr;
+
+ /* Find Session-Expires header */
+ se_hdr = (pjsip_sess_expires_hdr*) pjsip_msg_find_hdr_by_names(
+ msg, &STR_SE,
+ &STR_SHORT_SE, NULL);
+ if (se_hdr == NULL) {
+ /* Remote doesn't support/want Session Timers, check if local
+ * require or force to use Session Timers.
+ */
+ if (inv->options & PJSIP_INV_REQUIRE_TIMER) {
+ if (st_code)
+ *st_code = PJSIP_SC_EXTENSION_REQUIRED;
+ pjsip_timer_end_session(inv);
+ return PJSIP_ERRNO_FROM_SIP_STATUS(
+ PJSIP_SC_EXTENSION_REQUIRED);
+ }
+
+ if ((inv->options & PJSIP_INV_ALWAYS_USE_TIMER) == 0) {
+ /* Session Timers not forced */
+ pjsip_timer_end_session(inv);
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Make sure Session Timers is initialized */
+ if (inv->timer == NULL)
+ pjsip_timer_init_session(inv, NULL);
+
+ /* Session expiration period specified by remote is lower than our
+ * Min-SE.
+ */
+ if (se_hdr &&
+ se_hdr->sess_expires < inv->timer->setting.min_se)
+ {
+ /* See ticket #954, instead of returning non-PJ_SUCCESS (which
+ * may cause disconnecting call/dialog), let's just accept the
+ * SE and update our local SE, as long as it isn't less than 90s.
+ */
+ if (se_hdr->sess_expires >= ABS_MIN_SE) {
+ PJ_LOG(3, (inv->pool->obj_name,
+ "Peer responds with bad Session-Expires, %ds, "
+ "which is less than Min-SE specified in request, "
+ "%ds. Well, let's just accept and use it.",
+ se_hdr->sess_expires, inv->timer->setting.min_se));
+
+ inv->timer->setting.sess_expires = se_hdr->sess_expires;
+ inv->timer->setting.min_se = se_hdr->sess_expires;
+ }
+
+ //if (st_code)
+ // *st_code = PJSIP_SC_SESSION_TIMER_TOO_SMALL;
+ //pjsip_timer_end_session(inv);
+ //return PJSIP_ERRNO_FROM_SIP_STATUS(
+ // PJSIP_SC_SESSION_TIMER_TOO_SMALL);
+ }
+
+ /* Update SE. Session-Expires in response cannot be lower than Min-SE.
+ * Session-Expires in response can only be equal or lower than in
+ * request.
+ */
+ if (se_hdr &&
+ se_hdr->sess_expires <= inv->timer->setting.sess_expires &&
+ se_hdr->sess_expires >= inv->timer->setting.min_se)
+ {
+ /* Good SE from remote, update local SE */
+ inv->timer->setting.sess_expires = se_hdr->sess_expires;
+ }
+
+ /* Set the refresher */
+ if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAC) == 0)
+ inv->timer->refresher = TR_UAC;
+ else if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAS) == 0)
+ inv->timer->refresher = TR_UAS;
+ else
+ /* UAS should set the refresher, however, there is a case that
+ * UAS doesn't support/want Session Timers but the UAC insists
+ * to use Session Timers.
+ */
+ inv->timer->refresher = TR_UAC;
+
+ /* Remember our role in this transaction */
+ inv->timer->role = PJSIP_ROLE_UAC;
+
+ /* Finally, set active flag and start the Session Timers */
+ inv->timer->active = PJ_TRUE;
+ start_timer(inv);
+
+ } else if (pjsip_method_cmp(&rdata->msg_info.cseq->method,
+ &pjsip_update_method) == 0 &&
+ msg->line.status.code >= 400 && msg->line.status.code < 600)
+ {
+ /* This is to handle error response to previous UPDATE that was
+ * sent without SDP. In this case, retry sending UPDATE but
+ * with SDP this time.
+ * Note: the additional expressions are to check that the
+ * UPDATE was really the one sent by us, not by other
+ * call components (e.g. to change codec)
+ */
+ if (inv->timer->timer.id == 0 && inv->timer->use_update &&
+ inv->timer->with_sdp == PJ_FALSE)
+ {
+ inv->timer->with_sdp = PJ_TRUE;
+ timer_cb(NULL, &inv->timer->timer);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Handle incoming INVITE or UPDATE request.
+ */
+PJ_DEF(pj_status_t) pjsip_timer_process_req(pjsip_inv_session *inv,
+ const pjsip_rx_data *rdata,
+ pjsip_status_code *st_code)
+{
+ pjsip_min_se_hdr *min_se_hdr;
+ pjsip_sess_expires_hdr *se_hdr;
+ const pjsip_msg *msg;
+ unsigned min_se;
+
+ PJ_ASSERT_ON_FAIL(inv && rdata,
+ {if(st_code)*st_code=PJSIP_SC_INTERNAL_SERVER_ERROR;return PJ_EINVAL;});
+
+ /* Check if Session Timers is supported */
+ if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
+ return PJ_SUCCESS;
+
+ pj_assert(is_initialized);
+
+ msg = rdata->msg_info.msg;
+ pj_assert(msg->type == PJSIP_REQUEST_MSG);
+
+ /* Only process INVITE or UPDATE request */
+ if (msg->line.req.method.id != PJSIP_INVITE_METHOD &&
+ pjsip_method_cmp(&rdata->msg_info.cseq->method, &pjsip_update_method))
+ {
+ return PJ_SUCCESS;
+ }
+
+ /* Find Session-Expires header */
+ se_hdr = (pjsip_sess_expires_hdr*) pjsip_msg_find_hdr_by_names(
+ msg, &STR_SE, &STR_SHORT_SE, NULL);
+ if (se_hdr == NULL) {
+ /* Remote doesn't support/want Session Timers, check if local
+ * require or force to use Session Timers. Note that Supported and
+ * Require headers negotiation should have been verified by invite
+ * session.
+ */
+ if ((inv->options &
+ (PJSIP_INV_REQUIRE_TIMER | PJSIP_INV_ALWAYS_USE_TIMER)) == 0)
+ {
+ /* Session Timers not forced/required */
+ pjsip_timer_end_session(inv);
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Make sure Session Timers is initialized */
+ if (inv->timer == NULL)
+ pjsip_timer_init_session(inv, NULL);
+
+ /* Find Min-SE header */
+ min_se_hdr = (pjsip_min_se_hdr*) pjsip_msg_find_hdr_by_name(msg,
+ &STR_MIN_SE, NULL);
+ /* Update Min-SE */
+ min_se = inv->timer->setting.min_se;
+ if (min_se_hdr)
+ min_se = PJ_MAX(min_se_hdr->min_se, min_se);
+
+ /* Validate SE. Session-Expires cannot be lower than Min-SE
+ * (or 90 seconds if Min-SE is not set).
+ */
+ if (se_hdr && se_hdr->sess_expires < min_se) {
+ if (st_code)
+ *st_code = PJSIP_SC_SESSION_TIMER_TOO_SMALL;
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_SESSION_TIMER_TOO_SMALL);
+ }
+
+ /* Update SE. Note that there is a case that SE is not available in the
+ * request (which means remote doesn't want/support it), but local insists
+ * to use Session Timers.
+ */
+ if (se_hdr) {
+ /* Update SE as specified by peer. */
+ inv->timer->setting.sess_expires = se_hdr->sess_expires;
+ } else if (inv->timer->setting.sess_expires < min_se) {
+ /* There is no SE in the request (remote support Session Timers but
+ * doesn't want to use it, it just specify Min-SE) and local SE is
+ * lower than Min-SE specified by remote.
+ */
+ inv->timer->setting.sess_expires = min_se;
+ }
+
+ /* Set the refresher */
+ if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAC) == 0)
+ inv->timer->refresher = TR_UAC;
+ else if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAS) == 0)
+ inv->timer->refresher = TR_UAS;
+ else {
+ /* If refresher role (i.e: ours or peer) has been set/negotiated,
+ * better to keep it.
+ */
+ if (inv->timer->refresher != TR_UNKNOWN) {
+ pj_bool_t as_refresher;
+
+ /* Check our refresher role */
+ as_refresher =
+ (inv->timer->refresher==TR_UAC && inv->timer->role==PJSIP_ROLE_UAC) ||
+ (inv->timer->refresher==TR_UAS && inv->timer->role==PJSIP_ROLE_UAS);
+
+ /* Update refresher role */
+ inv->timer->refresher = as_refresher? TR_UAS : TR_UAC;
+ } else {
+ /* If UAC support timer (currently check the existance of
+ * Session-Expires header in the request), set UAC as refresher.
+ */
+ inv->timer->refresher = se_hdr? TR_UAC : TR_UAS;
+ }
+ }
+
+ /* Remember our role in this transaction */
+ inv->timer->role = PJSIP_ROLE_UAS;
+
+ /* Set active flag */
+ inv->timer->active = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Handle outgoing response with status code 2xx & 422.
+ */
+PJ_DEF(pj_status_t) pjsip_timer_update_resp(pjsip_inv_session *inv,
+ pjsip_tx_data *tdata)
+{
+ pjsip_msg *msg;
+
+ /* Check if Session Timers is supported */
+ if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0)
+ return PJ_SUCCESS;
+
+ pj_assert(is_initialized);
+ PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
+
+ msg = tdata->msg;
+
+ if (msg->line.status.code/100 == 2)
+ {
+ if (inv->timer && inv->timer->active) {
+ /* Add Session-Expires header and start the timer */
+ add_timer_headers(inv, tdata, PJ_TRUE, PJ_FALSE);
+ start_timer(inv);
+ }
+ }
+ else if (msg->line.status.code == PJSIP_SC_SESSION_TIMER_TOO_SMALL)
+ {
+ /* Add Min-SE header */
+ add_timer_headers(inv, tdata, PJ_FALSE, PJ_TRUE);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * End the Session Timers.
+ */
+PJ_DEF(pj_status_t) pjsip_timer_end_session(pjsip_inv_session *inv)
+{
+ PJ_ASSERT_RETURN(inv, PJ_EINVAL);
+
+ if (inv->timer) {
+ /* Reset active flag */
+ inv->timer->active = PJ_FALSE;
+
+ /* Stop Session Timers */
+ stop_timer(inv);
+ }
+
+ return PJ_SUCCESS;
+}
diff --git a/pjsip/src/pjsip-ua/sip_xfer.c b/pjsip/src/pjsip-ua/sip_xfer.c
new file mode 100644
index 0000000..cfa3e10
--- /dev/null
+++ b/pjsip/src/pjsip-ua/sip_xfer.c
@@ -0,0 +1,630 @@
+/* $Id: sip_xfer.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip-ua/sip_xfer.h>
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip/sip_dialog.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_transport.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+/* Subscription expiration */
+#ifndef PJSIP_XFER_EXPIRES
+# define PJSIP_XFER_EXPIRES 600
+#endif
+
+
+/*
+ * Refer module (mod-refer)
+ */
+static struct pjsip_module mod_xfer =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-refer", 9 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ NULL, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+/* Declare PJSIP_REFER_METHOD, so that if somebody declares this in
+ * sip_msg.h we can catch the error here.
+ */
+enum
+{
+ PJSIP_REFER_METHOD = PJSIP_OTHER_METHOD
+};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_refer_method = {
+ (pjsip_method_e) PJSIP_REFER_METHOD,
+ { "REFER", 5}
+};
+
+PJ_DEF(const pjsip_method*) pjsip_get_refer_method()
+{
+ return &pjsip_refer_method;
+}
+
+/*
+ * String constants
+ */
+const pj_str_t STR_REFER = { "refer", 5 };
+const pj_str_t STR_MESSAGE = { "message", 7 };
+const pj_str_t STR_SIPFRAG = { "sipfrag", 7 };
+const pj_str_t STR_SIPFRAG_VERSION = {";version=2.0", 12 };
+
+
+/*
+ * Transfer struct.
+ */
+struct pjsip_xfer
+{
+ pjsip_evsub *sub; /**< Event subscribtion record. */
+ pjsip_dialog *dlg; /**< The dialog. */
+ pjsip_evsub_user user_cb; /**< The user callback. */
+ pj_str_t refer_to_uri; /**< The full Refer-To URI. */
+ int last_st_code; /**< st_code sent in last NOTIFY */
+ pj_str_t last_st_text; /**< st_text sent in last NOTIFY */
+};
+
+
+typedef struct pjsip_xfer pjsip_xfer;
+
+
+
+/*
+ * Forward decl for evsub callback.
+ */
+static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
+static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+ pjsip_event *event);
+static void xfer_on_evsub_rx_refresh( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body);
+static void xfer_on_evsub_rx_notify( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body);
+static void xfer_on_evsub_client_refresh(pjsip_evsub *sub);
+static void xfer_on_evsub_server_timeout(pjsip_evsub *sub);
+
+
+/*
+ * Event subscription callback for xference.
+ */
+static pjsip_evsub_user xfer_user =
+{
+ &xfer_on_evsub_state,
+ &xfer_on_evsub_tsx_state,
+ &xfer_on_evsub_rx_refresh,
+ &xfer_on_evsub_rx_notify,
+ &xfer_on_evsub_client_refresh,
+ &xfer_on_evsub_server_timeout,
+};
+
+
+
+
+/*
+ * Initialize the REFER subsystem.
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_init_module(pjsip_endpoint *endpt)
+{
+ const pj_str_t accept = { "message/sipfrag;version=2.0", 27 };
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(mod_xfer.id == -1, PJ_EINVALIDOP);
+
+ status = pjsip_endpt_register_module(endpt, &mod_xfer);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = pjsip_endpt_add_capability( endpt, &mod_xfer, PJSIP_H_ALLOW,
+ NULL, 1,
+ &pjsip_get_refer_method()->name);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = pjsip_evsub_register_pkg(&mod_xfer, &STR_REFER,
+ PJSIP_XFER_EXPIRES, 1, &accept);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create transferer (sender of REFER request).
+ *
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_create_uac( pjsip_dialog *dlg,
+ const pjsip_evsub_user *user_cb,
+ pjsip_evsub **p_evsub )
+{
+ pj_status_t status;
+ pjsip_xfer *xfer;
+ pjsip_evsub *sub;
+
+ PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Create event subscription */
+ status = pjsip_evsub_create_uac( dlg, &xfer_user, &STR_REFER,
+ PJSIP_EVSUB_NO_EVENT_ID, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create xfer session */
+ xfer = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_xfer);
+ xfer->dlg = dlg;
+ xfer->sub = sub;
+ if (user_cb)
+ pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user));
+
+ /* Attach to evsub */
+ pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer);
+
+ *p_evsub = sub;
+
+on_return:
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+
+}
+
+
+
+
+/*
+ * Create transferee (receiver of REFER request).
+ *
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_create_uas( pjsip_dialog *dlg,
+ const pjsip_evsub_user *user_cb,
+ pjsip_rx_data *rdata,
+ pjsip_evsub **p_evsub )
+{
+ pjsip_evsub *sub;
+ pjsip_xfer *xfer;
+ const pj_str_t STR_EVENT = {"Event", 5 };
+ pjsip_event_hdr *event_hdr;
+ pj_status_t status;
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
+
+ /* Must be request message */
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Check that request is REFER */
+ PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
+ pjsip_get_refer_method())==0,
+ PJSIP_ENOTREFER);
+
+ /* Lock dialog */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* The evsub framework expects an Event header in the request,
+ * while a REFER request conveniently doesn't have one (pun intended!).
+ * So create a dummy Event header.
+ */
+ if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
+ &STR_EVENT, NULL)==NULL)
+ {
+ event_hdr = pjsip_event_hdr_create(rdata->tp_info.pool);
+ event_hdr->event_type = STR_REFER;
+ pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr*)event_hdr);
+ }
+
+ /* Create server subscription */
+ status = pjsip_evsub_create_uas( dlg, &xfer_user, rdata,
+ PJSIP_EVSUB_NO_EVENT_ID, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create server xfer subscription */
+ xfer = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_xfer);
+ xfer->dlg = dlg;
+ xfer->sub = sub;
+ if (user_cb)
+ pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user));
+
+ /* Attach to evsub */
+ pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer);
+
+ /* Done: */
+ *p_evsub = sub;
+
+on_return:
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+}
+
+
+
+/*
+ * Call this function to create request to initiate REFER subscription.
+ *
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_initiate( pjsip_evsub *sub,
+ const pj_str_t *refer_to_uri,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_xfer *xfer;
+ const pj_str_t refer_to = { "Refer-To", 8};
+ pjsip_tx_data *tdata;
+ pjsip_generic_string_hdr *hdr;
+ pj_status_t status;
+
+ /* sub and p_tdata argument must be valid. */
+ PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL);
+
+
+ /* Get the xfer object. */
+ xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+ PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
+
+ /* refer_to_uri argument MAY be NULL for subsequent REFER requests,
+ * but it MUST be specified in the first REFER.
+ */
+ PJ_ASSERT_RETURN((refer_to_uri || xfer->refer_to_uri.slen), PJ_EINVAL);
+
+ /* Lock dialog. */
+ pjsip_dlg_inc_lock(xfer->dlg);
+
+ /* Create basic REFER request */
+ status = pjsip_evsub_initiate(sub, pjsip_get_refer_method(), -1,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Save Refer-To URI. */
+ if (refer_to_uri == NULL) {
+ refer_to_uri = &xfer->refer_to_uri;
+ } else {
+ pj_strdup(xfer->dlg->pool, &xfer->refer_to_uri, refer_to_uri);
+ }
+
+ /* Create and add Refer-To header. */
+ hdr = pjsip_generic_string_hdr_create(tdata->pool, &refer_to,
+ refer_to_uri);
+ if (!hdr) {
+ pjsip_tx_data_dec_ref(tdata);
+ status = PJ_ENOMEM;
+ goto on_return;
+ }
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+
+
+ /* Done. */
+ *p_tdata = tdata;
+
+ status = PJ_SUCCESS;
+
+on_return:
+ pjsip_dlg_dec_lock(xfer->dlg);
+ return status;
+}
+
+
+/*
+ * Accept the incoming REFER request by sending 2xx response.
+ *
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_accept( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pjsip_hdr *hdr_list )
+{
+ /*
+ * Don't need to add custom headers, so just call basic
+ * evsub response.
+ */
+ return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
+}
+
+
+/*
+ * For notifier, create NOTIFY request to subscriber, and set the state
+ * of the subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_notify( pjsip_evsub *sub,
+ pjsip_evsub_state state,
+ int xfer_st_code,
+ const pj_str_t *xfer_st_text,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_xfer *xfer;
+ pjsip_param *param;
+ const pj_str_t reason = { "noresource", 10 };
+ char *body;
+ int bodylen;
+ pjsip_msg_body *msg_body;
+ pj_status_t status;
+
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+ /* Get the xfer object. */
+ xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+ PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
+
+
+ /* Lock object. */
+ pjsip_dlg_inc_lock(xfer->dlg);
+
+ /* Create the NOTIFY request.
+ * Note that reason is only used when state is TERMINATED, and
+ * the defined termination reason for REFER is "noresource".
+ */
+ status = pjsip_evsub_notify( sub, state, NULL, &reason, &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+
+ /* Check status text */
+ if (xfer_st_text==NULL || xfer_st_text->slen==0)
+ xfer_st_text = pjsip_get_status_text(xfer_st_code);
+
+ /* Save st_code and st_text, for current_notify() */
+ xfer->last_st_code = xfer_st_code;
+ pj_strdup(xfer->dlg->pool, &xfer->last_st_text, xfer_st_text);
+
+ /* Create sipfrag content. */
+ body = (char*) pj_pool_alloc(tdata->pool, 128);
+ bodylen = pj_ansi_snprintf(body, 128, "SIP/2.0 %u %.*s\r\n",
+ xfer_st_code,
+ (int)xfer_st_text->slen,
+ xfer_st_text->ptr);
+ PJ_ASSERT_ON_FAIL(bodylen > 0 && bodylen < 128,
+ {status=PJ_EBUG; pjsip_tx_data_dec_ref(tdata);
+ goto on_return; });
+
+
+ /* Create SIP message body. */
+ msg_body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body);
+ pjsip_media_type_init(&msg_body->content_type, (pj_str_t*)&STR_MESSAGE,
+ (pj_str_t*)&STR_SIPFRAG);
+ msg_body->data = body;
+ msg_body->len = bodylen;
+ msg_body->print_body = &pjsip_print_text_body;
+ msg_body->clone_data = &pjsip_clone_text_data;
+
+ param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
+ param->name = pj_str("version");
+ param->value = pj_str("2.0");
+ pj_list_push_back(&msg_body->content_type.param, param);
+
+ /* Attach sipfrag body. */
+ tdata->msg->body = msg_body;
+
+
+ /* Done. */
+ *p_tdata = tdata;
+
+
+on_return:
+ pjsip_dlg_dec_lock(xfer->dlg);
+ return status;
+
+}
+
+
+/*
+ * Send current state and the last sipfrag body.
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_current_notify( pjsip_evsub *sub,
+ pjsip_tx_data **p_tdata )
+{
+ pjsip_xfer *xfer;
+ pj_status_t status;
+
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+ /* Get the xfer object. */
+ xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+ PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
+
+ pjsip_dlg_inc_lock(xfer->dlg);
+
+ status = pjsip_xfer_notify(sub, pjsip_evsub_get_state(sub),
+ xfer->last_st_code, &xfer->last_st_text,
+ p_tdata);
+
+ pjsip_dlg_dec_lock(xfer->dlg);
+
+ return status;
+}
+
+
+/*
+ * Send request message.
+ */
+PJ_DEF(pj_status_t) pjsip_xfer_send_request( pjsip_evsub *sub,
+ pjsip_tx_data *tdata)
+{
+ return pjsip_evsub_send_request(sub, tdata);
+}
+
+
+/*
+ * This callback is called by event subscription when subscription
+ * state has changed.
+ */
+static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
+{
+ pjsip_xfer *xfer;
+
+ xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+ PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+ if (xfer->user_cb.on_evsub_state)
+ (*xfer->user_cb.on_evsub_state)(sub, event);
+
+}
+
+/*
+ * Called when transaction state has changed.
+ */
+static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pjsip_xfer *xfer;
+
+ xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+ PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+ if (xfer->user_cb.on_tsx_state)
+ (*xfer->user_cb.on_tsx_state)(sub, tsx, event);
+}
+
+/*
+ * Called when REFER is received to refresh subscription.
+ */
+static void xfer_on_evsub_rx_refresh( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsip_xfer *xfer;
+
+ xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+ PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+ if (xfer->user_cb.on_rx_refresh) {
+ (*xfer->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
+ res_hdr, p_body);
+
+ } else {
+ /* Implementors MUST send NOTIFY if it implements on_rx_refresh
+ * (implementor == "us" from evsub point of view.
+ */
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
+ status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
+ xfer->last_st_code,
+ &xfer->last_st_text,
+ &tdata);
+ } else {
+ status = pjsip_xfer_current_notify(sub, &tdata);
+ }
+
+ if (status == PJ_SUCCESS)
+ pjsip_xfer_send_request(sub, tdata);
+ }
+}
+
+
+/*
+ * Called when NOTIFY is received.
+ */
+static void xfer_on_evsub_rx_notify( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsip_xfer *xfer;
+
+ xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+ PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+ if (xfer->user_cb.on_rx_notify)
+ (*xfer->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text,
+ res_hdr, p_body);
+}
+
+/*
+ * Called when it's time to send SUBSCRIBE.
+ */
+static void xfer_on_evsub_client_refresh(pjsip_evsub *sub)
+{
+ pjsip_xfer *xfer;
+
+ xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+ PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+ if (xfer->user_cb.on_client_refresh) {
+ (*xfer->user_cb.on_client_refresh)(sub);
+ } else {
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ status = pjsip_evsub_initiate(sub, NULL, PJSIP_XFER_EXPIRES, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_xfer_send_request(sub, tdata);
+ }
+}
+
+
+/*
+ * Called when no refresh is received after the interval.
+ */
+static void xfer_on_evsub_server_timeout(pjsip_evsub *sub)
+{
+ pjsip_xfer *xfer;
+
+ xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
+ PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
+
+ if (xfer->user_cb.on_server_timeout) {
+ (*xfer->user_cb.on_server_timeout)(sub);
+ } else {
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
+ xfer->last_st_code,
+ &xfer->last_st_text, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_xfer_send_request(sub, tdata);
+ }
+}
+
diff --git a/pjsip/src/pjsip/sip_auth_aka.c b/pjsip/src/pjsip/sip_auth_aka.c
new file mode 100644
index 0000000..b3c2dde
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_aka.c
@@ -0,0 +1,204 @@
+/* $Id: sip_auth_aka.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_auth_aka.h>
+#include <pjsip/sip_errno.h>
+#include <pjlib-util/base64.h>
+#include <pjlib-util/md5.h>
+#include <pjlib-util/hmac_md5.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#if PJSIP_HAS_DIGEST_AKA_AUTH
+
+#include "../../third_party/milenage/milenage.h"
+
+/*
+ * Create MD5-AKA1 digest response.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_create_aka_response(
+ pj_pool_t *pool,
+ const pjsip_digest_challenge*chal,
+ const pjsip_cred_info *cred,
+ const pj_str_t *method,
+ pjsip_digest_credential *auth)
+{
+ pj_str_t nonce_bin;
+ int aka_version;
+ const pj_str_t pjsip_AKAv1_MD5 = { "AKAv1-MD5", 9 };
+ const pj_str_t pjsip_AKAv2_MD5 = { "AKAv2-MD5", 9 };
+ pj_uint8_t *chal_rand, *chal_sqnxoraka, *chal_mac;
+ pj_uint8_t k[PJSIP_AKA_KLEN];
+ pj_uint8_t op[PJSIP_AKA_OPLEN];
+ pj_uint8_t amf[PJSIP_AKA_AMFLEN];
+ pj_uint8_t res[PJSIP_AKA_RESLEN];
+ pj_uint8_t ck[PJSIP_AKA_CKLEN];
+ pj_uint8_t ik[PJSIP_AKA_IKLEN];
+ pj_uint8_t ak[PJSIP_AKA_AKLEN];
+ pj_uint8_t sqn[PJSIP_AKA_SQNLEN];
+ pj_uint8_t xmac[PJSIP_AKA_MACLEN];
+ pjsip_cred_info aka_cred;
+ int i, len;
+ pj_status_t status;
+
+ /* Check the algorithm is supported. */
+ if (chal->algorithm.slen==0 || pj_stricmp2(&chal->algorithm, "md5") == 0) {
+ /*
+ * A normal MD5 authentication is requested. Fallbackt to the usual
+ * MD5 digest creation.
+ */
+ pjsip_auth_create_digest(&auth->response, &auth->nonce, &auth->nc,
+ &auth->cnonce, &auth->qop, &auth->uri,
+ &auth->realm, cred, method);
+ return PJ_SUCCESS;
+
+ } else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5) == 0) {
+ /*
+ * AKA version 1 is requested.
+ */
+ aka_version = 1;
+
+ } else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv2_MD5) == 0) {
+ /*
+ * AKA version 2 is requested.
+ */
+ aka_version = 2;
+
+ } else {
+ /* Unsupported algorithm */
+ return PJSIP_EINVALIDALGORITHM;
+ }
+
+ /* Decode nonce */
+ nonce_bin.slen = len = PJ_BASE64_TO_BASE256_LEN(chal->nonce.slen);
+ nonce_bin.ptr = pj_pool_alloc(pool, nonce_bin.slen + 1);
+ status = pj_base64_decode(&chal->nonce, (pj_uint8_t*)nonce_bin.ptr, &len);
+ nonce_bin.slen = len;
+ if (status != PJ_SUCCESS)
+ return PJSIP_EAUTHINNONCE;
+
+ if (nonce_bin.slen < PJSIP_AKA_RANDLEN + PJSIP_AKA_AUTNLEN)
+ return PJSIP_EAUTHINNONCE;
+
+ /* Get RAND, AUTN, and MAC */
+ chal_rand = (pj_uint8_t*)(nonce_bin.ptr + 0);
+ chal_sqnxoraka = (pj_uint8_t*) (nonce_bin.ptr + PJSIP_AKA_RANDLEN);
+ chal_mac = (pj_uint8_t*) (nonce_bin.ptr + PJSIP_AKA_RANDLEN +
+ PJSIP_AKA_SQNLEN + PJSIP_AKA_AMFLEN);
+
+ /* Copy k. op, and amf */
+ pj_bzero(k, sizeof(k));
+ pj_bzero(op, sizeof(op));
+ pj_bzero(amf, sizeof(amf));
+
+ if (cred->ext.aka.k.slen)
+ pj_memcpy(k, cred->ext.aka.k.ptr, cred->ext.aka.k.slen);
+ if (cred->ext.aka.op.slen)
+ pj_memcpy(op, cred->ext.aka.op.ptr, cred->ext.aka.op.slen);
+ if (cred->ext.aka.amf.slen)
+ pj_memcpy(amf, cred->ext.aka.amf.ptr, cred->ext.aka.amf.slen);
+
+ /* Given key K and random challenge RAND, compute response RES,
+ * confidentiality key CK, integrity key IK and anonymity key AK.
+ */
+ f2345(k, chal_rand, res, ck, ik, ak, op);
+
+ /* Compute sequence number SQN */
+ for (i=0; i<PJSIP_AKA_SQNLEN; ++i)
+ sqn[i] = (pj_uint8_t) (chal_sqnxoraka[i] ^ ak[i]);
+
+ /* Verify MAC in the challenge */
+ /* Compute XMAC */
+ f1(k, chal_rand, sqn, amf, xmac, op);
+
+ if (pj_memcmp(chal_mac, xmac, PJSIP_AKA_MACLEN) != 0) {
+ return PJSIP_EAUTHINNONCE;
+ }
+
+ /* Build a temporary credential info to create MD5 digest, using
+ * "res" as the password.
+ */
+ pj_memcpy(&aka_cred, cred, sizeof(aka_cred));
+ aka_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+
+ /* Create a response */
+ if (aka_version == 1) {
+ /*
+ * For AKAv1, the password is RES
+ */
+ aka_cred.data.ptr = (char*)res;
+ aka_cred.data.slen = PJSIP_AKA_RESLEN;
+
+ pjsip_auth_create_digest(&auth->response, &chal->nonce,
+ &auth->nc, &auth->cnonce, &auth->qop,
+ &auth->uri, &chal->realm, &aka_cred, method);
+
+ } else if (aka_version == 2) {
+
+ /*
+ * For AKAv2, password is base64 encoded [1] parameters:
+ * PRF(RES||IK||CK,"http-digest-akav2-password")
+ *
+ * The pseudo-random function (PRF) is HMAC-MD5 in this case.
+ */
+
+ pj_str_t resikck;
+ const pj_str_t AKAv2_Passwd = { "http-digest-akav2-password", 26 };
+ pj_uint8_t hmac_digest[16];
+ char tmp_buf[48];
+ int hmac64_len;
+
+ resikck.slen = PJSIP_AKA_RESLEN + PJSIP_AKA_IKLEN + PJSIP_AKA_CKLEN;
+ pj_assert(resikck.slen <= PJ_ARRAY_SIZE(tmp_buf));
+ resikck.ptr = tmp_buf;
+ pj_memcpy(resikck.ptr + 0, res, PJSIP_AKA_RESLEN);
+ pj_memcpy(resikck.ptr + PJSIP_AKA_RESLEN, ik, PJSIP_AKA_IKLEN);
+ pj_memcpy(resikck.ptr + PJSIP_AKA_RESLEN + PJSIP_AKA_IKLEN,
+ ck, PJSIP_AKA_CKLEN);
+
+ pj_hmac_md5((const pj_uint8_t*)AKAv2_Passwd.ptr, AKAv2_Passwd.slen,
+ (const pj_uint8_t*)resikck.ptr, resikck.slen,
+ hmac_digest);
+
+ aka_cred.data.slen = hmac64_len =
+ PJ_BASE256_TO_BASE64_LEN(PJ_ARRAY_SIZE(hmac_digest));
+ pj_assert(aka_cred.data.slen+1 <= PJ_ARRAY_SIZE(tmp_buf));
+ aka_cred.data.ptr = tmp_buf;
+ pj_base64_encode(hmac_digest, PJ_ARRAY_SIZE(hmac_digest),
+ aka_cred.data.ptr, &len);
+ aka_cred.data.slen = hmac64_len;
+
+ pjsip_auth_create_digest(&auth->response, &chal->nonce,
+ &auth->nc, &auth->cnonce, &auth->qop,
+ &auth->uri, &chal->realm, &aka_cred, method);
+
+ } else {
+ pj_assert(!"Bug!");
+ return PJ_EBUG;
+ }
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJSIP_HAS_DIGEST_AKA_AUTH */
+
diff --git a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c
new file mode 100644
index 0000000..ae850b1
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_client.c
@@ -0,0 +1,1189 @@
+/* $Id: sip_auth_client.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <pjsip/sip_auth.h>
+#include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */
+#include <pjsip/sip_auth_aka.h>
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_util.h>
+#include <pjlib-util/md5.h>
+#include <pj/log.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/guid.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+
+
+
+/* A macro just to get rid of type mismatch between char and unsigned char */
+#define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, len)
+
+/* Logging. */
+#define THIS_FILE "sip_auth_client.c"
+#if 0
+# define AUTH_TRACE_(expr) PJ_LOG(3, expr)
+#else
+# define AUTH_TRACE_(expr)
+#endif
+
+#define PASSWD_MASK 0x000F
+#define EXT_MASK 0x00F0
+
+
+static void dup_bin(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src)
+{
+ dst->slen = src->slen;
+
+ if (dst->slen) {
+ dst->ptr = (char*) pj_pool_alloc(pool, src->slen);
+ pj_memcpy(dst->ptr, src->ptr, src->slen);
+ } else {
+ dst->ptr = NULL;
+ }
+}
+
+PJ_DEF(void) pjsip_cred_info_dup(pj_pool_t *pool,
+ pjsip_cred_info *dst,
+ const pjsip_cred_info *src)
+{
+ pj_memcpy(dst, src, sizeof(pjsip_cred_info));
+
+ pj_strdup_with_null(pool, &dst->realm, &src->realm);
+ pj_strdup_with_null(pool, &dst->scheme, &src->scheme);
+ pj_strdup_with_null(pool, &dst->username, &src->username);
+ pj_strdup_with_null(pool, &dst->data, &src->data);
+
+ if ((dst->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+ dup_bin(pool, &dst->ext.aka.k, &src->ext.aka.k);
+ dup_bin(pool, &dst->ext.aka.op, &src->ext.aka.op);
+ dup_bin(pool, &dst->ext.aka.amf, &src->ext.aka.amf);
+ }
+}
+
+
+PJ_DEF(int) pjsip_cred_info_cmp(const pjsip_cred_info *cred1,
+ const pjsip_cred_info *cred2)
+{
+ int result;
+
+ result = pj_strcmp(&cred1->realm, &cred2->realm);
+ if (result) goto on_return;
+ result = pj_strcmp(&cred1->scheme, &cred2->scheme);
+ if (result) goto on_return;
+ result = pj_strcmp(&cred1->username, &cred2->username);
+ if (result) goto on_return;
+ result = pj_strcmp(&cred1->data, &cred2->data);
+ if (result) goto on_return;
+ result = (cred1->data_type != cred2->data_type);
+ if (result) goto on_return;
+
+ if ((cred1->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+ result = pj_strcmp(&cred1->ext.aka.k, &cred2->ext.aka.k);
+ if (result) goto on_return;
+ result = pj_strcmp(&cred1->ext.aka.op, &cred2->ext.aka.op);
+ if (result) goto on_return;
+ result = pj_strcmp(&cred1->ext.aka.amf, &cred2->ext.aka.amf);
+ if (result) goto on_return;
+ }
+
+on_return:
+ return result;
+}
+
+PJ_DEF(void) pjsip_auth_clt_pref_dup( pj_pool_t *pool,
+ pjsip_auth_clt_pref *dst,
+ const pjsip_auth_clt_pref *src)
+{
+ pj_memcpy(dst, src, sizeof(pjsip_auth_clt_pref));
+ pj_strdup_with_null(pool, &dst->algorithm, &src->algorithm);
+}
+
+
+/* Transform digest to string.
+ * output must be at least PJSIP_MD5STRLEN+1 bytes.
+ *
+ * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
+ */
+static void digest2str(const unsigned char digest[], char *output)
+{
+ int i;
+ for (i = 0; i<16; ++i) {
+ pj_val_to_hex_digit(digest[i], output);
+ output += 2;
+ }
+}
+
+
+/*
+ * Create response digest based on the parameters and store the
+ * digest ASCII in 'result'.
+ */
+PJ_DEF(void) pjsip_auth_create_digest( pj_str_t *result,
+ const pj_str_t *nonce,
+ const pj_str_t *nc,
+ const pj_str_t *cnonce,
+ const pj_str_t *qop,
+ const pj_str_t *uri,
+ const pj_str_t *realm,
+ const pjsip_cred_info *cred_info,
+ const pj_str_t *method)
+{
+ char ha1[PJSIP_MD5STRLEN];
+ char ha2[PJSIP_MD5STRLEN];
+ unsigned char digest[16];
+ pj_md5_context pms;
+
+ pj_assert(result->slen >= PJSIP_MD5STRLEN);
+
+ AUTH_TRACE_((THIS_FILE, "Begin creating digest"));
+
+ if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) {
+ /***
+ *** ha1 = MD5(username ":" realm ":" password)
+ ***/
+ pj_md5_init(&pms);
+ MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, realm->ptr, realm->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen);
+ pj_md5_final(&pms, digest);
+
+ digest2str(digest, ha1);
+
+ } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) {
+ pj_assert(cred_info->data.slen == 32);
+ pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen );
+ } else {
+ pj_assert(!"Invalid data_type");
+ }
+
+ AUTH_TRACE_((THIS_FILE, " ha1=%.32s", ha1));
+
+ /***
+ *** ha2 = MD5(method ":" req_uri)
+ ***/
+ pj_md5_init(&pms);
+ MD5_APPEND( &pms, method->ptr, method->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, uri->ptr, uri->slen);
+ pj_md5_final(&pms, digest);
+ digest2str(digest, ha2);
+
+ AUTH_TRACE_((THIS_FILE, " ha2=%.32s", ha2));
+
+ /***
+ *** When qop is not used:
+ *** response = MD5(ha1 ":" nonce ":" ha2)
+ ***
+ *** When qop=auth is used:
+ *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2)
+ ***/
+ pj_md5_init(&pms);
+ MD5_APPEND( &pms, ha1, PJSIP_MD5STRLEN);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, nonce->ptr, nonce->slen);
+ if (qop && qop->slen != 0) {
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, nc->ptr, nc->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, cnonce->ptr, cnonce->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, qop->ptr, qop->slen);
+ }
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, ha2, PJSIP_MD5STRLEN);
+
+ /* This is the final response digest. */
+ pj_md5_final(&pms, digest);
+
+ /* Convert digest to string and store in chal->response. */
+ result->slen = PJSIP_MD5STRLEN;
+ digest2str(digest, result->ptr);
+
+ AUTH_TRACE_((THIS_FILE, " digest=%.32s", result->ptr));
+ AUTH_TRACE_((THIS_FILE, "Digest created"));
+}
+
+/*
+ * Finds out if qop offer contains "auth" token.
+ */
+static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer)
+{
+ pj_str_t qop;
+ char *p;
+
+ pj_strdup_with_null( pool, &qop, qop_offer);
+ p = qop.ptr;
+ while (*p) {
+ *p = (char)pj_tolower(*p);
+ ++p;
+ }
+
+ p = qop.ptr;
+ while (*p) {
+ if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') {
+ int e = *(p+4);
+ if (e=='"' || e==',' || e==0)
+ return PJ_TRUE;
+ else
+ p += 4;
+ } else {
+ ++p;
+ }
+ }
+
+ return PJ_FALSE;
+}
+
+/*
+ * Generate response digest.
+ * Most of the parameters to generate the digest (i.e. username, realm, uri,
+ * and nonce) are expected to be in the credential. Additional parameters (i.e.
+ * password and method param) should be supplied in the argument.
+ *
+ * The resulting digest will be stored in cred->response.
+ * The pool is used to allocate 32 bytes to store the digest in cred->response.
+ */
+static pj_status_t respond_digest( pj_pool_t *pool,
+ pjsip_digest_credential *cred,
+ const pjsip_digest_challenge *chal,
+ const pj_str_t *uri,
+ const pjsip_cred_info *cred_info,
+ const pj_str_t *cnonce,
+ pj_uint32_t nc,
+ const pj_str_t *method)
+{
+ const pj_str_t pjsip_AKAv1_MD5_STR = { "AKAv1-MD5", 9 };
+
+ /* Check algorithm is supported. We support MD5 and AKAv1-MD5. */
+ if (chal->algorithm.slen==0 ||
+ (pj_stricmp(&chal->algorithm, &pjsip_MD5_STR)==0 ||
+ pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5_STR)==0))
+ {
+ ;
+ }
+ else {
+ PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"",
+ chal->algorithm.slen, chal->algorithm.ptr));
+ return PJSIP_EINVALIDALGORITHM;
+ }
+
+ /* Build digest credential from arguments. */
+ pj_strdup(pool, &cred->username, &cred_info->username);
+ pj_strdup(pool, &cred->realm, &chal->realm);
+ pj_strdup(pool, &cred->nonce, &chal->nonce);
+ pj_strdup(pool, &cred->uri, uri);
+ pj_strdup(pool, &cred->algorithm, &chal->algorithm);
+ pj_strdup(pool, &cred->opaque, &chal->opaque);
+
+ /* Allocate memory. */
+ cred->response.ptr = (char*) pj_pool_alloc(pool, PJSIP_MD5STRLEN);
+ cred->response.slen = PJSIP_MD5STRLEN;
+
+ if (chal->qop.slen == 0) {
+ /* Server doesn't require quality of protection. */
+
+ if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+ /* Call application callback to create the response digest */
+ return (*cred_info->ext.aka.cb)(pool, chal, cred_info,
+ method, cred);
+ }
+ else {
+ /* Convert digest to string and store in chal->response. */
+ pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL,
+ NULL, NULL, uri, &chal->realm,
+ cred_info, method);
+ }
+
+ } else if (has_auth_qop(pool, &chal->qop)) {
+ /* Server requires quality of protection.
+ * We respond with selecting "qop=auth" protection.
+ */
+ cred->qop = pjsip_AUTH_STR;
+ cred->nc.ptr = (char*) pj_pool_alloc(pool, 16);
+ cred->nc.slen = pj_ansi_snprintf(cred->nc.ptr, 16, "%08u", nc);
+
+ if (cnonce && cnonce->slen) {
+ pj_strdup(pool, &cred->cnonce, cnonce);
+ } else {
+ pj_str_t dummy_cnonce = { "b39971", 6};
+ pj_strdup(pool, &cred->cnonce, &dummy_cnonce);
+ }
+
+ if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+ /* Call application callback to create the response digest */
+ return (*cred_info->ext.aka.cb)(pool, chal, cred_info,
+ method, cred);
+ }
+ else {
+ pjsip_auth_create_digest( &cred->response, &cred->nonce,
+ &cred->nc, cnonce, &pjsip_AUTH_STR,
+ uri, &chal->realm, cred_info, method );
+ }
+
+ } else {
+ /* Server requires quality protection that we don't support. */
+ PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s",
+ chal->qop.slen, chal->qop.ptr));
+ return PJSIP_EINVALIDQOP;
+ }
+
+ return PJ_SUCCESS;
+}
+
+#if defined(PJSIP_AUTH_QOP_SUPPORT) && PJSIP_AUTH_QOP_SUPPORT!=0
+/*
+ * Update authentication session with a challenge.
+ */
+static void update_digest_session( pj_pool_t *ses_pool,
+ pjsip_cached_auth *cached_auth,
+ const pjsip_www_authenticate_hdr *hdr )
+{
+ if (hdr->challenge.digest.qop.slen == 0) {
+#if PJSIP_AUTH_AUTO_SEND_NEXT!=0
+ if (!cached_auth->last_chal || pj_stricmp2(&hdr->scheme, "digest")) {
+ cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
+ pjsip_hdr_clone(ses_pool, hdr);
+ } else {
+ /* Only update if the new challenge is "significantly different"
+ * than the one in the cache, to reduce memory usage.
+ */
+ const pjsip_digest_challenge *d1 =
+ &cached_auth->last_chal->challenge.digest;
+ const pjsip_digest_challenge *d2 = &hdr->challenge.digest;
+
+ if (pj_strcmp(&d1->domain, &d2->domain) ||
+ pj_strcmp(&d1->realm, &d2->realm) ||
+ pj_strcmp(&d1->nonce, &d2->nonce) ||
+ pj_strcmp(&d1->opaque, &d2->opaque) ||
+ pj_strcmp(&d1->algorithm, &d2->algorithm) ||
+ pj_strcmp(&d1->qop, &d2->qop))
+ {
+ cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
+ pjsip_hdr_clone(ses_pool, hdr);
+ }
+ }
+#endif
+ return;
+ }
+
+ /* Initialize cnonce and qop if not present. */
+ if (cached_auth->cnonce.slen == 0) {
+ /* Save the whole challenge */
+ cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
+ pjsip_hdr_clone(ses_pool, hdr);
+
+ /* Create cnonce */
+ pj_create_unique_string( ses_pool, &cached_auth->cnonce );
+
+ /* Initialize nonce-count */
+ cached_auth->nc = 1;
+
+ /* Save realm. */
+ /* Note: allow empty realm (http://trac.pjsip.org/repos/ticket/1061)
+ pj_assert(cached_auth->realm.slen != 0);
+ */
+ if (cached_auth->realm.slen == 0) {
+ pj_strdup(ses_pool, &cached_auth->realm,
+ &hdr->challenge.digest.realm);
+ }
+
+ } else {
+ /* Update last_nonce and nonce-count */
+ if (!pj_strcmp(&hdr->challenge.digest.nonce,
+ &cached_auth->last_chal->challenge.digest.nonce))
+ {
+ /* Same nonce, increment nonce-count */
+ ++cached_auth->nc;
+ } else {
+ /* Server gives new nonce. */
+ pj_strdup(ses_pool, &cached_auth->last_chal->challenge.digest.nonce,
+ &hdr->challenge.digest.nonce);
+ /* Has the opaque changed? */
+ if (pj_strcmp(&cached_auth->last_chal->challenge.digest.opaque,
+ &hdr->challenge.digest.opaque))
+ {
+ pj_strdup(ses_pool,
+ &cached_auth->last_chal->challenge.digest.opaque,
+ &hdr->challenge.digest.opaque);
+ }
+ cached_auth->nc = 1;
+ }
+ }
+}
+#endif /* PJSIP_AUTH_QOP_SUPPORT */
+
+
+/* Find cached authentication in the list for the specified realm. */
+static pjsip_cached_auth *find_cached_auth( pjsip_auth_clt_sess *sess,
+ const pj_str_t *realm )
+{
+ pjsip_cached_auth *auth = sess->cached_auth.next;
+ while (auth != &sess->cached_auth) {
+ if (pj_stricmp(&auth->realm, realm) == 0)
+ return auth;
+ auth = auth->next;
+ }
+
+ return NULL;
+}
+
+/* Find credential to use for the specified realm and auth scheme. */
+static const pjsip_cred_info* auth_find_cred( const pjsip_auth_clt_sess *sess,
+ const pj_str_t *realm,
+ const pj_str_t *auth_scheme)
+{
+ unsigned i;
+ int wildcard = -1;
+
+ PJ_UNUSED_ARG(auth_scheme);
+
+ for (i=0; i<sess->cred_cnt; ++i) {
+ if (pj_stricmp(&sess->cred_info[i].realm, realm) == 0)
+ return &sess->cred_info[i];
+ else if (sess->cred_info[i].realm.slen == 1 &&
+ sess->cred_info[i].realm.ptr[0] == '*')
+ {
+ wildcard = i;
+ }
+ }
+
+ /* No matching realm. See if we have credential with wildcard ('*')
+ * as the realm.
+ */
+ if (wildcard != -1)
+ return &sess->cred_info[wildcard];
+
+ /* Nothing is suitable */
+ return NULL;
+}
+
+
+/* Init client session. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_init( pjsip_auth_clt_sess *sess,
+ pjsip_endpoint *endpt,
+ pj_pool_t *pool,
+ unsigned options)
+{
+ PJ_ASSERT_RETURN(sess && endpt && pool && (options==0), PJ_EINVAL);
+
+ sess->pool = pool;
+ sess->endpt = endpt;
+ sess->cred_cnt = 0;
+ sess->cred_info = NULL;
+ pj_list_init(&sess->cached_auth);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Clone session. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_clone( pj_pool_t *pool,
+ pjsip_auth_clt_sess *sess,
+ const pjsip_auth_clt_sess *rhs )
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(pool && sess && rhs, PJ_EINVAL);
+
+ pjsip_auth_clt_init(sess, (pjsip_endpoint*)rhs->endpt, pool, 0);
+
+ sess->cred_cnt = rhs->cred_cnt;
+ sess->cred_info = (pjsip_cred_info*)
+ pj_pool_alloc(pool,
+ sess->cred_cnt*sizeof(pjsip_cred_info));
+ for (i=0; i<rhs->cred_cnt; ++i) {
+ pj_strdup(pool, &sess->cred_info[i].realm, &rhs->cred_info[i].realm);
+ pj_strdup(pool, &sess->cred_info[i].scheme, &rhs->cred_info[i].scheme);
+ pj_strdup(pool, &sess->cred_info[i].username,
+ &rhs->cred_info[i].username);
+ sess->cred_info[i].data_type = rhs->cred_info[i].data_type;
+ pj_strdup(pool, &sess->cred_info[i].data, &rhs->cred_info[i].data);
+ }
+
+ /* TODO note:
+ * Cloning the full authentication client is quite a big task.
+ * We do only the necessary bits here, i.e. cloning the credentials.
+ * The drawback of this basic approach is, a forked dialog will have to
+ * re-authenticate itself on the next request because it has lost the
+ * cached authentication headers.
+ */
+ PJ_TODO(FULL_CLONE_OF_AUTH_CLIENT_SESSION);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Set client credentials. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess,
+ int cred_cnt,
+ const pjsip_cred_info *c)
+{
+ PJ_ASSERT_RETURN(sess && c, PJ_EINVAL);
+
+ if (cred_cnt == 0) {
+ sess->cred_cnt = 0;
+ } else {
+ int i;
+ sess->cred_info = (pjsip_cred_info*)
+ pj_pool_alloc(sess->pool, cred_cnt * sizeof(*c));
+ for (i=0; i<cred_cnt; ++i) {
+ sess->cred_info[i].data_type = c[i].data_type;
+
+ /* When data_type is PJSIP_CRED_DATA_EXT_AKA,
+ * callback must be specified.
+ */
+ if ((c[i].data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+
+#if !PJSIP_HAS_DIGEST_AKA_AUTH
+ if (!PJSIP_HAS_DIGEST_AKA_AUTH) {
+ pj_assert(!"PJSIP_HAS_DIGEST_AKA_AUTH is not enabled");
+ return PJSIP_EAUTHINAKACRED;
+ }
+#endif
+
+ /* Callback must be specified */
+ PJ_ASSERT_RETURN(c[i].ext.aka.cb != NULL, PJ_EINVAL);
+
+ /* Verify K len */
+ PJ_ASSERT_RETURN(c[i].ext.aka.k.slen <= PJSIP_AKA_KLEN,
+ PJSIP_EAUTHINAKACRED);
+
+ /* Verify OP len */
+ PJ_ASSERT_RETURN(c[i].ext.aka.op.slen <= PJSIP_AKA_OPLEN,
+ PJSIP_EAUTHINAKACRED);
+
+ /* Verify AMF len */
+ PJ_ASSERT_RETURN(c[i].ext.aka.amf.slen <= PJSIP_AKA_AMFLEN,
+ PJSIP_EAUTHINAKACRED);
+
+ sess->cred_info[i].ext.aka.cb = c[i].ext.aka.cb;
+ pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.k,
+ &c[i].ext.aka.k);
+ pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.op,
+ &c[i].ext.aka.op);
+ pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.amf,
+ &c[i].ext.aka.amf);
+ }
+
+ pj_strdup(sess->pool, &sess->cred_info[i].scheme, &c[i].scheme);
+ pj_strdup(sess->pool, &sess->cred_info[i].realm, &c[i].realm);
+ pj_strdup(sess->pool, &sess->cred_info[i].username, &c[i].username);
+ pj_strdup(sess->pool, &sess->cred_info[i].data, &c[i].data);
+ }
+ sess->cred_cnt = cred_cnt;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set the preference for the client authentication session.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_clt_set_prefs(pjsip_auth_clt_sess *sess,
+ const pjsip_auth_clt_pref *p)
+{
+ PJ_ASSERT_RETURN(sess && p, PJ_EINVAL);
+
+ pj_memcpy(&sess->pref, p, sizeof(*p));
+ pj_strdup(sess->pool, &sess->pref.algorithm, &p->algorithm);
+ //if (sess->pref.algorithm.slen == 0)
+ // sess->pref.algorithm = pj_str("md5");
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the preference for the client authentication session.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_clt_get_prefs(pjsip_auth_clt_sess *sess,
+ pjsip_auth_clt_pref *p)
+{
+ PJ_ASSERT_RETURN(sess && p, PJ_EINVAL);
+
+ pj_memcpy(p, &sess->pref, sizeof(pjsip_auth_clt_pref));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create Authorization/Proxy-Authorization response header based on the challege
+ * in WWW-Authenticate/Proxy-Authenticate header.
+ */
+static pj_status_t auth_respond( pj_pool_t *req_pool,
+ const pjsip_www_authenticate_hdr *hdr,
+ const pjsip_uri *uri,
+ const pjsip_cred_info *cred_info,
+ const pjsip_method *method,
+ pj_pool_t *sess_pool,
+ pjsip_cached_auth *cached_auth,
+ pjsip_authorization_hdr **p_h_auth)
+{
+ pjsip_authorization_hdr *hauth;
+ char tmp[PJSIP_MAX_URL_SIZE];
+ pj_str_t uri_str;
+ pj_pool_t *pool;
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(req_pool && hdr && uri && cred_info && method &&
+ sess_pool && cached_auth && p_h_auth, PJ_EINVAL);
+
+ /* Print URL in the original request. */
+ uri_str.ptr = tmp;
+ uri_str.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, tmp,sizeof(tmp));
+ if (uri_str.slen < 1) {
+ pj_assert(!"URL is too long!");
+ return PJSIP_EURITOOLONG;
+ }
+
+# if (PJSIP_AUTH_HEADER_CACHING)
+ {
+ pool = sess_pool;
+ PJ_UNUSED_ARG(req_pool);
+ }
+# else
+ {
+ pool = req_pool;
+ PJ_UNUSED_ARG(sess_pool);
+ }
+# endif
+
+ if (hdr->type == PJSIP_H_WWW_AUTHENTICATE)
+ hauth = pjsip_authorization_hdr_create(pool);
+ else if (hdr->type == PJSIP_H_PROXY_AUTHENTICATE)
+ hauth = pjsip_proxy_authorization_hdr_create(pool);
+ else {
+ pj_assert(!"Invalid response header!");
+ return PJSIP_EINVALIDHDR;
+ }
+
+ /* Only support digest scheme at the moment. */
+ if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
+ pj_str_t *cnonce = NULL;
+ pj_uint32_t nc = 1;
+
+ /* Update the session (nonce-count etc) if required. */
+# if PJSIP_AUTH_QOP_SUPPORT
+ {
+ if (cached_auth) {
+ update_digest_session( sess_pool, cached_auth, hdr );
+
+ cnonce = &cached_auth->cnonce;
+ nc = cached_auth->nc;
+ }
+ }
+# endif /* PJSIP_AUTH_QOP_SUPPORT */
+
+ hauth->scheme = pjsip_DIGEST_STR;
+ status = respond_digest( pool, &hauth->credential.digest,
+ &hdr->challenge.digest, &uri_str, cred_info,
+ cnonce, nc, &method->name);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set qop type in auth session the first time only. */
+ if (hdr->challenge.digest.qop.slen != 0 && cached_auth) {
+ if (cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+ pj_str_t *qop_val = &hauth->credential.digest.qop;
+ if (!pj_strcmp(qop_val, &pjsip_AUTH_STR)) {
+ cached_auth->qop_value = PJSIP_AUTH_QOP_AUTH;
+ } else {
+ cached_auth->qop_value = PJSIP_AUTH_QOP_UNKNOWN;
+ }
+ }
+ }
+ } else {
+ return PJSIP_EINVALIDAUTHSCHEME;
+ }
+
+ /* Keep the new authorization header in the cache, only
+ * if no qop is not present.
+ */
+# if PJSIP_AUTH_HEADER_CACHING
+ {
+ if (hauth && cached_auth && cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+ pjsip_cached_auth_hdr *cached_hdr;
+
+ /* Delete old header with the same method. */
+ cached_hdr = cached_auth->cached_hdr.next;
+ while (cached_hdr != &cached_auth->cached_hdr) {
+ if (pjsip_method_cmp(method, &cached_hdr->method)==0)
+ break;
+ cached_hdr = cached_hdr->next;
+ }
+
+ /* Save the header to the list. */
+ if (cached_hdr != &cached_auth->cached_hdr) {
+ cached_hdr->hdr = hauth;
+ } else {
+ cached_hdr = pj_pool_alloc(pool, sizeof(*cached_hdr));
+ pjsip_method_copy( pool, &cached_hdr->method, method);
+ cached_hdr->hdr = hauth;
+ pj_list_insert_before( &cached_auth->cached_hdr, cached_hdr );
+ }
+ }
+
+# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
+ if (hdr != cached_auth->last_chal) {
+ cached_auth->last_chal = pjsip_hdr_clone(sess_pool, hdr);
+ }
+# endif
+ }
+# endif
+
+ *p_h_auth = hauth;
+ return PJ_SUCCESS;
+
+}
+
+
+#if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
+static pj_status_t new_auth_for_req( pjsip_tx_data *tdata,
+ pjsip_auth_clt_sess *sess,
+ pjsip_cached_auth *auth,
+ pjsip_authorization_hdr **p_h_auth)
+{
+ const pjsip_cred_info *cred;
+ pjsip_authorization_hdr *hauth;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tdata && sess && auth, PJ_EINVAL);
+ PJ_ASSERT_RETURN(auth->last_chal != NULL, PJSIP_EAUTHNOPREVCHAL);
+
+ cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme );
+ if (!cred)
+ return PJSIP_ENOCREDENTIAL;
+
+ status = auth_respond( tdata->pool, auth->last_chal,
+ tdata->msg->line.req.uri,
+ cred, &tdata->msg->line.req.method,
+ sess->pool, auth, &hauth);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth);
+
+ if (p_h_auth)
+ *p_h_auth = hauth;
+
+ return PJ_SUCCESS;
+}
+#endif
+
+
+/* Find credential in list of (Proxy-)Authorization headers */
+static pjsip_authorization_hdr* get_header_for_realm(const pjsip_hdr *hdr_list,
+ const pj_str_t *realm)
+{
+ pjsip_authorization_hdr *h;
+
+ h = (pjsip_authorization_hdr*)hdr_list->next;
+ while (h != (pjsip_authorization_hdr*)hdr_list) {
+ if (pj_stricmp(&h->credential.digest.realm, realm)==0)
+ return h;
+ h = h->next;
+ }
+
+ return NULL;
+}
+
+
+/* Initialize outgoing request. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess,
+ pjsip_tx_data *tdata )
+{
+ const pjsip_method *method;
+ pjsip_cached_auth *auth;
+ pjsip_hdr added;
+
+ PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
+ PJ_ASSERT_RETURN(tdata->msg->type==PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Init list */
+ pj_list_init(&added);
+
+ /* Get the method. */
+ method = &tdata->msg->line.req.method;
+
+ auth = sess->cached_auth.next;
+ while (auth != &sess->cached_auth) {
+ /* Reset stale counter */
+ auth->stale_cnt = 0;
+
+ if (auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+# if defined(PJSIP_AUTH_HEADER_CACHING) && \
+ PJSIP_AUTH_HEADER_CACHING!=0
+ {
+ pjsip_cached_auth_hdr *entry = auth->cached_hdr.next;
+ while (entry != &auth->cached_hdr) {
+ if (pjsip_method_cmp(&entry->method, method)==0) {
+ pjsip_authorization_hdr *hauth;
+ hauth = pjsip_hdr_shallow_clone(tdata->pool, entry->hdr);
+ //pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+ pj_list_push_back(&added, hauth);
+ break;
+ }
+ entry = entry->next;
+ }
+
+# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+ PJSIP_AUTH_AUTO_SEND_NEXT!=0
+ {
+ if (entry == &auth->cached_hdr)
+ new_auth_for_req( tdata, sess, auth, NULL);
+ }
+# endif
+
+ }
+# elif defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+ PJSIP_AUTH_AUTO_SEND_NEXT!=0
+ {
+ new_auth_for_req( tdata, sess, auth, NULL);
+ }
+# endif
+
+ }
+# if defined(PJSIP_AUTH_QOP_SUPPORT) && \
+ defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+ (PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT)
+ else if (auth->qop_value == PJSIP_AUTH_QOP_AUTH) {
+ /* For qop="auth", we have to re-create the authorization header.
+ */
+ const pjsip_cred_info *cred;
+ pjsip_authorization_hdr *hauth;
+ pj_status_t status;
+
+ cred = auth_find_cred(sess, &auth->realm,
+ &auth->last_chal->scheme);
+ if (!cred) {
+ auth = auth->next;
+ continue;
+ }
+
+ status = auth_respond( tdata->pool, auth->last_chal,
+ tdata->msg->line.req.uri,
+ cred,
+ &tdata->msg->line.req.method,
+ sess->pool, auth, &hauth);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ //pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+ pj_list_push_back(&added, hauth);
+ }
+# endif /* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */
+
+ auth = auth->next;
+ }
+
+ if (sess->pref.initial_auth == PJ_FALSE) {
+ pjsip_hdr *h;
+
+ /* Don't want to send initial empty Authorization header, so
+ * just send whatever available in the list (maybe empty).
+ */
+
+ h = added.next;
+ while (h != &added) {
+ pjsip_hdr *next = h->next;
+ pjsip_msg_add_hdr(tdata->msg, h);
+ h = next;
+ }
+ } else {
+ /* For each realm, add either the cached authorization header
+ * or add an empty authorization header.
+ */
+ unsigned i;
+ char *uri_str;
+ int len;
+
+ uri_str = (char*)pj_pool_alloc(tdata->pool, PJSIP_MAX_URL_SIZE);
+ len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, tdata->msg->line.req.uri,
+ uri_str, PJSIP_MAX_URL_SIZE);
+ if (len < 1 || len >= PJSIP_MAX_URL_SIZE)
+ return PJSIP_EURITOOLONG;
+
+ for (i=0; i<sess->cred_cnt; ++i) {
+ pjsip_cred_info *c = &sess->cred_info[i];
+ pjsip_authorization_hdr *h;
+
+ h = get_header_for_realm(&added, &c->realm);
+ if (h) {
+ pj_list_erase(h);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)h);
+ } else {
+ pjsip_authorization_hdr *hs;
+
+ hs = pjsip_authorization_hdr_create(tdata->pool);
+ pj_strdup(tdata->pool, &hs->scheme, &c->scheme);
+ pj_strdup(tdata->pool, &hs->credential.digest.username,
+ &c->username);
+ pj_strdup(tdata->pool, &hs->credential.digest.realm,
+ &c->realm);
+ pj_strdup2(tdata->pool, &hs->credential.digest.uri,
+ uri_str);
+ pj_strdup(tdata->pool, &hs->credential.digest.algorithm,
+ &sess->pref.algorithm);
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hs);
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Process authorization challenge */
+static pj_status_t process_auth( pj_pool_t *req_pool,
+ const pjsip_www_authenticate_hdr *hchal,
+ const pjsip_uri *uri,
+ pjsip_tx_data *tdata,
+ pjsip_auth_clt_sess *sess,
+ pjsip_cached_auth *cached_auth,
+ pjsip_authorization_hdr **h_auth)
+{
+ const pjsip_cred_info *cred;
+ pjsip_authorization_hdr *sent_auth = NULL;
+ pjsip_hdr *hdr;
+ pj_status_t status;
+
+ /* See if we have sent authorization header for this realm */
+ hdr = tdata->msg->hdr.next;
+ while (hdr != &tdata->msg->hdr) {
+ if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE &&
+ hdr->type == PJSIP_H_AUTHORIZATION) ||
+ (hchal->type == PJSIP_H_PROXY_AUTHENTICATE &&
+ hdr->type == PJSIP_H_PROXY_AUTHORIZATION))
+ {
+ sent_auth = (pjsip_authorization_hdr*) hdr;
+ if (pj_stricmp(&hchal->challenge.common.realm,
+ &sent_auth->credential.common.realm )==0)
+ {
+ /* If this authorization has empty response, remove it. */
+ if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
+ sent_auth->credential.digest.response.slen == 0)
+ {
+ /* This is empty authorization, remove it. */
+ hdr = hdr->next;
+ pj_list_erase(sent_auth);
+ continue;
+ } else {
+ /* Found previous authorization attempt */
+ break;
+ }
+ }
+ }
+ hdr = hdr->next;
+ }
+
+ /* If we have sent, see if server rejected because of stale nonce or
+ * other causes.
+ */
+ if (hdr != &tdata->msg->hdr) {
+ pj_bool_t stale;
+
+ /* Detect "stale" state */
+ stale = hchal->challenge.digest.stale;
+ if (!stale) {
+ /* If stale is false, check is nonce has changed. Some servers
+ * (broken ones!) want to change nonce but they fail to set
+ * stale to true.
+ */
+ stale = pj_strcmp(&hchal->challenge.digest.nonce,
+ &sent_auth->credential.digest.nonce);
+ }
+
+ if (stale == PJ_FALSE) {
+ /* Our credential is rejected. No point in trying to re-supply
+ * the same credential.
+ */
+ PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: "
+ "server rejected with stale=false",
+ sent_auth->credential.digest.username.slen,
+ sent_auth->credential.digest.username.ptr,
+ sent_auth->credential.digest.realm.slen,
+ sent_auth->credential.digest.realm.ptr));
+ return PJSIP_EFAILEDCREDENTIAL;
+ }
+
+ cached_auth->stale_cnt++;
+ if (cached_auth->stale_cnt >= PJSIP_MAX_STALE_COUNT) {
+ /* Our credential is rejected. No point in trying to re-supply
+ * the same credential.
+ */
+ PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: "
+ "maximum number of stale retries exceeded",
+ sent_auth->credential.digest.username.slen,
+ sent_auth->credential.digest.username.ptr,
+ sent_auth->credential.digest.realm.slen,
+ sent_auth->credential.digest.realm.ptr));
+ return PJSIP_EAUTHSTALECOUNT;
+ }
+
+ /* Otherwise remove old, stale authorization header from the mesasge.
+ * We will supply a new one.
+ */
+ pj_list_erase(sent_auth);
+ }
+
+ /* Find credential to be used for the challenge. */
+ cred = auth_find_cred( sess, &hchal->challenge.common.realm,
+ &hchal->scheme);
+ if (!cred) {
+ const pj_str_t *realm = &hchal->challenge.common.realm;
+ PJ_LOG(4,(THIS_FILE,
+ "Unable to set auth for %s: can not find credential for %.*s/%.*s",
+ tdata->obj_name,
+ realm->slen, realm->ptr,
+ hchal->scheme.slen, hchal->scheme.ptr));
+ return PJSIP_ENOCREDENTIAL;
+ }
+
+ /* Respond to authorization challenge. */
+ status = auth_respond( req_pool, hchal, uri, cred,
+ &tdata->msg->line.req.method,
+ sess->pool, cached_auth, h_auth);
+ return status;
+}
+
+
+/* Reinitialize outgoing request after 401/407 response is received.
+ * The purpose of this function is:
+ * - to add a Authorization/Proxy-Authorization header.
+ * - to put the newly created Authorization/Proxy-Authorization header
+ * in cached_list.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess,
+ const pjsip_rx_data *rdata,
+ pjsip_tx_data *old_request,
+ pjsip_tx_data **new_request )
+{
+ pjsip_tx_data *tdata;
+ const pjsip_hdr *hdr;
+ unsigned chal_cnt;
+ pjsip_via_hdr *via;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(sess && rdata && old_request && new_request,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG,
+ PJSIP_ENOTRESPONSEMSG);
+ PJ_ASSERT_RETURN(old_request->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->line.status.code == 401 ||
+ rdata->msg_info.msg->line.status.code == 407,
+ PJSIP_EINVALIDSTATUS);
+
+ tdata = old_request;
+ tdata->auth_retry = PJ_FALSE;
+
+ /*
+ * Respond to each authentication challenge.
+ */
+ hdr = rdata->msg_info.msg->hdr.next;
+ chal_cnt = 0;
+ while (hdr != &rdata->msg_info.msg->hdr) {
+ pjsip_cached_auth *cached_auth;
+ const pjsip_www_authenticate_hdr *hchal;
+ pjsip_authorization_hdr *hauth;
+
+ /* Find WWW-Authenticate or Proxy-Authenticate header. */
+ while (hdr != &rdata->msg_info.msg->hdr &&
+ hdr->type != PJSIP_H_WWW_AUTHENTICATE &&
+ hdr->type != PJSIP_H_PROXY_AUTHENTICATE)
+ {
+ hdr = hdr->next;
+ }
+ if (hdr == &rdata->msg_info.msg->hdr)
+ break;
+
+ hchal = (const pjsip_www_authenticate_hdr*) hdr;
+ ++chal_cnt;
+
+ /* Find authentication session for this realm, create a new one
+ * if not present.
+ */
+ cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm );
+ if (!cached_auth) {
+ cached_auth = PJ_POOL_ZALLOC_T( sess->pool, pjsip_cached_auth);
+ pj_strdup( sess->pool, &cached_auth->realm, &hchal->challenge.common.realm);
+ cached_auth->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE);
+# if (PJSIP_AUTH_HEADER_CACHING)
+ {
+ pj_list_init(&cached_auth->cached_hdr);
+ }
+# endif
+ pj_list_insert_before( &sess->cached_auth, cached_auth );
+ }
+
+ /* Create authorization header for this challenge, and update
+ * authorization session.
+ */
+ status = process_auth( tdata->pool, hchal, tdata->msg->line.req.uri,
+ tdata, sess, cached_auth, &hauth);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add to the message. */
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+
+ /* Process next header. */
+ hdr = hdr->next;
+ }
+
+ /* Check if challenge is present */
+ if (chal_cnt == 0)
+ return PJSIP_EAUTHNOCHAL;
+
+ /* Remove branch param in Via header. */
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ via->branch_param.slen = 0;
+
+ /* Restore strict route set.
+ * See http://trac.pjsip.org/repos/ticket/492
+ */
+ pjsip_restore_strict_route_set(tdata);
+
+ /* Must invalidate the message! */
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Retrying.. */
+ tdata->auth_retry = PJ_TRUE;
+
+ /* Increment reference counter. */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Done. */
+ *new_request = tdata;
+ return PJ_SUCCESS;
+
+}
+
diff --git a/pjsip/src/pjsip/sip_auth_msg.c b/pjsip/src/pjsip/sip_auth_msg.c
new file mode 100644
index 0000000..5167b46
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_msg.c
@@ -0,0 +1,343 @@
+/* $Id: sip_auth_msg.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_auth_msg.h>
+#include <pjsip/sip_auth_parser.h>
+#include <pjsip/sip_parser.h>
+#include <pj/pool.h>
+#include <pj/list.h>
+#include <pj/string.h>
+#include <pj/assert.h>
+#include <pjsip/print_util.h>
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Authorization and Proxy-Authorization header.
+ */
+static pjsip_authorization_hdr* pjsip_authorization_hdr_clone( pj_pool_t *pool,
+ const pjsip_authorization_hdr *hdr);
+static pjsip_authorization_hdr* pjsip_authorization_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_authorization_hdr *hdr);
+static int pjsip_authorization_hdr_print( pjsip_authorization_hdr *hdr,
+ char *buf, pj_size_t size);
+
+static pjsip_hdr_vptr authorization_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_authorization_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_authorization_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_authorization_hdr_print,
+};
+
+
+PJ_DEF(pjsip_authorization_hdr*) pjsip_authorization_hdr_create(pj_pool_t *pool)
+{
+ pjsip_authorization_hdr *hdr;
+ hdr = PJ_POOL_ZALLOC_T(pool, pjsip_authorization_hdr);
+ init_hdr(hdr, PJSIP_H_AUTHORIZATION, &authorization_hdr_vptr);
+ pj_list_init(&hdr->credential.common.other_param);
+ return hdr;
+}
+
+PJ_DEF(pjsip_proxy_authorization_hdr*) pjsip_proxy_authorization_hdr_create(pj_pool_t *pool)
+{
+ pjsip_proxy_authorization_hdr *hdr;
+ hdr = PJ_POOL_ZALLOC_T(pool, pjsip_proxy_authorization_hdr);
+ init_hdr(hdr, PJSIP_H_PROXY_AUTHORIZATION, &authorization_hdr_vptr);
+ pj_list_init(&hdr->credential.common.other_param);
+ return hdr;
+}
+
+static int print_digest_credential(pjsip_digest_credential *cred, char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ copy_advance_pair_quote_cond(buf, "username=", 9, cred->username, '"', '"');
+ copy_advance_pair_quote_cond(buf, ", realm=", 8, cred->realm, '"', '"');
+ copy_advance_pair_quote(buf, ", nonce=", 8, cred->nonce, '"', '"');
+ copy_advance_pair_quote_cond(buf, ", uri=", 6, cred->uri, '"', '"');
+ copy_advance_pair_quote(buf, ", response=", 11, cred->response, '"', '"');
+ copy_advance_pair(buf, ", algorithm=", 12, cred->algorithm);
+ copy_advance_pair_quote_cond(buf, ", cnonce=", 9, cred->cnonce, '"', '"');
+ copy_advance_pair_quote_cond(buf, ", opaque=", 9, cred->opaque, '"', '"');
+ //Note: there's no dbl-quote in qop in Authorization header
+ // (unlike WWW-Authenticate)
+ //copy_advance_pair_quote_cond(buf, ", qop=", 6, cred->qop, '"', '"');
+ copy_advance_pair(buf, ", qop=", 6, cred->qop);
+ copy_advance_pair(buf, ", nc=", 5, cred->nc);
+
+ printed = pjsip_param_print_on(&cred->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ',');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return (int) (buf-startbuf);
+}
+
+static int print_pgp_credential(pjsip_pgp_credential *cred, char *buf, pj_size_t size)
+{
+ PJ_UNUSED_ARG(cred);
+ PJ_UNUSED_ARG(buf);
+ PJ_UNUSED_ARG(size);
+ return -1;
+}
+
+static int pjsip_authorization_hdr_print( pjsip_authorization_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+
+ copy_advance(buf, hdr->name);
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ copy_advance(buf, hdr->scheme);
+ *buf++ = ' ';
+
+ if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0)
+ {
+ printed = print_digest_credential(&hdr->credential.digest, buf, endbuf - buf);
+ }
+ else if (pj_stricmp(&hdr->scheme, &pjsip_PGP_STR) == 0)
+ {
+ printed = print_pgp_credential(&hdr->credential.pgp, buf, endbuf - buf);
+ }
+ else {
+ pj_assert(0);
+ return -1;
+ }
+
+ if (printed == -1)
+ return -1;
+
+ buf += printed;
+ *buf = '\0';
+ return (int)(buf-startbuf);
+}
+
+static pjsip_authorization_hdr* pjsip_authorization_hdr_clone( pj_pool_t *pool,
+ const pjsip_authorization_hdr *rhs)
+{
+ /* This function also serves Proxy-Authorization header. */
+ pjsip_authorization_hdr *hdr;
+ if (rhs->type == PJSIP_H_AUTHORIZATION)
+ hdr = pjsip_authorization_hdr_create(pool);
+ else
+ hdr = pjsip_proxy_authorization_hdr_create(pool);
+
+ pj_strdup(pool, &hdr->scheme, &rhs->scheme);
+
+ if (pj_stricmp2(&hdr->scheme, "digest") == 0) {
+ pj_strdup(pool, &hdr->credential.digest.username, &rhs->credential.digest.username);
+ pj_strdup(pool, &hdr->credential.digest.realm, &rhs->credential.digest.realm);
+ pj_strdup(pool, &hdr->credential.digest.nonce, &rhs->credential.digest.nonce);
+ pj_strdup(pool, &hdr->credential.digest.uri, &rhs->credential.digest.uri);
+ pj_strdup(pool, &hdr->credential.digest.response, &rhs->credential.digest.response);
+ pj_strdup(pool, &hdr->credential.digest.algorithm, &rhs->credential.digest.algorithm);
+ pj_strdup(pool, &hdr->credential.digest.cnonce, &rhs->credential.digest.cnonce);
+ pj_strdup(pool, &hdr->credential.digest.opaque, &rhs->credential.digest.opaque);
+ pj_strdup(pool, &hdr->credential.digest.qop, &rhs->credential.digest.qop);
+ pj_strdup(pool, &hdr->credential.digest.nc, &rhs->credential.digest.nc);
+ pjsip_param_clone(pool, &hdr->credential.digest.other_param, &rhs->credential.digest.other_param);
+ } else if (pj_stricmp2(&hdr->scheme, "pgp") == 0) {
+ pj_assert(0);
+ return NULL;
+ } else {
+ pj_assert(0);
+ return NULL;
+ }
+
+ return hdr;
+}
+
+static pjsip_authorization_hdr*
+pjsip_authorization_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_authorization_hdr *rhs)
+{
+ /* This function also serves Proxy-Authorization header. */
+ pjsip_authorization_hdr *hdr;
+ hdr = PJ_POOL_ALLOC_T(pool, pjsip_authorization_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->credential.common.other_param,
+ &rhs->credential.common.other_param);
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Proxy-Authenticate and WWW-Authenticate header.
+ */
+static int pjsip_www_authenticate_hdr_print( pjsip_www_authenticate_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_clone( pj_pool_t *pool,
+ const pjsip_www_authenticate_hdr *hdr);
+static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_www_authenticate_hdr *hdr);
+
+static pjsip_hdr_vptr www_authenticate_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_www_authenticate_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_www_authenticate_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_www_authenticate_hdr_print,
+};
+
+
+PJ_DEF(pjsip_www_authenticate_hdr*) pjsip_www_authenticate_hdr_create(pj_pool_t *pool)
+{
+ pjsip_www_authenticate_hdr *hdr;
+ hdr = PJ_POOL_ZALLOC_T(pool, pjsip_www_authenticate_hdr);
+ init_hdr(hdr, PJSIP_H_WWW_AUTHENTICATE, &www_authenticate_hdr_vptr);
+ pj_list_init(&hdr->challenge.common.other_param);
+ return hdr;
+}
+
+
+PJ_DEF(pjsip_proxy_authenticate_hdr*) pjsip_proxy_authenticate_hdr_create(pj_pool_t *pool)
+{
+ pjsip_proxy_authenticate_hdr *hdr;
+ hdr = PJ_POOL_ZALLOC_T(pool, pjsip_proxy_authenticate_hdr);
+ init_hdr(hdr, PJSIP_H_PROXY_AUTHENTICATE, &www_authenticate_hdr_vptr);
+ pj_list_init(&hdr->challenge.common.other_param);
+ return hdr;
+}
+
+static int print_digest_challenge( pjsip_digest_challenge *chal,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ /* Allow empty realm, see http://trac.pjsip.org/repos/ticket/1061 */
+ copy_advance_pair_quote(buf, " realm=", 7, chal->realm, '"', '"');
+ copy_advance_pair_quote_cond(buf, ",domain=", 8, chal->domain, '"', '"');
+ copy_advance_pair_quote_cond(buf, ",nonce=", 7, chal->nonce, '"', '"');
+ copy_advance_pair_quote_cond(buf, ",opaque=", 8, chal->opaque, '"', '"');
+ if (chal->stale) {
+ pj_str_t true_str = { "true", 4 };
+ copy_advance_pair(buf, ",stale=", 7, true_str);
+ }
+ copy_advance_pair(buf, ",algorithm=", 11, chal->algorithm);
+ copy_advance_pair_quote_cond(buf, ",qop=", 5, chal->qop, '"', '"');
+
+ printed = pjsip_param_print_on(&chal->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ',');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return (int)(buf-startbuf);
+}
+
+static int print_pgp_challenge( pjsip_pgp_challenge *chal,
+ char *buf, pj_size_t size)
+{
+ PJ_UNUSED_ARG(chal);
+ PJ_UNUSED_ARG(buf);
+ PJ_UNUSED_ARG(size);
+ return -1;
+}
+
+static int pjsip_www_authenticate_hdr_print( pjsip_www_authenticate_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+
+ copy_advance(buf, hdr->name);
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ copy_advance(buf, hdr->scheme);
+ *buf++ = ' ';
+
+ if (pj_stricmp2(&hdr->scheme, "digest") == 0)
+ printed = print_digest_challenge(&hdr->challenge.digest, buf, endbuf - buf);
+ else if (pj_stricmp2(&hdr->scheme, "pgp") == 0)
+ printed = print_pgp_challenge(&hdr->challenge.pgp, buf, endbuf - buf);
+ else {
+ pj_assert(0);
+ return -1;
+ }
+
+ if (printed == -1)
+ return -1;
+
+ buf += printed;
+ *buf = '\0';
+ return (int)(buf-startbuf);
+}
+
+static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_clone( pj_pool_t *pool,
+ const pjsip_www_authenticate_hdr *rhs)
+{
+ /* This function also serves Proxy-Authenticate header. */
+ pjsip_www_authenticate_hdr *hdr;
+ if (rhs->type == PJSIP_H_WWW_AUTHENTICATE)
+ hdr = pjsip_www_authenticate_hdr_create(pool);
+ else
+ hdr = pjsip_proxy_authenticate_hdr_create(pool);
+
+ pj_strdup(pool, &hdr->scheme, &rhs->scheme);
+
+ if (pj_stricmp2(&hdr->scheme, "digest") == 0) {
+ pj_strdup(pool, &hdr->challenge.digest.realm, &rhs->challenge.digest.realm);
+ pj_strdup(pool, &hdr->challenge.digest.domain, &rhs->challenge.digest.domain);
+ pj_strdup(pool, &hdr->challenge.digest.nonce, &rhs->challenge.digest.nonce);
+ pj_strdup(pool, &hdr->challenge.digest.opaque, &rhs->challenge.digest.opaque);
+ hdr->challenge.digest.stale = rhs->challenge.digest.stale;
+ pj_strdup(pool, &hdr->challenge.digest.algorithm, &rhs->challenge.digest.algorithm);
+ pj_strdup(pool, &hdr->challenge.digest.qop, &rhs->challenge.digest.qop);
+ pjsip_param_clone(pool, &hdr->challenge.digest.other_param,
+ &rhs->challenge.digest.other_param);
+ } else if (pj_stricmp2(&hdr->scheme, "pgp") == 0) {
+ pj_assert(0);
+ return NULL;
+ } else {
+ pj_assert(0);
+ return NULL;
+ }
+
+ return hdr;
+
+}
+
+static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_www_authenticate_hdr *rhs)
+{
+ /* This function also serves Proxy-Authenticate header. */
+ pjsip_www_authenticate_hdr *hdr;
+ hdr = PJ_POOL_ALLOC_T(pool, pjsip_www_authenticate_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->challenge.common.other_param,
+ &rhs->challenge.common.other_param);
+ return hdr;
+}
+
+
diff --git a/pjsip/src/pjsip/sip_auth_parser.c b/pjsip/src/pjsip/sip_auth_parser.c
new file mode 100644
index 0000000..823372c
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_parser.c
@@ -0,0 +1,308 @@
+/* $Id: sip_auth_parser.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_auth_parser.h>
+#include <pjsip/sip_auth_msg.h>
+#include <pjsip/sip_parser.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+#include <pj/except.h>
+#include <pj/pool.h>
+
+static pjsip_hdr* parse_hdr_authorization ( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_proxy_authorization ( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_www_authenticate ( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_proxy_authenticate ( pjsip_parse_ctx *ctx );
+
+static void parse_digest_credential ( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_digest_credential *cred);
+static void parse_pgp_credential ( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_pgp_credential *cred);
+static void parse_digest_challenge ( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_digest_challenge *chal);
+static void parse_pgp_challenge ( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_pgp_challenge *chal);
+
+const pj_str_t pjsip_USERNAME_STR = { "username", 8 },
+ pjsip_REALM_STR = { "realm", 5},
+ pjsip_NONCE_STR = { "nonce", 5},
+ pjsip_URI_STR = { "uri", 3 },
+ pjsip_RESPONSE_STR = { "response", 8 },
+ pjsip_ALGORITHM_STR = { "algorithm", 9 },
+ pjsip_DOMAIN_STR = { "domain", 6 },
+ pjsip_STALE_STR = { "stale", 5},
+ pjsip_QOP_STR = { "qop", 3},
+ pjsip_CNONCE_STR = { "cnonce", 6},
+ pjsip_OPAQUE_STR = { "opaque", 6},
+ pjsip_NC_STR = { "nc", 2},
+ pjsip_TRUE_STR = { "true", 4},
+ pjsip_QUOTED_TRUE_STR = { "\"true\"", 6},
+ pjsip_FALSE_STR = { "false", 5},
+ pjsip_QUOTED_FALSE_STR = { "\"false\"", 7},
+ pjsip_DIGEST_STR = { "Digest", 6},
+ pjsip_QUOTED_DIGEST_STR = { "\"Digest\"", 8},
+ pjsip_PGP_STR = { "PGP", 3 },
+ pjsip_QUOTED_PGP_STR = { "\"PGP\"", 5 },
+ pjsip_MD5_STR = { "md5", 3 },
+ pjsip_QUOTED_MD5_STR = { "\"md5\"", 5},
+ pjsip_AUTH_STR = { "auth", 4},
+ pjsip_QUOTED_AUTH_STR = { "\"auth\"", 6 };
+
+
+static void parse_digest_credential( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_digest_credential *cred)
+{
+ pj_list_init(&cred->other_param);
+
+ for (;;) {
+ pj_str_t name, value;
+
+ pjsip_parse_param_imp(scanner, pool, &name, &value,
+ PJSIP_PARSE_REMOVE_QUOTE);
+
+ if (!pj_stricmp(&name, &pjsip_USERNAME_STR)) {
+ cred->username = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_REALM_STR)) {
+ cred->realm = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_NONCE_STR)) {
+ cred->nonce = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_URI_STR)) {
+ cred->uri = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_RESPONSE_STR)) {
+ cred->response = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_ALGORITHM_STR)) {
+ cred->algorithm = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_CNONCE_STR)) {
+ cred->cnonce = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_OPAQUE_STR)) {
+ cred->opaque = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_QOP_STR)) {
+ cred->qop = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_NC_STR)) {
+ cred->nc = value;
+
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = name;
+ p->value = value;
+ pj_list_insert_before(&cred->other_param, p);
+ }
+
+ /* Eat comma */
+ if (!pj_scan_is_eof(scanner) && *scanner->curptr == ',')
+ pj_scan_get_char(scanner);
+ else
+ break;
+ }
+}
+
+static void parse_pgp_credential( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_pgp_credential *cred)
+{
+ PJ_UNUSED_ARG(scanner);
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(cred);
+
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+}
+
+static void parse_digest_challenge( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_digest_challenge *chal)
+{
+ pj_list_init(&chal->other_param);
+
+ for (;;) {
+ pj_str_t name, value;
+
+ pjsip_parse_param_imp(scanner, pool, &name, &value,
+ PJSIP_PARSE_REMOVE_QUOTE);
+
+ if (!pj_stricmp(&name, &pjsip_REALM_STR)) {
+ chal->realm = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_DOMAIN_STR)) {
+ chal->domain = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_NONCE_STR)) {
+ chal->nonce = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_OPAQUE_STR)) {
+ chal->opaque = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_STALE_STR)) {
+ if (!pj_stricmp(&value, &pjsip_TRUE_STR) ||
+ !pj_stricmp(&value, &pjsip_QUOTED_TRUE_STR))
+ {
+ chal->stale = 1;
+ }
+
+ } else if (!pj_stricmp(&name, &pjsip_ALGORITHM_STR)) {
+ chal->algorithm = value;
+
+
+ } else if (!pj_stricmp(&name, &pjsip_QOP_STR)) {
+ chal->qop = value;
+
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = name;
+ p->value = value;
+ pj_list_insert_before(&chal->other_param, p);
+ }
+
+ /* Eat comma */
+ if (!pj_scan_is_eof(scanner) && *scanner->curptr == ',')
+ pj_scan_get_char(scanner);
+ else
+ break;
+ }
+}
+
+static void parse_pgp_challenge( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_pgp_challenge *chal)
+{
+ PJ_UNUSED_ARG(scanner);
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(chal);
+
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+}
+
+static void int_parse_hdr_authorization( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_authorization_hdr *hdr)
+{
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ if (*scanner->curptr == '"') {
+ pj_scan_get_quote(scanner, '"', '"', &hdr->scheme);
+ hdr->scheme.ptr++;
+ hdr->scheme.slen -= 2;
+ } else {
+ pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &hdr->scheme);
+ }
+
+ if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
+
+ parse_digest_credential(scanner, pool, &hdr->credential.digest);
+
+ } else if (!pj_stricmp(&hdr->scheme, &pjsip_PGP_STR)) {
+
+ parse_pgp_credential( scanner, pool, &hdr->credential.pgp);
+
+ } else {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ pjsip_parse_end_hdr_imp( scanner );
+}
+
+static void int_parse_hdr_authenticate( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_www_authenticate_hdr *hdr)
+{
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ if (*scanner->curptr == '"') {
+ pj_scan_get_quote(scanner, '"', '"', &hdr->scheme);
+ hdr->scheme.ptr++;
+ hdr->scheme.slen -= 2;
+ } else {
+ pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &hdr->scheme);
+ }
+
+ if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
+
+ parse_digest_challenge(scanner, pool, &hdr->challenge.digest);
+
+ } else if (!pj_stricmp(&hdr->scheme, &pjsip_PGP_STR)) {
+
+ parse_pgp_challenge(scanner, pool, &hdr->challenge.pgp);
+
+ } else {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ pjsip_parse_end_hdr_imp( scanner );
+}
+
+
+static pjsip_hdr* parse_hdr_authorization( pjsip_parse_ctx *ctx )
+{
+ pjsip_authorization_hdr *hdr = pjsip_authorization_hdr_create(ctx->pool);
+ int_parse_hdr_authorization(ctx->scanner, ctx->pool, hdr);
+ return (pjsip_hdr*)hdr;
+}
+
+static pjsip_hdr* parse_hdr_proxy_authorization( pjsip_parse_ctx *ctx )
+{
+ pjsip_proxy_authorization_hdr *hdr =
+ pjsip_proxy_authorization_hdr_create(ctx->pool);
+ int_parse_hdr_authorization(ctx->scanner, ctx->pool, hdr);
+ return (pjsip_hdr*)hdr;
+}
+
+static pjsip_hdr* parse_hdr_www_authenticate( pjsip_parse_ctx *ctx )
+{
+ pjsip_www_authenticate_hdr *hdr =
+ pjsip_www_authenticate_hdr_create(ctx->pool);
+ int_parse_hdr_authenticate(ctx->scanner, ctx->pool, hdr);
+ return (pjsip_hdr*)hdr;
+}
+
+static pjsip_hdr* parse_hdr_proxy_authenticate( pjsip_parse_ctx *ctx )
+{
+ pjsip_proxy_authenticate_hdr *hdr =
+ pjsip_proxy_authenticate_hdr_create(ctx->pool);
+ int_parse_hdr_authenticate(ctx->scanner, ctx->pool, hdr);
+ return (pjsip_hdr*)hdr;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_auth_init_parser()
+{
+ pj_status_t status;
+
+ status = pjsip_register_hdr_parser( "Authorization", NULL,
+ &parse_hdr_authorization);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ status = pjsip_register_hdr_parser( "Proxy-Authorization", NULL,
+ &parse_hdr_proxy_authorization);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ status = pjsip_register_hdr_parser( "WWW-Authenticate", NULL,
+ &parse_hdr_www_authenticate);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ status = pjsip_register_hdr_parser( "Proxy-Authenticate", NULL,
+ &parse_hdr_proxy_authenticate);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(void) pjsip_auth_deinit_parser()
+{
+}
+
diff --git a/pjsip/src/pjsip/sip_auth_parser_wrap.cpp b/pjsip/src/pjsip/sip_auth_parser_wrap.cpp
new file mode 100644
index 0000000..8d4f8a3
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_parser_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_auth_parser_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_auth_parser.c"
diff --git a/pjsip/src/pjsip/sip_auth_server.c b/pjsip/src/pjsip/sip_auth_server.c
new file mode 100644
index 0000000..248e6cc
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_server.c
@@ -0,0 +1,225 @@
+/* $Id: sip_auth_server.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <pjsip/sip_auth.h>
+#include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */
+#include <pjsip/sip_auth_msg.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_transport.h>
+#include <pj/string.h>
+#include <pj/assert.h>
+
+
+/*
+ * Initialize server authorization session data structure to serve the
+ * specified realm and to use lookup_func function to look for the credential
+ * info.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_srv_init( pj_pool_t *pool,
+ pjsip_auth_srv *auth_srv,
+ const pj_str_t *realm,
+ pjsip_auth_lookup_cred *lookup,
+ unsigned options )
+{
+ PJ_ASSERT_RETURN(pool && auth_srv && realm && lookup, PJ_EINVAL);
+
+ pj_strdup( pool, &auth_srv->realm, realm);
+ auth_srv->lookup = lookup;
+ auth_srv->is_proxy = (options & PJSIP_AUTH_SRV_IS_PROXY);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Verify incoming Authorization/Proxy-Authorization header against the
+ * specified credential.
+ */
+static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr,
+ const pj_str_t *method,
+ const pjsip_cred_info *cred_info )
+{
+ if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) {
+ char digest_buf[PJSIP_MD5STRLEN];
+ pj_str_t digest;
+ const pjsip_digest_credential *dig = &hdr->credential.digest;
+
+ /* Check that username and realm match.
+ * These checks should have been performed before entering this
+ * function.
+ */
+ PJ_ASSERT_RETURN(pj_strcmp(&dig->username, &cred_info->username) == 0,
+ PJ_EINVALIDOP);
+ PJ_ASSERT_RETURN(pj_strcmp(&dig->realm, &cred_info->realm) == 0,
+ PJ_EINVALIDOP);
+
+ /* Prepare for our digest calculation. */
+ digest.ptr = digest_buf;
+ digest.slen = PJSIP_MD5STRLEN;
+
+ /* Create digest for comparison. */
+ pjsip_auth_create_digest(&digest,
+ &hdr->credential.digest.nonce,
+ &hdr->credential.digest.nc,
+ &hdr->credential.digest.cnonce,
+ &hdr->credential.digest.qop,
+ &hdr->credential.digest.uri,
+ &cred_info->realm,
+ cred_info,
+ method );
+
+ /* Compare digest. */
+ return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ?
+ PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST;
+
+ } else {
+ pj_assert(!"Unsupported authentication scheme");
+ return PJSIP_EINVALIDAUTHSCHEME;
+ }
+}
+
+
+/*
+ * Request the authorization server framework to verify the authorization
+ * information in the specified request in rdata.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv,
+ pjsip_rx_data *rdata,
+ int *status_code)
+{
+ pjsip_authorization_hdr *h_auth;
+ pjsip_msg *msg = rdata->msg_info.msg;
+ pjsip_hdr_e htype;
+ pj_str_t acc_name;
+ pjsip_cred_info cred_info;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(auth_srv && rdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG);
+
+ htype = auth_srv->is_proxy ? PJSIP_H_PROXY_AUTHORIZATION :
+ PJSIP_H_AUTHORIZATION;
+
+ /* Initialize status with 200. */
+ *status_code = 200;
+
+ /* Find authorization header for our realm. */
+ h_auth = (pjsip_authorization_hdr*) pjsip_msg_find_hdr(msg, htype, NULL);
+ while (h_auth) {
+ if (!pj_stricmp(&h_auth->credential.common.realm, &auth_srv->realm))
+ break;
+
+ h_auth = h_auth->next;
+ if (h_auth == (void*) &msg->hdr) {
+ h_auth = NULL;
+ break;
+ }
+
+ h_auth=(pjsip_authorization_hdr*)pjsip_msg_find_hdr(msg,htype,h_auth);
+ }
+
+ if (!h_auth) {
+ *status_code = auth_srv->is_proxy ? 407 : 401;
+ return PJSIP_EAUTHNOAUTH;
+ }
+
+ /* Check authorization scheme. */
+ if (pj_stricmp(&h_auth->scheme, &pjsip_DIGEST_STR) == 0)
+ acc_name = h_auth->credential.digest.username;
+ else {
+ *status_code = auth_srv->is_proxy ? 407 : 401;
+ return PJSIP_EINVALIDAUTHSCHEME;
+ }
+
+ /* Find the credential information for the account. */
+ status = (*auth_srv->lookup)(rdata->tp_info.pool, &auth_srv->realm,
+ &acc_name, &cred_info);
+ if (status != PJ_SUCCESS) {
+ *status_code = PJSIP_SC_FORBIDDEN;
+ return status;
+ }
+
+ /* Authenticate with the specified credential. */
+ status = pjsip_auth_verify(h_auth, &msg->line.req.method.name,
+ &cred_info);
+ if (status != PJ_SUCCESS) {
+ *status_code = PJSIP_SC_FORBIDDEN;
+ }
+ return status;
+}
+
+
+/*
+ * Add authentication challenge headers to the outgoing response in tdata.
+ * Application may specify its customized nonce and opaque for the challenge,
+ * or can leave the value to NULL to make the function fills them in with
+ * random characters.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv,
+ const pj_str_t *qop,
+ const pj_str_t *nonce,
+ const pj_str_t *opaque,
+ pj_bool_t stale,
+ pjsip_tx_data *tdata)
+{
+ pjsip_www_authenticate_hdr *hdr;
+ char nonce_buf[16];
+ pj_str_t random;
+
+ PJ_ASSERT_RETURN( auth_srv && tdata, PJ_EINVAL );
+
+ random.ptr = nonce_buf;
+ random.slen = sizeof(nonce_buf);
+
+ /* Create the header. */
+ if (auth_srv->is_proxy)
+ hdr = pjsip_proxy_authenticate_hdr_create(tdata->pool);
+ else
+ hdr = pjsip_www_authenticate_hdr_create(tdata->pool);
+
+ /* Initialize header.
+ * Note: only support digest authentication now.
+ */
+ hdr->scheme = pjsip_DIGEST_STR;
+ hdr->challenge.digest.algorithm = pjsip_MD5_STR;
+ if (nonce) {
+ pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, nonce);
+ } else {
+ pj_create_random_string(nonce_buf, sizeof(nonce_buf));
+ pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, &random);
+ }
+ if (opaque) {
+ pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, opaque);
+ } else {
+ pj_create_random_string(nonce_buf, sizeof(nonce_buf));
+ pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random);
+ }
+ if (qop) {
+ pj_strdup(tdata->pool, &hdr->challenge.digest.qop, qop);
+ } else {
+ hdr->challenge.digest.qop.slen = 0;
+ }
+ pj_strdup(tdata->pool, &hdr->challenge.digest.realm, &auth_srv->realm);
+ hdr->challenge.digest.stale = stale;
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsip/sip_config.c b/pjsip/src/pjsip/sip_config.c
new file mode 100644
index 0000000..8742c8a
--- /dev/null
+++ b/pjsip/src/pjsip/sip_config.c
@@ -0,0 +1,54 @@
+/* $Id: sip_config.c 3999 2012-03-30 07:10:13Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <pjsip/sip_config.h>
+
+/* pjsip configuration instance, initialized with default values */
+pjsip_cfg_t pjsip_sip_cfg_var =
+{
+ /* Global settings */
+ {
+ PJSIP_ALLOW_PORT_IN_FROMTO_HDR,
+ 0,
+ PJSIP_DONT_SWITCH_TO_TCP
+ },
+
+ /* Transaction settings */
+ {
+ PJSIP_MAX_TSX_COUNT,
+ PJSIP_T1_TIMEOUT,
+ PJSIP_T2_TIMEOUT,
+ PJSIP_T4_TIMEOUT,
+ PJSIP_TD_TIMEOUT
+ },
+
+ /* Client registration client */
+ {
+ PJSIP_REGISTER_CLIENT_CHECK_CONTACT
+ }
+};
+
+
+#ifdef PJ_DLL
+PJ_DEF(pjsip_cfg_t*) pjsip_cfg(void)
+{
+ return &pjsip_sip_cfg_var;
+}
+#endif
diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c
new file mode 100644
index 0000000..008cad2
--- /dev/null
+++ b/pjsip/src/pjsip/sip_dialog.c
@@ -0,0 +1,2250 @@
+/* $Id: sip_dialog.c 4173 2012-06-20 10:39:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_dialog.h>
+#include <pjsip/sip_ua_layer.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_transaction.h>
+#include <pj/assert.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/guid.h>
+#include <pj/rand.h>
+#include <pj/array.h>
+#include <pj/except.h>
+#include <pj/hash.h>
+#include <pj/log.h>
+
+#define THIS_FILE "sip_dialog.c"
+
+long pjsip_dlg_lock_tls_id;
+
+/* Config */
+pj_bool_t pjsip_include_allow_hdr_in_dlg = PJSIP_INCLUDE_ALLOW_HDR_IN_DLG;
+
+/* Contact header string */
+static const pj_str_t HCONTACT = { "Contact", 7 };
+
+
+PJ_DEF(pj_bool_t) pjsip_method_creates_dialog(const pjsip_method *m)
+{
+ const pjsip_method subscribe = { PJSIP_OTHER_METHOD, {"SUBSCRIBE", 9}};
+ const pjsip_method refer = { PJSIP_OTHER_METHOD, {"REFER", 5}};
+ const pjsip_method notify = { PJSIP_OTHER_METHOD, {"NOTIFY", 6}};
+ const pjsip_method update = { PJSIP_OTHER_METHOD, {"UPDATE", 6}};
+
+ return m->id == PJSIP_INVITE_METHOD ||
+ (pjsip_method_cmp(m, &subscribe)==0) ||
+ (pjsip_method_cmp(m, &refer)==0) ||
+ (pjsip_method_cmp(m, &notify)==0) ||
+ (pjsip_method_cmp(m, &update)==0);
+}
+
+static pj_status_t create_dialog( pjsip_user_agent *ua,
+ pjsip_dialog **p_dlg)
+{
+ pjsip_endpoint *endpt;
+ pj_pool_t *pool;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ endpt = pjsip_ua_get_endpt(ua);
+ if (!endpt)
+ return PJ_EINVALIDOP;
+
+ pool = pjsip_endpt_create_pool(endpt, "dlg%p",
+ PJSIP_POOL_LEN_DIALOG,
+ PJSIP_POOL_INC_DIALOG);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ dlg = PJ_POOL_ZALLOC_T(pool, pjsip_dialog);
+ PJ_ASSERT_RETURN(dlg != NULL, PJ_ENOMEM);
+
+ dlg->pool = pool;
+ pj_ansi_snprintf(dlg->obj_name, sizeof(dlg->obj_name), "dlg%p", dlg);
+ dlg->ua = ua;
+ dlg->endpt = endpt;
+ dlg->state = PJSIP_DIALOG_STATE_NULL;
+ dlg->add_allow = pjsip_include_allow_hdr_in_dlg;
+
+ pj_list_init(&dlg->inv_hdr);
+ pj_list_init(&dlg->rem_cap_hdr);
+
+ status = pj_mutex_create_recursive(pool, dlg->obj_name, &dlg->mutex_);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pjsip_target_set_init(&dlg->target_set);
+
+ *p_dlg = dlg;
+ return PJ_SUCCESS;
+
+on_error:
+ if (dlg->mutex_)
+ pj_mutex_destroy(dlg->mutex_);
+ pjsip_endpt_release_pool(endpt, pool);
+ return status;
+}
+
+static void destroy_dialog( pjsip_dialog *dlg )
+{
+ if (dlg->mutex_) {
+ pj_mutex_destroy(dlg->mutex_);
+ dlg->mutex_ = NULL;
+ }
+ if (dlg->tp_sel.type != PJSIP_TPSELECTOR_NONE) {
+ pjsip_tpselector_dec_ref(&dlg->tp_sel);
+ pj_bzero(&dlg->tp_sel, sizeof(pjsip_tpselector));
+ }
+ pjsip_endpt_release_pool(dlg->endpt, dlg->pool);
+}
+
+
+/*
+ * Create an UAC dialog.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua,
+ const pj_str_t *local_uri,
+ const pj_str_t *local_contact,
+ const pj_str_t *remote_uri,
+ const pj_str_t *target,
+ pjsip_dialog **p_dlg)
+{
+ pj_status_t status;
+ pj_str_t tmp;
+ pjsip_dialog *dlg;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(ua && local_uri && remote_uri && p_dlg, PJ_EINVAL);
+
+ /* Create dialog instance. */
+ status = create_dialog(ua, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Parse target. */
+ pj_strdup_with_null(dlg->pool, &tmp, target ? target : remote_uri);
+ dlg->target = pjsip_parse_uri(dlg->pool, tmp.ptr, tmp.slen, 0);
+ if (!dlg->target) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ /* Put any header param in the target URI into INVITE header list. */
+ if (PJSIP_URI_SCHEME_IS_SIP(dlg->target) ||
+ PJSIP_URI_SCHEME_IS_SIPS(dlg->target))
+ {
+ pjsip_param *param;
+ pjsip_sip_uri *uri = (pjsip_sip_uri*)pjsip_uri_get_uri(dlg->target);
+
+ param = uri->header_param.next;
+ while (param != &uri->header_param) {
+ pjsip_hdr *hdr;
+ int c;
+
+ c = param->value.ptr[param->value.slen];
+ param->value.ptr[param->value.slen] = '\0';
+
+ hdr = (pjsip_hdr*)
+ pjsip_parse_hdr(dlg->pool, &param->name, param->value.ptr,
+ param->value.slen, NULL);
+
+ param->value.ptr[param->value.slen] = (char)c;
+
+ if (hdr == NULL) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+ pj_list_push_back(&dlg->inv_hdr, hdr);
+
+ param = param->next;
+ }
+
+ /* Now must remove any header params from URL, since that would
+ * create another header in pjsip_endpt_create_request().
+ */
+ pj_list_init(&uri->header_param);
+ }
+
+ /* Add target to the target set */
+ pjsip_target_set_add_uri(&dlg->target_set, dlg->pool, dlg->target, 0);
+
+ /* Init local info. */
+ dlg->local.info = pjsip_from_hdr_create(dlg->pool);
+ pj_strdup_with_null(dlg->pool, &dlg->local.info_str, local_uri);
+ dlg->local.info->uri = pjsip_parse_uri(dlg->pool,
+ dlg->local.info_str.ptr,
+ dlg->local.info_str.slen, 0);
+ if (!dlg->local.info->uri) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ /* Generate local tag. */
+ pj_create_unique_string(dlg->pool, &dlg->local.info->tag);
+
+ /* Calculate hash value of local tag. */
+ dlg->local.tag_hval = pj_hash_calc(0, dlg->local.info->tag.ptr,
+ dlg->local.info->tag.slen);
+
+ /* Randomize local CSeq. */
+ dlg->local.first_cseq = pj_rand() & 0x7FFF;
+ dlg->local.cseq = dlg->local.first_cseq;
+
+ /* Init local contact. */
+ pj_strdup_with_null(dlg->pool, &tmp,
+ local_contact ? local_contact : local_uri);
+ dlg->local.contact = (pjsip_contact_hdr*)
+ pjsip_parse_hdr(dlg->pool, &HCONTACT, tmp.ptr,
+ tmp.slen, NULL);
+ if (!dlg->local.contact) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ /* Init remote info. */
+ dlg->remote.info = pjsip_to_hdr_create(dlg->pool);
+ pj_strdup_with_null(dlg->pool, &dlg->remote.info_str, remote_uri);
+ dlg->remote.info->uri = pjsip_parse_uri(dlg->pool,
+ dlg->remote.info_str.ptr,
+ dlg->remote.info_str.slen, 0);
+ if (!dlg->remote.info->uri) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ /* Remove header param from remote.info_str, if any */
+ if (PJSIP_URI_SCHEME_IS_SIP(dlg->remote.info->uri) ||
+ PJSIP_URI_SCHEME_IS_SIPS(dlg->remote.info->uri))
+ {
+ pjsip_sip_uri *sip_uri = (pjsip_sip_uri *)
+ pjsip_uri_get_uri(dlg->remote.info->uri);
+ if (!pj_list_empty(&sip_uri->header_param)) {
+ pj_str_t tmp;
+
+ /* Remove all header param */
+ pj_list_init(&sip_uri->header_param);
+
+ /* Print URI */
+ tmp.ptr = (char*) pj_pool_alloc(dlg->pool,
+ dlg->remote.info_str.slen);
+ tmp.slen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
+ sip_uri, tmp.ptr,
+ dlg->remote.info_str.slen);
+
+ if (tmp.slen < 1) {
+ status = PJSIP_EURITOOLONG;
+ goto on_error;
+ }
+
+ /* Assign remote.info_str */
+ dlg->remote.info_str = tmp;
+ }
+ }
+
+
+ /* Initialize remote's CSeq to -1. */
+ dlg->remote.cseq = dlg->remote.first_cseq = -1;
+
+ /* Initial role is UAC. */
+ dlg->role = PJSIP_ROLE_UAC;
+
+ /* Secure? */
+ dlg->secure = PJSIP_URI_SCHEME_IS_SIPS(dlg->target);
+
+ /* Generate Call-ID header. */
+ dlg->call_id = pjsip_cid_hdr_create(dlg->pool);
+ pj_create_unique_string(dlg->pool, &dlg->call_id->id);
+
+ /* Initial route set is empty. */
+ pj_list_init(&dlg->route_set);
+
+ /* Init client authentication session. */
+ status = pjsip_auth_clt_init(&dlg->auth_sess, dlg->endpt,
+ dlg->pool, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Register this dialog to user agent. */
+ status = pjsip_ua_register_dlg( ua, dlg );
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Done! */
+ *p_dlg = dlg;
+
+
+ PJ_LOG(5,(dlg->obj_name, "UAC dialog created"));
+
+ return PJ_SUCCESS;
+
+on_error:
+ destroy_dialog(dlg);
+ return status;
+}
+
+
+/*
+ * Create UAS dialog.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua,
+ pjsip_rx_data *rdata,
+ const pj_str_t *contact,
+ pjsip_dialog **p_dlg)
+{
+ pj_status_t status;
+ pjsip_hdr *pos = NULL;
+ pjsip_contact_hdr *contact_hdr;
+ pjsip_rr_hdr *rr;
+ pjsip_transaction *tsx = NULL;
+ pj_str_t tmp;
+ enum { TMP_LEN=128};
+ pj_ssize_t len;
+ pjsip_dialog *dlg;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(ua && rdata && p_dlg, PJ_EINVAL);
+
+ /* rdata must have request message. */
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Request must not have To tag.
+ * This should have been checked in the user agent (or application?).
+ */
+ PJ_ASSERT_RETURN(rdata->msg_info.to->tag.slen == 0, PJ_EINVALIDOP);
+
+ /* The request must be a dialog establishing request. */
+ PJ_ASSERT_RETURN(
+ pjsip_method_creates_dialog(&rdata->msg_info.msg->line.req.method),
+ PJ_EINVALIDOP);
+
+ /* Create dialog instance. */
+ status = create_dialog(ua, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Temprary string for getting the string representation of
+ * both local and remote URI.
+ */
+ tmp.ptr = (char*) pj_pool_alloc(rdata->tp_info.pool, TMP_LEN);
+
+ /* Init local info from the To header. */
+ dlg->local.info = (pjsip_fromto_hdr*)
+ pjsip_hdr_clone(dlg->pool, rdata->msg_info.to);
+ pjsip_fromto_hdr_set_from(dlg->local.info);
+
+ /* Generate local tag. */
+ pj_create_unique_string(dlg->pool, &dlg->local.info->tag);
+
+
+ /* Print the local info. */
+ len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
+ dlg->local.info->uri, tmp.ptr, TMP_LEN);
+ if (len < 1) {
+ pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->");
+ tmp.slen = pj_ansi_strlen(tmp.ptr);
+ } else
+ tmp.slen = len;
+
+ /* Save the local info. */
+ pj_strdup(dlg->pool, &dlg->local.info_str, &tmp);
+
+ /* Calculate hash value of local tag. */
+ dlg->local.tag_hval = pj_hash_calc(0, dlg->local.info->tag.ptr,
+ dlg->local.info->tag.slen);
+
+
+ /* Randomize local cseq */
+ dlg->local.first_cseq = pj_rand() & 0x7FFF;
+ dlg->local.cseq = dlg->local.first_cseq;
+
+ /* Init local contact. */
+ /* TODO:
+ * Section 12.1.1, paragraph about using SIPS URI in Contact.
+ * If the request that initiated the dialog contained a SIPS URI
+ * in the Request-URI or in the top Record-Route header field value,
+ * if there was any, or the Contact header field if there was no
+ * Record-Route header field, the Contact header field in the response
+ * MUST be a SIPS URI.
+ */
+ if (contact) {
+ pj_str_t tmp;
+
+ pj_strdup_with_null(dlg->pool, &tmp, contact);
+ dlg->local.contact = (pjsip_contact_hdr*)
+ pjsip_parse_hdr(dlg->pool, &HCONTACT, tmp.ptr,
+ tmp.slen, NULL);
+ if (!dlg->local.contact) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ } else {
+ dlg->local.contact = pjsip_contact_hdr_create(dlg->pool);
+ dlg->local.contact->uri = dlg->local.info->uri;
+ }
+
+ /* Init remote info from the From header. */
+ dlg->remote.info = (pjsip_fromto_hdr*)
+ pjsip_hdr_clone(dlg->pool, rdata->msg_info.from);
+ pjsip_fromto_hdr_set_to(dlg->remote.info);
+
+ /* Print the remote info. */
+ len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
+ dlg->remote.info->uri, tmp.ptr, TMP_LEN);
+ if (len < 1) {
+ pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->");
+ tmp.slen = pj_ansi_strlen(tmp.ptr);
+ } else
+ tmp.slen = len;
+
+ /* Save the remote info. */
+ pj_strdup(dlg->pool, &dlg->remote.info_str, &tmp);
+
+
+ /* Init remote's contact from Contact header.
+ * Iterate the Contact URI until we find sip: or sips: scheme.
+ */
+ do {
+ contact_hdr = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
+ pos);
+ if (contact_hdr) {
+ if (!contact_hdr->uri ||
+ (!PJSIP_URI_SCHEME_IS_SIP(contact_hdr->uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(contact_hdr->uri)))
+ {
+ pos = (pjsip_hdr*)contact_hdr->next;
+ if (pos == &rdata->msg_info.msg->hdr)
+ contact_hdr = NULL;
+ } else {
+ break;
+ }
+ }
+ } while (contact_hdr);
+
+ if (!contact_hdr) {
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
+ goto on_error;
+ }
+
+ dlg->remote.contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(dlg->pool, (pjsip_hdr*)contact_hdr);
+
+ /* Init remote's CSeq from CSeq header */
+ dlg->remote.cseq = dlg->remote.first_cseq = rdata->msg_info.cseq->cseq;
+
+ /* Set initial target to remote's Contact. */
+ dlg->target = dlg->remote.contact->uri;
+
+ /* Initial role is UAS */
+ dlg->role = PJSIP_ROLE_UAS;
+
+ /* Secure?
+ * RFC 3261 Section 12.1.1:
+ * If the request arrived over TLS, and the Request-URI contained a
+ * SIPS URI, the 'secure' flag is set to TRUE.
+ */
+ dlg->secure = PJSIP_TRANSPORT_IS_SECURE(rdata->tp_info.transport) &&
+ PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.msg->line.req.uri);
+
+ /* Call-ID */
+ dlg->call_id = (pjsip_cid_hdr*)
+ pjsip_hdr_clone(dlg->pool, rdata->msg_info.cid);
+
+ /* Route set.
+ * RFC 3261 Section 12.1.1:
+ * The route set MUST be set to the list of URIs in the Record-Route
+ * header field from the request, taken in order and preserving all URI
+ * parameters. If no Record-Route header field is present in the request,
+ * the route set MUST be set to the empty set.
+ */
+ pj_list_init(&dlg->route_set);
+ rr = rdata->msg_info.record_route;
+ while (rr != NULL) {
+ pjsip_route_hdr *route;
+
+ /* Clone the Record-Route, change the type to Route header. */
+ route = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, rr);
+ pjsip_routing_hdr_set_route(route);
+
+ /* Add to route set. */
+ pj_list_push_back(&dlg->route_set, route);
+
+ /* Find next Record-Route header. */
+ rr = rr->next;
+ if (rr == (void*)&rdata->msg_info.msg->hdr)
+ break;
+ rr = (pjsip_route_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg,
+ PJSIP_H_RECORD_ROUTE, rr);
+ }
+ dlg->route_set_frozen = PJ_TRUE;
+
+ /* Init client authentication session. */
+ status = pjsip_auth_clt_init(&dlg->auth_sess, dlg->endpt,
+ dlg->pool, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Create UAS transaction for this request. */
+ status = pjsip_tsx_create_uas(dlg->ua, rdata, &tsx);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Associate this dialog to the transaction. */
+ tsx->mod_data[dlg->ua->id] = dlg;
+
+ /* Increment tsx counter */
+ ++dlg->tsx_count;
+
+ /* Calculate hash value of remote tag. */
+ dlg->remote.tag_hval = pj_hash_calc(0, dlg->remote.info->tag.ptr,
+ dlg->remote.info->tag.slen);
+
+ /* Update remote capabilities info */
+ pjsip_dlg_update_remote_cap(dlg, rdata->msg_info.msg, PJ_TRUE);
+
+ /* Register this dialog to user agent. */
+ status = pjsip_ua_register_dlg( ua, dlg );
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Put this dialog in rdata's mod_data */
+ rdata->endpt_info.mod_data[ua->id] = dlg;
+
+ PJ_TODO(DIALOG_APP_TIMER);
+
+ /* Feed the first request to the transaction. */
+ pjsip_tsx_recv_msg(tsx, rdata);
+
+ /* Done. */
+ *p_dlg = dlg;
+ PJ_LOG(5,(dlg->obj_name, "UAS dialog created"));
+ return PJ_SUCCESS;
+
+on_error:
+ if (tsx) {
+ pjsip_tsx_terminate(tsx, 500);
+ pj_assert(dlg->tsx_count>0);
+ --dlg->tsx_count;
+ }
+
+ destroy_dialog(dlg);
+ return status;
+}
+
+
+/*
+ * Bind dialog to a specific transport/listener.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_transport( pjsip_dialog *dlg,
+ const pjsip_tpselector *sel)
+{
+ /* Validate */
+ PJ_ASSERT_RETURN(dlg && sel, PJ_EINVAL);
+
+ /* Start locking the dialog. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Decrement reference counter of previous transport selector */
+ pjsip_tpselector_dec_ref(&dlg->tp_sel);
+
+ /* Copy transport selector structure .*/
+ pj_memcpy(&dlg->tp_sel, sel, sizeof(*sel));
+
+ /* Increment reference counter */
+ pjsip_tpselector_add_ref(&dlg->tp_sel);
+
+ /* Unlock dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Set "sent-by" field of Via header.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_via_sent_by( pjsip_dialog *dlg,
+ pjsip_host_port *via_addr,
+ pjsip_transport *via_tp)
+{
+ PJ_ASSERT_RETURN(dlg, PJ_EINVAL);
+
+ if (!via_addr)
+ pj_bzero(&dlg->via_addr, sizeof(dlg->via_addr));
+ else
+ dlg->via_addr = *via_addr;
+ dlg->via_tp = via_tp;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create forked dialog from a response.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_fork( const pjsip_dialog *first_dlg,
+ const pjsip_rx_data *rdata,
+ pjsip_dialog **new_dlg )
+{
+ pjsip_dialog *dlg;
+ const pjsip_msg *msg = rdata->msg_info.msg;
+ const pjsip_hdr *end_hdr, *hdr;
+ const pjsip_contact_hdr *contact;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(first_dlg && rdata && new_dlg, PJ_EINVAL);
+
+ /* rdata must be response message. */
+ PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG,
+ PJSIP_ENOTRESPONSEMSG);
+
+ /* Status code MUST be 1xx (but not 100), or 2xx */
+ status = msg->line.status.code;
+ PJ_ASSERT_RETURN( (status/100==1 && status!=100) ||
+ (status/100==2), PJ_EBUG);
+
+ /* To tag must present in the response. */
+ PJ_ASSERT_RETURN(rdata->msg_info.to->tag.slen != 0, PJSIP_EMISSINGTAG);
+
+ /* Find Contact header in the response */
+ contact = (const pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL);
+ if (contact == NULL || contact->uri == NULL)
+ return PJSIP_EMISSINGHDR;
+
+ /* Create the dialog. */
+ status = create_dialog((pjsip_user_agent*)first_dlg->ua, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set remote target from the response. */
+ dlg->target = (pjsip_uri*) pjsip_uri_clone(dlg->pool, contact->uri);
+
+ /* Clone local info. */
+ dlg->local.info = (pjsip_fromto_hdr*)
+ pjsip_hdr_clone(dlg->pool, first_dlg->local.info);
+
+ /* Clone local tag. */
+ pj_strdup(dlg->pool, &dlg->local.info->tag, &first_dlg->local.info->tag);
+ dlg->local.tag_hval = first_dlg->local.tag_hval;
+
+ /* Clone local CSeq. */
+ dlg->local.first_cseq = first_dlg->local.first_cseq;
+ dlg->local.cseq = first_dlg->local.cseq;
+
+ /* Clone local Contact. */
+ dlg->local.contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(dlg->pool, first_dlg->local.contact);
+
+ /* Clone remote info. */
+ dlg->remote.info = (pjsip_fromto_hdr*)
+ pjsip_hdr_clone(dlg->pool, first_dlg->remote.info);
+
+ /* Set remote tag from the response. */
+ pj_strdup(dlg->pool, &dlg->remote.info->tag, &rdata->msg_info.to->tag);
+
+ /* Initialize remote's CSeq to -1. */
+ dlg->remote.cseq = dlg->remote.first_cseq = -1;
+
+ /* Initial role is UAC. */
+ dlg->role = PJSIP_ROLE_UAC;
+
+ /* Dialog state depends on the response. */
+ status = msg->line.status.code/100;
+ if (status == 1 || status == 2)
+ dlg->state = PJSIP_DIALOG_STATE_ESTABLISHED;
+ else {
+ pj_assert(!"Invalid status code");
+ dlg->state = PJSIP_DIALOG_STATE_NULL;
+ }
+
+ /* Secure? */
+ dlg->secure = PJSIP_URI_SCHEME_IS_SIPS(dlg->target);
+
+ /* Clone Call-ID header. */
+ dlg->call_id = (pjsip_cid_hdr*)
+ pjsip_hdr_clone(dlg->pool, first_dlg->call_id);
+
+ /* Get route-set from the response. */
+ pj_list_init(&dlg->route_set);
+ end_hdr = &msg->hdr;
+ for (hdr=msg->hdr.prev; hdr!=end_hdr; hdr=hdr->prev) {
+ if (hdr->type == PJSIP_H_RECORD_ROUTE) {
+ pjsip_route_hdr *r;
+ r = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, hdr);
+ pjsip_routing_hdr_set_route(r);
+ pj_list_push_back(&dlg->route_set, r);
+ }
+ }
+
+ //dlg->route_set_frozen = PJ_TRUE;
+
+ /* Clone client authentication session. */
+ status = pjsip_auth_clt_clone(dlg->pool, &dlg->auth_sess,
+ &first_dlg->auth_sess);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Register this dialog to user agent. */
+ status = pjsip_ua_register_dlg(dlg->ua, dlg );
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Done! */
+ *new_dlg = dlg;
+
+ PJ_LOG(5,(dlg->obj_name, "Forked dialog created"));
+ return PJ_SUCCESS;
+
+on_error:
+ destroy_dialog(dlg);
+ return status;
+}
+
+
+/*
+ * Destroy dialog.
+ */
+static pj_status_t unregister_and_destroy_dialog( pjsip_dialog *dlg )
+{
+ pj_status_t status;
+
+ /* Lock must have been held. */
+
+ /* Check dialog state. */
+ /* Number of sessions must be zero. */
+ PJ_ASSERT_RETURN(dlg->sess_count==0, PJ_EINVALIDOP);
+
+ /* MUST not have pending transactions. */
+ PJ_ASSERT_RETURN(dlg->tsx_count==0, PJ_EINVALIDOP);
+
+ /* Unregister from user agent. */
+ status = pjsip_ua_unregister_dlg(dlg->ua, dlg);
+ if (status != PJ_SUCCESS) {
+ pj_assert(!"Unexpected failed unregistration!");
+ return status;
+ }
+
+ /* Log */
+ PJ_LOG(5,(dlg->obj_name, "Dialog destroyed"));
+
+ /* Destroy this dialog. */
+ destroy_dialog(dlg);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Forcefully terminate dialog.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_terminate( pjsip_dialog *dlg )
+{
+ /* Number of sessions must be zero. */
+ PJ_ASSERT_RETURN(dlg->sess_count==0, PJ_EINVALIDOP);
+
+ /* MUST not have pending transactions. */
+ PJ_ASSERT_RETURN(dlg->tsx_count==0, PJ_EINVALIDOP);
+
+ return unregister_and_destroy_dialog(dlg);
+}
+
+
+/*
+ * Set route_set
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_route_set( pjsip_dialog *dlg,
+ const pjsip_route_hdr *route_set )
+{
+ pjsip_route_hdr *r;
+
+ PJ_ASSERT_RETURN(dlg, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Clear route set. */
+ pj_list_init(&dlg->route_set);
+
+ if (!route_set) {
+ pjsip_dlg_dec_lock(dlg);
+ return PJ_SUCCESS;
+ }
+
+ r = route_set->next;
+ while (r != route_set) {
+ pjsip_route_hdr *new_r;
+
+ new_r = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, r);
+ pj_list_push_back(&dlg->route_set, new_r);
+
+ r = r->next;
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Increment session counter.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_inc_session( pjsip_dialog *dlg,
+ pjsip_module *mod )
+{
+ PJ_ASSERT_RETURN(dlg && mod, PJ_EINVAL);
+
+ pj_log_push_indent();
+
+ pjsip_dlg_inc_lock(dlg);
+ ++dlg->sess_count;
+ pjsip_dlg_dec_lock(dlg);
+
+ PJ_LOG(5,(dlg->obj_name, "Session count inc to %d by %.*s",
+ dlg->sess_count, (int)mod->name.slen, mod->name.ptr));
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+/*
+ * Lock dialog and increment session counter temporarily
+ * to prevent it from being deleted. In addition, it must lock
+ * the user agent's dialog table first, to prevent deadlock.
+ */
+PJ_DEF(void) pjsip_dlg_inc_lock(pjsip_dialog *dlg)
+{
+ PJ_LOG(6,(dlg->obj_name, "Entering pjsip_dlg_inc_lock(), sess_count=%d",
+ dlg->sess_count));
+
+ pj_mutex_lock(dlg->mutex_);
+ dlg->sess_count++;
+
+ PJ_LOG(6,(dlg->obj_name, "Leaving pjsip_dlg_inc_lock(), sess_count=%d",
+ dlg->sess_count));
+}
+
+/* Try to acquire dialog's mutex, but bail out if mutex can not be
+ * acquired immediately.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_try_inc_lock(pjsip_dialog *dlg)
+{
+ pj_status_t status;
+
+ PJ_LOG(6,(dlg->obj_name,"Entering pjsip_dlg_try_inc_lock(), sess_count=%d",
+ dlg->sess_count));
+
+ status = pj_mutex_trylock(dlg->mutex_);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(6,(dlg->obj_name, "pjsip_dlg_try_inc_lock() failed"));
+ return status;
+ }
+
+ dlg->sess_count++;
+
+ PJ_LOG(6,(dlg->obj_name, "Leaving pjsip_dlg_try_inc_lock(), sess_count=%d",
+ dlg->sess_count));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Unlock dialog and decrement session counter.
+ * It may delete the dialog!
+ */
+PJ_DEF(void) pjsip_dlg_dec_lock(pjsip_dialog *dlg)
+{
+ PJ_ASSERT_ON_FAIL(dlg!=NULL, return);
+
+ PJ_LOG(6,(dlg->obj_name, "Entering pjsip_dlg_dec_lock(), sess_count=%d",
+ dlg->sess_count));
+
+ pj_assert(dlg->sess_count > 0);
+ --dlg->sess_count;
+
+ if (dlg->sess_count==0 && dlg->tsx_count==0) {
+ pj_mutex_unlock(dlg->mutex_);
+ pj_mutex_lock(dlg->mutex_);
+ unregister_and_destroy_dialog(dlg);
+ } else {
+ pj_mutex_unlock(dlg->mutex_);
+ }
+
+ PJ_LOG(6,(THIS_FILE, "Leaving pjsip_dlg_dec_lock() (dlg=%p)", dlg));
+}
+
+
+
+/*
+ * Decrement session counter.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_dec_session( pjsip_dialog *dlg,
+ pjsip_module *mod)
+{
+ PJ_ASSERT_RETURN(dlg, PJ_EINVAL);
+
+ pj_log_push_indent();
+
+ PJ_LOG(5,(dlg->obj_name, "Session count dec to %d by %.*s",
+ dlg->sess_count-1, (int)mod->name.slen, mod->name.ptr));
+
+ pjsip_dlg_inc_lock(dlg);
+ --dlg->sess_count;
+ pjsip_dlg_dec_lock(dlg);
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+/*
+ * Check if the module is registered as a usage
+ */
+PJ_DEF(pj_bool_t) pjsip_dlg_has_usage( pjsip_dialog *dlg,
+ pjsip_module *mod)
+{
+ unsigned index;
+ pj_bool_t found = PJ_FALSE;
+
+ pjsip_dlg_inc_lock(dlg);
+ for (index=0; index<dlg->usage_cnt; ++index) {
+ if (dlg->usage[index] == mod) {
+ found = PJ_TRUE;
+ break;
+ }
+ }
+ pjsip_dlg_dec_lock(dlg);
+
+ return found;
+}
+
+/*
+ * Add usage.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_add_usage( pjsip_dialog *dlg,
+ pjsip_module *mod,
+ void *mod_data )
+{
+ unsigned index;
+
+ PJ_ASSERT_RETURN(dlg && mod, PJ_EINVAL);
+ PJ_ASSERT_RETURN(mod->id >= 0 && mod->id < PJSIP_MAX_MODULE,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(dlg->usage_cnt < PJSIP_MAX_MODULE, PJ_EBUG);
+
+ PJ_LOG(5,(dlg->obj_name,
+ "Module %.*s added as dialog usage, data=%p",
+ (int)mod->name.slen, mod->name.ptr, mod_data));
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Usages are sorted on priority, lowest number first.
+ * Find position to put the new module, also makes sure that
+ * this module has not been registered before.
+ */
+ for (index=0; index<dlg->usage_cnt; ++index) {
+ if (dlg->usage[index] == mod) {
+ /* Module may be registered more than once in the same dialog.
+ * For example, when call transfer fails, application may retry
+ * call transfer on the same dialog.
+ * So return PJ_SUCCESS here.
+ */
+ PJ_LOG(4,(dlg->obj_name,
+ "Module %.*s already registered as dialog usage, "
+ "updating the data %p",
+ (int)mod->name.slen, mod->name.ptr, mod_data));
+ dlg->mod_data[mod->id] = mod_data;
+
+ pjsip_dlg_dec_lock(dlg);
+ return PJ_SUCCESS;
+
+ //pj_assert(!"This module is already registered");
+ //pjsip_dlg_dec_lock(dlg);
+ //return PJSIP_ETYPEEXISTS;
+ }
+
+ if (dlg->usage[index]->priority > mod->priority)
+ break;
+ }
+
+ /* index holds position to put the module.
+ * Insert module at this index.
+ */
+ pj_array_insert(dlg->usage, sizeof(dlg->usage[0]), dlg->usage_cnt,
+ index, &mod);
+
+ /* Set module data. */
+ dlg->mod_data[mod->id] = mod_data;
+
+ /* Increment count. */
+ ++dlg->usage_cnt;
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Attach module specific data to the dialog. Application can also set
+ * the value directly by accessing dlg->mod_data[module_id].
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_mod_data( pjsip_dialog *dlg,
+ int mod_id,
+ void *data )
+{
+ PJ_ASSERT_RETURN(dlg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(mod_id >= 0 && mod_id < PJSIP_MAX_MODULE,
+ PJ_EINVAL);
+ dlg->mod_data[mod_id] = data;
+ return PJ_SUCCESS;
+}
+
+/**
+ * Get module specific data previously attached to the dialog. Application
+ * can also get value directly by accessing dlg->mod_data[module_id].
+ */
+PJ_DEF(void*) pjsip_dlg_get_mod_data( pjsip_dialog *dlg,
+ int mod_id)
+{
+ PJ_ASSERT_RETURN(dlg, NULL);
+ PJ_ASSERT_RETURN(mod_id >= 0 && mod_id < PJSIP_MAX_MODULE,
+ NULL);
+ return dlg->mod_data[mod_id];
+}
+
+
+/*
+ * Create a new request within dialog (i.e. after the dialog session has been
+ * established). The construction of such requests follows the rule in
+ * RFC3261 section 12.2.1.
+ */
+static pj_status_t dlg_create_request_throw( pjsip_dialog *dlg,
+ const pjsip_method *method,
+ int cseq,
+ pjsip_tx_data **p_tdata )
+{
+ pjsip_tx_data *tdata;
+ pjsip_contact_hdr *contact;
+ pjsip_route_hdr *route, *end_list;
+ pj_status_t status;
+
+ /* Contact Header field.
+ * Contact can only be present in requests that establish dialog (in the
+ * core SIP spec, only INVITE).
+ */
+ if (pjsip_method_creates_dialog(method))
+ contact = dlg->local.contact;
+ else
+ contact = NULL;
+
+ /*
+ * Create the request by cloning from the headers in the
+ * dialog.
+ */
+ status = pjsip_endpt_create_request_from_hdr(dlg->endpt,
+ method,
+ dlg->target,
+ dlg->local.info,
+ dlg->remote.info,
+ contact,
+ dlg->call_id,
+ cseq,
+ NULL,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Just copy dialog route-set to Route header.
+ * The transaction will do the processing as specified in Section 12.2.1
+ * of RFC 3261 in function tsx_process_route() in sip_transaction.c.
+ */
+ route = dlg->route_set.next;
+ end_list = &dlg->route_set;
+ for (; route != end_list; route = route->next ) {
+ pjsip_route_hdr *r;
+ r = (pjsip_route_hdr*) pjsip_hdr_shallow_clone( tdata->pool, route );
+ pjsip_routing_hdr_set_route(r);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)r);
+ }
+
+ /* Copy authorization headers, if request is not ACK or CANCEL. */
+ if (method->id != PJSIP_ACK_METHOD && method->id != PJSIP_CANCEL_METHOD) {
+ status = pjsip_auth_clt_init_req( &dlg->auth_sess, tdata );
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Done. */
+ *p_tdata = tdata;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Create outgoing request.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_create_request( pjsip_dialog *dlg,
+ const pjsip_method *method,
+ int cseq,
+ pjsip_tx_data **p_tdata)
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata = NULL;
+ PJ_USE_EXCEPTION;
+
+ PJ_ASSERT_RETURN(dlg && method && p_tdata, PJ_EINVAL);
+
+ /* Lock dialog. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Use outgoing CSeq and increment it by one. */
+ if (cseq < 0)
+ cseq = dlg->local.cseq + 1;
+
+ /* Keep compiler happy */
+ status = PJ_EBUG;
+
+ /* Create the request. */
+ PJ_TRY {
+ status = dlg_create_request_throw(dlg, method, cseq, &tdata);
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ }
+ PJ_END;
+
+ /* Failed! Delete transmit data. */
+ if (status != PJ_SUCCESS && tdata) {
+ pjsip_tx_data_dec_ref( tdata );
+ tdata = NULL;
+ }
+
+ /* Unlock dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ *p_tdata = tdata;
+
+ return status;
+}
+
+
+/*
+ * Send request statefully, and update dialog'c CSeq.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_send_request( pjsip_dialog *dlg,
+ pjsip_tx_data *tdata,
+ int mod_data_id,
+ void *mod_data)
+{
+ pjsip_transaction *tsx;
+ pjsip_msg *msg = tdata->msg;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(dlg && tdata && tdata->msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ pj_log_push_indent();
+ PJ_LOG(5,(dlg->obj_name, "Sending %s",
+ pjsip_tx_data_get_info(tdata)));
+
+ /* Lock and increment session */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* If via_addr is set, use this address for the Via header. */
+ if (dlg->via_addr.host.slen > 0) {
+ tdata->via_addr = dlg->via_addr;
+ tdata->via_tp = dlg->via_tp;
+ }
+
+ /* Update dialog's CSeq and message's CSeq if request is not
+ * ACK nor CANCEL.
+ */
+ if (msg->line.req.method.id != PJSIP_CANCEL_METHOD &&
+ msg->line.req.method.id != PJSIP_ACK_METHOD)
+ {
+ pjsip_cseq_hdr *ch;
+
+ ch = PJSIP_MSG_CSEQ_HDR(msg);
+ PJ_ASSERT_RETURN(ch!=NULL, PJ_EBUG);
+
+ ch->cseq = dlg->local.cseq++;
+
+ /* Force the whole message to be re-printed. */
+ pjsip_tx_data_invalidate_msg( tdata );
+ }
+
+ /* Create a new transaction if method is not ACK.
+ * The transaction user is the user agent module.
+ */
+ if (msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ int tsx_count;
+
+ status = pjsip_tsx_create_uac(dlg->ua, tdata, &tsx);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Set transport selector */
+ status = pjsip_tsx_set_transport(tsx, &dlg->tp_sel);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Attach this dialog to the transaction, so that user agent
+ * will dispatch events to this dialog.
+ */
+ tsx->mod_data[dlg->ua->id] = dlg;
+
+ /* Copy optional caller's mod_data, if present */
+ if (mod_data_id >= 0 && mod_data_id < PJSIP_MAX_MODULE)
+ tsx->mod_data[mod_data_id] = mod_data;
+
+ /* Increment transaction counter. */
+ tsx_count = ++dlg->tsx_count;
+
+ /* Send the message. */
+ status = pjsip_tsx_send_msg(tsx, tdata);
+ if (status != PJ_SUCCESS) {
+ if (dlg->tsx_count == tsx_count)
+ pjsip_tsx_terminate(tsx, tsx->status_code);
+ goto on_error;
+ }
+
+ } else {
+ /* Set transport selector */
+ pjsip_tx_data_set_transport(tdata, &dlg->tp_sel);
+
+ /* Send request */
+ status = pjsip_endpt_send_request_stateless(dlg->endpt, tdata,
+ NULL, NULL);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ }
+
+ /* Unlock dialog, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ /* Unlock dialog, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Whatever happen delete the message. */
+ pjsip_tx_data_dec_ref( tdata );
+ pj_log_pop_indent();
+ return status;
+}
+
+/* Add standard headers for certain types of response */
+static void dlg_beautify_response(pjsip_dialog *dlg,
+ pj_bool_t add_headers,
+ int st_code,
+ pjsip_tx_data *tdata)
+{
+ pjsip_cseq_hdr *cseq;
+ int st_class;
+ const pjsip_hdr *c_hdr;
+ pjsip_hdr *hdr;
+
+ cseq = PJSIP_MSG_CSEQ_HDR(tdata->msg);
+ pj_assert(cseq != NULL);
+
+ st_class = st_code / 100;
+
+ /* Contact, Allow, Supported header. */
+ if (add_headers && pjsip_method_creates_dialog(&cseq->method)) {
+ /* Add Contact header for 1xx, 2xx, 3xx and 485 response. */
+ if (st_class==2 || st_class==3 || (st_class==1 && st_code != 100) ||
+ st_code==485)
+ {
+ /* Add contact header only if one is not present. */
+ if (pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL) == 0 &&
+ pjsip_msg_find_hdr_by_name(tdata->msg, &HCONTACT, NULL) == 0)
+ {
+ hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool,
+ dlg->local.contact);
+ pjsip_msg_add_hdr(tdata->msg, hdr);
+ }
+ }
+
+ /* Add Allow header in 18x, 2xx and 405 response. */
+ if ((((st_code/10==18 || st_class==2) && dlg->add_allow)
+ || st_code==405) &&
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ALLOW, NULL)==NULL)
+ {
+ c_hdr = pjsip_endpt_get_capability(dlg->endpt,
+ PJSIP_H_ALLOW, NULL);
+ if (c_hdr) {
+ hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, c_hdr);
+ pjsip_msg_add_hdr(tdata->msg, hdr);
+ }
+ }
+
+ /* Add Supported header in 2xx response. */
+ if (st_class==2 &&
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_SUPPORTED, NULL)==NULL)
+ {
+ c_hdr = pjsip_endpt_get_capability(dlg->endpt,
+ PJSIP_H_SUPPORTED, NULL);
+ if (c_hdr) {
+ hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, c_hdr);
+ pjsip_msg_add_hdr(tdata->msg, hdr);
+ }
+ }
+
+ }
+
+ /* Add To tag in all responses except 100 */
+ if (st_code != 100) {
+ pjsip_to_hdr *to;
+
+ to = PJSIP_MSG_TO_HDR(tdata->msg);
+ pj_assert(to != NULL);
+
+ to->tag = dlg->local.info->tag;
+
+ if (dlg->state == PJSIP_DIALOG_STATE_NULL)
+ dlg->state = PJSIP_DIALOG_STATE_ESTABLISHED;
+ }
+}
+
+
+/*
+ * Create response.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_create_response( pjsip_dialog *dlg,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ pjsip_tx_data **p_tdata)
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ /* Create generic response.
+ * This will initialize response's Via, To, From, Call-ID, CSeq
+ * and Record-Route headers from the request.
+ */
+ status = pjsip_endpt_create_response(dlg->endpt,
+ rdata, st_code, st_text, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Lock the dialog. */
+ pjsip_dlg_inc_lock(dlg);
+
+ dlg_beautify_response(dlg, PJ_FALSE, st_code, tdata);
+
+ /* Unlock the dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Done. */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Modify response.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_modify_response( pjsip_dialog *dlg,
+ pjsip_tx_data *tdata,
+ int st_code,
+ const pj_str_t *st_text)
+{
+ pjsip_hdr *hdr;
+
+ PJ_ASSERT_RETURN(dlg && tdata && tdata->msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG,
+ PJSIP_ENOTRESPONSEMSG);
+ PJ_ASSERT_RETURN(st_code >= 100 && st_code <= 699, PJ_EINVAL);
+
+ /* Lock and increment session */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Replace status code and reason */
+ tdata->msg->line.status.code = st_code;
+ if (st_text) {
+ pj_strdup(tdata->pool, &tdata->msg->line.status.reason, st_text);
+ } else {
+ tdata->msg->line.status.reason = *pjsip_get_status_text(st_code);
+ }
+
+ /* Remove existing Contact header (without this, when dialog sent
+ * 180 and then 302, the Contact in 302 will not get updated).
+ */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
+ if (hdr)
+ pj_list_erase(hdr);
+
+ /* Add tag etc. if necessary */
+ dlg_beautify_response(dlg, st_code/100 <= 2, st_code, tdata);
+
+
+ /* Must add reference counter, since tsx_send_msg() will decrement it */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Force to re-print message. */
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Unlock dialog and dec session, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Send response statefully.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_send_response( pjsip_dialog *dlg,
+ pjsip_transaction *tsx,
+ pjsip_tx_data *tdata)
+{
+ pj_status_t status;
+
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(dlg && tsx && tdata && tdata->msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG,
+ PJSIP_ENOTRESPONSEMSG);
+
+ /* The transaction must belong to this dialog. */
+ PJ_ASSERT_RETURN(tsx->mod_data[dlg->ua->id] == dlg, PJ_EINVALIDOP);
+
+ pj_log_push_indent();
+
+ PJ_LOG(5,(dlg->obj_name, "Sending %s",
+ pjsip_tx_data_get_info(tdata)));
+
+ /* Check that transaction method and cseq match the response.
+ * This operation is sloooww (search CSeq header twice), that's why
+ * we only do it in debug mode.
+ */
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ PJ_ASSERT_RETURN( PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq == tsx->cseq &&
+ pjsip_method_cmp(&PJSIP_MSG_CSEQ_HDR(tdata->msg)->method,
+ &tsx->method)==0,
+ PJ_EINVALIDOP);
+#endif
+
+ /* Must acquire dialog first, to prevent deadlock */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Last chance to add mandatory headers before the response is
+ * sent.
+ */
+ dlg_beautify_response(dlg, PJ_TRUE, tdata->msg->line.status.code, tdata);
+
+ /* If the dialog is locked to transport, make sure that transaction
+ * is locked to the same transport too.
+ */
+ if (dlg->tp_sel.type != tsx->tp_sel.type ||
+ dlg->tp_sel.u.ptr != tsx->tp_sel.u.ptr)
+ {
+ status = pjsip_tsx_set_transport(tsx, &dlg->tp_sel);
+ pj_assert(status == PJ_SUCCESS);
+ }
+
+ /* Ask transaction to send the response */
+ status = pjsip_tsx_send_msg(tsx, tdata);
+
+ /* This function must decrement transmit data request counter
+ * regardless of the operation status. The transaction only
+ * decrements the counter if the operation is successful.
+ */
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+
+ return status;
+}
+
+
+/*
+ * Combo function to create and send response statefully.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_respond( pjsip_dialog *dlg,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ const pjsip_hdr *hdr_list,
+ const pjsip_msg_body *body )
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(dlg && rdata && rdata->msg_info.msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* The transaction must belong to this dialog. */
+ PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata) &&
+ pjsip_rdata_get_tsx(rdata)->mod_data[dlg->ua->id] == dlg,
+ PJ_EINVALIDOP);
+
+ /* Create the response. */
+ status = pjsip_dlg_create_response(dlg, rdata, st_code, st_text, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add additional header, if any */
+ if (hdr_list) {
+ const pjsip_hdr *hdr;
+
+ hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pjsip_msg_add_hdr(tdata->msg,
+ (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+ }
+
+ /* Add the message body, if any. */
+ if (body) {
+ tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body);
+ }
+
+ /* Send the response. */
+ return pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
+}
+
+
+/* This function is called by user agent upon receiving incoming request
+ * message.
+ */
+void pjsip_dlg_on_rx_request( pjsip_dialog *dlg, pjsip_rx_data *rdata )
+{
+ pj_status_t status;
+ pjsip_transaction *tsx = NULL;
+ pj_bool_t processed = PJ_FALSE;
+ unsigned i;
+
+ PJ_LOG(5,(dlg->obj_name, "Received %s",
+ pjsip_rx_data_get_info(rdata)));
+ pj_log_push_indent();
+
+ /* Lock dialog and increment session. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Check CSeq */
+ if (rdata->msg_info.cseq->cseq <= dlg->remote.cseq &&
+ rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD &&
+ rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD)
+ {
+ /* Invalid CSeq.
+ * Respond statelessly with 500 (Internal Server Error)
+ */
+ pj_str_t warn_text;
+
+ /* Unlock dialog and dec session, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ pj_assert(pjsip_rdata_get_tsx(rdata) == NULL);
+ warn_text = pj_str("Invalid CSeq");
+ pjsip_endpt_respond_stateless(dlg->endpt,
+ rdata, 500, &warn_text, NULL, NULL);
+ pj_log_pop_indent();
+ return;
+ }
+
+ /* Update CSeq. */
+ dlg->remote.cseq = rdata->msg_info.cseq->cseq;
+
+ /* Update To tag if necessary.
+ * This only happens if UAS sends a new request before answering
+ * our request (e.g. UAS sends NOTIFY before answering our
+ * SUBSCRIBE request).
+ */
+ if (dlg->remote.info->tag.slen == 0) {
+ pj_strdup(dlg->pool, &dlg->remote.info->tag,
+ &rdata->msg_info.from->tag);
+ }
+
+ /* Create UAS transaction for this request. */
+ if (pjsip_rdata_get_tsx(rdata) == NULL &&
+ rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD)
+ {
+ status = pjsip_tsx_create_uas(dlg->ua, rdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ /* Once case for this is when re-INVITE contains same
+ * Via branch value as previous INVITE (ticket #965).
+ */
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t reason;
+
+ reason = pj_strerror(status, errmsg, sizeof(errmsg));
+ pjsip_endpt_respond_stateless(dlg->endpt, rdata, 500, &reason,
+ NULL, NULL);
+ goto on_return;
+ }
+
+ /* Put this dialog in the transaction data. */
+ tsx->mod_data[dlg->ua->id] = dlg;
+
+ /* Add transaction count. */
+ ++dlg->tsx_count;
+ }
+
+ /* Update the target URI if this is a target refresh request.
+ * We have passed the basic checking for the request, I think we
+ * should update the target URI regardless of whether the request
+ * is accepted or not (e.g. when re-INVITE is answered with 488,
+ * we would still need to update the target URI, otherwise our
+ * target URI would be wrong, wouldn't it).
+ */
+ if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method)) {
+ pjsip_contact_hdr *contact;
+
+ contact = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
+ NULL);
+ if (contact && contact->uri &&
+ (dlg->remote.contact==NULL ||
+ pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI,
+ dlg->remote.contact->uri,
+ contact->uri)))
+ {
+ dlg->remote.contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(dlg->pool, contact);
+ dlg->target = dlg->remote.contact->uri;
+ }
+ }
+
+ /* Report the request to dialog usages. */
+ for (i=0; i<dlg->usage_cnt; ++i) {
+
+ if (!dlg->usage[i]->on_rx_request)
+ continue;
+
+ processed = (*dlg->usage[i]->on_rx_request)(rdata);
+
+ if (processed)
+ break;
+ }
+
+ /* Feed the first request to the transaction. */
+ if (tsx)
+ pjsip_tsx_recv_msg(tsx, rdata);
+
+ /* If no dialog usages has claimed the processing of the transaction,
+ * and if transaction has not sent final response, respond with
+ * 500/Internal Server Error.
+ */
+ if (!processed && tsx && tsx->status_code < 200) {
+ pjsip_tx_data *tdata;
+ const pj_str_t reason = { "Unhandled by dialog usages", 26};
+
+ PJ_LOG(4,(tsx->obj_name, "%s was unhandled by "
+ "dialog usages, sending 500 response",
+ pjsip_rx_data_get_info(rdata)));
+
+ status = pjsip_dlg_create_response(dlg, rdata, 500, &reason, &tdata);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_dlg_send_response(dlg, tsx, tdata);
+ }
+ }
+
+on_return:
+ /* Unlock dialog and dec session, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+}
+
+/* Update route-set from incoming message */
+static void dlg_update_routeset(pjsip_dialog *dlg, const pjsip_rx_data *rdata)
+{
+ const pjsip_hdr *hdr, *end_hdr;
+ pj_int32_t msg_cseq;
+ const pjsip_msg *msg;
+
+ msg = rdata->msg_info.msg;
+ msg_cseq = rdata->msg_info.cseq->cseq;
+
+ /* Ignore if route set has been frozen */
+ if (dlg->route_set_frozen)
+ return;
+
+ /* Only update route set if this message belongs to the same
+ * transaction as the initial transaction that establishes dialog.
+ */
+ if (dlg->role == PJSIP_ROLE_UAC) {
+
+ /* Ignore subsequent request from remote */
+ if (msg->type != PJSIP_RESPONSE_MSG)
+ return;
+
+ /* Ignore subsequent responses with higher CSeq than initial CSeq.
+ * Unfortunately this would be broken when the first request is
+ * challenged!
+ */
+ //if (msg_cseq != dlg->local.first_cseq)
+ // return;
+
+ } else {
+
+ /* For callee dialog, route set should have been set by initial
+ * request and it will have been rejected by dlg->route_set_frozen
+ * check above.
+ */
+ pj_assert(!"Should not happen");
+
+ }
+
+ /* Based on the checks above, we should only get response message here */
+ pj_assert(msg->type == PJSIP_RESPONSE_MSG);
+
+ /* Ignore if this is not 1xx or 2xx response */
+ if (msg->line.status.code >= 300)
+ return;
+
+ /* Reset route set */
+ pj_list_init(&dlg->route_set);
+
+ /* Update route set */
+ end_hdr = &msg->hdr;
+ for (hdr=msg->hdr.prev; hdr!=end_hdr; hdr=hdr->prev) {
+ if (hdr->type == PJSIP_H_RECORD_ROUTE) {
+ pjsip_route_hdr *r;
+ r = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, hdr);
+ pjsip_routing_hdr_set_route(r);
+ pj_list_push_back(&dlg->route_set, r);
+ }
+ }
+
+ PJ_LOG(5,(dlg->obj_name, "Route-set updated"));
+
+ /* Freeze the route set only when the route set comes in 2xx response.
+ * If it is in 1xx response, prepare to recompute the route set when
+ * the 2xx response comes in.
+ *
+ * There is a debate whether route set should be frozen when the dialog
+ * is established with reliable provisional response, but I think
+ * it is safer to not freeze the route set (thus recompute the route set
+ * upon receiving 2xx response). Also RFC 3261 says so in 13.2.2.4.
+ *
+ * The pjsip_method_creates_dialog() check protects from wrongly
+ * freezing the route set upon receiving 200/OK response for PRACK.
+ */
+ if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) &&
+ PJSIP_IS_STATUS_IN_CLASS(msg->line.status.code, 200))
+ {
+ dlg->route_set_frozen = PJ_TRUE;
+ PJ_LOG(5,(dlg->obj_name, "Route-set frozen"));
+ }
+}
+
+
+/* This function is called by user agent upon receiving incoming response
+ * message.
+ */
+void pjsip_dlg_on_rx_response( pjsip_dialog *dlg, pjsip_rx_data *rdata )
+{
+ unsigned i;
+ int res_code;
+
+ PJ_LOG(5,(dlg->obj_name, "Received %s",
+ pjsip_rx_data_get_info(rdata)));
+ pj_log_push_indent();
+
+ /* Lock the dialog and inc session. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Check that rdata already has dialog in mod_data. */
+ pj_assert(pjsip_rdata_get_dlg(rdata) == dlg);
+
+ /* Keep the response's status code */
+ res_code = rdata->msg_info.msg->line.status.code;
+
+ /* When we receive response that establishes dialog, update To tag,
+ * route set and dialog target.
+ *
+ * The second condition of the "if" is a workaround for forking.
+ * Originally, the dialog takes the first To tag seen and set it as
+ * the remote tag. If the tag in 2xx response is different than this
+ * tag, ACK will be sent with wrong To tag and incoming request with
+ * this tag will be rejected with 481.
+ *
+ * The workaround for this is to take the To tag received in the
+ * 2xx response and set it as remote tag.
+ *
+ * New update:
+ * We also need to update the dialog for 1xx responses, to handle the
+ * case when 100rel is used, otherwise PRACK will be sent to the
+ * wrong target.
+ */
+ if ((dlg->state == PJSIP_DIALOG_STATE_NULL &&
+ pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) &&
+ (res_code > 100 && res_code < 300) &&
+ rdata->msg_info.to->tag.slen)
+ ||
+ (dlg->role==PJSIP_ROLE_UAC &&
+ !dlg->uac_has_2xx &&
+ res_code > 100 &&
+ res_code/100 <= 2 &&
+ pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) &&
+ pj_strcmp(&dlg->remote.info->tag, &rdata->msg_info.to->tag)))
+ {
+ pjsip_contact_hdr *contact;
+
+ /* Update remote capability info, when To tags in the dialog remote
+ * info and the incoming response are different, e.g: first response
+ * with To-tag or forking, apply strict update.
+ */
+ pjsip_dlg_update_remote_cap(dlg, rdata->msg_info.msg,
+ pj_strcmp(&dlg->remote.info->tag,
+ &rdata->msg_info.to->tag));
+
+ /* Update To tag. */
+ pj_strdup(dlg->pool, &dlg->remote.info->tag, &rdata->msg_info.to->tag);
+ /* No need to update remote's tag_hval since its never used. */
+
+ /* RFC 3271 Section 12.1.2:
+ * The route set MUST be set to the list of URIs in the Record-Route
+ * header field from the response, taken in reverse order and
+ * preserving all URI parameters. If no Record-Route header field
+ * is present in the response, the route set MUST be set to the
+ * empty set. This route set, even if empty, overrides any pre-existing
+ * route set for future requests in this dialog.
+ */
+ dlg_update_routeset(dlg, rdata);
+
+ /* The remote target MUST be set to the URI from the Contact header
+ * field of the response.
+ */
+ contact = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
+ NULL);
+ if (contact && contact->uri &&
+ (dlg->remote.contact==NULL ||
+ pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI,
+ dlg->remote.contact->uri,
+ contact->uri)))
+ {
+ dlg->remote.contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(dlg->pool, contact);
+ dlg->target = dlg->remote.contact->uri;
+ }
+
+ dlg->state = PJSIP_DIALOG_STATE_ESTABLISHED;
+
+ /* Prevent dialog from being updated just in case more 2xx
+ * gets through this dialog (it shouldn't happen).
+ */
+ if (dlg->role==PJSIP_ROLE_UAC && !dlg->uac_has_2xx &&
+ res_code/100==2)
+ {
+ dlg->uac_has_2xx = PJ_TRUE;
+ }
+ }
+
+ /* Update remote target (again) when receiving 2xx response messages
+ * that's defined as target refresh.
+ *
+ * Also upon receiving 2xx response, recheck again the route set.
+ * This is for compatibility with RFC 2543, as described in Section
+ * 13.2.2.4 of RFC 3261:
+
+ If the dialog identifier in the 2xx response matches the dialog
+ identifier of an existing dialog, the dialog MUST be transitioned to
+ the "confirmed" state, and the route set for the dialog MUST be
+ recomputed based on the 2xx response using the procedures of Section
+ 12.2.1.2.
+
+ Note that the only piece of state that is recomputed is the route
+ set. Other pieces of state such as the highest sequence numbers
+ (remote and local) sent within the dialog are not recomputed. The
+ route set only is recomputed for backwards compatibility. RFC
+ 2543 did not mandate mirroring of the Record-Route header field in
+ a 1xx, only 2xx.
+ */
+ if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) &&
+ res_code/100 == 2)
+ {
+ pjsip_contact_hdr *contact;
+
+ contact = (pjsip_contact_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg,
+ PJSIP_H_CONTACT,
+ NULL);
+ if (contact && contact->uri &&
+ (dlg->remote.contact==NULL ||
+ pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI,
+ dlg->remote.contact->uri,
+ contact->uri)))
+ {
+ dlg->remote.contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(dlg->pool, contact);
+ dlg->target = dlg->remote.contact->uri;
+ }
+
+ dlg_update_routeset(dlg, rdata);
+
+ /* Update remote capability info after the first 2xx response
+ * (ticket #1539). Note that the remote capability retrieved here
+ * will be assumed to remain unchanged for the duration of the dialog.
+ */
+ if (dlg->role==PJSIP_ROLE_UAC && !dlg->uac_has_2xx) {
+ pjsip_dlg_update_remote_cap(dlg, rdata->msg_info.msg, PJ_FALSE);
+ dlg->uac_has_2xx = PJ_TRUE;
+ }
+ }
+
+ /* Pass to dialog usages. */
+ for (i=0; i<dlg->usage_cnt; ++i) {
+ pj_bool_t processed;
+
+ if (!dlg->usage[i]->on_rx_response)
+ continue;
+
+ processed = (*dlg->usage[i]->on_rx_response)(rdata);
+
+ if (processed)
+ break;
+ }
+
+ /* Handle the case of forked response, when the application creates
+ * the forked dialog but not the invite session. In this case, the
+ * forked 200/OK response will be unhandled, and we must send ACK
+ * here.
+ */
+ if (dlg->usage_cnt==0) {
+ pj_status_t status;
+
+ if (rdata->msg_info.cseq->method.id==PJSIP_INVITE_METHOD &&
+ rdata->msg_info.msg->line.status.code/100 == 2)
+ {
+ pjsip_tx_data *ack;
+
+ status = pjsip_dlg_create_request(dlg, &pjsip_ack_method,
+ rdata->msg_info.cseq->cseq,
+ &ack);
+ if (status == PJ_SUCCESS)
+ status = pjsip_dlg_send_request(dlg, ack, -1, NULL);
+ } else if (rdata->msg_info.msg->line.status.code==401 ||
+ rdata->msg_info.msg->line.status.code==407)
+ {
+ pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
+ pjsip_tx_data *tdata;
+
+ status = pjsip_auth_clt_reinit_req( &dlg->auth_sess,
+ rdata, tsx->last_tx,
+ &tdata);
+
+ if (status == PJ_SUCCESS) {
+ /* Re-send request. */
+ status = pjsip_dlg_send_request(dlg, tdata, -1, NULL);
+ }
+ }
+ }
+
+ /* Unhandled response does not necessarily mean error because
+ dialog usages may choose to process the transaction state instead.
+ if (i==dlg->usage_cnt) {
+ PJ_LOG(4,(dlg->obj_name, "%s was not claimed by any dialog usages",
+ pjsip_rx_data_get_info(rdata)));
+ }
+ */
+
+ /* Unlock dialog and dec session, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ pj_log_pop_indent();
+}
+
+/* This function is called by user agent upon receiving transaction
+ * state notification.
+ */
+void pjsip_dlg_on_tsx_state( pjsip_dialog *dlg,
+ pjsip_transaction *tsx,
+ pjsip_event *e )
+{
+ unsigned i;
+
+ PJ_LOG(5,(dlg->obj_name, "Transaction %s state changed to %s",
+ tsx->obj_name, pjsip_tsx_state_str(tsx->state)));
+ pj_log_push_indent();
+
+ /* Lock the dialog and increment session. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Pass to dialog usages. */
+ for (i=0; i<dlg->usage_cnt; ++i) {
+
+ if (!dlg->usage[i]->on_tsx_state)
+ continue;
+
+ (*dlg->usage[i]->on_tsx_state)(tsx, e);
+ }
+
+
+ /* It is possible that the transaction is terminated and this function
+ * is called while we're calling on_tsx_state(). So only decrement
+ * the tsx_count if we're still attached to the transaction.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
+ tsx->mod_data[dlg->ua->id] == dlg)
+ {
+ pj_assert(dlg->tsx_count>0);
+ --dlg->tsx_count;
+ tsx->mod_data[dlg->ua->id] = NULL;
+ }
+
+ /* Unlock dialog and dec session, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Check if the specified capability is supported by remote.
+ */
+PJ_DEF(pjsip_dialog_cap_status) pjsip_dlg_remote_has_cap(
+ pjsip_dialog *dlg,
+ int htype,
+ const pj_str_t *hname,
+ const pj_str_t *token)
+{
+ const pjsip_generic_array_hdr *hdr;
+ pjsip_dialog_cap_status cap_status = PJSIP_DIALOG_CAP_UNSUPPORTED;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(dlg && token, PJSIP_DIALOG_CAP_UNKNOWN);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ hdr = (const pjsip_generic_array_hdr*)
+ pjsip_dlg_get_remote_cap_hdr(dlg, htype, hname);
+ if (!hdr) {
+ cap_status = PJSIP_DIALOG_CAP_UNKNOWN;
+ } else {
+ for (i=0; i<hdr->count; ++i) {
+ if (!pj_stricmp(&hdr->values[i], token)) {
+ cap_status = PJSIP_DIALOG_CAP_SUPPORTED;
+ break;
+ }
+ }
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return cap_status;
+}
+
+
+/*
+ * Update remote capability of ACCEPT, ALLOW, and SUPPORTED from
+ * the received message.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_update_remote_cap(pjsip_dialog *dlg,
+ const pjsip_msg *msg,
+ pj_bool_t strict)
+{
+ pjsip_hdr_e htypes[] =
+ { PJSIP_H_ACCEPT, PJSIP_H_ALLOW, PJSIP_H_SUPPORTED };
+ unsigned i;
+
+ PJ_ASSERT_RETURN(dlg && msg, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Retrieve all specified capability header types */
+ for (i = 0; i < PJ_ARRAY_SIZE(htypes); ++i) {
+ const pjsip_generic_array_hdr *hdr;
+ pj_status_t status;
+
+ /* Find this capability type in the message */
+ hdr = (const pjsip_generic_array_hdr*)
+ pjsip_msg_find_hdr(msg, htypes[i], NULL);
+ if (!hdr) {
+ /* Not found.
+ * If strict update is specified, remote this capability type
+ * from the capability list.
+ */
+ if (strict)
+ pjsip_dlg_remove_remote_cap_hdr(dlg, htypes[i], NULL);
+ } else {
+ /* Found, a capability type may be specified in multiple headers,
+ * so combine all the capability tags/values into a temporary
+ * header.
+ */
+ pjsip_generic_array_hdr tmp_hdr;
+
+ /* Init temporary header */
+ pjsip_generic_array_hdr_init(dlg->pool, &tmp_hdr, NULL);
+ pj_memcpy(&tmp_hdr, hdr, sizeof(pjsip_hdr));
+
+ while (hdr) {
+ unsigned j;
+
+ /* Append the header content to temporary header */
+ for(j=0; j<hdr->count &&
+ tmp_hdr.count<PJSIP_GENERIC_ARRAY_MAX_COUNT; ++j)
+ {
+ tmp_hdr.values[tmp_hdr.count++] = hdr->values[j];
+ }
+
+ /* Get the next header for this capability */
+ hdr = (const pjsip_generic_array_hdr*)
+ pjsip_msg_find_hdr(msg, htypes[i], hdr->next);
+ }
+
+ /* Save this capability */
+ status = pjsip_dlg_set_remote_cap_hdr(dlg, &tmp_hdr);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+ }
+ }
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the value of the specified capability header field of remote.
+ */
+PJ_DEF(const pjsip_hdr*) pjsip_dlg_get_remote_cap_hdr(pjsip_dialog *dlg,
+ int htype,
+ const pj_str_t *hname)
+{
+ pjsip_hdr *hdr;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(dlg, NULL);
+ PJ_ASSERT_RETURN((htype != PJSIP_H_OTHER) || (hname && hname->slen),
+ NULL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ hdr = dlg->rem_cap_hdr.next;
+ while (hdr != &dlg->rem_cap_hdr) {
+ if ((htype != PJSIP_H_OTHER && htype == hdr->type) ||
+ (htype == PJSIP_H_OTHER && pj_stricmp(&hdr->name, hname) == 0))
+ {
+ pjsip_dlg_dec_lock(dlg);
+ return hdr;
+ }
+ hdr = hdr->next;
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return NULL;
+}
+
+
+/*
+ * Set remote capability header from a SIP header containing array
+ * of capability tags/values.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_remote_cap_hdr(
+ pjsip_dialog *dlg,
+ const pjsip_generic_array_hdr *cap_hdr)
+{
+ pjsip_generic_array_hdr *hdr;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(dlg && cap_hdr, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Find the header. */
+ hdr = (pjsip_generic_array_hdr*)
+ pjsip_dlg_get_remote_cap_hdr(dlg, cap_hdr->type, &cap_hdr->name);
+
+ /* Quick compare if the capability is up to date */
+ if (hdr && hdr->count == cap_hdr->count) {
+ unsigned i;
+ pj_bool_t uptodate = PJ_TRUE;
+
+ for (i=0; i<hdr->count; ++i) {
+ if (pj_stricmp(&hdr->values[i], &cap_hdr->values[i]))
+ uptodate = PJ_FALSE;
+ }
+
+ /* Capability is up to date, just return PJ_SUCCESS */
+ if (uptodate) {
+ pjsip_dlg_dec_lock(dlg);
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Remove existing capability header if any */
+ if (hdr)
+ pj_list_erase(hdr);
+
+ /* Add the new capability header */
+ hdr = (pjsip_generic_array_hdr*) pjsip_hdr_clone(dlg->pool, cap_hdr);
+ hdr->type = cap_hdr->type;
+ pj_strdup(dlg->pool, &hdr->name, &cap_hdr->name);
+ pj_list_push_back(&dlg->rem_cap_hdr, hdr);
+
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+/*
+ * Remove a remote capability header.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_remove_remote_cap_hdr(pjsip_dialog *dlg,
+ int htype,
+ const pj_str_t *hname)
+{
+ pjsip_generic_array_hdr *hdr;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(dlg, PJ_EINVAL);
+ PJ_ASSERT_RETURN((htype != PJSIP_H_OTHER) || (hname && hname->slen),
+ PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ hdr = (pjsip_generic_array_hdr*)
+ pjsip_dlg_get_remote_cap_hdr(dlg, htype, hname);
+ if (!hdr) {
+ pjsip_dlg_dec_lock(dlg);
+ return PJ_ENOTFOUND;
+ }
+
+ pj_list_erase(hdr);
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
diff --git a/pjsip/src/pjsip/sip_dialog_wrap.cpp b/pjsip/src/pjsip/sip_dialog_wrap.cpp
new file mode 100644
index 0000000..5bf3935
--- /dev/null
+++ b/pjsip/src/pjsip/sip_dialog_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_dialog_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_dialog.c"
diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c
new file mode 100644
index 0000000..2510d14
--- /dev/null
+++ b/pjsip/src/pjsip/sip_endpoint.c
@@ -0,0 +1,1245 @@
+/* $Id: sip_endpoint.c 4154 2012-06-05 10:41:17Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_private.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_resolve.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_errno.h>
+#include <pj/except.h>
+#include <pj/log.h>
+#include <pj/string.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/hash.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/lock.h>
+
+#define PJSIP_EX_NO_MEMORY pj_NO_MEMORY_EXCEPTION()
+#define THIS_FILE "sip_endpoint.c"
+
+#define MAX_METHODS 32
+
+
+/* List of SIP endpoint exit callback. */
+typedef struct exit_cb
+{
+ PJ_DECL_LIST_MEMBER (struct exit_cb);
+ pjsip_endpt_exit_callback func;
+} exit_cb;
+
+
+/**
+ * The SIP endpoint.
+ */
+struct pjsip_endpoint
+{
+ /** Pool to allocate memory for the endpoint. */
+ pj_pool_t *pool;
+
+ /** Mutex for the pool, hash table, and event list/queue. */
+ pj_mutex_t *mutex;
+
+ /** Pool factory. */
+ pj_pool_factory *pf;
+
+ /** Name. */
+ pj_str_t name;
+
+ /** Timer heap. */
+ pj_timer_heap_t *timer_heap;
+
+ /** Transport manager. */
+ pjsip_tpmgr *transport_mgr;
+
+ /** Ioqueue. */
+ pj_ioqueue_t *ioqueue;
+
+ /** Last ioqueue err */
+ pj_status_t ioq_last_err;
+
+ /** DNS Resolver. */
+ pjsip_resolver_t *resolver;
+
+ /** Modules lock. */
+ pj_rwmutex_t *mod_mutex;
+
+ /** Modules. */
+ pjsip_module *modules[PJSIP_MAX_MODULE];
+
+ /** Module list, sorted by priority. */
+ pjsip_module module_list;
+
+ /** Capability header list. */
+ pjsip_hdr cap_hdr;
+
+ /** Additional request headers. */
+ pjsip_hdr req_hdr;
+
+ /** List of exit callback. */
+ exit_cb exit_cb_list;
+};
+
+
+#if defined(PJSIP_SAFE_MODULE) && PJSIP_SAFE_MODULE!=0
+# define LOCK_MODULE_ACCESS(ept) pj_rwmutex_lock_read(ept->mod_mutex)
+# define UNLOCK_MODULE_ACCESS(ept) pj_rwmutex_unlock_read(ept->mod_mutex)
+#else
+# define LOCK_MODULE_ACCESS(endpt)
+# define UNLOCK_MODULE_ACCESS(endpt)
+#endif
+
+
+
+/*
+ * Prototypes.
+ */
+static void endpt_on_rx_msg( pjsip_endpoint*,
+ pj_status_t, pjsip_rx_data*);
+static pj_status_t endpt_on_tx_msg( pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata );
+static pj_status_t unload_module(pjsip_endpoint *endpt,
+ pjsip_module *mod);
+
+/* Defined in sip_parser.c */
+void init_sip_parser(void);
+void deinit_sip_parser(void);
+
+/* Defined in sip_tel_uri.c */
+pj_status_t pjsip_tel_uri_subsys_init(void);
+
+
+/*
+ * This is the global handler for memory allocation failure, for pools that
+ * are created by the endpoint (by default, all pools ARE allocated by
+ * endpoint). The error is handled by throwing exception, and hopefully,
+ * the exception will be handled by the application (or this library).
+ */
+static void pool_callback( pj_pool_t *pool, pj_size_t size )
+{
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(size);
+
+ PJ_THROW(PJSIP_EX_NO_MEMORY);
+}
+
+
+/* Compare module name, used for searching module based on name. */
+static int cmp_mod_name(void *name, const void *mod)
+{
+ return pj_stricmp((const pj_str_t*)name, &((pjsip_module*)mod)->name);
+}
+
+/*
+ * Register new module to the endpoint.
+ * The endpoint will then call the load and start function in the module to
+ * properly initialize the module, and assign a unique module ID for the
+ * module.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_register_module( pjsip_endpoint *endpt,
+ pjsip_module *mod )
+{
+ pj_status_t status = PJ_SUCCESS;
+ pjsip_module *m;
+ unsigned i;
+
+ pj_rwmutex_lock_write(endpt->mod_mutex);
+
+ /* Make sure that this module has not been registered. */
+ PJ_ASSERT_ON_FAIL( pj_list_find_node(&endpt->module_list, mod) == NULL,
+ {status = PJ_EEXISTS; goto on_return;});
+
+ /* Make sure that no module with the same name has been registered. */
+ PJ_ASSERT_ON_FAIL( pj_list_search(&endpt->module_list, &mod->name,
+ &cmp_mod_name)==NULL,
+ {status = PJ_EEXISTS; goto on_return; });
+
+ /* Find unused ID for this module. */
+ for (i=0; i<PJ_ARRAY_SIZE(endpt->modules); ++i) {
+ if (endpt->modules[i] == NULL)
+ break;
+ }
+ if (i == PJ_ARRAY_SIZE(endpt->modules)) {
+ pj_assert(!"Too many modules registered!");
+ status = PJ_ETOOMANY;
+ goto on_return;
+ }
+
+ /* Assign the ID. */
+ mod->id = i;
+
+ /* Try to load the module. */
+ if (mod->load) {
+ status = (*mod->load)(endpt);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ }
+
+ /* Try to start the module. */
+ if (mod->start) {
+ status = (*mod->start)();
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ }
+
+ /* Save the module. */
+ endpt->modules[i] = mod;
+
+ /* Put in the module list, sorted by priority. */
+ m = endpt->module_list.next;
+ while (m != &endpt->module_list) {
+ if (m->priority > mod->priority)
+ break;
+ m = m->next;
+ }
+ pj_list_insert_before(m, mod);
+
+ /* Done. */
+
+ PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" registered",
+ (int)mod->name.slen, mod->name.ptr));
+
+on_return:
+ pj_rwmutex_unlock_write(endpt->mod_mutex);
+ return status;
+}
+
+/*
+ * Unregister a module from the endpoint.
+ * The endpoint will then call the stop and unload function in the module to
+ * properly shutdown the module.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_unregister_module( pjsip_endpoint *endpt,
+ pjsip_module *mod )
+{
+ pj_status_t status;
+
+ pj_rwmutex_lock_write(endpt->mod_mutex);
+
+ /* Make sure the module exists in the list. */
+ PJ_ASSERT_ON_FAIL( pj_list_find_node(&endpt->module_list, mod) == mod,
+ {status = PJ_ENOTFOUND;goto on_return;} );
+
+ /* Make sure the module exists in the array. */
+ PJ_ASSERT_ON_FAIL( mod->id>=0 &&
+ mod->id<(int)PJ_ARRAY_SIZE(endpt->modules) &&
+ endpt->modules[mod->id] == mod,
+ {status = PJ_ENOTFOUND; goto on_return;});
+
+ /* Try to stop the module. */
+ if (mod->stop) {
+ status = (*mod->stop)();
+ if (status != PJ_SUCCESS) goto on_return;
+ }
+
+ /* Unload module */
+ status = unload_module(endpt, mod);
+
+on_return:
+ pj_rwmutex_unlock_write(endpt->mod_mutex);
+
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(3,(THIS_FILE, "Module \"%.*s\" can not be unregistered: %s",
+ (int)mod->name.slen, mod->name.ptr, errmsg));
+ }
+
+ return status;
+}
+
+static pj_status_t unload_module(pjsip_endpoint *endpt,
+ pjsip_module *mod)
+{
+ pj_status_t status;
+
+ /* Try to unload the module. */
+ if (mod->unload) {
+ status = (*mod->unload)();
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Module MUST NOT set module ID to -1. */
+ pj_assert(mod->id >= 0);
+
+ /* Remove module from array. */
+ endpt->modules[mod->id] = NULL;
+
+ /* Remove module from list. */
+ pj_list_erase(mod);
+
+ /* Set module Id to -1. */
+ mod->id = -1;
+
+ /* Done. */
+ status = PJ_SUCCESS;
+
+ PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" unregistered",
+ (int)mod->name.slen, mod->name.ptr));
+
+ return status;
+}
+
+
+/*
+ * Get the value of the specified capability header field.
+ */
+PJ_DEF(const pjsip_hdr*) pjsip_endpt_get_capability( pjsip_endpoint *endpt,
+ int htype,
+ const pj_str_t *hname)
+{
+ pjsip_hdr *hdr = endpt->cap_hdr.next;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt != NULL, NULL);
+ PJ_ASSERT_RETURN(htype != PJSIP_H_OTHER || hname, NULL);
+
+ if (htype != PJSIP_H_OTHER) {
+ while (hdr != &endpt->cap_hdr) {
+ if (hdr->type == htype)
+ return hdr;
+ hdr = hdr->next;
+ }
+ }
+ return NULL;
+}
+
+
+/*
+ * Check if the specified capability is supported.
+ */
+PJ_DEF(pj_bool_t) pjsip_endpt_has_capability( pjsip_endpoint *endpt,
+ int htype,
+ const pj_str_t *hname,
+ const pj_str_t *token)
+{
+ const pjsip_generic_array_hdr *hdr;
+ unsigned i;
+
+ hdr = (const pjsip_generic_array_hdr*)
+ pjsip_endpt_get_capability(endpt, htype, hname);
+ if (!hdr)
+ return PJ_FALSE;
+
+ PJ_ASSERT_RETURN(token != NULL, PJ_FALSE);
+
+ for (i=0; i<hdr->count; ++i) {
+ if (!pj_stricmp(&hdr->values[i], token))
+ return PJ_TRUE;
+ }
+
+ return PJ_FALSE;
+}
+
+/*
+ * Add or register new capabilities as indicated by the tags to the
+ * appropriate header fields in the endpoint.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_add_capability( pjsip_endpoint *endpt,
+ pjsip_module *mod,
+ int htype,
+ const pj_str_t *hname,
+ unsigned count,
+ const pj_str_t tags[])
+{
+ pjsip_generic_array_hdr *hdr;
+ unsigned i;
+
+ PJ_UNUSED_ARG(mod);
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt!=NULL && count>0 && tags, PJ_EINVAL);
+ PJ_ASSERT_RETURN(htype==PJSIP_H_ACCEPT ||
+ htype==PJSIP_H_ALLOW ||
+ htype==PJSIP_H_SUPPORTED,
+ PJ_EINVAL);
+
+ /* Find the header. */
+ hdr = (pjsip_generic_array_hdr*) pjsip_endpt_get_capability(endpt,
+ htype, hname);
+
+ /* Create the header when it's not present */
+ if (hdr == NULL) {
+ switch (htype) {
+ case PJSIP_H_ACCEPT:
+ hdr = pjsip_accept_hdr_create(endpt->pool);
+ break;
+ case PJSIP_H_ALLOW:
+ hdr = pjsip_allow_hdr_create(endpt->pool);
+ break;
+ case PJSIP_H_SUPPORTED:
+ hdr = pjsip_supported_hdr_create(endpt->pool);
+ break;
+ default:
+ return PJ_EINVAL;
+ }
+
+ if (hdr) {
+ pj_list_push_back(&endpt->cap_hdr, hdr);
+ }
+ }
+
+ /* Add the tags to the header. */
+ for (i=0; i<count; ++i) {
+ pj_strdup(endpt->pool, &hdr->values[hdr->count], &tags[i]);
+ ++hdr->count;
+ }
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get additional headers to be put in outgoing request message.
+ */
+PJ_DEF(const pjsip_hdr*) pjsip_endpt_get_request_headers(pjsip_endpoint *endpt)
+{
+ return &endpt->req_hdr;
+}
+
+
+/*
+ * Initialize endpoint.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create(pj_pool_factory *pf,
+ const char *name,
+ pjsip_endpoint **p_endpt)
+{
+ pj_status_t status;
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pjsip_max_fwd_hdr *mf_hdr;
+ pj_lock_t *lock = NULL;
+
+
+ status = pj_register_strerror(PJSIP_ERRNO_START, PJ_ERRNO_SPACE_SIZE,
+ &pjsip_strerror);
+ pj_assert(status == PJ_SUCCESS);
+
+ PJ_LOG(5, (THIS_FILE, "Creating endpoint instance..."));
+
+ *p_endpt = NULL;
+
+ /* Create pool */
+ pool = pj_pool_create(pf, "pept%p",
+ PJSIP_POOL_LEN_ENDPT, PJSIP_POOL_INC_ENDPT,
+ &pool_callback);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ /* Create endpoint. */
+ endpt = PJ_POOL_ZALLOC_T(pool, pjsip_endpoint);
+ endpt->pool = pool;
+ endpt->pf = pf;
+
+ /* Init modules list. */
+ pj_list_init(&endpt->module_list);
+
+ /* Initialize exit callback list. */
+ pj_list_init(&endpt->exit_cb_list);
+
+ /* Create R/W mutex for module manipulation. */
+ status = pj_rwmutex_create(endpt->pool, "ept%p", &endpt->mod_mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init parser. */
+ init_sip_parser();
+
+ /* Init tel: uri */
+ pjsip_tel_uri_subsys_init();
+
+ /* Get name. */
+ if (name != NULL) {
+ pj_str_t temp;
+ pj_strdup_with_null(endpt->pool, &endpt->name, pj_cstr(&temp, name));
+ } else {
+ pj_strdup_with_null(endpt->pool, &endpt->name, pj_gethostname());
+ }
+
+ /* Create mutex for the events, etc. */
+ status = pj_mutex_create_recursive( endpt->pool, "ept%p", &endpt->mutex );
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Create timer heap to manage all timers within this endpoint. */
+ status = pj_timer_heap_create( endpt->pool, PJSIP_MAX_TIMER_COUNT,
+ &endpt->timer_heap);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Set recursive lock for the timer heap. */
+ status = pj_lock_create_recursive_mutex( endpt->pool, "edpt%p", &lock);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+ pj_timer_heap_set_lock(endpt->timer_heap, lock, PJ_TRUE);
+
+ /* Set maximum timed out entries to process in a single poll. */
+ pj_timer_heap_set_max_timed_out_per_poll(endpt->timer_heap,
+ PJSIP_MAX_TIMED_OUT_ENTRIES);
+
+ /* Create ioqueue. */
+ status = pj_ioqueue_create( endpt->pool, PJSIP_MAX_TRANSPORTS, &endpt->ioqueue);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Create transport manager. */
+ status = pjsip_tpmgr_create( endpt->pool, endpt,
+ &endpt_on_rx_msg,
+ &endpt_on_tx_msg,
+ &endpt->transport_mgr);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Create asynchronous DNS resolver. */
+ status = pjsip_resolver_create(endpt->pool, &endpt->resolver);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(4, (THIS_FILE, "Error creating resolver instance"));
+ goto on_error;
+ }
+
+ /* Initialize request headers. */
+ pj_list_init(&endpt->req_hdr);
+
+ /* Add "Max-Forwards" for request header. */
+ mf_hdr = pjsip_max_fwd_hdr_create(endpt->pool,
+ PJSIP_MAX_FORWARDS_VALUE);
+ pj_list_insert_before( &endpt->req_hdr, mf_hdr);
+
+ /* Initialize capability header list. */
+ pj_list_init(&endpt->cap_hdr);
+
+
+ /* Done. */
+ *p_endpt = endpt;
+ return status;
+
+on_error:
+ if (endpt->transport_mgr) {
+ pjsip_tpmgr_destroy(endpt->transport_mgr);
+ endpt->transport_mgr = NULL;
+ }
+ if (endpt->ioqueue) {
+ pj_ioqueue_destroy(endpt->ioqueue);
+ endpt->ioqueue = NULL;
+ }
+ if (endpt->timer_heap) {
+ pj_timer_heap_destroy(endpt->timer_heap);
+ endpt->timer_heap = NULL;
+ }
+ if (endpt->mutex) {
+ pj_mutex_destroy(endpt->mutex);
+ endpt->mutex = NULL;
+ }
+ if (endpt->mod_mutex) {
+ pj_rwmutex_destroy(endpt->mod_mutex);
+ endpt->mod_mutex = NULL;
+ }
+ pj_pool_release( endpt->pool );
+
+ PJ_LOG(4, (THIS_FILE, "Error creating endpoint"));
+ return status;
+}
+
+/*
+ * Destroy endpoint.
+ */
+PJ_DEF(void) pjsip_endpt_destroy(pjsip_endpoint *endpt)
+{
+ pjsip_module *mod;
+ exit_cb *ecb;
+
+ PJ_LOG(5, (THIS_FILE, "Destroying endpoing instance.."));
+
+ /* Phase 1: stop all modules */
+ mod = endpt->module_list.prev;
+ while (mod != &endpt->module_list) {
+ pjsip_module *prev = mod->prev;
+ if (mod->stop) {
+ (*mod->stop)();
+ }
+ mod = prev;
+ }
+
+ /* Phase 2: unload modules. */
+ mod = endpt->module_list.prev;
+ while (mod != &endpt->module_list) {
+ pjsip_module *prev = mod->prev;
+ unload_module(endpt, mod);
+ mod = prev;
+ }
+
+ /* Destroy resolver */
+ pjsip_resolver_destroy(endpt->resolver);
+
+ /* Shutdown and destroy all transports. */
+ pjsip_tpmgr_destroy(endpt->transport_mgr);
+
+ /* Destroy ioqueue */
+ pj_ioqueue_destroy(endpt->ioqueue);
+
+ /* Destroy timer heap */
+#if PJ_TIMER_DEBUG
+ pj_timer_heap_dump(endpt->timer_heap);
+#endif
+ pj_timer_heap_destroy(endpt->timer_heap);
+
+ /* Call all registered exit callbacks */
+ ecb = endpt->exit_cb_list.next;
+ while (ecb != &endpt->exit_cb_list) {
+ (*ecb->func)(endpt);
+ ecb = ecb->next;
+ }
+
+ /* Delete endpoint mutex. */
+ pj_mutex_destroy(endpt->mutex);
+
+ /* Deinit parser */
+ deinit_sip_parser();
+
+ /* Delete module's mutex */
+ pj_rwmutex_destroy(endpt->mod_mutex);
+
+ /* Finally destroy pool. */
+ pj_pool_release(endpt->pool);
+
+ PJ_LOG(4, (THIS_FILE, "Endpoint %p destroyed", endpt));
+}
+
+/*
+ * Get endpoint name.
+ */
+PJ_DEF(const pj_str_t*) pjsip_endpt_name(const pjsip_endpoint *endpt)
+{
+ return &endpt->name;
+}
+
+
+/*
+ * Create new pool.
+ */
+PJ_DEF(pj_pool_t*) pjsip_endpt_create_pool( pjsip_endpoint *endpt,
+ const char *pool_name,
+ pj_size_t initial,
+ pj_size_t increment )
+{
+ pj_pool_t *pool;
+
+ /* Lock endpoint mutex. */
+ /* No need to lock mutex. Factory is thread safe.
+ pj_mutex_lock(endpt->mutex);
+ */
+
+ /* Create pool */
+ pool = pj_pool_create( endpt->pf, pool_name,
+ initial, increment, &pool_callback);
+
+ /* Unlock mutex. */
+ /* No need to lock mutex. Factory is thread safe.
+ pj_mutex_unlock(endpt->mutex);
+ */
+
+ if (!pool) {
+ PJ_LOG(4, (THIS_FILE, "Unable to create pool %s!", pool_name));
+ }
+
+ return pool;
+}
+
+/*
+ * Return back pool to endpoint's pool manager to be either destroyed or
+ * recycled.
+ */
+PJ_DEF(void) pjsip_endpt_release_pool( pjsip_endpoint *endpt, pj_pool_t *pool )
+{
+ PJ_LOG(6, (THIS_FILE, "Releasing pool %s", pj_pool_getobjname(pool)));
+
+ /* Don't need to acquire mutex since pool factory is thread safe
+ pj_mutex_lock(endpt->mutex);
+ */
+ pj_pool_release( pool );
+
+ PJ_UNUSED_ARG(endpt);
+ /*
+ pj_mutex_unlock(endpt->mutex);
+ */
+}
+
+
+PJ_DEF(pj_status_t) pjsip_endpt_handle_events2(pjsip_endpoint *endpt,
+ const pj_time_val *max_timeout,
+ unsigned *p_count)
+{
+ /* timeout is 'out' var. This just to make compiler happy. */
+ pj_time_val timeout = { 0, 0};
+ unsigned count = 0, net_event_count = 0;
+ int c;
+
+ PJ_LOG(6, (THIS_FILE, "pjsip_endpt_handle_events()"));
+
+ /* Poll the timer. The timer heap has its own mutex for better
+ * granularity, so we don't need to lock end endpoint.
+ */
+ timeout.sec = timeout.msec = 0;
+ c = pj_timer_heap_poll( endpt->timer_heap, &timeout );
+ if (c > 0)
+ count += c;
+
+ /* timer_heap_poll should never ever returns negative value, or otherwise
+ * ioqueue_poll() will block forever!
+ */
+ pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
+ if (timeout.msec >= 1000) timeout.msec = 999;
+
+ /* If caller specifies maximum time to wait, then compare the value with
+ * the timeout to wait from timer, and use the minimum value.
+ */
+ if (max_timeout && PJ_TIME_VAL_GT(timeout, *max_timeout)) {
+ timeout = *max_timeout;
+ }
+
+ /* Poll ioqueue.
+ * Repeat polling the ioqueue while we have immediate events, because
+ * timer heap may process more than one events, so if we only process
+ * one network events at a time (such as when IOCP backend is used),
+ * the ioqueue may have trouble keeping up with the request rate.
+ *
+ * For example, for each send() request, one network event will be
+ * reported by ioqueue for the send() completion. If we don't poll
+ * the ioqueue often enough, the send() completion will not be
+ * reported in timely manner.
+ */
+ do {
+ c = pj_ioqueue_poll( endpt->ioqueue, &timeout);
+ if (c < 0) {
+ pj_status_t err = pj_get_netos_error();
+ pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
+ if (p_count)
+ *p_count = count;
+ return err;
+ } else if (c == 0) {
+ break;
+ } else {
+ net_event_count += c;
+ timeout.sec = timeout.msec = 0;
+ }
+ } while (c > 0 && net_event_count < PJSIP_MAX_NET_EVENTS);
+
+ count += net_event_count;
+ if (p_count)
+ *p_count = count;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Handle events.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_handle_events(pjsip_endpoint *endpt,
+ const pj_time_val *max_timeout)
+{
+ return pjsip_endpt_handle_events2(endpt, max_timeout, NULL);
+}
+
+/*
+ * Schedule timer.
+ */
+#if PJ_TIMER_DEBUG
+PJ_DEF(pj_status_t) pjsip_endpt_schedule_timer_dbg(pjsip_endpoint *endpt,
+ pj_timer_entry *entry,
+ const pj_time_val *delay,
+ const char *src_file,
+ int src_line)
+{
+ PJ_LOG(6, (THIS_FILE, "pjsip_endpt_schedule_timer(entry=%p, delay=%u.%u)",
+ entry, delay->sec, delay->msec));
+ return pj_timer_heap_schedule_dbg(endpt->timer_heap, entry, delay,
+ src_file, src_line);
+}
+#else
+PJ_DEF(pj_status_t) pjsip_endpt_schedule_timer( pjsip_endpoint *endpt,
+ pj_timer_entry *entry,
+ const pj_time_val *delay )
+{
+ PJ_LOG(6, (THIS_FILE, "pjsip_endpt_schedule_timer(entry=%p, delay=%u.%u)",
+ entry, delay->sec, delay->msec));
+ return pj_timer_heap_schedule( endpt->timer_heap, entry, delay );
+}
+#endif
+
+/*
+ * Cancel the previously registered timer.
+ */
+PJ_DEF(void) pjsip_endpt_cancel_timer( pjsip_endpoint *endpt,
+ pj_timer_entry *entry )
+{
+ PJ_LOG(6, (THIS_FILE, "pjsip_endpt_cancel_timer(entry=%p)", entry));
+ pj_timer_heap_cancel( endpt->timer_heap, entry );
+}
+
+/*
+ * Get the timer heap instance of the SIP endpoint.
+ */
+PJ_DEF(pj_timer_heap_t*) pjsip_endpt_get_timer_heap(pjsip_endpoint *endpt)
+{
+ return endpt->timer_heap;
+}
+
+/*
+ * This is the callback that is called by the transport manager when it
+ * receives a message from the network.
+ */
+static void endpt_on_rx_msg( pjsip_endpoint *endpt,
+ pj_status_t status,
+ pjsip_rx_data *rdata )
+{
+ pjsip_msg *msg = rdata->msg_info.msg;
+
+ if (status != PJ_SUCCESS) {
+ char info[30];
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ info[0] = '\0';
+
+ if (status == PJSIP_EMISSINGHDR) {
+ pj_str_t p;
+
+ p.ptr = info; p.slen = 0;
+
+ if (rdata->msg_info.cid == NULL || rdata->msg_info.cid->id.slen)
+ pj_strcpy2(&p, "Call-ID");
+ if (rdata->msg_info.from == NULL)
+ pj_strcpy2(&p, " From");
+ if (rdata->msg_info.to == NULL)
+ pj_strcpy2(&p, " To");
+ if (rdata->msg_info.via == NULL)
+ pj_strcpy2(&p, " Via");
+ if (rdata->msg_info.cseq == NULL)
+ pj_strcpy2(&p, " CSeq");
+
+ p.ptr[p.slen] = '\0';
+ }
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(1, (THIS_FILE,
+ "Error processing packet from %s:%d: %s %s [code %d]:\n"
+ "%.*s\n"
+ "-- end of packet.",
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ errmsg,
+ info,
+ status,
+ (int)rdata->msg_info.len,
+ rdata->msg_info.msg_buf));
+ return;
+ }
+
+ PJ_LOG(5, (THIS_FILE, "Processing incoming message: %s",
+ pjsip_rx_data_get_info(rdata)));
+ pj_log_push_indent();
+
+#if defined(PJSIP_CHECK_VIA_SENT_BY) && PJSIP_CHECK_VIA_SENT_BY != 0
+ /* For response, check that the value in Via sent-by match the transport.
+ * If not matched, silently drop the response.
+ * Ref: RFC3261 Section 18.1.2 Receiving Response
+ */
+ if (msg->type == PJSIP_RESPONSE_MSG) {
+ const pj_str_t *local_addr;
+ int port = rdata->msg_info.via->sent_by.port;
+ pj_bool_t mismatch = PJ_FALSE;
+ if (port == 0) {
+ pjsip_transport_type_e type;
+ type = (pjsip_transport_type_e)rdata->tp_info.transport->key.type;
+ port = pjsip_transport_get_default_port_for_type(type);
+ }
+ local_addr = &rdata->tp_info.transport->local_name.host;
+
+ if (pj_strcmp(&rdata->msg_info.via->sent_by.host, local_addr) != 0) {
+
+ /* The RFC says that we should drop response when sent-by
+ * address mismatch. But it could happen (e.g. with SER) when
+ * endpoint with private IP is sending request to public
+ * server.
+
+ mismatch = PJ_TRUE;
+
+ */
+
+ } else if (port != rdata->tp_info.transport->local_name.port) {
+ /* Port or address mismatch, we should discard response */
+ /* But we saw one implementation (we don't want to name it to
+ * protect the innocence) which put wrong sent-by port although
+ * the "rport" parameter is correct.
+ * So we discard the response only if the port doesn't match
+ * both the port in sent-by and rport. We try to be lenient here!
+ */
+ if (rdata->msg_info.via->rport_param !=
+ rdata->tp_info.transport->local_name.port)
+ mismatch = PJ_TRUE;
+ else {
+ PJ_LOG(4,(THIS_FILE, "Message %s from %s has mismatch port in "
+ "sent-by but the rport parameter is "
+ "correct",
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name));
+ }
+ }
+
+ if (mismatch) {
+ PJ_TODO(ENDPT_REPORT_WHEN_DROPPING_MESSAGE);
+ PJ_LOG(4,(THIS_FILE, "Dropping response %s from %s:%d because "
+ "sent-by is mismatch",
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port));
+ pj_log_pop_indent();
+ return;
+ }
+ }
+#endif
+
+
+ /* Distribute to modules, starting from modules with highest priority */
+ LOCK_MODULE_ACCESS(endpt);
+
+ if (msg->type == PJSIP_REQUEST_MSG) {
+ pjsip_module *mod;
+ pj_bool_t handled = PJ_FALSE;
+
+ mod = endpt->module_list.next;
+ while (mod != &endpt->module_list) {
+ if (mod->on_rx_request)
+ handled = (*mod->on_rx_request)(rdata);
+ if (handled)
+ break;
+ mod = mod->next;
+ }
+
+ /* No module is able to handle the request. */
+ if (!handled) {
+ PJ_TODO(ENDPT_RESPOND_UNHANDLED_REQUEST);
+ PJ_LOG(4,(THIS_FILE, "Message %s from %s:%d was dropped/unhandled by"
+ " any modules",
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port));
+ }
+
+ } else {
+ pjsip_module *mod;
+ pj_bool_t handled = PJ_FALSE;
+
+ mod = endpt->module_list.next;
+ while (mod != &endpt->module_list) {
+ if (mod->on_rx_response)
+ handled = (*mod->on_rx_response)(rdata);
+ if (handled)
+ break;
+ mod = mod->next;
+ }
+
+ if (!handled) {
+ PJ_LOG(4,(THIS_FILE, "Message %s from %s:%d was dropped/unhandled"
+ " by any modules",
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port));
+ }
+ }
+
+ UNLOCK_MODULE_ACCESS(endpt);
+
+ /* Must clear mod_data before returning rdata to transport, since
+ * rdata may be reused.
+ */
+ pj_bzero(&rdata->endpt_info, sizeof(rdata->endpt_info));
+
+ pj_log_pop_indent();
+}
+
+/*
+ * This callback is called by transport manager before message is sent.
+ * Modules may inspect the message before it's actually sent.
+ */
+static pj_status_t endpt_on_tx_msg( pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata )
+{
+ pj_status_t status = PJ_SUCCESS;
+ pjsip_module *mod;
+
+ /* Distribute to modules, starting from modules with LOWEST priority */
+ LOCK_MODULE_ACCESS(endpt);
+
+ mod = endpt->module_list.prev;
+ if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+ while (mod != &endpt->module_list) {
+ if (mod->on_tx_request)
+ status = (*mod->on_tx_request)(tdata);
+ if (status != PJ_SUCCESS)
+ break;
+ mod = mod->prev;
+ }
+
+ } else {
+ while (mod != &endpt->module_list) {
+ if (mod->on_tx_response)
+ status = (*mod->on_tx_response)(tdata);
+ if (status != PJ_SUCCESS)
+ break;
+ mod = mod->prev;
+ }
+ }
+
+ UNLOCK_MODULE_ACCESS(endpt);
+
+ return status;
+}
+
+
+/*
+ * Create transmit data buffer.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_tdata( pjsip_endpoint *endpt,
+ pjsip_tx_data **p_tdata)
+{
+ return pjsip_tx_data_create(endpt->transport_mgr, p_tdata);
+}
+
+/*
+ * Create the DNS resolver instance.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_resolver(pjsip_endpoint *endpt,
+ pj_dns_resolver **p_resv)
+{
+#if PJSIP_HAS_RESOLVER
+ PJ_ASSERT_RETURN(endpt && p_resv, PJ_EINVAL);
+ return pj_dns_resolver_create( endpt->pf, NULL, 0, endpt->timer_heap,
+ endpt->ioqueue, p_resv);
+#else
+ PJ_UNUSED_ARG(endpt);
+ PJ_UNUSED_ARG(p_resv);
+ pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)");
+ return PJ_EINVALIDOP;
+#endif
+}
+
+/*
+ * Set DNS resolver to be used by the SIP resolver.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_set_resolver( pjsip_endpoint *endpt,
+ pj_dns_resolver *resv)
+{
+ return pjsip_resolver_set_resolver(endpt->resolver, resv);
+}
+
+/*
+ * Get the DNS resolver being used by the SIP resolver.
+ */
+PJ_DEF(pj_dns_resolver*) pjsip_endpt_get_resolver(pjsip_endpoint *endpt)
+{
+ PJ_ASSERT_RETURN(endpt, NULL);
+ return pjsip_resolver_get_resolver(endpt->resolver);
+}
+
+/*
+ * Resolve
+ */
+PJ_DEF(void) pjsip_endpt_resolve( pjsip_endpoint *endpt,
+ pj_pool_t *pool,
+ pjsip_host_info *target,
+ void *token,
+ pjsip_resolver_callback *cb)
+{
+ pjsip_resolve( endpt->resolver, pool, target, token, cb);
+}
+
+/*
+ * Get transport manager.
+ */
+PJ_DEF(pjsip_tpmgr*) pjsip_endpt_get_tpmgr(pjsip_endpoint *endpt)
+{
+ return endpt->transport_mgr;
+}
+
+/*
+ * Get ioqueue instance.
+ */
+PJ_DEF(pj_ioqueue_t*) pjsip_endpt_get_ioqueue(pjsip_endpoint *endpt)
+{
+ return endpt->ioqueue;
+}
+
+/*
+ * Find/create transport.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport(pjsip_endpoint *endpt,
+ pjsip_transport_type_e type,
+ const pj_sockaddr_t *remote,
+ int addr_len,
+ const pjsip_tpselector *sel,
+ pjsip_transport **transport)
+{
+ return pjsip_tpmgr_acquire_transport(endpt->transport_mgr, type,
+ remote, addr_len, sel, transport);
+}
+
+
+/*
+ * Find/create transport.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport2(pjsip_endpoint *endpt,
+ pjsip_transport_type_e type,
+ const pj_sockaddr_t *remote,
+ int addr_len,
+ const pjsip_tpselector *sel,
+ pjsip_tx_data *tdata,
+ pjsip_transport **transport)
+{
+ return pjsip_tpmgr_acquire_transport2(endpt->transport_mgr, type, remote,
+ addr_len, sel, tdata, transport);
+}
+
+
+/*
+ * Report error.
+ */
+PJ_DEF(void) pjsip_endpt_log_error( pjsip_endpoint *endpt,
+ const char *sender,
+ pj_status_t error_code,
+ const char *format,
+ ... )
+{
+#if PJ_LOG_MAX_LEVEL > 0
+ char newformat[256];
+ int len;
+ va_list marker;
+
+ va_start(marker, format);
+
+ PJ_UNUSED_ARG(endpt);
+
+ len = pj_ansi_strlen(format);
+ if (len < (int)sizeof(newformat)-30) {
+ pj_str_t errstr;
+
+ pj_ansi_strcpy(newformat, format);
+ pj_ansi_snprintf(newformat+len, sizeof(newformat)-len-1,
+ ": [err %d] ", error_code);
+ len += pj_ansi_strlen(newformat+len);
+
+ errstr = pj_strerror( error_code, newformat+len,
+ sizeof(newformat)-len-1);
+
+ len += errstr.slen;
+ newformat[len] = '\0';
+
+ pj_log(sender, 1, newformat, marker);
+ } else {
+ pj_log(sender, 1, format, marker);
+ }
+
+ va_end(marker);
+#else
+ PJ_UNUSED_ARG(format);
+ PJ_UNUSED_ARG(error_code);
+ PJ_UNUSED_ARG(sender);
+ PJ_UNUSED_ARG(endpt);
+#endif
+}
+
+
+/*
+ * Dump endpoint.
+ */
+PJ_DEF(void) pjsip_endpt_dump( pjsip_endpoint *endpt, pj_bool_t detail )
+{
+#if PJ_LOG_MAX_LEVEL >= 3
+ PJ_LOG(5, (THIS_FILE, "pjsip_endpt_dump()"));
+
+ /* Lock mutex. */
+ pj_mutex_lock(endpt->mutex);
+
+ PJ_LOG(3, (THIS_FILE, "Dumping endpoint %p:", endpt));
+
+ /* Dumping pool factory. */
+ pj_pool_factory_dump(endpt->pf, detail);
+
+ /* Pool health. */
+ PJ_LOG(3, (THIS_FILE," Endpoint pool capacity=%u, used_size=%u",
+ pj_pool_get_capacity(endpt->pool),
+ pj_pool_get_used_size(endpt->pool)));
+
+ /* Resolver */
+#if PJSIP_HAS_RESOLVER
+ if (pjsip_endpt_get_resolver(endpt)) {
+ pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), detail);
+ }
+#endif
+
+ /* Transports.
+ */
+ pjsip_tpmgr_dump_transports( endpt->transport_mgr );
+
+ /* Timer. */
+#if PJ_TIMER_DEBUG
+ pj_timer_heap_dump(endpt->timer_heap);
+#else
+ PJ_LOG(3,(THIS_FILE, " Timer heap has %u entries",
+ pj_timer_heap_count(endpt->timer_heap)));
+#endif
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(endpt->mutex);
+#else
+ PJ_UNUSED_ARG(endpt);
+ PJ_UNUSED_ARG(detail);
+ PJ_LOG(3,(THIS_FILE, "pjsip_end_dump: can't dump because it's disabled."));
+#endif
+}
+
+
+PJ_DEF(pj_status_t) pjsip_endpt_atexit( pjsip_endpoint *endpt,
+ pjsip_endpt_exit_callback func)
+{
+ exit_cb *new_cb;
+
+ PJ_ASSERT_RETURN(endpt && func, PJ_EINVAL);
+
+ new_cb = PJ_POOL_ZALLOC_T(endpt->pool, exit_cb);
+ new_cb->func = func;
+
+ pj_mutex_lock(endpt->mutex);
+ pj_list_push_back(&endpt->exit_cb_list, new_cb);
+ pj_mutex_unlock(endpt->mutex);
+
+ return PJ_SUCCESS;
+}
diff --git a/pjsip/src/pjsip/sip_endpoint_wrap.cpp b/pjsip/src/pjsip/sip_endpoint_wrap.cpp
new file mode 100644
index 0000000..1386023
--- /dev/null
+++ b/pjsip/src/pjsip/sip_endpoint_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_endpoint_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_endpoint.c"
diff --git a/pjsip/src/pjsip/sip_errno.c b/pjsip/src/pjsip/sip_errno.c
new file mode 100644
index 0000000..211db39
--- /dev/null
+++ b/pjsip/src/pjsip/sip_errno.c
@@ -0,0 +1,211 @@
+/* $Id: sip_errno.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_msg.h>
+#include <pj/string.h>
+#include <pj/errno.h>
+
+/* PJSIP's own error codes/messages
+ * MUST KEEP THIS ARRAY SORTED!!
+ */
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+static const struct
+{
+ int code;
+ const char *msg;
+} err_str[] =
+{
+ /* Generic SIP errors */
+ PJ_BUILD_ERR( PJSIP_EBUSY, "Object is busy" ),
+ PJ_BUILD_ERR( PJSIP_ETYPEEXISTS , "Object with the same type exists" ),
+ PJ_BUILD_ERR( PJSIP_ESHUTDOWN, "SIP stack shutting down" ),
+ PJ_BUILD_ERR( PJSIP_ENOTINITIALIZED,"SIP object is not initialized." ),
+ PJ_BUILD_ERR( PJSIP_ENOROUTESET, "Missing route set (for tel: URI)" ),
+
+ /* Messaging errors */
+ PJ_BUILD_ERR( PJSIP_EINVALIDMSG, "Invalid message/syntax error" ),
+ PJ_BUILD_ERR( PJSIP_ENOTREQUESTMSG, "Expecting request message"),
+ PJ_BUILD_ERR( PJSIP_ENOTRESPONSEMSG,"Expecting response message"),
+ PJ_BUILD_ERR( PJSIP_EMSGTOOLONG, "Message too long" ),
+ PJ_BUILD_ERR( PJSIP_EPARTIALMSG, "Partial message" ),
+
+ PJ_BUILD_ERR( PJSIP_EINVALIDSTATUS, "Invalid/unexpected SIP status code"),
+
+ PJ_BUILD_ERR( PJSIP_EINVALIDURI, "Invalid URI" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDSCHEME, "Invalid URI scheme" ),
+ PJ_BUILD_ERR( PJSIP_EMISSINGREQURI, "Missing Request-URI" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDREQURI, "Invalid Request URI" ),
+ PJ_BUILD_ERR( PJSIP_EURITOOLONG, "URI is too long" ),
+
+ PJ_BUILD_ERR( PJSIP_EMISSINGHDR, "Missing required header(s)" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDHDR, "Invalid header field"),
+ PJ_BUILD_ERR( PJSIP_EINVALIDVIA, "Invalid Via header" ),
+ PJ_BUILD_ERR( PJSIP_EMULTIPLEVIA, "Multiple Via headers in response" ),
+
+ PJ_BUILD_ERR( PJSIP_EMISSINGBODY, "Missing message body" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDMETHOD, "Invalid/unexpected method" ),
+
+ /* Transport errors */
+ PJ_BUILD_ERR( PJSIP_EUNSUPTRANSPORT,"Unsupported transport"),
+ PJ_BUILD_ERR( PJSIP_EPENDINGTX, "Transmit buffer already pending"),
+ PJ_BUILD_ERR( PJSIP_ERXOVERFLOW, "Rx buffer overflow"),
+ PJ_BUILD_ERR( PJSIP_EBUFDESTROYED, "Buffer destroyed"),
+ PJ_BUILD_ERR( PJSIP_ETPNOTSUITABLE, "Unsuitable transport selected"),
+ PJ_BUILD_ERR( PJSIP_ETPNOTAVAIL, "Transport not available for use"),
+
+ /* Transaction errors */
+ PJ_BUILD_ERR( PJSIP_ETSXDESTROYED, "Transaction has been destroyed"),
+ PJ_BUILD_ERR( PJSIP_ENOTSX, "No transaction is associated with the object "
+ "(expecting stateful processing)" ),
+
+ /* URI comparison status */
+ PJ_BUILD_ERR( PJSIP_ECMPSCHEME, "URI scheme mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPUSER, "URI user part mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPPASSWD, "URI password part mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPHOST, "URI host part mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPPORT, "URI port mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPTRANSPORTPRM,"URI transport param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPTTLPARAM, "URI ttl param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPUSERPARAM, "URI user param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPMETHODPARAM,"URI method param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPMADDRPARAM, "URI maddr param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPOTHERPARAM, "URI other param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPHEADERPARAM,"URI header parameter mismatch" ),
+
+ /* Authentication. */
+ PJ_BUILD_ERR( PJSIP_EFAILEDCREDENTIAL, "Credential failed to authenticate"),
+ PJ_BUILD_ERR( PJSIP_ENOCREDENTIAL, "No suitable credential"),
+ PJ_BUILD_ERR( PJSIP_EINVALIDALGORITHM, "Invalid/unsupported digest algorithm" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDQOP, "Invalid/unsupported digest qop" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDAUTHSCHEME,"Unsupported authentication scheme" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHNOPREVCHAL, "No previous challenge" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHNOAUTH, "No suitable authorization header" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHACCNOTFOUND, "Account or credential not found" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHACCDISABLED, "Account or credential is disabled" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHINVALIDREALM, "Invalid authorization realm"),
+ PJ_BUILD_ERR( PJSIP_EAUTHINVALIDDIGEST,"Invalid authorization digest" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHSTALECOUNT, "Maximum number of stale retries exceeded"),
+ PJ_BUILD_ERR( PJSIP_EAUTHINNONCE, "Invalid nonce value in authentication challenge"),
+ PJ_BUILD_ERR( PJSIP_EAUTHINAKACRED, "Invalid AKA credential"),
+ PJ_BUILD_ERR( PJSIP_EAUTHNOCHAL, "No challenge is found"),
+
+ /* UA/dialog layer. */
+ PJ_BUILD_ERR( PJSIP_EMISSINGTAG, "Missing From/To tag parameter" ),
+ PJ_BUILD_ERR( PJSIP_ENOTREFER, "Expecting REFER request") ,
+ PJ_BUILD_ERR( PJSIP_ENOREFERSESSION,"Not associated with REFER subscription"),
+
+ /* Invite session. */
+ PJ_BUILD_ERR( PJSIP_ESESSIONTERMINATED, "INVITE session already terminated" ),
+ PJ_BUILD_ERR( PJSIP_ESESSIONSTATE, "Invalid INVITE session state" ),
+ PJ_BUILD_ERR( PJSIP_ESESSIONINSECURE, "Require secure session/transport"),
+
+ /* SSL errors */
+ PJ_BUILD_ERR( PJSIP_TLS_EUNKNOWN, "Unknown TLS error" ),
+ PJ_BUILD_ERR( PJSIP_TLS_EINVMETHOD, "Invalid SSL protocol method" ),
+ PJ_BUILD_ERR( PJSIP_TLS_ECACERT, "Error loading/verifying SSL CA list file"),
+ PJ_BUILD_ERR( PJSIP_TLS_ECERTFILE, "Error loading SSL certificate chain file"),
+ PJ_BUILD_ERR( PJSIP_TLS_EKEYFILE, "Error adding private key from SSL certificate file"),
+ PJ_BUILD_ERR( PJSIP_TLS_ECIPHER, "Error setting SSL cipher list"),
+ PJ_BUILD_ERR( PJSIP_TLS_ECTX, "Error creating SSL context"),
+ PJ_BUILD_ERR( PJSIP_TLS_ESSLCONN, "Error creating SSL connection object"),
+ PJ_BUILD_ERR( PJSIP_TLS_ECONNECT, "Unknown error when performing SSL connect()"),
+ PJ_BUILD_ERR( PJSIP_TLS_EACCEPT, "Unknown error when performing SSL accept()"),
+ PJ_BUILD_ERR( PJSIP_TLS_ESEND, "Unknown error when sending SSL data"),
+ PJ_BUILD_ERR( PJSIP_TLS_EREAD, "Unknown error when reading SSL data"),
+ PJ_BUILD_ERR( PJSIP_TLS_ETIMEDOUT, "SSL negotiation has timed out"),
+ PJ_BUILD_ERR( PJSIP_TLS_ECERTVERIF, "SSL certificate verification error"),
+};
+
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+/*
+ * pjsip_strerror()
+ */
+PJ_DEF(pj_str_t) pjsip_strerror( pj_status_t statcode,
+ char *buf, pj_size_t bufsize )
+{
+ pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+ if (statcode >= PJSIP_ERRNO_START && statcode < PJSIP_ERRNO_START+800)
+ {
+ /* Status code. */
+ const pj_str_t *status_text =
+ pjsip_get_status_text(PJSIP_ERRNO_TO_SIP_STATUS(statcode));
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, status_text, bufsize);
+ return errstr;
+ }
+ else if (statcode >= PJSIP_ERRNO_START_PJSIP &&
+ statcode < PJSIP_ERRNO_START_PJSIP + 1000)
+ {
+ /* Find the error in the table.
+ * Use binary search!
+ */
+ int first = 0;
+ int n = PJ_ARRAY_SIZE(err_str);
+
+ while (n > 0) {
+ int half = n/2;
+ int mid = first + half;
+
+ if (err_str[mid].code < statcode) {
+ first = mid+1;
+ n -= (half+1);
+ } else if (err_str[mid].code > statcode) {
+ n = half;
+ } else {
+ first = mid;
+ break;
+ }
+ }
+
+
+ if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+ pj_str_t msg;
+
+ msg.ptr = (char*)err_str[first].msg;
+ msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ }
+ }
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+ /* Error not found. */
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown pjsip error %d",
+ statcode);
+
+ return errstr;
+
+}
+
diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c
new file mode 100644
index 0000000..edbf367
--- /dev/null
+++ b/pjsip/src/pjsip/sip_msg.c
@@ -0,0 +1,2218 @@
+/* $Id: sip_msg.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/print_util.h>
+#include <pjsip/sip_errno.h>
+#include <pj/ctype.h>
+#include <pj/guid.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pjlib-util/string.h>
+
+PJ_DEF_DATA(const pjsip_method) pjsip_invite_method =
+ { PJSIP_INVITE_METHOD, { "INVITE",6 }};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_cancel_method =
+ { PJSIP_CANCEL_METHOD, { "CANCEL",6 }};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_ack_method =
+ { PJSIP_ACK_METHOD, { "ACK",3}};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_bye_method =
+ { PJSIP_BYE_METHOD, { "BYE",3}};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_register_method =
+ { PJSIP_REGISTER_METHOD, { "REGISTER", 8}};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_options_method =
+ { PJSIP_OPTIONS_METHOD, { "OPTIONS",7}};
+
+
+/** INVITE method constant. */
+PJ_DEF(const pjsip_method*) pjsip_get_invite_method(void)
+{
+ return &pjsip_invite_method;
+}
+
+/** CANCEL method constant. */
+PJ_DEF(const pjsip_method*) pjsip_get_cancel_method(void)
+{
+ return &pjsip_cancel_method;
+}
+
+/** ACK method constant. */
+PJ_DEF(const pjsip_method*) pjsip_get_ack_method(void)
+{
+ return &pjsip_ack_method;
+}
+
+/** BYE method constant. */
+PJ_DEF(const pjsip_method*) pjsip_get_bye_method(void)
+{
+ return &pjsip_bye_method;
+}
+
+/** REGISTER method constant.*/
+PJ_DEF(const pjsip_method*) pjsip_get_register_method(void)
+{
+ return &pjsip_register_method;
+}
+
+/** OPTIONS method constant. */
+PJ_DEF(const pjsip_method*) pjsip_get_options_method(void)
+{
+ return &pjsip_options_method;
+}
+
+
+static const pj_str_t *method_names[] =
+{
+ &pjsip_invite_method.name,
+ &pjsip_cancel_method.name,
+ &pjsip_ack_method.name,
+ &pjsip_bye_method.name,
+ &pjsip_register_method.name,
+ &pjsip_options_method.name
+};
+
+const pjsip_hdr_name_info_t pjsip_hdr_names[] =
+{
+ { "Accept", 6, NULL }, // PJSIP_H_ACCEPT,
+ { "Accept-Encoding", 15, NULL }, // PJSIP_H_ACCEPT_ENCODING,
+ { "Accept-Language", 15, NULL }, // PJSIP_H_ACCEPT_LANGUAGE,
+ { "Alert-Info", 10, NULL }, // PJSIP_H_ALERT_INFO,
+ { "Allow", 5, NULL }, // PJSIP_H_ALLOW,
+ { "Authentication-Info",19, NULL }, // PJSIP_H_AUTHENTICATION_INFO,
+ { "Authorization", 13, NULL }, // PJSIP_H_AUTHORIZATION,
+ { "Call-ID", 7, "i" }, // PJSIP_H_CALL_ID,
+ { "Call-Info", 9, NULL }, // PJSIP_H_CALL_INFO,
+ { "Contact", 7, "m" }, // PJSIP_H_CONTACT,
+ { "Content-Disposition",19, NULL }, // PJSIP_H_CONTENT_DISPOSITION,
+ { "Content-Encoding", 16, "e" }, // PJSIP_H_CONTENT_ENCODING,
+ { "Content-Language", 16, NULL }, // PJSIP_H_CONTENT_LANGUAGE,
+ { "Content-Length", 14, "l" }, // PJSIP_H_CONTENT_LENGTH,
+ { "Content-Type", 12, "c" }, // PJSIP_H_CONTENT_TYPE,
+ { "CSeq", 4, NULL }, // PJSIP_H_CSEQ,
+ { "Date", 4, NULL }, // PJSIP_H_DATE,
+ { "Error-Info", 10, NULL }, // PJSIP_H_ERROR_INFO,
+ { "Expires", 7, NULL }, // PJSIP_H_EXPIRES,
+ { "From", 4, "f" }, // PJSIP_H_FROM,
+ { "In-Reply-To", 11, NULL }, // PJSIP_H_IN_REPLY_TO,
+ { "Max-Forwards", 12, NULL }, // PJSIP_H_MAX_FORWARDS,
+ { "MIME-Version", 12, NULL }, // PJSIP_H_MIME_VERSION,
+ { "Min-Expires", 11, NULL }, // PJSIP_H_MIN_EXPIRES,
+ { "Organization", 12, NULL }, // PJSIP_H_ORGANIZATION,
+ { "Priority", 8, NULL }, // PJSIP_H_PRIORITY,
+ { "Proxy-Authenticate", 18, NULL }, // PJSIP_H_PROXY_AUTHENTICATE,
+ { "Proxy-Authorization",19, NULL }, // PJSIP_H_PROXY_AUTHORIZATION,
+ { "Proxy-Require", 13, NULL }, // PJSIP_H_PROXY_REQUIRE,
+ { "Record-Route", 12, NULL }, // PJSIP_H_RECORD_ROUTE,
+ { "Reply-To", 8, NULL }, // PJSIP_H_REPLY_TO,
+ { "Require", 7, NULL }, // PJSIP_H_REQUIRE,
+ { "Retry-After", 11, NULL }, // PJSIP_H_RETRY_AFTER,
+ { "Route", 5, NULL }, // PJSIP_H_ROUTE,
+ { "Server", 6, NULL }, // PJSIP_H_SERVER,
+ { "Subject", 7, "s" }, // PJSIP_H_SUBJECT,
+ { "Supported", 9, "k" }, // PJSIP_H_SUPPORTED,
+ { "Timestamp", 9, NULL }, // PJSIP_H_TIMESTAMP,
+ { "To", 2, "t" }, // PJSIP_H_TO,
+ { "Unsupported", 11, NULL }, // PJSIP_H_UNSUPPORTED,
+ { "User-Agent", 10, NULL }, // PJSIP_H_USER_AGENT,
+ { "Via", 3, "v" }, // PJSIP_H_VIA,
+ { "Warning", 7, NULL }, // PJSIP_H_WARNING,
+ { "WWW-Authenticate", 16, NULL }, // PJSIP_H_WWW_AUTHENTICATE,
+
+ { "_Unknown-Header", 15, NULL }, // PJSIP_H_OTHER,
+};
+
+pj_bool_t pjsip_use_compact_form = PJSIP_ENCODE_SHORT_HNAME;
+
+static pj_str_t status_phrase[710];
+static int print_media_type(char *buf, unsigned len,
+ const pjsip_media_type *media);
+
+static int init_status_phrase()
+{
+ unsigned i;
+ pj_str_t default_reason_phrase = { "Default status message", 22};
+
+ for (i=0; i<PJ_ARRAY_SIZE(status_phrase); ++i)
+ status_phrase[i] = default_reason_phrase;
+
+ pj_strset2( &status_phrase[100], "Trying");
+ pj_strset2( &status_phrase[180], "Ringing");
+ pj_strset2( &status_phrase[181], "Call Is Being Forwarded");
+ pj_strset2( &status_phrase[182], "Queued");
+ pj_strset2( &status_phrase[183], "Session Progress");
+
+ pj_strset2( &status_phrase[200], "OK");
+ pj_strset2( &status_phrase[202], "Accepted");
+
+ pj_strset2( &status_phrase[300], "Multiple Choices");
+ pj_strset2( &status_phrase[301], "Moved Permanently");
+ pj_strset2( &status_phrase[302], "Moved Temporarily");
+ pj_strset2( &status_phrase[305], "Use Proxy");
+ pj_strset2( &status_phrase[380], "Alternative Service");
+
+ pj_strset2( &status_phrase[400], "Bad Request");
+ pj_strset2( &status_phrase[401], "Unauthorized");
+ pj_strset2( &status_phrase[402], "Payment Required");
+ pj_strset2( &status_phrase[403], "Forbidden");
+ pj_strset2( &status_phrase[404], "Not Found");
+ pj_strset2( &status_phrase[405], "Method Not Allowed");
+ pj_strset2( &status_phrase[406], "Not Acceptable");
+ pj_strset2( &status_phrase[407], "Proxy Authentication Required");
+ pj_strset2( &status_phrase[408], "Request Timeout");
+ pj_strset2( &status_phrase[410], "Gone");
+ pj_strset2( &status_phrase[413], "Request Entity Too Large");
+ pj_strset2( &status_phrase[414], "Request URI Too Long");
+ pj_strset2( &status_phrase[415], "Unsupported Media Type");
+ pj_strset2( &status_phrase[416], "Unsupported URI Scheme");
+ pj_strset2( &status_phrase[420], "Bad Extension");
+ pj_strset2( &status_phrase[421], "Extension Required");
+ pj_strset2( &status_phrase[422], "Session Timer Too Small");
+ pj_strset2( &status_phrase[423], "Interval Too Brief");
+ pj_strset2( &status_phrase[480], "Temporarily Unavailable");
+ pj_strset2( &status_phrase[481], "Call/Transaction Does Not Exist");
+ pj_strset2( &status_phrase[482], "Loop Detected");
+ pj_strset2( &status_phrase[483], "Too Many Hops");
+ pj_strset2( &status_phrase[484], "Address Incompleted");
+ pj_strset2( &status_phrase[485], "Ambiguous");
+ pj_strset2( &status_phrase[486], "Busy Here");
+ pj_strset2( &status_phrase[487], "Request Terminated");
+ pj_strset2( &status_phrase[488], "Not Acceptable Here");
+ pj_strset2( &status_phrase[489], "Bad Event");
+ pj_strset2( &status_phrase[490], "Request Updated");
+ pj_strset2( &status_phrase[491], "Request Pending");
+ pj_strset2( &status_phrase[493], "Undecipherable");
+
+ pj_strset2( &status_phrase[500], "Internal Server Error");
+ pj_strset2( &status_phrase[501], "Not Implemented");
+ pj_strset2( &status_phrase[502], "Bad Gateway");
+ pj_strset2( &status_phrase[503], "Service Unavailable");
+ pj_strset2( &status_phrase[504], "Server Timeout");
+ pj_strset2( &status_phrase[505], "Version Not Supported");
+ pj_strset2( &status_phrase[513], "Message Too Large");
+ pj_strset2( &status_phrase[580], "Precondition Failure");
+
+ pj_strset2( &status_phrase[600], "Busy Everywhere");
+ pj_strset2( &status_phrase[603], "Decline");
+ pj_strset2( &status_phrase[604], "Does Not Exist Anywhere");
+ pj_strset2( &status_phrase[606], "Not Acceptable");
+
+ pj_strset2( &status_phrase[701], "No response from destination server");
+ pj_strset2( &status_phrase[702], "Unable to resolve destination server");
+ pj_strset2( &status_phrase[703], "Error sending message to destination server");
+
+ return 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Method.
+ */
+
+PJ_DEF(void) pjsip_method_init( pjsip_method *m,
+ pj_pool_t *pool,
+ const pj_str_t *str)
+{
+ pj_str_t dup;
+ pjsip_method_init_np(m, pj_strdup(pool, &dup, str));
+}
+
+PJ_DEF(void) pjsip_method_set( pjsip_method *m, pjsip_method_e me )
+{
+ pj_assert(me < PJSIP_OTHER_METHOD);
+ m->id = me;
+ m->name = *method_names[me];
+}
+
+PJ_DEF(void) pjsip_method_init_np(pjsip_method *m,
+ pj_str_t *str)
+{
+ unsigned i;
+ for (i=0; i<PJ_ARRAY_SIZE(method_names); ++i) {
+ if (pj_memcmp(str->ptr, method_names[i]->ptr, str->slen)==0 ||
+ pj_stricmp(str, method_names[i])==0)
+ {
+ m->id = (pjsip_method_e)i;
+ m->name = *method_names[i];
+ return;
+ }
+ }
+ m->id = PJSIP_OTHER_METHOD;
+ m->name = *str;
+}
+
+PJ_DEF(void) pjsip_method_copy( pj_pool_t *pool,
+ pjsip_method *method,
+ const pjsip_method *rhs )
+{
+ method->id = rhs->id;
+ if (rhs->id != PJSIP_OTHER_METHOD) {
+ method->name = rhs->name;
+ } else {
+ pj_strdup(pool, &method->name, &rhs->name);
+ }
+}
+
+
+PJ_DEF(int) pjsip_method_cmp( const pjsip_method *m1, const pjsip_method *m2)
+{
+ if (m1->id == m2->id) {
+ if (m1->id != PJSIP_OTHER_METHOD)
+ return 0;
+ /* Method comparison is case sensitive! */
+ return pj_strcmp(&m1->name, &m2->name);
+ }
+
+ return ( m1->id < m2->id ) ? -1 : 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Message.
+ */
+
+PJ_DEF(pjsip_msg*) pjsip_msg_create( pj_pool_t *pool, pjsip_msg_type_e type)
+{
+ pjsip_msg *msg = PJ_POOL_ALLOC_T(pool, pjsip_msg);
+ pj_list_init(&msg->hdr);
+ msg->type = type;
+ msg->body = NULL;
+ return msg;
+}
+
+PJ_DEF(pjsip_msg*) pjsip_msg_clone( pj_pool_t *pool, const pjsip_msg *src)
+{
+ pjsip_msg *dst;
+ const pjsip_hdr *sh;
+
+ dst = pjsip_msg_create(pool, src->type);
+
+ /* Clone request/status line */
+ if (src->type == PJSIP_REQUEST_MSG) {
+ pjsip_method_copy(pool, &dst->line.req.method, &src->line.req.method);
+ dst->line.req.uri = (pjsip_uri*) pjsip_uri_clone(pool,
+ src->line.req.uri);
+ } else {
+ dst->line.status.code = src->line.status.code;
+ pj_strdup(pool, &dst->line.status.reason, &src->line.status.reason);
+ }
+
+ /* Clone headers */
+ sh = src->hdr.next;
+ while (sh != &src->hdr) {
+ pjsip_hdr *dh = (pjsip_hdr*) pjsip_hdr_clone(pool, sh);
+ pjsip_msg_add_hdr(dst, dh);
+ sh = sh->next;
+ }
+
+ /* Clone message body */
+ if (src->body) {
+ dst->body = pjsip_msg_body_clone(pool, src->body);
+ }
+
+ return dst;
+}
+
+PJ_DEF(void*) pjsip_msg_find_hdr( const pjsip_msg *msg,
+ pjsip_hdr_e hdr_type, const void *start)
+{
+ const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=&msg->hdr;
+
+ if (hdr == NULL) {
+ hdr = msg->hdr.next;
+ }
+ for (; hdr!=end; hdr = hdr->next) {
+ if (hdr->type == hdr_type)
+ return (void*)hdr;
+ }
+ return NULL;
+}
+
+PJ_DEF(void*) pjsip_msg_find_hdr_by_name( const pjsip_msg *msg,
+ const pj_str_t *name,
+ const void *start)
+{
+ const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr;
+
+ if (hdr == NULL) {
+ hdr = msg->hdr.next;
+ }
+ for (; hdr!=end; hdr = hdr->next) {
+ if (pj_stricmp(&hdr->name, name) == 0)
+ return (void*)hdr;
+ }
+ return NULL;
+}
+
+PJ_DEF(void*) pjsip_msg_find_hdr_by_names( const pjsip_msg *msg,
+ const pj_str_t *name,
+ const pj_str_t *sname,
+ const void *start)
+{
+ const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr;
+
+ if (hdr == NULL) {
+ hdr = msg->hdr.next;
+ }
+ for (; hdr!=end; hdr = hdr->next) {
+ if (pj_stricmp(&hdr->name, name) == 0)
+ return (void*)hdr;
+ if (pj_stricmp(&hdr->name, sname) == 0)
+ return (void*)hdr;
+ }
+ return NULL;
+}
+
+PJ_DEF(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg,
+ pjsip_hdr_e hdr_type, void *start)
+{
+ pjsip_hdr *hdr = (pjsip_hdr*) pjsip_msg_find_hdr(msg, hdr_type, start);
+ if (hdr) {
+ pj_list_erase(hdr);
+ }
+ return hdr;
+}
+
+PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg,
+ char *buf, pj_size_t size)
+{
+ char *p=buf, *end=buf+size;
+ int len;
+ pjsip_hdr *hdr;
+ pj_str_t clen_hdr = { "Content-Length: ", 16};
+
+ if (pjsip_use_compact_form) {
+ clen_hdr.ptr = "l: ";
+ clen_hdr.slen = 3;
+ }
+
+ /* Get a wild guess on how many bytes are typically needed.
+ * We'll check this later in detail, but this serves as a quick check.
+ */
+ if (size < 256)
+ return -1;
+
+ /* Print request line or status line depending on message type */
+ if (msg->type == PJSIP_REQUEST_MSG) {
+ pjsip_uri *uri;
+
+ /* Add method. */
+ len = msg->line.req.method.name.slen;
+ pj_memcpy(p, msg->line.req.method.name.ptr, len);
+ p += len;
+ *p++ = ' ';
+
+ /* Add URI */
+ uri = (pjsip_uri*) pjsip_uri_get_uri(msg->line.req.uri);
+ len = pjsip_uri_print( PJSIP_URI_IN_REQ_URI, uri, p, end-p);
+ if (len < 1)
+ return -1;
+ p += len;
+
+ /* Add ' SIP/2.0' */
+ if (end-p < 16)
+ return -1;
+ pj_memcpy(p, " SIP/2.0\r\n", 10);
+ p += 10;
+
+ } else {
+
+ /* Add 'SIP/2.0 ' */
+ pj_memcpy(p, "SIP/2.0 ", 8);
+ p += 8;
+
+ /* Add status code. */
+ len = pj_utoa(msg->line.status.code, p);
+ p += len;
+ *p++ = ' ';
+
+ /* Add reason text. */
+ len = msg->line.status.reason.slen;
+ pj_memcpy(p, msg->line.status.reason.ptr, len );
+ p += len;
+
+ /* Add newline. */
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+
+ /* Print each of the headers. */
+ for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) {
+ len = pjsip_hdr_print_on(hdr, p, end-p);
+ if (len < 0)
+ return -1;
+
+ if (len > 0) {
+ p += len;
+ if (p+3 >= end)
+ return -1;
+
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+ }
+
+ /* Process message body. */
+ if (msg->body) {
+ enum { CLEN_SPACE = 5 };
+ char *clen_pos = NULL;
+
+ /* Automaticly adds Content-Type and Content-Length headers, only
+ * if content_type is set in the message body.
+ */
+ if (msg->body->content_type.type.slen) {
+ pj_str_t ctype_hdr = { "Content-Type: ", 14};
+ const pjsip_media_type *media = &msg->body->content_type;
+
+ if (pjsip_use_compact_form) {
+ ctype_hdr.ptr = "c: ";
+ ctype_hdr.slen = 3;
+ }
+
+ /* Add Content-Type header. */
+ if ( (end-p) < 24 + media->type.slen + media->subtype.slen) {
+ return -1;
+ }
+ pj_memcpy(p, ctype_hdr.ptr, ctype_hdr.slen);
+ p += ctype_hdr.slen;
+ p += print_media_type(p, end-p, media);
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Add Content-Length header. */
+ if ((end-p) < clen_hdr.slen + 12 + 2) {
+ return -1;
+ }
+ pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen);
+ p += clen_hdr.slen;
+
+ /* Print blanks after "Content-Length:", this is where we'll put
+ * the content length value after we know the length of the
+ * body.
+ */
+ pj_memset(p, ' ', CLEN_SPACE);
+ clen_pos = p;
+ p += CLEN_SPACE;
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+
+ /* Add blank newline. */
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Print the message body itself. */
+ len = (*msg->body->print_body)(msg->body, p, end-p);
+ if (len < 0) {
+ return -1;
+ }
+ p += len;
+
+ /* Now that we have the length of the body, print this to the
+ * Content-Length header.
+ */
+ if (clen_pos) {
+ char tmp[16];
+ len = pj_utoa(len, tmp);
+ if (len > CLEN_SPACE) len = CLEN_SPACE;
+ pj_memcpy(clen_pos+CLEN_SPACE-len, tmp, len);
+ }
+
+ } else {
+ /* There's no message body.
+ * Add Content-Length with zero value.
+ */
+ if ((end-p) < clen_hdr.slen+8) {
+ return -1;
+ }
+ pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen);
+ p += clen_hdr.slen;
+ *p++ = ' ';
+ *p++ = '0';
+ *p++ = '\r';
+ *p++ = '\n';
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+
+ *p = '\0';
+ return p-buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+PJ_DEF(void*) pjsip_hdr_clone( pj_pool_t *pool, const void *hdr_ptr )
+{
+ const pjsip_hdr *hdr = (const pjsip_hdr*) hdr_ptr;
+ return (*hdr->vptr->clone)(pool, hdr_ptr);
+}
+
+
+PJ_DEF(void*) pjsip_hdr_shallow_clone( pj_pool_t *pool, const void *hdr_ptr )
+{
+ const pjsip_hdr *hdr = (const pjsip_hdr*) hdr_ptr;
+ return (*hdr->vptr->shallow_clone)(pool, hdr_ptr);
+}
+
+PJ_DEF(int) pjsip_hdr_print_on( void *hdr_ptr, char *buf, pj_size_t len)
+{
+ pjsip_hdr *hdr = (pjsip_hdr*) hdr_ptr;
+ return (*hdr->vptr->print_on)(hdr_ptr, buf, len);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Status/Reason Phrase
+ */
+
+PJ_DEF(const pj_str_t*) pjsip_get_status_text(int code)
+{
+ static int is_initialized;
+ if (is_initialized == 0) {
+ is_initialized = 1;
+ init_status_phrase();
+ }
+
+ return (code>=100 &&
+ code<(int)(sizeof(status_phrase)/sizeof(status_phrase[0]))) ?
+ &status_phrase[code] : &status_phrase[0];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Media type
+ */
+/*
+ * Init media type.
+ */
+PJ_DEF(void) pjsip_media_type_init( pjsip_media_type *mt,
+ pj_str_t *type,
+ pj_str_t *subtype)
+{
+ pj_bzero(mt, sizeof(*mt));
+ pj_list_init(&mt->param);
+ if (type)
+ mt->type = *type;
+ if (subtype)
+ mt->subtype = *subtype;
+}
+
+PJ_DEF(void) pjsip_media_type_init2( pjsip_media_type *mt,
+ char *type,
+ char *subtype)
+{
+ pj_str_t s_type, s_subtype;
+
+ if (type) {
+ s_type = pj_str(type);
+ } else {
+ s_type.ptr = NULL;
+ s_type.slen = 0;
+ }
+
+ if (subtype) {
+ s_subtype = pj_str(subtype);
+ } else {
+ s_subtype.ptr = NULL;
+ s_subtype.slen = 0;
+ }
+
+ pjsip_media_type_init(mt, &s_type, &s_subtype);
+}
+
+/*
+ * Compare two media types.
+ */
+PJ_DEF(int) pjsip_media_type_cmp( const pjsip_media_type *mt1,
+ const pjsip_media_type *mt2,
+ pj_bool_t cmp_param)
+{
+ int rc;
+
+ PJ_ASSERT_RETURN(mt1 && mt2, 1);
+
+ rc = pj_stricmp(&mt1->type, &mt2->type);
+ if (rc) return rc;
+
+ rc = pj_stricmp(&mt1->subtype, &mt2->subtype);
+ if (rc) return rc;
+
+ if (cmp_param) {
+ rc = pjsip_param_cmp(&mt1->param, &mt2->param, (cmp_param==1));
+ }
+
+ return rc;
+}
+
+PJ_DEF(void) pjsip_media_type_cp( pj_pool_t *pool,
+ pjsip_media_type *dst,
+ const pjsip_media_type *src)
+{
+ PJ_ASSERT_ON_FAIL(pool && dst && src, return);
+ pj_strdup(pool, &dst->type, &src->type);
+ pj_strdup(pool, &dst->subtype, &src->subtype);
+ pjsip_param_clone(pool, &dst->param, &src->param);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Generic pjsip_hdr_names/hvalue header.
+ */
+
+static int pjsip_generic_string_hdr_print( pjsip_generic_string_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_generic_string_hdr* pjsip_generic_string_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_string_hdr *hdr);
+static pjsip_generic_string_hdr* pjsip_generic_string_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_string_hdr *hdr );
+
+static pjsip_hdr_vptr generic_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_generic_string_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_generic_string_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_generic_string_hdr_print,
+};
+
+
+PJ_DEF(void) pjsip_generic_string_hdr_init2(pjsip_generic_string_hdr *hdr,
+ pj_str_t *hname,
+ pj_str_t *hvalue)
+{
+ init_hdr(hdr, PJSIP_H_OTHER, &generic_hdr_vptr);
+ if (hname) {
+ hdr->name = *hname;
+ hdr->sname = *hname;
+ }
+ if (hvalue) {
+ hdr->hvalue = *hvalue;
+ } else {
+ hdr->hvalue.ptr = NULL;
+ hdr->hvalue.slen = 0;
+ }
+}
+
+
+PJ_DEF(pjsip_generic_string_hdr*) pjsip_generic_string_hdr_init(pj_pool_t *pool,
+ void *mem,
+ const pj_str_t *hnames,
+ const pj_str_t *hvalue)
+{
+ pjsip_generic_string_hdr *hdr = (pjsip_generic_string_hdr*) mem;
+ pj_str_t dup_hname, dup_hval;
+
+ if (hnames) {
+ pj_strdup(pool, &dup_hname, hnames);
+ } else {
+ dup_hname.slen = 0;
+ }
+
+ if (hvalue) {
+ pj_strdup(pool, &dup_hval, hvalue);
+ } else {
+ dup_hval.slen = 0;
+ }
+
+ pjsip_generic_string_hdr_init2(hdr, &dup_hname, &dup_hval);
+ return hdr;
+}
+
+PJ_DEF(pjsip_generic_string_hdr*) pjsip_generic_string_hdr_create(pj_pool_t *pool,
+ const pj_str_t *hnames,
+ const pj_str_t *hvalue)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_generic_string_hdr));
+ return pjsip_generic_string_hdr_init(pool, mem, hnames, hvalue);
+}
+
+static int pjsip_generic_string_hdr_print( pjsip_generic_string_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ if ((pj_ssize_t)size < hname->slen + hdr->hvalue.slen + 5)
+ return -1;
+
+ pj_memcpy(p, hname->ptr, hname->slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+ pj_memcpy(p, hdr->hvalue.ptr, hdr->hvalue.slen);
+ p += hdr->hvalue.slen;
+ *p = '\0';
+
+ return p - buf;
+}
+
+static pjsip_generic_string_hdr* pjsip_generic_string_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_string_hdr *rhs)
+{
+ pjsip_generic_string_hdr *hdr;
+
+ hdr = pjsip_generic_string_hdr_create(pool, &rhs->name, &rhs->hvalue);
+
+ hdr->type = rhs->type;
+ pj_strdup(pool, &hdr->sname, &rhs->sname);
+ return hdr;
+}
+
+static pjsip_generic_string_hdr* pjsip_generic_string_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_string_hdr *rhs )
+{
+ pjsip_generic_string_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_string_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Generic pjsip_hdr_names/integer value header.
+ */
+
+static int pjsip_generic_int_hdr_print( pjsip_generic_int_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_generic_int_hdr* pjsip_generic_int_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_int_hdr *hdr);
+static pjsip_generic_int_hdr* pjsip_generic_int_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_int_hdr *hdr );
+
+static pjsip_hdr_vptr generic_int_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_generic_int_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_generic_int_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_generic_int_hdr_print,
+};
+
+PJ_DEF(pjsip_generic_int_hdr*) pjsip_generic_int_hdr_init( pj_pool_t *pool,
+ void *mem,
+ const pj_str_t *hnames,
+ int value)
+{
+ pjsip_generic_int_hdr *hdr = (pjsip_generic_int_hdr*) mem;
+
+ init_hdr(hdr, PJSIP_H_OTHER, &generic_int_hdr_vptr);
+ if (hnames) {
+ pj_strdup(pool, &hdr->name, hnames);
+ hdr->sname = hdr->name;
+ }
+ hdr->ivalue = value;
+ return hdr;
+}
+
+PJ_DEF(pjsip_generic_int_hdr*) pjsip_generic_int_hdr_create( pj_pool_t *pool,
+ const pj_str_t *hnames,
+ int value)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_generic_int_hdr));
+ return pjsip_generic_int_hdr_init(pool, mem, hnames, value);
+}
+
+static int pjsip_generic_int_hdr_print( pjsip_generic_int_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ if ((pj_ssize_t)size < hname->slen + 15)
+ return -1;
+
+ pj_memcpy(p, hname->ptr, hname->slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+
+ p += pj_utoa(hdr->ivalue, p);
+
+ return p - buf;
+}
+
+static pjsip_generic_int_hdr* pjsip_generic_int_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_int_hdr *rhs)
+{
+ pjsip_generic_int_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_int_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+static pjsip_generic_int_hdr* pjsip_generic_int_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_int_hdr *rhs )
+{
+ pjsip_generic_int_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_int_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Generic array header.
+ */
+static int pjsip_generic_array_hdr_print( pjsip_generic_array_hdr *hdr, char *buf, pj_size_t size);
+static pjsip_generic_array_hdr* pjsip_generic_array_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_array_hdr *hdr);
+static pjsip_generic_array_hdr* pjsip_generic_array_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_array_hdr *hdr);
+
+static pjsip_hdr_vptr generic_array_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_generic_array_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_generic_array_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_generic_array_hdr_print,
+};
+
+
+PJ_DEF(pjsip_generic_array_hdr*) pjsip_generic_array_hdr_init( pj_pool_t *pool,
+ void *mem,
+ const pj_str_t *hnames)
+{
+ pjsip_generic_array_hdr *hdr = (pjsip_generic_array_hdr*) mem;
+
+ init_hdr(hdr, PJSIP_H_OTHER, &generic_array_hdr_vptr);
+ if (hnames) {
+ pj_strdup(pool, &hdr->name, hnames);
+ hdr->sname = hdr->name;
+ }
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_generic_array_hdr*) pjsip_generic_array_hdr_create( pj_pool_t *pool,
+ const pj_str_t *hnames)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_generic_array_hdr));
+ return pjsip_generic_array_hdr_init(pool, mem, hnames);
+
+}
+
+static int pjsip_generic_array_hdr_print( pjsip_generic_array_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf, *endbuf = buf+size;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ copy_advance(p, (*hname));
+ *p++ = ':';
+ *p++ = ' ';
+
+ if (hdr->count > 0) {
+ unsigned i;
+ int printed;
+ copy_advance(p, hdr->values[0]);
+ for (i=1; i<hdr->count; ++i) {
+ copy_advance_pair(p, ", ", 2, hdr->values[i]);
+ }
+ }
+
+ return p - buf;
+}
+
+static pjsip_generic_array_hdr* pjsip_generic_array_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_array_hdr *rhs)
+{
+ unsigned i;
+ pjsip_generic_array_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_array_hdr);
+
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ for (i=0; i<rhs->count; ++i) {
+ pj_strdup(pool, &hdr->values[i], &rhs->values[i]);
+ }
+
+ return hdr;
+}
+
+
+static pjsip_generic_array_hdr* pjsip_generic_array_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_array_hdr *rhs)
+{
+ pjsip_generic_array_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_array_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Accept header.
+ */
+PJ_DEF(pjsip_accept_hdr*) pjsip_accept_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_accept_hdr *hdr = (pjsip_accept_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_ACCEPT, &generic_array_hdr_vptr);
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_accept_hdr*) pjsip_accept_hdr_create(pj_pool_t *pool)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_accept_hdr));
+ return pjsip_accept_hdr_init(pool, mem);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Allow header.
+ */
+
+PJ_DEF(pjsip_allow_hdr*) pjsip_allow_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_allow_hdr *hdr = (pjsip_allow_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_ALLOW, &generic_array_hdr_vptr);
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_allow_hdr*) pjsip_allow_hdr_create(pj_pool_t *pool)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_allow_hdr));
+ return pjsip_allow_hdr_init(pool, mem);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Call-ID header.
+ */
+
+PJ_DEF(pjsip_cid_hdr*) pjsip_cid_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_cid_hdr *hdr = (pjsip_cid_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_CALL_ID, &generic_hdr_vptr);
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_cid_hdr*) pjsip_cid_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_cid_hdr));
+ return pjsip_cid_hdr_init(pool, mem);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Content-Length header.
+ */
+static int pjsip_clen_hdr_print( pjsip_clen_hdr *hdr, char *buf, pj_size_t size);
+static pjsip_clen_hdr* pjsip_clen_hdr_clone( pj_pool_t *pool, const pjsip_clen_hdr *hdr);
+#define pjsip_clen_hdr_shallow_clone pjsip_clen_hdr_clone
+
+static pjsip_hdr_vptr clen_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_clen_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_clen_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_clen_hdr_print,
+};
+
+PJ_DEF(pjsip_clen_hdr*) pjsip_clen_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_clen_hdr *hdr = (pjsip_clen_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_CONTENT_LENGTH, &clen_hdr_vptr);
+ hdr->len = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_clen_hdr*) pjsip_clen_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_clen_hdr));
+ return pjsip_clen_hdr_init(pool, mem);
+}
+
+static int pjsip_clen_hdr_print( pjsip_clen_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ int len;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ if ((pj_ssize_t)size < hname->slen + 14)
+ return -1;
+
+ pj_memcpy(p, hname->ptr, hname->slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+
+ len = pj_utoa(hdr->len, p);
+ p += len;
+ *p = '\0';
+
+ return p-buf;
+}
+
+static pjsip_clen_hdr* pjsip_clen_hdr_clone( pj_pool_t *pool, const pjsip_clen_hdr *rhs)
+{
+ pjsip_clen_hdr *hdr = pjsip_clen_hdr_create(pool);
+ hdr->len = rhs->len;
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * CSeq header.
+ */
+static int pjsip_cseq_hdr_print( pjsip_cseq_hdr *hdr, char *buf, pj_size_t size);
+static pjsip_cseq_hdr* pjsip_cseq_hdr_clone( pj_pool_t *pool, const pjsip_cseq_hdr *hdr);
+static pjsip_cseq_hdr* pjsip_cseq_hdr_shallow_clone( pj_pool_t *pool, const pjsip_cseq_hdr *hdr );
+
+static pjsip_hdr_vptr cseq_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_cseq_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_cseq_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_cseq_hdr_print,
+};
+
+PJ_DEF(pjsip_cseq_hdr*) pjsip_cseq_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_cseq_hdr *hdr = (pjsip_cseq_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_CSEQ, &cseq_hdr_vptr);
+ hdr->cseq = 0;
+ hdr->method.id = PJSIP_OTHER_METHOD;
+ hdr->method.name.ptr = NULL;
+ hdr->method.name.slen = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_cseq_hdr*) pjsip_cseq_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_cseq_hdr));
+ return pjsip_cseq_hdr_init(pool, mem);
+}
+
+static int pjsip_cseq_hdr_print( pjsip_cseq_hdr *hdr, char *buf, pj_size_t size)
+{
+ char *p = buf;
+ int len;
+ /* CSeq doesn't have compact form */
+
+ if ((pj_ssize_t)size < hdr->name.slen + hdr->method.name.slen + 15)
+ return -1;
+
+ pj_memcpy(p, hdr->name.ptr, hdr->name.slen);
+ p += hdr->name.slen;
+ *p++ = ':';
+ *p++ = ' ';
+
+ len = pj_utoa(hdr->cseq, p);
+ p += len;
+ *p++ = ' ';
+
+ pj_memcpy(p, hdr->method.name.ptr, hdr->method.name.slen);
+ p += hdr->method.name.slen;
+
+ *p = '\0';
+
+ return p-buf;
+}
+
+static pjsip_cseq_hdr* pjsip_cseq_hdr_clone( pj_pool_t *pool,
+ const pjsip_cseq_hdr *rhs)
+{
+ pjsip_cseq_hdr *hdr = pjsip_cseq_hdr_create(pool);
+ hdr->cseq = rhs->cseq;
+ pjsip_method_copy(pool, &hdr->method, &rhs->method);
+ return hdr;
+}
+
+static pjsip_cseq_hdr* pjsip_cseq_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_cseq_hdr *rhs )
+{
+ pjsip_cseq_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_cseq_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Contact header.
+ */
+static int pjsip_contact_hdr_print( pjsip_contact_hdr *hdr, char *buf, pj_size_t size);
+static pjsip_contact_hdr* pjsip_contact_hdr_clone( pj_pool_t *pool, const pjsip_contact_hdr *hdr);
+static pjsip_contact_hdr* pjsip_contact_hdr_shallow_clone( pj_pool_t *pool, const pjsip_contact_hdr *);
+
+static pjsip_hdr_vptr contact_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_contact_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_contact_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_contact_hdr_print,
+};
+
+PJ_DEF(pjsip_contact_hdr*) pjsip_contact_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_contact_hdr *hdr = (pjsip_contact_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(mem, sizeof(pjsip_contact_hdr));
+ init_hdr(hdr, PJSIP_H_CONTACT, &contact_hdr_vptr);
+ hdr->expires = -1;
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+PJ_DEF(pjsip_contact_hdr*) pjsip_contact_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_contact_hdr));
+ return pjsip_contact_hdr_init(pool, mem);
+}
+
+static int pjsip_contact_hdr_print( pjsip_contact_hdr *hdr, char *buf,
+ pj_size_t size)
+{
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ if (hdr->star) {
+ char *p = buf;
+ if ((pj_ssize_t)size < hname->slen + 6)
+ return -1;
+ pj_memcpy(p, hname->ptr, hname->slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+ *p++ = '*';
+ return p - buf;
+
+ } else {
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+
+ copy_advance(buf, (*hname));
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ printed = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, hdr->uri,
+ buf, endbuf-buf);
+ if (printed < 1)
+ return -1;
+
+ buf += printed;
+
+ if (hdr->q1000) {
+ unsigned frac;
+
+ if (buf+19 >= endbuf)
+ return -1;
+
+ /*
+ printed = sprintf(buf, ";q=%u.%03u",
+ hdr->q1000/1000, hdr->q1000 % 1000);
+ */
+ pj_memcpy(buf, ";q=", 3);
+ printed = pj_utoa(hdr->q1000/1000, buf+3);
+ buf += printed + 3;
+ frac = hdr->q1000 % 1000;
+ if (frac != 0) {
+ *buf++ = '.';
+ if ((frac % 100)==0) frac /= 100;
+ if ((frac % 10)==0) frac /= 10;
+ printed = pj_utoa(frac, buf);
+ buf += printed;
+ }
+ }
+
+ if (hdr->expires >= 0) {
+ if (buf+23 >= endbuf)
+ return -1;
+
+ pj_memcpy(buf, ";expires=", 9);
+ printed = pj_utoa(hdr->expires, buf+9);
+ buf += printed + 9;
+ }
+
+ printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC,
+ ';');
+ if (printed < 0)
+ return printed;
+ buf += printed;
+
+ return buf-startbuf;
+ }
+}
+
+static pjsip_contact_hdr* pjsip_contact_hdr_clone(pj_pool_t *pool,
+ const pjsip_contact_hdr *rhs)
+{
+ pjsip_contact_hdr *hdr = pjsip_contact_hdr_create(pool);
+
+ hdr->star = rhs->star;
+ if (hdr->star)
+ return hdr;
+
+ hdr->uri = (pjsip_uri*) pjsip_uri_clone(pool, rhs->uri);
+ hdr->q1000 = rhs->q1000;
+ hdr->expires = rhs->expires;
+ pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_contact_hdr*
+pjsip_contact_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_contact_hdr *rhs)
+{
+ pjsip_contact_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_contact_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Content-Type header..
+ */
+static int pjsip_ctype_hdr_print( pjsip_ctype_hdr *hdr, char *buf,
+ pj_size_t size);
+static pjsip_ctype_hdr* pjsip_ctype_hdr_clone(pj_pool_t *pool,
+ const pjsip_ctype_hdr *hdr);
+#define pjsip_ctype_hdr_shallow_clone pjsip_ctype_hdr_clone
+
+static pjsip_hdr_vptr ctype_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_ctype_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_ctype_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_ctype_hdr_print,
+};
+
+PJ_DEF(pjsip_ctype_hdr*) pjsip_ctype_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_ctype_hdr *hdr = (pjsip_ctype_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(mem, sizeof(pjsip_ctype_hdr));
+ init_hdr(hdr, PJSIP_H_CONTENT_TYPE, &ctype_hdr_vptr);
+ pj_list_init(&hdr->media.param);
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_ctype_hdr*) pjsip_ctype_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_ctype_hdr));
+ return pjsip_ctype_hdr_init(pool, mem);
+}
+
+static int print_media_type(char *buf, unsigned len,
+ const pjsip_media_type *media)
+{
+ char *p = buf;
+ pj_ssize_t printed;
+ const pjsip_parser_const_t *pc;
+
+ pj_memcpy(p, media->type.ptr, media->type.slen);
+ p += media->type.slen;
+ *p++ = '/';
+ pj_memcpy(p, media->subtype.ptr, media->subtype.slen);
+ p += media->subtype.slen;
+
+ pc = pjsip_parser_const();
+ printed = pjsip_param_print_on(&media->param, p, buf+len-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return -1;
+
+ p += printed;
+
+ return p-buf;
+}
+
+
+PJ_DEF(int) pjsip_media_type_print(char *buf, unsigned len,
+ const pjsip_media_type *media)
+{
+ return print_media_type(buf, len, media);
+}
+
+static int pjsip_ctype_hdr_print( pjsip_ctype_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ int len;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ if ((pj_ssize_t)size < hname->slen +
+ hdr->media.type.slen + hdr->media.subtype.slen + 8)
+ {
+ return -1;
+ }
+
+ pj_memcpy(p, hname->ptr, hname->slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+
+ len = print_media_type(p, buf+size-p, &hdr->media);
+ p += len;
+
+ *p = '\0';
+ return p-buf;
+}
+
+static pjsip_ctype_hdr* pjsip_ctype_hdr_clone( pj_pool_t *pool,
+ const pjsip_ctype_hdr *rhs)
+{
+ pjsip_ctype_hdr *hdr = pjsip_ctype_hdr_create(pool);
+ pj_strdup(pool, &hdr->media.type, &rhs->media.type);
+ pj_strdup(pool, &hdr->media.subtype, &rhs->media.subtype);
+ pjsip_param_clone(pool, &hdr->media.param, &rhs->media.param);
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Expires header.
+ */
+PJ_DEF(pjsip_expires_hdr*) pjsip_expires_hdr_init( pj_pool_t *pool,
+ void *mem,
+ int value)
+{
+ pjsip_expires_hdr *hdr = (pjsip_expires_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_EXPIRES, &generic_int_hdr_vptr);
+ hdr->ivalue = value;
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_expires_hdr*) pjsip_expires_hdr_create( pj_pool_t *pool,
+ int value )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_expires_hdr));
+ return pjsip_expires_hdr_init(pool, mem, value);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * To or From header.
+ */
+static int pjsip_fromto_hdr_print( pjsip_fromto_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_fromto_hdr* pjsip_fromto_hdr_clone( pj_pool_t *pool,
+ const pjsip_fromto_hdr *hdr);
+static pjsip_fromto_hdr* pjsip_fromto_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_fromto_hdr *hdr);
+
+
+static pjsip_hdr_vptr fromto_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_fromto_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_fromto_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_fromto_hdr_print,
+};
+
+PJ_DEF(pjsip_from_hdr*) pjsip_from_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_from_hdr *hdr = (pjsip_from_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(mem, sizeof(pjsip_from_hdr));
+ init_hdr(hdr, PJSIP_H_FROM, &fromto_hdr_vptr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+PJ_DEF(pjsip_from_hdr*) pjsip_from_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_from_hdr));
+ return pjsip_from_hdr_init(pool, mem);
+}
+
+PJ_DEF(pjsip_to_hdr*) pjsip_to_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_to_hdr *hdr = (pjsip_to_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(mem, sizeof(pjsip_to_hdr));
+ init_hdr(hdr, PJSIP_H_TO, &fromto_hdr_vptr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_to_hdr*) pjsip_to_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_to_hdr));
+ return pjsip_to_hdr_init(pool, mem);
+}
+
+PJ_DEF(pjsip_from_hdr*) pjsip_fromto_hdr_set_from( pjsip_fromto_hdr *hdr )
+{
+ hdr->type = PJSIP_H_FROM;
+ hdr->name.ptr = pjsip_hdr_names[PJSIP_H_FROM].name;
+ hdr->name.slen = pjsip_hdr_names[PJSIP_H_FROM].name_len;
+ hdr->sname.ptr = pjsip_hdr_names[PJSIP_H_FROM].sname;
+ hdr->sname.slen = 1;
+ return hdr;
+}
+
+PJ_DEF(pjsip_to_hdr*) pjsip_fromto_hdr_set_to( pjsip_fromto_hdr *hdr )
+{
+ hdr->type = PJSIP_H_TO;
+ hdr->name.ptr = pjsip_hdr_names[PJSIP_H_TO].name;
+ hdr->name.slen = pjsip_hdr_names[PJSIP_H_TO].name_len;
+ hdr->sname.ptr = pjsip_hdr_names[PJSIP_H_TO].sname;
+ hdr->sname.slen = 1;
+ return hdr;
+}
+
+static int pjsip_fromto_hdr_print( pjsip_fromto_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ copy_advance(buf, (*hname));
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ printed = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, hdr->uri,
+ buf, endbuf-buf);
+ if (printed < 1)
+ return -1;
+
+ buf += printed;
+
+ copy_advance_pair_escape(buf, ";tag=", 5, hdr->tag,
+ pc->pjsip_TOKEN_SPEC);
+
+ printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return buf-startbuf;
+}
+
+static pjsip_fromto_hdr* pjsip_fromto_hdr_clone( pj_pool_t *pool,
+ const pjsip_fromto_hdr *rhs)
+{
+ pjsip_fromto_hdr *hdr = pjsip_from_hdr_create(pool);
+
+ hdr->type = rhs->type;
+ hdr->name = rhs->name;
+ hdr->sname = rhs->sname;
+ hdr->uri = (pjsip_uri*) pjsip_uri_clone(pool, rhs->uri);
+ pj_strdup( pool, &hdr->tag, &rhs->tag);
+ pjsip_param_clone( pool, &hdr->other_param, &rhs->other_param);
+
+ return hdr;
+}
+
+static pjsip_fromto_hdr*
+pjsip_fromto_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_fromto_hdr *rhs)
+{
+ pjsip_fromto_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_fromto_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone( pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Max-Forwards header.
+ */
+PJ_DEF(pjsip_max_fwd_hdr*) pjsip_max_fwd_hdr_init( pj_pool_t *pool,
+ void *mem,
+ int value)
+{
+ pjsip_max_fwd_hdr *hdr = (pjsip_max_fwd_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_MAX_FORWARDS, &generic_int_hdr_vptr);
+ hdr->ivalue = value;
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_max_fwd_hdr*) pjsip_max_fwd_hdr_create(pj_pool_t *pool,
+ int value)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_max_fwd_hdr));
+ return pjsip_max_fwd_hdr_init(pool, mem, value);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Min-Expires header.
+ */
+PJ_DEF(pjsip_min_expires_hdr*) pjsip_min_expires_hdr_init( pj_pool_t *pool,
+ void *mem,
+ int value )
+{
+ pjsip_min_expires_hdr *hdr = (pjsip_min_expires_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_MIN_EXPIRES, &generic_int_hdr_vptr);
+ hdr->ivalue = value;
+ return hdr;
+}
+
+PJ_DEF(pjsip_min_expires_hdr*) pjsip_min_expires_hdr_create(pj_pool_t *pool,
+ int value )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_min_expires_hdr));
+ return pjsip_min_expires_hdr_init(pool, mem, value );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Record-Route and Route header.
+ */
+static int pjsip_routing_hdr_print( pjsip_routing_hdr *r, char *buf, pj_size_t size );
+static pjsip_routing_hdr* pjsip_routing_hdr_clone( pj_pool_t *pool, const pjsip_routing_hdr *r );
+static pjsip_routing_hdr* pjsip_routing_hdr_shallow_clone( pj_pool_t *pool, const pjsip_routing_hdr *r );
+
+static pjsip_hdr_vptr routing_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_routing_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_routing_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_routing_hdr_print,
+};
+
+PJ_DEF(pjsip_rr_hdr*) pjsip_rr_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_rr_hdr *hdr = (pjsip_rr_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_RECORD_ROUTE, &routing_hdr_vptr);
+ pjsip_name_addr_init(&hdr->name_addr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_rr_hdr*) pjsip_rr_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_rr_hdr));
+ return pjsip_rr_hdr_init(pool, mem);
+}
+
+PJ_DEF(pjsip_route_hdr*) pjsip_route_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_route_hdr *hdr = (pjsip_route_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_ROUTE, &routing_hdr_vptr);
+ pjsip_name_addr_init(&hdr->name_addr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+PJ_DEF(pjsip_route_hdr*) pjsip_route_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_route_hdr));
+ return pjsip_route_hdr_init(pool, mem);
+}
+
+PJ_DEF(pjsip_rr_hdr*) pjsip_routing_hdr_set_rr( pjsip_routing_hdr *hdr )
+{
+ hdr->type = PJSIP_H_RECORD_ROUTE;
+ hdr->name.ptr = pjsip_hdr_names[PJSIP_H_RECORD_ROUTE].name;
+ hdr->name.slen = pjsip_hdr_names[PJSIP_H_RECORD_ROUTE].name_len;
+ hdr->sname = hdr->name;
+ return hdr;
+}
+
+PJ_DEF(pjsip_route_hdr*) pjsip_routing_hdr_set_route( pjsip_routing_hdr *hdr )
+{
+ hdr->type = PJSIP_H_ROUTE;
+ hdr->name.ptr = pjsip_hdr_names[PJSIP_H_ROUTE].name;
+ hdr->name.slen = pjsip_hdr_names[PJSIP_H_ROUTE].name_len;
+ hdr->sname = hdr->name;
+ return hdr;
+}
+
+static int pjsip_routing_hdr_print( pjsip_routing_hdr *hdr,
+ char *buf, pj_size_t size )
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+ pjsip_sip_uri *sip_uri;
+ pjsip_param *p;
+
+ /* Check the proprietary param 'hide', don't print this header
+ * if it exists in the route URI.
+ */
+ sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(hdr->name_addr.uri);
+ p = sip_uri->other_param.next;
+ while (p != &sip_uri->other_param) {
+ const pj_str_t st_hide = {"hide", 4};
+
+ if (pj_stricmp(&p->name, &st_hide) == 0) {
+ /* Check if param 'hide' is specified without 'lr'. */
+ pj_assert(sip_uri->lr_param != 0);
+ return 0;
+ }
+ p = p->next;
+ }
+
+ /* Route and Record-Route don't compact forms */
+
+ copy_advance(buf, hdr->name);
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ printed = pjsip_uri_print(PJSIP_URI_IN_ROUTING_HDR, &hdr->name_addr, buf,
+ endbuf-buf);
+ if (printed < 1)
+ return -1;
+ buf += printed;
+
+ printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return buf-startbuf;
+}
+
+static pjsip_routing_hdr* pjsip_routing_hdr_clone( pj_pool_t *pool,
+ const pjsip_routing_hdr *rhs )
+{
+ pjsip_routing_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_routing_hdr);
+
+ init_hdr(hdr, rhs->type, rhs->vptr);
+ pjsip_name_addr_init(&hdr->name_addr);
+ pjsip_name_addr_assign(pool, &hdr->name_addr, &rhs->name_addr);
+ pjsip_param_clone( pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_routing_hdr* pjsip_routing_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_routing_hdr *rhs )
+{
+ pjsip_routing_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_routing_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone( pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Require header.
+ */
+PJ_DEF(pjsip_require_hdr*) pjsip_require_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_require_hdr *hdr = (pjsip_require_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_REQUIRE, &generic_array_hdr_vptr);
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_require_hdr*) pjsip_require_hdr_create(pj_pool_t *pool)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_require_hdr));
+ return pjsip_require_hdr_init(pool, mem);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Retry-After header.
+ */
+static int pjsip_retry_after_hdr_print(pjsip_retry_after_hdr *r,
+ char *buf, pj_size_t size );
+static pjsip_retry_after_hdr* pjsip_retry_after_hdr_clone(pj_pool_t *pool,
+ const pjsip_retry_after_hdr *r);
+static pjsip_retry_after_hdr*
+pjsip_retry_after_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_retry_after_hdr *r );
+
+static pjsip_hdr_vptr retry_after_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_retry_after_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_retry_after_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_retry_after_hdr_print,
+};
+
+
+PJ_DEF(pjsip_retry_after_hdr*) pjsip_retry_after_hdr_init( pj_pool_t *pool,
+ void *mem,
+ int value )
+{
+ pjsip_retry_after_hdr *hdr = (pjsip_retry_after_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_RETRY_AFTER, &retry_after_hdr_vptr);
+ hdr->ivalue = value;
+ hdr->comment.slen = 0;
+ pj_list_init(&hdr->param);
+ return hdr;
+}
+
+PJ_DEF(pjsip_retry_after_hdr*) pjsip_retry_after_hdr_create(pj_pool_t *pool,
+ int value )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_retry_after_hdr));
+ return pjsip_retry_after_hdr_init(pool, mem, value );
+}
+
+
+static int pjsip_retry_after_hdr_print(pjsip_retry_after_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf + size;
+ const pj_str_t *hname = &hdr->name;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+ int printed;
+
+ if ((pj_ssize_t)size < hdr->name.slen + 2+11)
+ return -1;
+
+ pj_memcpy(p, hdr->name.ptr, hdr->name.slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+
+ p += pj_utoa(hdr->ivalue, p);
+
+ if (hdr->comment.slen) {
+ pj_bool_t enclosed;
+
+ if (endbuf-p < hdr->comment.slen + 3)
+ return -1;
+
+ enclosed = (*hdr->comment.ptr == '(');
+ if (!enclosed)
+ *p++ = '(';
+ pj_memcpy(p, hdr->comment.ptr, hdr->comment.slen);
+ p += hdr->comment.slen;
+ if (!enclosed)
+ *p++ = ')';
+
+ if (!pj_list_empty(&hdr->param))
+ *p++ = ' ';
+ }
+
+ printed = pjsip_param_print_on(&hdr->param, p, endbuf-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC,
+ ';');
+ if (printed < 0)
+ return printed;
+
+ p += printed;
+
+ return p - buf;
+}
+
+static pjsip_retry_after_hdr* pjsip_retry_after_hdr_clone(pj_pool_t *pool,
+ const pjsip_retry_after_hdr *rhs)
+{
+ pjsip_retry_after_hdr *hdr = pjsip_retry_after_hdr_create(pool, rhs->ivalue);
+ pj_strdup(pool, &hdr->comment, &rhs->comment);
+ pjsip_param_clone(pool, &hdr->param, &rhs->param);
+ return hdr;
+}
+
+static pjsip_retry_after_hdr*
+pjsip_retry_after_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_retry_after_hdr *rhs)
+{
+ pjsip_retry_after_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_retry_after_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->param, &rhs->param);
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Supported header.
+ */
+PJ_DEF(pjsip_supported_hdr*) pjsip_supported_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_supported_hdr *hdr = (pjsip_supported_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+ init_hdr(hdr, PJSIP_H_SUPPORTED, &generic_array_hdr_vptr);
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_supported_hdr*) pjsip_supported_hdr_create(pj_pool_t *pool)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_supported_hdr));
+ return pjsip_supported_hdr_init(pool, mem);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Unsupported header.
+ */
+PJ_DEF(pjsip_unsupported_hdr*) pjsip_unsupported_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_unsupported_hdr *hdr = (pjsip_unsupported_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_UNSUPPORTED, &generic_array_hdr_vptr);
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_unsupported_hdr*) pjsip_unsupported_hdr_create(pj_pool_t *pool)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_unsupported_hdr));
+ return pjsip_unsupported_hdr_init(pool, mem);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Via header.
+ */
+static int pjsip_via_hdr_print( pjsip_via_hdr *hdr, char *buf, pj_size_t size);
+static pjsip_via_hdr* pjsip_via_hdr_clone( pj_pool_t *pool, const pjsip_via_hdr *hdr);
+static pjsip_via_hdr* pjsip_via_hdr_shallow_clone( pj_pool_t *pool, const pjsip_via_hdr *hdr );
+
+static pjsip_hdr_vptr via_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_via_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_via_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_via_hdr_print,
+};
+
+PJ_DEF(pjsip_via_hdr*) pjsip_via_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_via_hdr *hdr = (pjsip_via_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(mem, sizeof(pjsip_via_hdr));
+ init_hdr(hdr, PJSIP_H_VIA, &via_hdr_vptr);
+ hdr->ttl_param = -1;
+ hdr->rport_param = -1;
+ pj_list_init(&hdr->other_param);
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_via_hdr*) pjsip_via_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_via_hdr));
+ return pjsip_via_hdr_init(pool, mem);
+}
+
+static int pjsip_via_hdr_print( pjsip_via_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ pj_str_t sip_ver = { "SIP/2.0/", 8 };
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ if ((pj_ssize_t)size < hname->slen + sip_ver.slen +
+ hdr->transport.slen + hdr->sent_by.host.slen + 12)
+ {
+ return -1;
+ }
+
+ /* pjsip_hdr_names */
+ copy_advance(buf, (*hname));
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ /* SIP/2.0/transport host:port */
+ pj_memcpy(buf, sip_ver.ptr, sip_ver.slen);
+ buf += sip_ver.slen;
+ //pj_memcpy(buf, hdr->transport.ptr, hdr->transport.slen);
+ /* Convert transport type to UPPERCASE (some endpoints want that) */
+ {
+ int i;
+ for (i=0; i<hdr->transport.slen; ++i) {
+ buf[i] = (char)pj_toupper(hdr->transport.ptr[i]);
+ }
+ }
+ buf += hdr->transport.slen;
+ *buf++ = ' ';
+
+ /* Check if host contains IPv6 */
+ if (pj_memchr(hdr->sent_by.host.ptr, ':', hdr->sent_by.host.slen)) {
+ copy_advance_pair_quote_cond(buf, "", 0, hdr->sent_by.host, '[', ']');
+ } else {
+ copy_advance_check(buf, hdr->sent_by.host);
+ }
+
+ if (hdr->sent_by.port != 0) {
+ *buf++ = ':';
+ printed = pj_utoa(hdr->sent_by.port, buf);
+ buf += printed;
+ }
+
+ if (hdr->ttl_param >= 0) {
+ size = endbuf-buf;
+ if (size < 14)
+ return -1;
+ pj_memcpy(buf, ";ttl=", 5);
+ printed = pj_utoa(hdr->ttl_param, buf+5);
+ buf += printed + 5;
+ }
+
+ if (hdr->rport_param >= 0) {
+ size = endbuf-buf;
+ if (size < 14)
+ return -1;
+ pj_memcpy(buf, ";rport", 6);
+ buf += 6;
+ if (hdr->rport_param > 0) {
+ *buf++ = '=';
+ buf += pj_utoa(hdr->rport_param, buf);
+ }
+ }
+
+
+ if (hdr->maddr_param.slen) {
+ /* Detect IPv6 IP address */
+ if (pj_memchr(hdr->maddr_param.ptr, ':', hdr->maddr_param.slen)) {
+ copy_advance_pair_quote_cond(buf, ";maddr=", 7, hdr->maddr_param,
+ '[', ']');
+ } else {
+ copy_advance_pair(buf, ";maddr=", 7, hdr->maddr_param);
+ }
+ }
+
+ copy_advance_pair(buf, ";received=", 10, hdr->recvd_param);
+ copy_advance_pair_escape(buf, ";branch=", 8, hdr->branch_param,
+ pc->pjsip_TOKEN_SPEC);
+
+ printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return buf-startbuf;
+}
+
+static pjsip_via_hdr* pjsip_via_hdr_clone( pj_pool_t *pool,
+ const pjsip_via_hdr *rhs)
+{
+ pjsip_via_hdr *hdr = pjsip_via_hdr_create(pool);
+ pj_strdup(pool, &hdr->transport, &rhs->transport);
+ pj_strdup(pool, &hdr->sent_by.host, &rhs->sent_by.host);
+ hdr->sent_by.port = rhs->sent_by.port;
+ hdr->ttl_param = rhs->ttl_param;
+ hdr->rport_param = rhs->rport_param;
+ pj_strdup(pool, &hdr->maddr_param, &rhs->maddr_param);
+ pj_strdup(pool, &hdr->recvd_param, &rhs->recvd_param);
+ pj_strdup(pool, &hdr->branch_param, &rhs->branch_param);
+ pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_via_hdr* pjsip_via_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_via_hdr *rhs )
+{
+ pjsip_via_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_via_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Warning header.
+ */
+PJ_DEF(pjsip_warning_hdr*) pjsip_warning_hdr_create( pj_pool_t *pool,
+ int code,
+ const pj_str_t *host,
+ const pj_str_t *text)
+{
+ const pj_str_t str_warning = { "Warning", 7 };
+ pj_str_t hvalue;
+
+ hvalue.ptr = (char*) pj_pool_alloc(pool, 10 + /* code */
+ host->slen + 2 + /* host */
+ text->slen + 2); /* text */
+ hvalue.slen = pj_ansi_sprintf(hvalue.ptr, "%u %.*s \"%.*s\"",
+ code, (int)host->slen, host->ptr,
+ (int)text->slen, text->ptr);
+
+ return pjsip_generic_string_hdr_create(pool, &str_warning, &hvalue);
+}
+
+PJ_DEF(pjsip_warning_hdr*) pjsip_warning_hdr_create_from_status(pj_pool_t *pool,
+ const pj_str_t *host,
+ pj_status_t status)
+{
+ char errbuf[PJ_ERR_MSG_SIZE];
+ pj_str_t text;
+
+ text = pj_strerror(status, errbuf, sizeof(errbuf));
+ return pjsip_warning_hdr_create(pool, 399, host, &text);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Message body manipulations.
+ */
+PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size)
+{
+ if (size < msg_body->len)
+ return -1;
+ pj_memcpy(buf, msg_body->data, msg_body->len);
+ return msg_body->len;
+}
+
+PJ_DEF(void*) pjsip_clone_text_data( pj_pool_t *pool, const void *data,
+ unsigned len)
+{
+ char *newdata = "";
+
+ if (len) {
+ newdata = (char*) pj_pool_alloc(pool, len);
+ pj_memcpy(newdata, data, len);
+ }
+ return newdata;
+}
+
+PJ_DEF(pj_status_t) pjsip_msg_body_copy( pj_pool_t *pool,
+ pjsip_msg_body *dst_body,
+ const pjsip_msg_body *src_body )
+{
+ /* First check if clone_data field is initialized. */
+ PJ_ASSERT_RETURN( src_body->clone_data!=NULL, PJ_EINVAL );
+
+ /* Duplicate content-type */
+ pjsip_media_type_cp(pool, &dst_body->content_type,
+ &src_body->content_type);
+
+ /* Duplicate data. */
+ dst_body->data = (*src_body->clone_data)(pool, src_body->data,
+ src_body->len );
+
+ /* Length. */
+ dst_body->len = src_body->len;
+
+ /* Function pointers. */
+ dst_body->print_body = src_body->print_body;
+ dst_body->clone_data = src_body->clone_data;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pjsip_msg_body*) pjsip_msg_body_clone( pj_pool_t *pool,
+ const pjsip_msg_body *body )
+{
+ pjsip_msg_body *new_body;
+ pj_status_t status;
+
+ new_body = PJ_POOL_ALLOC_T(pool, pjsip_msg_body);
+ PJ_ASSERT_RETURN(new_body, NULL);
+
+ status = pjsip_msg_body_copy(pool, new_body, body);
+
+ return (status==PJ_SUCCESS) ? new_body : NULL;
+}
+
+
+PJ_DEF(pjsip_msg_body*) pjsip_msg_body_create( pj_pool_t *pool,
+ const pj_str_t *type,
+ const pj_str_t *subtype,
+ const pj_str_t *text )
+{
+ pjsip_msg_body *body;
+
+ PJ_ASSERT_RETURN(pool && type && subtype && text, NULL);
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ PJ_ASSERT_RETURN(body != NULL, NULL);
+
+ pj_strdup(pool, &body->content_type.type, type);
+ pj_strdup(pool, &body->content_type.subtype, subtype);
+ pj_list_init(&body->content_type.param);
+
+ body->data = pj_pool_alloc(pool, text->slen);
+ pj_memcpy(body->data, text->ptr, text->slen);
+ body->len = text->slen;
+
+ body->clone_data = &pjsip_clone_text_data;
+ body->print_body = &pjsip_print_text_body;
+
+ return body;
+}
+
diff --git a/pjsip/src/pjsip/sip_multipart.c b/pjsip/src/pjsip/sip_multipart.c
new file mode 100644
index 0000000..8fa9d75
--- /dev/null
+++ b/pjsip/src/pjsip/sip_multipart.c
@@ -0,0 +1,658 @@
+/* $Id: sip_multipart.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_multipart.h>
+#include <pjsip/sip_parser.h>
+#include <pjlib-util/scanner.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/errno.h>
+#include <pj/except.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE "sip_multipart.c"
+
+#define IS_SPACE(c) ((c)==' ' || (c)=='\t')
+
+#if 0
+# define TRACE_(x) PJ_LOG(4,x)
+#else
+# define TRACE_(x)
+#endif
+
+extern pj_bool_t pjsip_use_compact_form;
+
+/* Type of "data" in multipart pjsip_msg_body */
+struct multipart_data
+{
+ pj_str_t boundary;
+ pjsip_multipart_part part_head;
+};
+
+
+static int multipart_print_body(struct pjsip_msg_body *msg_body,
+ char *buf, pj_size_t size)
+{
+ const struct multipart_data *m_data;
+ pj_str_t clen_hdr = { "Content-Length: ", 16};
+ pjsip_multipart_part *part;
+ char *p = buf, *end = buf+size;
+
+#define SIZE_LEFT() (end-p)
+
+ m_data = (const struct multipart_data*)msg_body->data;
+
+ PJ_ASSERT_RETURN(m_data && !pj_list_empty(&m_data->part_head), PJ_EINVAL);
+
+ part = m_data->part_head.next;
+ while (part != &m_data->part_head) {
+ enum { CLEN_SPACE = 5 };
+ char *clen_pos;
+ const pjsip_hdr *hdr;
+
+ clen_pos = NULL;
+
+ /* Print delimiter */
+ if (SIZE_LEFT() <= (m_data->boundary.slen+8) << 1)
+ return -1;
+ *p++ = 13; *p++ = 10; *p++ = '-'; *p++ = '-';
+ pj_memcpy(p, m_data->boundary.ptr, m_data->boundary.slen);
+ p += m_data->boundary.slen;
+ *p++ = 13; *p++ = 10;
+
+ /* Print optional headers */
+ hdr = part->hdr.next;
+ while (hdr != &part->hdr) {
+ int printed = pjsip_hdr_print_on((pjsip_hdr*)hdr, p,
+ SIZE_LEFT()-2);
+ if (printed < 0)
+ return -1;
+ p += printed;
+ *p++ = '\r';
+ *p++ = '\n';
+ hdr = hdr->next;
+ }
+
+ /* Automaticly adds Content-Type and Content-Length headers, only
+ * if content_type is set in the message body.
+ */
+ if (part->body && part->body->content_type.type.slen) {
+ pj_str_t ctype_hdr = { "Content-Type: ", 14};
+ const pjsip_media_type *media = &part->body->content_type;
+
+ if (pjsip_use_compact_form) {
+ ctype_hdr.ptr = "c: ";
+ ctype_hdr.slen = 3;
+ }
+
+ /* Add Content-Type header. */
+ if ( (end-p) < 24 + media->type.slen + media->subtype.slen) {
+ return -1;
+ }
+ pj_memcpy(p, ctype_hdr.ptr, ctype_hdr.slen);
+ p += ctype_hdr.slen;
+ p += pjsip_media_type_print(p, end-p, media);
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Add Content-Length header. */
+ if ((end-p) < clen_hdr.slen + 12 + 2) {
+ return -1;
+ }
+ pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen);
+ p += clen_hdr.slen;
+
+ /* Print blanks after "Content-Length:", this is where we'll put
+ * the content length value after we know the length of the
+ * body.
+ */
+ pj_memset(p, ' ', CLEN_SPACE);
+ clen_pos = p;
+ p += CLEN_SPACE;
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+
+ /* Empty newline */
+ *p++ = 13; *p++ = 10;
+
+ /* Print the body */
+ pj_assert(part->body != NULL);
+ if (part->body) {
+ int printed = part->body->print_body(part->body, p, SIZE_LEFT());
+ if (printed < 0)
+ return -1;
+ p += printed;
+
+ /* Now that we have the length of the body, print this to the
+ * Content-Length header.
+ */
+ if (clen_pos) {
+ char tmp[16];
+ int len;
+
+ len = pj_utoa(printed, tmp);
+ if (len > CLEN_SPACE) len = CLEN_SPACE;
+ pj_memcpy(clen_pos+CLEN_SPACE-len, tmp, len);
+ }
+ }
+
+ part = part->next;
+ }
+
+ /* Print closing delimiter */
+ if (SIZE_LEFT() < m_data->boundary.slen+8)
+ return -1;
+ *p++ = 13; *p++ = 10; *p++ = '-'; *p++ = '-';
+ pj_memcpy(p, m_data->boundary.ptr, m_data->boundary.slen);
+ p += m_data->boundary.slen;
+ *p++ = '-'; *p++ = '-'; *p++ = 13; *p++ = 10;
+
+#undef SIZE_LEFT
+
+ return p - buf;
+}
+
+static void* multipart_clone_data(pj_pool_t *pool, const void *data,
+ unsigned len)
+{
+ const struct multipart_data *src;
+ struct multipart_data *dst;
+ const pjsip_multipart_part *src_part;
+
+ PJ_UNUSED_ARG(len);
+
+ src = (const struct multipart_data*) data;
+ dst = PJ_POOL_ALLOC_T(pool, struct multipart_data);
+
+ pj_strdup(pool, &dst->boundary, &src->boundary);
+
+ src_part = src->part_head.next;
+ while (src_part != &src->part_head) {
+ pjsip_multipart_part *dst_part;
+ const pjsip_hdr *src_hdr;
+
+ dst_part = pjsip_multipart_create_part(pool);
+
+ src_hdr = src_part->hdr.next;
+ while (src_hdr != &src_part->hdr) {
+ pjsip_hdr *dst_hdr = (pjsip_hdr*)pjsip_hdr_clone(pool, src_hdr);
+ pj_list_push_back(&dst_part->hdr, dst_hdr);
+ src_hdr = src_hdr->next;
+ }
+
+ dst_part->body = pjsip_msg_body_clone(pool, src_part->body);
+
+ pj_list_push_back(&dst->part_head, dst_part);
+
+ src_part = src_part->next;
+ }
+
+ return (void*)dst;
+}
+
+/*
+ * Create an empty multipart body.
+ */
+PJ_DEF(pjsip_msg_body*) pjsip_multipart_create( pj_pool_t *pool,
+ const pjsip_media_type *ctype,
+ const pj_str_t *boundary)
+{
+ pjsip_msg_body *body;
+ pjsip_param *ctype_param;
+ struct multipart_data *mp_data;
+ pj_str_t STR_BOUNDARY = { "boundary", 8 };
+
+ PJ_ASSERT_RETURN(pool, NULL);
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+
+ /* content-type */
+ if (ctype && ctype->type.slen) {
+ pjsip_media_type_cp(pool, &body->content_type, ctype);
+ } else {
+ pj_str_t STR_MULTIPART = {"multipart", 9};
+ pj_str_t STR_MIXED = { "mixed", 5 };
+
+ pjsip_media_type_init(&body->content_type,
+ &STR_MULTIPART, &STR_MIXED);
+ }
+
+ /* multipart data */
+ mp_data = PJ_POOL_ZALLOC_T(pool, struct multipart_data);
+ pj_list_init(&mp_data->part_head);
+ if (boundary) {
+ pj_strdup(pool, &mp_data->boundary, boundary);
+ } else {
+ pj_create_unique_string(pool, &mp_data->boundary);
+ }
+ body->data = mp_data;
+
+ /* Add ";boundary" parameter to content_type parameter. */
+ ctype_param = pjsip_param_find(&body->content_type.param, &STR_BOUNDARY);
+ if (!ctype_param) {
+ ctype_param = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ ctype_param->name = STR_BOUNDARY;
+ pj_list_push_back(&body->content_type.param, ctype_param);
+ }
+ ctype_param->value = mp_data->boundary;
+
+ /* function pointers */
+ body->print_body = &multipart_print_body;
+ body->clone_data = &multipart_clone_data;
+
+ return body;
+}
+
+/*
+ * Create an empty multipart part.
+ */
+PJ_DEF(pjsip_multipart_part*) pjsip_multipart_create_part(pj_pool_t *pool)
+{
+ pjsip_multipart_part *mp;
+
+ mp = PJ_POOL_ZALLOC_T(pool, pjsip_multipart_part);
+ pj_list_init(&mp->hdr);
+
+ return mp;
+}
+
+
+/*
+ * Deep clone.
+ */
+PJ_DEF(pjsip_multipart_part*)
+pjsip_multipart_clone_part(pj_pool_t *pool,
+ const pjsip_multipart_part *src)
+{
+ pjsip_multipart_part *dst;
+ const pjsip_hdr *hdr;
+
+ dst = pjsip_multipart_create_part(pool);
+
+ hdr = src->hdr.next;
+ while (hdr != &src->hdr) {
+ pj_list_push_back(&dst->hdr, pjsip_hdr_clone(pool, hdr));
+ hdr = hdr->next;
+ }
+
+ dst->body = pjsip_msg_body_clone(pool, src->body);
+
+ return dst;
+}
+
+
+/*
+ * Add a part into multipart bodies.
+ */
+PJ_DEF(pj_status_t) pjsip_multipart_add_part( pj_pool_t *pool,
+ pjsip_msg_body *mp,
+ pjsip_multipart_part *part)
+{
+ struct multipart_data *m_data;
+
+ /* All params must be specified */
+ PJ_ASSERT_RETURN(pool && mp && part, PJ_EINVAL);
+
+ /* mp must really point to an actual multipart msg body */
+ PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, PJ_EINVAL);
+
+ /* The multipart part must contain a valid message body */
+ PJ_ASSERT_RETURN(part->body && part->body->print_body, PJ_EINVAL);
+
+ m_data = (struct multipart_data*)mp->data;
+ pj_list_push_back(&m_data->part_head, part);
+
+ PJ_UNUSED_ARG(pool);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get the first part of multipart bodies.
+ */
+PJ_DEF(pjsip_multipart_part*)
+pjsip_multipart_get_first_part(const pjsip_msg_body *mp)
+{
+ struct multipart_data *m_data;
+
+ /* Must specify mandatory params */
+ PJ_ASSERT_RETURN(mp, NULL);
+
+ /* mp must really point to an actual multipart msg body */
+ PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
+
+ m_data = (struct multipart_data*)mp->data;
+ if (pj_list_empty(&m_data->part_head))
+ return NULL;
+
+ return m_data->part_head.next;
+}
+
+/*
+ * Get the next part after the specified part.
+ */
+PJ_DEF(pjsip_multipart_part*)
+pjsip_multipart_get_next_part(const pjsip_msg_body *mp,
+ pjsip_multipart_part *part)
+{
+ struct multipart_data *m_data;
+
+ /* Must specify mandatory params */
+ PJ_ASSERT_RETURN(mp && part, NULL);
+
+ /* mp must really point to an actual multipart msg body */
+ PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
+
+ m_data = (struct multipart_data*)mp->data;
+
+ /* the part parameter must be really member of the list */
+ PJ_ASSERT_RETURN(pj_list_find_node(&m_data->part_head, part) != NULL,
+ NULL);
+
+ if (part->next == &m_data->part_head)
+ return NULL;
+
+ return part->next;
+}
+
+/*
+ * Find a body inside multipart bodies which has the specified content type.
+ */
+PJ_DEF(pjsip_multipart_part*)
+pjsip_multipart_find_part( const pjsip_msg_body *mp,
+ const pjsip_media_type *content_type,
+ const pjsip_multipart_part *start)
+{
+ struct multipart_data *m_data;
+ pjsip_multipart_part *part;
+
+ /* Must specify mandatory params */
+ PJ_ASSERT_RETURN(mp && content_type, NULL);
+
+ /* mp must really point to an actual multipart msg body */
+ PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
+
+ m_data = (struct multipart_data*)mp->data;
+
+ if (start)
+ part = start->next;
+ else
+ part = m_data->part_head.next;
+
+ while (part != &m_data->part_head) {
+ if (pjsip_media_type_cmp(&part->body->content_type,
+ content_type, 0)==0)
+ {
+ return part;
+ }
+ part = part->next;
+ }
+
+ return NULL;
+}
+
+/* Parse a multipart part. "pct" is parent content-type */
+static pjsip_multipart_part *parse_multipart_part(pj_pool_t *pool,
+ char *start,
+ pj_size_t len,
+ const pjsip_media_type *pct)
+{
+ pjsip_multipart_part *part = pjsip_multipart_create_part(pool);
+ char *p = start, *end = start+len, *end_hdr = NULL, *start_body = NULL;
+ pjsip_ctype_hdr *ctype_hdr = NULL;
+
+ TRACE_((THIS_FILE, "Parsing part: begin--\n%.*s\n--end",
+ (int)len, start));
+
+ /* Find the end of header area, by looking at an empty line */
+ for (;;) {
+ while (p!=end && *p!='\n') ++p;
+ if (p==end) {
+ start_body = end;
+ break;
+ }
+ if ((p==start) || (p==start+1 && *(p-1)=='\r')) {
+ /* Empty header section */
+ end_hdr = start;
+ start_body = ++p;
+ break;
+ } else if (p==end-1) {
+ /* Empty body section */
+ end_hdr = end;
+ start_body = ++p;
+ } else if ((p>=start+1 && *(p-1)=='\n') ||
+ (p>=start+2 && *(p-1)=='\r' && *(p-2)=='\n'))
+ {
+ /* Found it */
+ end_hdr = (*(p-1)=='\r') ? (p-1) : p;
+ start_body = ++p;
+ break;
+ } else {
+ ++p;
+ }
+ }
+
+ /* Parse the headers */
+ if (end_hdr-start > 0) {
+ pjsip_hdr *hdr;
+ pj_status_t status;
+
+ status = pjsip_parse_headers(pool, start, end_hdr-start,
+ &part->hdr, 0);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(2,(THIS_FILE, status, "Warning: error parsing multipart"
+ " header"));
+ }
+
+ /* Find Content-Type header */
+ hdr = part->hdr.next;
+ while (hdr != &part->hdr) {
+ TRACE_((THIS_FILE, "Header parsed: %.*s", (int)hdr->name.slen,
+ hdr->name.ptr));
+ if (hdr->type == PJSIP_H_CONTENT_TYPE) {
+ ctype_hdr = (pjsip_ctype_hdr*)hdr;
+ }
+ hdr = hdr->next;
+ }
+ }
+
+ /* Assign the body */
+ part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ if (ctype_hdr) {
+ pjsip_media_type_cp(pool, &part->body->content_type, &ctype_hdr->media);
+ } else if (pct && pj_stricmp2(&pct->subtype, "digest")==0) {
+ part->body->content_type.type = pj_str("message");
+ part->body->content_type.subtype = pj_str("rfc822");
+ } else {
+ part->body->content_type.type = pj_str("text");
+ part->body->content_type.subtype = pj_str("plain");
+ }
+
+ if (start_body < end) {
+ part->body->data = start_body;
+ part->body->len = end - start_body;
+ } else {
+ part->body->data = (void*)"";
+ part->body->len = 0;
+ }
+ TRACE_((THIS_FILE, "Body parsed: \"%.*s\"", (int)part->body->len,
+ part->body->data));
+ part->body->print_body = &pjsip_print_text_body;
+ part->body->clone_data = &pjsip_clone_text_data;
+
+ return part;
+}
+
+/* Public function to parse multipart message bodies into its parts */
+PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool,
+ char *buf, pj_size_t len,
+ const pjsip_media_type *ctype,
+ unsigned options)
+{
+ pj_str_t boundary, delim;
+ char *curptr, *endptr;
+ const pjsip_param *ctype_param;
+ const pj_str_t STR_BOUNDARY = { "boundary", 8 };
+ pjsip_msg_body *body = NULL;
+
+ PJ_ASSERT_RETURN(pool && buf && len && ctype && !options, NULL);
+
+ TRACE_((THIS_FILE, "Started parsing multipart body"));
+
+ /* Get the boundary value in the ctype */
+ boundary.ptr = NULL;
+ boundary.slen = 0;
+ ctype_param = pjsip_param_find(&ctype->param, &STR_BOUNDARY);
+ if (ctype_param) {
+ boundary = ctype_param->value;
+ if (boundary.slen>2 && *boundary.ptr=='"') {
+ /* Remove quote */
+ boundary.ptr++;
+ boundary.slen -= 2;
+ }
+ TRACE_((THIS_FILE, "Boundary is specified: '%.*s'", (int)boundary.slen,
+ boundary.ptr));
+ }
+
+ if (!boundary.slen) {
+ /* Boundary not found or not specified. Try to be clever, get
+ * the boundary from the body.
+ */
+ char *p=buf, *end=buf+len;
+
+ PJ_LOG(4,(THIS_FILE, "Warning: boundary parameter not found or "
+ "not specified when parsing multipart body"));
+
+ /* Find the first "--". This "--" must be right after a CRLF, unless
+ * it really appears at the start of the buffer.
+ */
+ for (;;) {
+ while (p!=end && *p!='-') ++p;
+ if (p!=end && *(p+1)=='-' &&
+ ((p>buf && *(p-1)=='\n') || (p==buf)))
+ {
+ p+=2;
+ break;
+ } else {
+ ++p;
+ }
+ }
+
+ if (p==end) {
+ /* Unable to determine boundary. Maybe this is not a multipart
+ * message?
+ */
+ PJ_LOG(4,(THIS_FILE, "Error: multipart boundary not specified and"
+ " unable to calculate from the body"));
+ return NULL;
+ }
+
+ boundary.ptr = p;
+ while (p!=end && !pj_isspace(*p)) ++p;
+ boundary.slen = p - boundary.ptr;
+
+ TRACE_((THIS_FILE, "Boundary is calculated: '%.*s'",
+ (int)boundary.slen, boundary.ptr));
+ }
+
+ /* Build the delimiter:
+ * delimiter = "--" boundary
+ */
+ delim.slen = boundary.slen+2;
+ delim.ptr = (char*)pj_pool_alloc(pool, (int)delim.slen);
+ delim.ptr[0] = '-';
+ delim.ptr[1] = '-';
+ pj_memcpy(delim.ptr+2, boundary.ptr, boundary.slen);
+
+ /* Start parsing the body, skip until the first delimiter. */
+ curptr = buf;
+ endptr = buf + len;
+ {
+ pj_str_t body;
+
+ body.ptr = buf; body.slen = len;
+ curptr = pj_strstr(&body, &delim);
+ if (!curptr)
+ return NULL;
+ }
+
+ body = pjsip_multipart_create(pool, ctype, &boundary);
+
+ for (;;) {
+ char *start_body, *end_body;
+ pjsip_multipart_part *part;
+
+ /* Eat the boundary */
+ curptr += delim.slen;
+ if (*curptr=='-' && curptr<endptr-1 && *(curptr+1)=='-') {
+ /* Found the closing delimiter */
+ curptr += 2;
+ break;
+ }
+ /* Optional whitespace after delimiter */
+ while (curptr!=endptr && IS_SPACE(*curptr)) ++curptr;
+ /* Mandatory CRLF */
+ if (*curptr=='\r') ++curptr;
+ if (*curptr!='\n') {
+ /* Expecting a newline here */
+ return NULL;
+ }
+ ++curptr;
+
+ /* We now in the start of the body */
+ start_body = curptr;
+
+ /* Find the next delimiter */
+ {
+ pj_str_t subbody;
+
+ subbody.ptr = curptr; subbody.slen = endptr - curptr;
+ curptr = pj_strstr(&subbody, &delim);
+ if (!curptr) {
+ /* We're really expecting end delimiter to be found. */
+ return NULL;
+ }
+ }
+
+ end_body = curptr;
+
+ /* The newline preceeding the delimiter is conceptually part of
+ * the delimiter, so trim it from the body.
+ */
+ if (*(end_body-1) == '\n')
+ --end_body;
+ if (*(end_body-1) == '\r')
+ --end_body;
+
+ /* Now that we have determined the part's boundary, parse it
+ * to get the header and body part of the part.
+ */
+ part = parse_multipart_part(pool, start_body, end_body - start_body,
+ ctype);
+ if (part) {
+ pjsip_multipart_add_part(pool, body, part);
+ }
+ }
+
+ return body;
+}
+
diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c
new file mode 100644
index 0000000..2d46fe9
--- /dev/null
+++ b/pjsip/src/pjsip/sip_parser.c
@@ -0,0 +1,2379 @@
+/* $Id: sip_parser.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_parser.h>
+#include <pjsip/sip_uri.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_multipart.h>
+#include <pjsip/sip_auth_parser.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_transport.h> /* rdata structure */
+#include <pjlib-util/scanner.h>
+#include <pjlib-util/string.h>
+#include <pj/except.h>
+#include <pj/log.h>
+#include <pj/hash.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/ctype.h>
+#include <pj/assert.h>
+
+#define THIS_FILE "sip_parser.c"
+
+#define ALNUM
+#define RESERVED ";/?:@&=+$,"
+#define MARK "-_.!~*'()"
+#define UNRESERVED ALNUM MARK
+#define ESCAPED "%"
+#define USER_UNRESERVED "&=+$,;?/"
+#define PASS "&=+$,"
+#define TOKEN "-.!%*_`'~+" /* '=' was removed for parsing
+ * param */
+#define HOST "_-."
+#define HEX_DIGIT "abcdefABCDEF"
+#define PARAM_CHAR "[]/:&+$" UNRESERVED ESCAPED
+#define HNV_UNRESERVED "[]/?:+$"
+#define HDR_CHAR HNV_UNRESERVED UNRESERVED ESCAPED
+
+/* A generic URI can consist of (For a complete BNF see RFC 2396):
+ #?;:@&=+-_.!~*'()%$,/
+ */
+#define GENERIC_URI_CHARS "#?;:@&=+-_.!~*'()%$,/" "%"
+
+#define UNREACHED(expr)
+
+#define IS_NEWLINE(c) ((c)=='\r' || (c)=='\n')
+#define IS_SPACE(c) ((c)==' ' || (c)=='\t')
+
+/*
+ * Header parser records.
+ */
+typedef struct handler_rec
+{
+ char hname[PJSIP_MAX_HNAME_LEN+1];
+ pj_size_t hname_len;
+ pj_uint32_t hname_hash;
+ pjsip_parse_hdr_func *handler;
+} handler_rec;
+
+static handler_rec handler[PJSIP_MAX_HEADER_TYPES];
+static unsigned handler_count;
+static int parser_is_initialized;
+
+/*
+ * URI parser records.
+ */
+typedef struct uri_parser_rec
+{
+ pj_str_t scheme;
+ pjsip_parse_uri_func *parse;
+} uri_parser_rec;
+
+static uri_parser_rec uri_handler[PJSIP_MAX_URI_TYPES];
+static unsigned uri_handler_count;
+
+/*
+ * Global vars (also extern).
+ */
+int PJSIP_SYN_ERR_EXCEPTION = -1;
+
+/* Parser constants */
+static pjsip_parser_const_t pconst =
+{
+ { "user", 4}, /* pjsip_USER_STR */
+ { "method", 6}, /* pjsip_METHOD_STR */
+ { "transport", 9}, /* pjsip_TRANSPORT_STR */
+ { "maddr", 5 }, /* pjsip_MADDR_STR */
+ { "lr", 2 }, /* pjsip_LR_STR */
+ { "sip", 3 }, /* pjsip_SIP_STR */
+ { "sips", 4 }, /* pjsip_SIPS_STR */
+ { "tel", 3 }, /* pjsip_TEL_STR */
+ { "branch", 6 }, /* pjsip_BRANCH_STR */
+ { "ttl", 3 }, /* pjsip_TTL_STR */
+ { "received", 8 }, /* pjsip_RECEIVED_STR */
+ { "q", 1 }, /* pjsip_Q_STR */
+ { "expires", 7 }, /* pjsip_EXPIRES_STR */
+ { "tag", 3 }, /* pjsip_TAG_STR */
+ { "rport", 5} /* pjsip_RPORT_STR */
+};
+
+/* Character Input Specification buffer. */
+static pj_cis_buf_t cis_buf;
+
+
+/*
+ * Forward decl.
+ */
+static pjsip_msg * int_parse_msg( pjsip_parse_ctx *ctx,
+ pjsip_parser_err_report *err_list);
+static void int_parse_param( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_str_t *pname,
+ pj_str_t *pvalue,
+ unsigned option);
+static void int_parse_uri_param( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_str_t *pname,
+ pj_str_t *pvalue,
+ unsigned option);
+static void int_parse_hparam( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_str_t *hname,
+ pj_str_t *hvalue );
+static void int_parse_req_line( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pjsip_request_line *req_line);
+static int int_is_next_user( pj_scanner *scanner);
+static void int_parse_status_line( pj_scanner *scanner,
+ pjsip_status_line *line);
+static void int_parse_user_pass( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_str_t *user,
+ pj_str_t *pass);
+static void int_parse_uri_host_port( pj_scanner *scanner,
+ pj_str_t *p_host,
+ int *p_port);
+static pjsip_uri * int_parse_uri_or_name_addr( pj_scanner *scanner,
+ pj_pool_t *pool,
+ unsigned option);
+static void* int_parse_sip_url( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_bool_t parse_params);
+static pjsip_name_addr *
+ int_parse_name_addr( pj_scanner *scanner,
+ pj_pool_t *pool );
+static void* int_parse_other_uri(pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_bool_t parse_params);
+static void parse_hdr_end( pj_scanner *scanner );
+
+static pjsip_hdr* parse_hdr_accept( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_allow( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_call_id( pjsip_parse_ctx *ctx);
+static pjsip_hdr* parse_hdr_contact( pjsip_parse_ctx *ctx);
+static pjsip_hdr* parse_hdr_content_len( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_content_type( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_cseq( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_expires( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_from( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_max_forwards( pjsip_parse_ctx *ctx);
+static pjsip_hdr* parse_hdr_min_expires( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_rr( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_route( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_require( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_retry_after( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_supported( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_to( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_unsupported( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_via( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_generic_string( pjsip_parse_ctx *ctx);
+
+/* Convert non NULL terminated string to integer. */
+static unsigned long pj_strtoul_mindigit(const pj_str_t *str,
+ unsigned mindig)
+{
+ unsigned long value;
+ unsigned i;
+
+ value = 0;
+ for (i=0; i<(unsigned)str->slen; ++i) {
+ value = value * 10 + (str->ptr[i] - '0');
+ }
+ for (; i<mindig; ++i) {
+ value = value * 10;
+ }
+ return value;
+}
+
+/* Case insensitive comparison */
+#define parser_stricmp(s1, s2) (s1.slen!=s2.slen || pj_stricmp_alnum(&s1, &s2))
+
+
+/* Get a token and unescape */
+PJ_INLINE(void) parser_get_and_unescape(pj_scanner *scanner, pj_pool_t *pool,
+ const pj_cis_t *spec,
+ const pj_cis_t *unesc_spec,
+ pj_str_t *token)
+{
+#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(spec);
+ pj_scan_get_unescape(scanner, unesc_spec, token);
+#else
+ PJ_UNUSED_ARG(unesc_spec);
+ pj_scan_get(scanner, spec, token);
+ *token = pj_str_unescape(pool, token);
+#endif
+}
+
+
+
+/* Syntax error handler for parser. */
+static void on_syntax_error(pj_scanner *scanner)
+{
+ PJ_UNUSED_ARG(scanner);
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+}
+
+/* Get parser constants. */
+PJ_DEF(const pjsip_parser_const_t*) pjsip_parser_const(void)
+{
+ return &pconst;
+}
+
+/* Concatenate unrecognized params into single string. */
+PJ_DEF(void) pjsip_concat_param_imp(pj_str_t *param, pj_pool_t *pool,
+ const pj_str_t *pname,
+ const pj_str_t *pvalue,
+ int sepchar)
+{
+ char *new_param, *p;
+ int len;
+
+ len = param->slen + pname->slen + pvalue->slen + 3;
+ p = new_param = (char*) pj_pool_alloc(pool, len);
+
+ if (param->slen) {
+ int old_len = param->slen;
+ pj_memcpy(p, param->ptr, old_len);
+ p += old_len;
+ }
+ *p++ = (char)sepchar;
+ pj_memcpy(p, pname->ptr, pname->slen);
+ p += pname->slen;
+
+ if (pvalue->slen) {
+ *p++ = '=';
+ pj_memcpy(p, pvalue->ptr, pvalue->slen);
+ p += pvalue->slen;
+ }
+
+ *p = '\0';
+
+ param->ptr = new_param;
+ param->slen = p - new_param;
+}
+
+/* Initialize static properties of the parser. */
+static pj_status_t init_parser()
+{
+ pj_status_t status;
+
+ /*
+ * Syntax error exception number.
+ */
+ pj_assert (PJSIP_SYN_ERR_EXCEPTION == -1);
+ status = pj_exception_id_alloc("PJSIP syntax error",
+ &PJSIP_SYN_ERR_EXCEPTION);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /*
+ * Init character input spec (cis)
+ */
+
+ pj_cis_buf_init(&cis_buf);
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_DIGIT_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_num(&pconst.pjsip_DIGIT_SPEC);
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_ALPHA_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_alpha( &pconst.pjsip_ALPHA_SPEC );
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_alpha( &pconst.pjsip_ALNUM_SPEC );
+ pj_cis_add_num( &pconst.pjsip_ALNUM_SPEC );
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_NOT_NEWLINE);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_NOT_NEWLINE, "\r\n");
+ pj_cis_invert(&pconst.pjsip_NOT_NEWLINE);
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_NOT_COMMA_OR_NEWLINE);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_NOT_COMMA_OR_NEWLINE, ",\r\n");
+ pj_cis_invert(&pconst.pjsip_NOT_COMMA_OR_NEWLINE);
+
+ status = pj_cis_dup(&pconst.pjsip_TOKEN_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_TOKEN_SPEC, TOKEN);
+
+ status = pj_cis_dup(&pconst.pjsip_TOKEN_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_del_str(&pconst.pjsip_TOKEN_SPEC_ESC, "%");
+
+ status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC, &pconst.pjsip_TOKEN_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC, ":");
+
+ status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC_ESC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC, ":");
+
+ status = pj_cis_dup(&pconst.pjsip_HOST_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_HOST_SPEC, HOST);
+
+ status = pj_cis_dup(&pconst.pjsip_HEX_SPEC, &pconst.pjsip_DIGIT_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_HEX_SPEC, HEX_DIGIT);
+
+ status = pj_cis_dup(&pconst.pjsip_PARAM_CHAR_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_PARAM_CHAR_SPEC, PARAM_CHAR);
+
+ status = pj_cis_dup(&pconst.pjsip_PARAM_CHAR_SPEC_ESC, &pconst.pjsip_PARAM_CHAR_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_del_str(&pconst.pjsip_PARAM_CHAR_SPEC_ESC, ESCAPED);
+
+ status = pj_cis_dup(&pconst.pjsip_HDR_CHAR_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_HDR_CHAR_SPEC, HDR_CHAR);
+
+ status = pj_cis_dup(&pconst.pjsip_HDR_CHAR_SPEC_ESC, &pconst.pjsip_HDR_CHAR_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_del_str(&pconst.pjsip_HDR_CHAR_SPEC_ESC, ESCAPED);
+
+ status = pj_cis_dup(&pconst.pjsip_USER_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_USER_SPEC, UNRESERVED ESCAPED USER_UNRESERVED );
+
+ status = pj_cis_dup(&pconst.pjsip_USER_SPEC_ESC, &pconst.pjsip_USER_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_del_str( &pconst.pjsip_USER_SPEC_ESC, ESCAPED);
+
+ status = pj_cis_dup(&pconst.pjsip_USER_SPEC_LENIENT, &pconst.pjsip_USER_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_USER_SPEC_LENIENT, "#");
+
+ status = pj_cis_dup(&pconst.pjsip_USER_SPEC_LENIENT_ESC, &pconst.pjsip_USER_SPEC_ESC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_USER_SPEC_LENIENT_ESC, "#");
+
+ status = pj_cis_dup(&pconst.pjsip_PASSWD_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_PASSWD_SPEC, UNRESERVED ESCAPED PASS);
+
+ status = pj_cis_dup(&pconst.pjsip_PASSWD_SPEC_ESC, &pconst.pjsip_PASSWD_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_del_str( &pconst.pjsip_PASSWD_SPEC_ESC, ESCAPED);
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_PROBE_USER_HOST_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_PROBE_USER_HOST_SPEC, "@ \n>");
+ pj_cis_invert( &pconst.pjsip_PROBE_USER_HOST_SPEC );
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_DISPLAY_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_DISPLAY_SPEC, ":\r\n<");
+ pj_cis_invert(&pconst.pjsip_DISPLAY_SPEC);
+
+ status = pj_cis_dup(&pconst.pjsip_OTHER_URI_CONTENT, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_OTHER_URI_CONTENT, GENERIC_URI_CHARS);
+
+ /*
+ * Register URI parsers.
+ */
+
+ status = pjsip_register_uri_parser("sip", &int_parse_sip_url);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_uri_parser("sips", &int_parse_sip_url);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /*
+ * Register header parsers.
+ */
+
+ status = pjsip_register_hdr_parser( "Accept", NULL, &parse_hdr_accept);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Allow", NULL, &parse_hdr_allow);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Call-ID", "i", &parse_hdr_call_id);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Contact", "m", &parse_hdr_contact);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Content-Length", "l",
+ &parse_hdr_content_len);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Content-Type", "c",
+ &parse_hdr_content_type);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "CSeq", NULL, &parse_hdr_cseq);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Expires", NULL, &parse_hdr_expires);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "From", "f", &parse_hdr_from);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Max-Forwards", NULL,
+ &parse_hdr_max_forwards);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Min-Expires", NULL,
+ &parse_hdr_min_expires);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Record-Route", NULL, &parse_hdr_rr);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Route", NULL, &parse_hdr_route);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Require", NULL, &parse_hdr_require);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Retry-After", NULL,
+ &parse_hdr_retry_after);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Supported", "k",
+ &parse_hdr_supported);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "To", "t", &parse_hdr_to);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Unsupported", NULL,
+ &parse_hdr_unsupported);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Via", "v", &parse_hdr_via);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /*
+ * Register auth parser.
+ */
+
+ status = pjsip_auth_init_parser();
+
+ return status;
+}
+
+void init_sip_parser(void)
+{
+ pj_enter_critical_section();
+ if (++parser_is_initialized == 1) {
+ init_parser();
+ }
+ pj_leave_critical_section();
+}
+
+void deinit_sip_parser(void)
+{
+ pj_enter_critical_section();
+ if (--parser_is_initialized == 0) {
+ /* Clear header handlers */
+ pj_bzero(handler, sizeof(handler));
+ handler_count = 0;
+
+ /* Clear URI handlers */
+ pj_bzero(uri_handler, sizeof(uri_handler));
+ uri_handler_count = 0;
+
+ /* Deregister exception ID */
+ pj_exception_id_free(PJSIP_SYN_ERR_EXCEPTION);
+ PJSIP_SYN_ERR_EXCEPTION = -1;
+ }
+ pj_leave_critical_section();
+}
+
+/* Compare the handler record with header name, and return:
+ * - 0 if handler match.
+ * - <0 if handler is 'less' than the header name.
+ * - >0 if handler is 'greater' than header name.
+ */
+PJ_INLINE(int) compare_handler( const handler_rec *r1,
+ const char *name,
+ pj_size_t name_len,
+ pj_uint32_t hash )
+{
+ PJ_UNUSED_ARG(name_len);
+
+ /* Compare hashed value. */
+ if (r1->hname_hash < hash)
+ return -1;
+ if (r1->hname_hash > hash)
+ return 1;
+
+ /* Compare length. */
+ /*
+ if (r1->hname_len < name_len)
+ return -1;
+ if (r1->hname_len > name_len)
+ return 1;
+ */
+
+ /* Equal length and equal hash. compare the strings. */
+ return pj_memcmp(r1->hname, name, name_len);
+}
+
+/* Register one handler for one header name. */
+static pj_status_t int_register_parser( const char *name,
+ pjsip_parse_hdr_func *fptr )
+{
+ unsigned pos;
+ handler_rec rec;
+
+ if (handler_count >= PJ_ARRAY_SIZE(handler)) {
+ pj_assert(!"Too many handlers!");
+ return PJ_ETOOMANY;
+ }
+
+ /* Initialize temporary handler. */
+ rec.handler = fptr;
+ rec.hname_len = strlen(name);
+ if (rec.hname_len >= sizeof(rec.hname)) {
+ pj_assert(!"Header name is too long!");
+ return PJ_ENAMETOOLONG;
+ }
+ /* Copy name. */
+ pj_memcpy(rec.hname, name, rec.hname_len);
+ rec.hname[rec.hname_len] = '\0';
+
+ /* Calculate hash value. */
+ rec.hname_hash = pj_hash_calc(0, rec.hname, rec.hname_len);
+
+ /* Get the pos to insert the new handler. */
+ for (pos=0; pos < handler_count; ++pos) {
+ int d;
+ d = compare_handler(&handler[pos], rec.hname, rec.hname_len,
+ rec.hname_hash);
+ if (d == 0) {
+ pj_assert(0);
+ return PJ_EEXISTS;
+ }
+ if (d > 0) {
+ break;
+ }
+ }
+
+ /* Shift handlers. */
+ if (pos != handler_count) {
+ pj_memmove( &handler[pos+1], &handler[pos],
+ (handler_count-pos)*sizeof(handler_rec));
+ }
+ /* Add new handler. */
+ pj_memcpy( &handler[pos], &rec, sizeof(handler_rec));
+ ++handler_count;
+
+ return PJ_SUCCESS;
+}
+
+/* Register parser handler. If both header name and short name are valid,
+ * then two instances of handler will be registered.
+ */
+PJ_DEF(pj_status_t) pjsip_register_hdr_parser( const char *hname,
+ const char *hshortname,
+ pjsip_parse_hdr_func *fptr)
+{
+ unsigned i, len;
+ char hname_lcase[PJSIP_MAX_HNAME_LEN+1];
+ pj_status_t status;
+
+ /* Check that name is not too long */
+ len = pj_ansi_strlen(hname);
+ if (len > PJSIP_MAX_HNAME_LEN) {
+ pj_assert(!"Header name is too long!");
+ return PJ_ENAMETOOLONG;
+ }
+
+ /* Register the normal Mixed-Case name */
+ status = int_register_parser(hname, fptr);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Get the lower-case name */
+ for (i=0; i<len; ++i) {
+ hname_lcase[i] = (char)pj_tolower(hname[i]);
+ }
+ hname_lcase[len] = '\0';
+
+ /* Register the lower-case version of the name */
+ status = int_register_parser(hname_lcase, fptr);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+
+ /* Register the shortname version of the name */
+ if (hshortname) {
+ status = int_register_parser(hshortname, fptr);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+ return PJ_SUCCESS;
+}
+
+
+/* Find handler to parse the header name. */
+static pjsip_parse_hdr_func * find_handler_imp(pj_uint32_t hash,
+ const pj_str_t *hname)
+{
+ handler_rec *first;
+ int comp;
+ unsigned n;
+
+ /* Binary search for the handler. */
+ comp = -1;
+ first = &handler[0];
+ n = handler_count;
+ for (; n > 0; ) {
+ unsigned half = n / 2;
+ handler_rec *mid = first + half;
+
+ comp = compare_handler(mid, hname->ptr, hname->slen, hash);
+ if (comp < 0) {
+ first = ++mid;
+ n -= half + 1;
+ } else if (comp==0) {
+ first = mid;
+ break;
+ } else {
+ n = half;
+ }
+ }
+
+ return comp==0 ? first->handler : NULL;
+}
+
+
+/* Find handler to parse the header name. */
+static pjsip_parse_hdr_func* find_handler(const pj_str_t *hname)
+{
+ pj_uint32_t hash;
+ char hname_copy[PJSIP_MAX_HNAME_LEN];
+ pj_str_t tmp;
+ pjsip_parse_hdr_func *handler;
+
+ if (hname->slen >= PJSIP_MAX_HNAME_LEN) {
+ /* Guaranteed not to be able to find handler. */
+ return NULL;
+ }
+
+ /* First, common case, try to find handler with exact name */
+ hash = pj_hash_calc(0, hname->ptr, hname->slen);
+ handler = find_handler_imp(hash, hname);
+ if (handler)
+ return handler;
+
+
+ /* If not found, try converting the header name to lowercase and
+ * search again.
+ */
+ hash = pj_hash_calc_tolower(0, hname_copy, hname);
+ tmp.ptr = hname_copy;
+ tmp.slen = hname->slen;
+ return find_handler_imp(hash, &tmp);
+}
+
+
+/* Find URI handler. */
+static pjsip_parse_uri_func* find_uri_handler(const pj_str_t *scheme)
+{
+ unsigned i;
+ for (i=0; i<uri_handler_count; ++i) {
+ if (parser_stricmp(uri_handler[i].scheme, (*scheme))==0)
+ return uri_handler[i].parse;
+ }
+ return &int_parse_other_uri;
+}
+
+/* Register URI parser. */
+PJ_DEF(pj_status_t) pjsip_register_uri_parser( char *scheme,
+ pjsip_parse_uri_func *func)
+{
+ if (uri_handler_count >= PJ_ARRAY_SIZE(uri_handler))
+ return PJ_ETOOMANY;
+
+ uri_handler[uri_handler_count].scheme = pj_str((char*)scheme);
+ uri_handler[uri_handler_count].parse = func;
+ ++uri_handler_count;
+
+ return PJ_SUCCESS;
+}
+
+/* Public function to parse SIP message. */
+PJ_DEF(pjsip_msg*) pjsip_parse_msg( pj_pool_t *pool,
+ char *buf, pj_size_t size,
+ pjsip_parser_err_report *err_list)
+{
+ pjsip_msg *msg = NULL;
+ pj_scanner scanner;
+ pjsip_parse_ctx context;
+
+ pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER,
+ &on_syntax_error);
+
+ context.scanner = &scanner;
+ context.pool = pool;
+ context.rdata = NULL;
+
+ msg = int_parse_msg(&context, err_list);
+
+ pj_scan_fini(&scanner);
+ return msg;
+}
+
+/* Public function to parse as rdata.*/
+PJ_DEF(pjsip_msg *) pjsip_parse_rdata( char *buf, pj_size_t size,
+ pjsip_rx_data *rdata )
+{
+ pj_scanner scanner;
+ pjsip_parse_ctx context;
+
+ pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER,
+ &on_syntax_error);
+
+ context.scanner = &scanner;
+ context.pool = rdata->tp_info.pool;
+ context.rdata = rdata;
+
+ rdata->msg_info.msg = int_parse_msg(&context, &rdata->msg_info.parse_err);
+
+ pj_scan_fini(&scanner);
+ return rdata->msg_info.msg;
+}
+
+/* Determine if a message has been received. */
+PJ_DEF(pj_bool_t) pjsip_find_msg( const char *buf, pj_size_t size,
+ pj_bool_t is_datagram, pj_size_t *msg_size)
+{
+#if PJ_HAS_TCP
+ const char *hdr_end;
+ const char *body_start;
+ const char *pos;
+ const char *line;
+ int content_length = -1;
+ pj_str_t cur_msg;
+ const pj_str_t end_hdr = { "\n\r\n", 3};
+
+ *msg_size = size;
+
+ /* For datagram, the whole datagram IS the message. */
+ if (is_datagram) {
+ return PJ_SUCCESS;
+ }
+
+
+ /* Find the end of header area by finding an empty line.
+ * Don't use plain strstr() since we want to be able to handle
+ * NULL character in the message
+ */
+ cur_msg.ptr = (char*)buf; cur_msg.slen = size;
+ pos = pj_strstr(&cur_msg, &end_hdr);
+ if (pos == NULL) {
+ return PJSIP_EPARTIALMSG;
+ }
+
+ hdr_end = pos+1;
+ body_start = pos+3;
+
+ /* Find "Content-Length" header the hard way. */
+ line = pj_strchr(&cur_msg, '\n');
+ while (line && line < hdr_end) {
+ ++line;
+ if ( ((*line=='C' || *line=='c') &&
+ strnicmp_alnum(line, "Content-Length", 14) == 0) ||
+ ((*line=='l' || *line=='L') &&
+ (*(line+1)==' ' || *(line+1)=='\t' || *(line+1)==':')))
+ {
+ /* Try to parse the header. */
+ pj_scanner scanner;
+ PJ_USE_EXCEPTION;
+
+ pj_scan_init(&scanner, (char*)line, hdr_end-line,
+ PJ_SCAN_AUTOSKIP_WS_HEADER, &on_syntax_error);
+
+ PJ_TRY {
+ pj_str_t str_clen;
+
+ /* Get "Content-Length" or "L" name */
+ if (*line=='C' || *line=='c')
+ pj_scan_advance_n(&scanner, 14, PJ_TRUE);
+ else if (*line=='l' || *line=='L')
+ pj_scan_advance_n(&scanner, 1, PJ_TRUE);
+
+ /* Get colon */
+ if (pj_scan_get_char(&scanner) != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ /* Get number */
+ pj_scan_get(&scanner, &pconst.pjsip_DIGIT_SPEC, &str_clen);
+
+ /* Get newline. */
+ pj_scan_get_newline(&scanner);
+
+ /* Found a valid Content-Length header. */
+ content_length = pj_strtoul(&str_clen);
+ }
+ PJ_CATCH_ANY {
+ content_length = -1;
+ }
+ PJ_END
+
+ pj_scan_fini(&scanner);
+ }
+
+ /* Found valid Content-Length? */
+ if (content_length != -1)
+ break;
+
+ /* Go to next line. */
+ cur_msg.slen -= (line - cur_msg.ptr);
+ cur_msg.ptr = (char*)line;
+ line = pj_strchr(&cur_msg, '\n');
+ }
+
+ /* Found Content-Length? */
+ if (content_length == -1) {
+ return PJSIP_EMISSINGHDR;
+ }
+
+ /* Enough packet received? */
+ *msg_size = (body_start - buf) + content_length;
+ return (*msg_size) <= size ? PJ_SUCCESS : PJSIP_EPARTIALMSG;
+#else
+ PJ_UNUSED_ARG(buf);
+ PJ_UNUSED_ARG(is_datagram);
+ *msg_size = size;
+ return PJ_SUCCESS;
+#endif
+}
+
+/* Public function to parse URI */
+PJ_DEF(pjsip_uri*) pjsip_parse_uri( pj_pool_t *pool,
+ char *buf, pj_size_t size,
+ unsigned option)
+{
+ pj_scanner scanner;
+ pjsip_uri *uri = NULL;
+ PJ_USE_EXCEPTION;
+
+ pj_scan_init(&scanner, buf, size, 0, &on_syntax_error);
+
+
+ PJ_TRY {
+ uri = int_parse_uri_or_name_addr(&scanner, pool, option);
+ }
+ PJ_CATCH_ANY {
+ uri = NULL;
+ }
+ PJ_END;
+
+ /* Must have exhausted all inputs. */
+ if (pj_scan_is_eof(&scanner) || IS_NEWLINE(*scanner.curptr)) {
+ /* Success. */
+ pj_scan_fini(&scanner);
+ return uri;
+ }
+
+ /* Still have some characters unparsed. */
+ pj_scan_fini(&scanner);
+ return NULL;
+}
+
+/* SIP version */
+static void parse_sip_version(pj_scanner *scanner)
+{
+ pj_str_t SIP = { "SIP", 3 };
+ pj_str_t V2 = { "2.0", 3 };
+ pj_str_t sip, version;
+
+ pj_scan_get( scanner, &pconst.pjsip_ALPHA_SPEC, &sip);
+ if (pj_scan_get_char(scanner) != '/')
+ on_syntax_error(scanner);
+ pj_scan_get_n( scanner, 3, &version);
+ if (pj_stricmp(&sip, &SIP) || pj_stricmp(&version, &V2))
+ on_syntax_error(scanner);
+}
+
+static pj_bool_t is_next_sip_version(pj_scanner *scanner)
+{
+ pj_str_t SIP = { "SIP", 3 };
+ pj_str_t sip;
+ int c;
+
+ c = pj_scan_peek(scanner, &pconst.pjsip_ALPHA_SPEC, &sip);
+ /* return TRUE if it is "SIP" followed by "/" or space.
+ * we include space since the "/" may be separated by space,
+ * although this would mean it would return TRUE if it is a
+ * request and the method is "SIP"!
+ */
+ return c && (c=='/' || c==' ' || c=='\t') && pj_stricmp(&sip, &SIP)==0;
+}
+
+/* Internal function to parse SIP message */
+static pjsip_msg *int_parse_msg( pjsip_parse_ctx *ctx,
+ pjsip_parser_err_report *err_list)
+{
+ pj_bool_t parsing_headers;
+ pjsip_msg *msg = NULL;
+ pj_str_t hname;
+ pjsip_ctype_hdr *ctype_hdr = NULL;
+ pj_scanner *scanner = ctx->scanner;
+ pj_pool_t *pool = ctx->pool;
+ PJ_USE_EXCEPTION;
+
+ parsing_headers = PJ_FALSE;
+
+retry_parse:
+ PJ_TRY
+ {
+ if (parsing_headers)
+ goto parse_headers;
+
+ /* Skip leading newlines. */
+ while (IS_NEWLINE(*scanner->curptr)) {
+ pj_scan_get_newline(scanner);
+ }
+
+ /* Check if we still have valid packet.
+ * Sometimes endpoints just send blank (CRLF) packets just to keep
+ * NAT bindings open.
+ */
+ if (pj_scan_is_eof(scanner))
+ return NULL;
+
+ /* Parse request or status line */
+ if (is_next_sip_version(scanner)) {
+ msg = pjsip_msg_create(pool, PJSIP_RESPONSE_MSG);
+ int_parse_status_line( scanner, &msg->line.status );
+ } else {
+ msg = pjsip_msg_create(pool, PJSIP_REQUEST_MSG);
+ int_parse_req_line(scanner, pool, &msg->line.req );
+ }
+
+ parsing_headers = PJ_TRUE;
+
+parse_headers:
+ /* Parse headers. */
+ do {
+ pjsip_parse_hdr_func * handler;
+ pjsip_hdr *hdr = NULL;
+
+ /* Init hname just in case parsing fails.
+ * Ref: PROTOS #2412
+ */
+ hname.slen = 0;
+
+ /* Get hname. */
+ pj_scan_get( scanner, &pconst.pjsip_TOKEN_SPEC, &hname);
+ if (pj_scan_get_char( scanner ) != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ /* Find handler. */
+ handler = find_handler(&hname);
+
+ /* Call the handler if found.
+ * If no handler is found, then treat the header as generic
+ * hname/hvalue pair.
+ */
+ if (handler) {
+ hdr = (*handler)(ctx);
+
+ /* Note:
+ * hdr MAY BE NULL, if parsing does not yield a new header
+ * instance, e.g. the values have been added to existing
+ * header. See http://trac.pjsip.org/repos/ticket/940
+ */
+
+ /* Check if we've just parsed a Content-Type header.
+ * We will check for a message body if we've got Content-Type
+ * header.
+ */
+ if (hdr && hdr->type == PJSIP_H_CONTENT_TYPE) {
+ ctype_hdr = (pjsip_ctype_hdr*)hdr;
+ }
+
+ } else {
+ hdr = parse_hdr_generic_string(ctx);
+ hdr->name = hdr->sname = hname;
+ }
+
+
+ /* Single parse of header line can produce multiple headers.
+ * For example, if one Contact: header contains Contact list
+ * separated by comma, then these Contacts will be split into
+ * different Contact headers.
+ * So here we must insert list instead of just insert one header.
+ */
+ if (hdr)
+ pj_list_insert_nodes_before(&msg->hdr, hdr);
+
+ /* Parse until EOF or an empty line is found. */
+ } while (!pj_scan_is_eof(scanner) && !IS_NEWLINE(*scanner->curptr));
+
+ parsing_headers = PJ_FALSE;
+
+ /* If empty line is found, eat it. */
+ if (!pj_scan_is_eof(scanner)) {
+ if (IS_NEWLINE(*scanner->curptr)) {
+ pj_scan_get_newline(scanner);
+ }
+ }
+
+ /* If we have Content-Type header, treat the rest of the message
+ * as body.
+ */
+ if (ctype_hdr && scanner->curptr!=scanner->end) {
+ /* New: if Content-Type indicates that this is a multipart
+ * message body, parse it.
+ */
+ const pj_str_t STR_MULTIPART = { "multipart", 9 };
+ pjsip_msg_body *body;
+
+ if (pj_stricmp(&ctype_hdr->media.type, &STR_MULTIPART)==0) {
+ body = pjsip_multipart_parse(pool, scanner->curptr,
+ scanner->end - scanner->curptr,
+ &ctype_hdr->media, 0);
+ } else {
+ body = PJ_POOL_ALLOC_T(pool, pjsip_msg_body);
+ pjsip_media_type_cp(pool, &body->content_type,
+ &ctype_hdr->media);
+
+ body->data = scanner->curptr;
+ body->len = scanner->end - scanner->curptr;
+ body->print_body = &pjsip_print_text_body;
+ body->clone_data = &pjsip_clone_text_data;
+ }
+
+ msg->body = body;
+ }
+ }
+ PJ_CATCH_ANY
+ {
+ /* Exception was thrown during parsing.
+ * Skip until newline, and parse next header.
+ */
+ if (err_list) {
+ pjsip_parser_err_report *err_info;
+
+ err_info = PJ_POOL_ALLOC_T(pool, pjsip_parser_err_report);
+ err_info->except_code = PJ_GET_EXCEPTION();
+ err_info->line = scanner->line;
+ /* Scanner's column is zero based, so add 1 */
+ err_info->col = pj_scan_get_col(scanner) + 1;
+ if (parsing_headers)
+ err_info->hname = hname;
+ else if (msg && msg->type == PJSIP_REQUEST_MSG)
+ err_info->hname = pj_str("Request Line");
+ else if (msg && msg->type == PJSIP_RESPONSE_MSG)
+ err_info->hname = pj_str("Status Line");
+ else
+ err_info->hname.slen = 0;
+
+ pj_list_insert_before(err_list, err_info);
+ }
+
+ if (parsing_headers) {
+ if (!pj_scan_is_eof(scanner)) {
+ /* Skip until next line.
+ * Watch for header continuation.
+ */
+ do {
+ pj_scan_skip_line(scanner);
+ } while (IS_SPACE(*scanner->curptr));
+ }
+
+ /* Restore flag. Flag may be set in int_parse_sip_url() */
+ scanner->skip_ws = PJ_SCAN_AUTOSKIP_WS_HEADER;
+
+ /* Continue parse next header, if any. */
+ if (!pj_scan_is_eof(scanner) && !IS_NEWLINE(*scanner->curptr)) {
+ goto retry_parse;
+ }
+ }
+
+ msg = NULL;
+ }
+ PJ_END;
+
+ return msg;
+}
+
+
+/* Parse parameter (pname ["=" pvalue]). */
+static void parse_param_imp( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *pname, pj_str_t *pvalue,
+ const pj_cis_t *spec, const pj_cis_t *esc_spec,
+ unsigned option)
+{
+ /* pname */
+ parser_get_and_unescape(scanner, pool, spec, esc_spec, pname);
+
+ /* init pvalue */
+ pvalue->ptr = NULL;
+ pvalue->slen = 0;
+
+ /* pvalue, if any */
+ if (*scanner->curptr == '=') {
+ pj_scan_get_char(scanner);
+ if (!pj_scan_is_eof(scanner)) {
+ /* pvalue can be a quoted string. */
+ if (*scanner->curptr == '"') {
+ pj_scan_get_quote( scanner, '"', '"', pvalue);
+ if (option & PJSIP_PARSE_REMOVE_QUOTE) {
+ pvalue->ptr++;
+ pvalue->slen -= 2;
+ }
+ } else if (*scanner->curptr == '[') {
+ /* pvalue can be a quoted IPv6; in this case, the
+ * '[' and ']' quote characters are to be removed
+ * from the pvalue.
+ */
+ pj_scan_get_char(scanner);
+ pj_scan_get_until_ch(scanner, ']', pvalue);
+ pj_scan_get_char(scanner);
+ } else if(pj_cis_match(spec, *scanner->curptr)) {
+ parser_get_and_unescape(scanner, pool, spec, esc_spec, pvalue);
+ }
+ }
+ }
+}
+
+/* Parse parameter (pname ["=" pvalue]) using token. */
+PJ_DEF(void) pjsip_parse_param_imp(pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *pname, pj_str_t *pvalue,
+ unsigned option)
+{
+ parse_param_imp(scanner, pool, pname, pvalue, &pconst.pjsip_TOKEN_SPEC,
+ &pconst.pjsip_TOKEN_SPEC_ESC, option);
+}
+
+
+/* Parse parameter (pname ["=" pvalue]) using paramchar. */
+PJ_DEF(void) pjsip_parse_uri_param_imp( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *pname, pj_str_t *pvalue,
+ unsigned option)
+{
+ parse_param_imp(scanner,pool, pname, pvalue, &pconst.pjsip_PARAM_CHAR_SPEC,
+ &pconst.pjsip_PARAM_CHAR_SPEC_ESC, option);
+}
+
+
+/* Parse parameter (";" pname ["=" pvalue]) in SIP header. */
+static void int_parse_param( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *pname, pj_str_t *pvalue,
+ unsigned option)
+{
+ /* Get ';' character */
+ pj_scan_get_char(scanner);
+
+ /* Get pname and optionally pvalue */
+ pjsip_parse_param_imp(scanner, pool, pname, pvalue, option);
+}
+
+/* Parse parameter (";" pname ["=" pvalue]) in URI. */
+static void int_parse_uri_param( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *pname, pj_str_t *pvalue,
+ unsigned option)
+{
+ /* Get ';' character */
+ pj_scan_get_char(scanner);
+
+ /* Get pname and optionally pvalue */
+ pjsip_parse_uri_param_imp(scanner, pool, pname, pvalue,
+ option);
+}
+
+
+/* Parse header parameter. */
+static void int_parse_hparam( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *hname, pj_str_t *hvalue )
+{
+ /* Get '?' or '&' character. */
+ pj_scan_get_char(scanner);
+
+ /* hname */
+ parser_get_and_unescape(scanner, pool, &pconst.pjsip_HDR_CHAR_SPEC,
+ &pconst.pjsip_HDR_CHAR_SPEC_ESC, hname);
+
+ /* Init hvalue */
+ hvalue->ptr = NULL;
+ hvalue->slen = 0;
+
+ /* pvalue, if any */
+ if (*scanner->curptr == '=') {
+ pj_scan_get_char(scanner);
+ if (!pj_scan_is_eof(scanner) &&
+ pj_cis_match(&pconst.pjsip_HDR_CHAR_SPEC, *scanner->curptr))
+ {
+ parser_get_and_unescape(scanner, pool, &pconst.pjsip_HDR_CHAR_SPEC,
+ &pconst.pjsip_HDR_CHAR_SPEC_ESC, hvalue);
+ }
+ }
+}
+
+/* Parse host part:
+ * host = hostname / IPv4address / IPv6reference
+ */
+static void int_parse_host(pj_scanner *scanner, pj_str_t *host)
+{
+ if (*scanner->curptr == '[') {
+ /* Note: the '[' and ']' characters are removed from the host */
+ pj_scan_get_char(scanner);
+ pj_scan_get_until_ch(scanner, ']', host);
+ pj_scan_get_char(scanner);
+ } else {
+ pj_scan_get( scanner, &pconst.pjsip_HOST_SPEC, host);
+ }
+}
+
+/* Parse host:port in URI. */
+static void int_parse_uri_host_port( pj_scanner *scanner,
+ pj_str_t *host, int *p_port)
+{
+ int_parse_host(scanner, host);
+
+ /* RFC3261 section 19.1.2: host don't need to be unescaped */
+ if (*scanner->curptr == ':') {
+ pj_str_t port;
+ pj_scan_get_char(scanner);
+ pj_scan_get(scanner, &pconst.pjsip_DIGIT_SPEC, &port);
+ *p_port = pj_strtoul(&port);
+ } else {
+ *p_port = 0;
+ }
+}
+
+/* Determine if the next token in an URI is a user specification. */
+static int int_is_next_user(pj_scanner *scanner)
+{
+ pj_str_t dummy;
+ int is_user;
+
+ /* Find character '@'. If this character exist, then the token
+ * must be a username.
+ */
+ if (pj_scan_peek( scanner, &pconst.pjsip_PROBE_USER_HOST_SPEC, &dummy) == '@')
+ is_user = 1;
+ else
+ is_user = 0;
+
+ return is_user;
+}
+
+/* Parse user:pass tokens in an URI. */
+static void int_parse_user_pass( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *user, pj_str_t *pass)
+{
+ parser_get_and_unescape(scanner, pool, &pconst.pjsip_USER_SPEC_LENIENT,
+ &pconst.pjsip_USER_SPEC_LENIENT_ESC, user);
+
+ if ( *scanner->curptr == ':') {
+ pj_scan_get_char( scanner );
+ parser_get_and_unescape(scanner, pool, &pconst.pjsip_PASSWD_SPEC,
+ &pconst.pjsip_PASSWD_SPEC_ESC, pass);
+ } else {
+ pass->ptr = NULL;
+ pass->slen = 0;
+ }
+
+ /* Get the '@' */
+ pj_scan_get_char( scanner );
+}
+
+/* Parse all types of URI. */
+static pjsip_uri *int_parse_uri_or_name_addr( pj_scanner *scanner, pj_pool_t *pool,
+ unsigned opt)
+{
+ pjsip_uri *uri;
+ int is_name_addr = 0;
+
+ /* Exhaust any whitespaces. */
+ pj_scan_skip_whitespace(scanner);
+
+ if (*scanner->curptr=='"' || *scanner->curptr=='<') {
+ uri = (pjsip_uri*)int_parse_name_addr( scanner, pool );
+ is_name_addr = 1;
+ } else {
+ pj_str_t scheme;
+ int next_ch;
+
+ next_ch = pj_scan_peek( scanner, &pconst.pjsip_DISPLAY_SPEC, &scheme);
+
+ if (next_ch==':') {
+ pjsip_parse_uri_func *func = find_uri_handler(&scheme);
+
+ if (func == NULL) {
+ /* Unsupported URI scheme */
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ uri = (pjsip_uri*)
+ (*func)(scanner, pool,
+ (opt & PJSIP_PARSE_URI_IN_FROM_TO_HDR)==0);
+
+
+ } else {
+ uri = (pjsip_uri*)int_parse_name_addr( scanner, pool );
+ is_name_addr = 1;
+ }
+ }
+
+ /* Should we return the URI object as name address? */
+ if (opt & PJSIP_PARSE_URI_AS_NAMEADDR) {
+ if (is_name_addr == 0) {
+ pjsip_name_addr *name_addr;
+
+ name_addr = pjsip_name_addr_create(pool);
+ name_addr->uri = uri;
+
+ uri = (pjsip_uri*)name_addr;
+ }
+ }
+
+ return uri;
+}
+
+/* Parse URI. */
+static pjsip_uri *int_parse_uri(pj_scanner *scanner, pj_pool_t *pool,
+ pj_bool_t parse_params)
+{
+ /* Bug:
+ * This function should not call back int_parse_name_addr() because
+ * it is called by that function. This would cause stack overflow
+ * with PROTOS test #1223.
+ if (*scanner->curptr=='"' || *scanner->curptr=='<') {
+ return (pjsip_uri*)int_parse_name_addr( scanner, pool );
+ } else {
+ */
+ pj_str_t scheme;
+ int colon;
+ pjsip_parse_uri_func *func;
+
+ /* Get scheme. */
+ colon = pj_scan_peek(scanner, &pconst.pjsip_TOKEN_SPEC, &scheme);
+ if (colon != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ func = find_uri_handler(&scheme);
+ if (func) {
+ return (pjsip_uri*)(*func)(scanner, pool, parse_params);
+
+ } else {
+ /* Unsupported URI scheme */
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ UNREACHED({ return NULL; /* Not reached. */ })
+ }
+
+ /*
+ }
+ */
+}
+
+/* Parse "sip:" and "sips:" URI.
+ * This actually returns (pjsip_sip_uri*) type,
+ */
+static void* int_parse_sip_url( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_bool_t parse_params)
+{
+ pj_str_t scheme;
+ pjsip_sip_uri *url = NULL;
+ int colon;
+ int skip_ws = scanner->skip_ws;
+ scanner->skip_ws = 0;
+
+ pj_scan_get(scanner, &pconst.pjsip_TOKEN_SPEC, &scheme);
+ colon = pj_scan_get_char(scanner);
+ if (colon != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ if (parser_stricmp(scheme, pconst.pjsip_SIP_STR)==0) {
+ url = pjsip_sip_uri_create(pool, 0);
+
+ } else if (parser_stricmp(scheme, pconst.pjsip_SIPS_STR)==0) {
+ url = pjsip_sip_uri_create(pool, 1);
+
+ } else {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ /* should not reach here */
+ UNREACHED({
+ pj_assert(0);
+ return 0;
+ })
+ }
+
+ if (int_is_next_user(scanner)) {
+ int_parse_user_pass(scanner, pool, &url->user, &url->passwd);
+ }
+
+ /* Get host:port */
+ int_parse_uri_host_port(scanner, &url->host, &url->port);
+
+ /* Get URL parameters. */
+ if (parse_params) {
+ while (*scanner->curptr == ';' ) {
+ pj_str_t pname, pvalue;
+
+ int_parse_uri_param( scanner, pool, &pname, &pvalue, 0);
+
+ if (!parser_stricmp(pname, pconst.pjsip_USER_STR) && pvalue.slen) {
+ url->user_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_METHOD_STR) && pvalue.slen) {
+ url->method_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_TRANSPORT_STR) && pvalue.slen) {
+ url->transport_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_TTL_STR) && pvalue.slen) {
+ url->ttl_param = pj_strtoul(&pvalue);
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_MADDR_STR) && pvalue.slen) {
+ url->maddr_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_LR_STR)) {
+ url->lr_param = 1;
+
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = pname;
+ p->value = pvalue;
+ pj_list_insert_before(&url->other_param, p);
+ }
+ }
+ }
+
+ /* Get header params. */
+ if (parse_params && *scanner->curptr == '?') {
+ do {
+ pjsip_param *param;
+ param = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ int_parse_hparam(scanner, pool, &param->name, &param->value);
+ pj_list_insert_before(&url->header_param, param);
+ } while (*scanner->curptr == '&');
+ }
+
+ scanner->skip_ws = skip_ws;
+ pj_scan_skip_whitespace(scanner);
+ return url;
+}
+
+/* Parse nameaddr. */
+static pjsip_name_addr *int_parse_name_addr( pj_scanner *scanner,
+ pj_pool_t *pool )
+{
+ int has_bracket;
+ pjsip_name_addr *name_addr;
+
+ name_addr = pjsip_name_addr_create(pool);
+
+ if (*scanner->curptr == '"') {
+ pj_scan_get_quote( scanner, '"', '"', &name_addr->display);
+ /* Trim the leading and ending quote */
+ name_addr->display.ptr++;
+ name_addr->display.slen -= 2;
+
+ } else if (*scanner->curptr != '<') {
+ int next;
+ pj_str_t dummy;
+
+ /* This can be either the start of display name,
+ * the start of URL ("sip:", "sips:", "tel:", etc.), or '<' char.
+ * We're only interested in display name, because SIP URL
+ * will be parser later.
+ */
+ next = pj_scan_peek(scanner, &pconst.pjsip_DISPLAY_SPEC, &dummy);
+ if (next == '<') {
+ /* Ok, this is what we're looking for, a display name. */
+ pj_scan_get_until_ch( scanner, '<', &name_addr->display);
+ pj_strtrim(&name_addr->display);
+ }
+ }
+
+ /* Manually skip whitespace. */
+ pj_scan_skip_whitespace(scanner);
+
+ /* Get the SIP-URL */
+ has_bracket = (*scanner->curptr == '<');
+ if (has_bracket)
+ pj_scan_get_char(scanner);
+ name_addr->uri = int_parse_uri( scanner, pool, PJ_TRUE );
+ if (has_bracket) {
+ if (pj_scan_get_char(scanner) != '>')
+ PJ_THROW( PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ return name_addr;
+}
+
+
+/* Parse other URI */
+static void* int_parse_other_uri(pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_bool_t parse_params)
+{
+ pjsip_other_uri *uri = 0;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+ int skip_ws = scanner->skip_ws;
+
+ PJ_UNUSED_ARG(parse_params);
+
+ scanner->skip_ws = 0;
+
+ uri = pjsip_other_uri_create(pool);
+
+ pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &uri->scheme);
+ if (pj_scan_get_char(scanner) != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ pj_scan_get(scanner, &pc->pjsip_OTHER_URI_CONTENT, &uri->content);
+ scanner->skip_ws = skip_ws;
+
+ return uri;
+}
+
+
+/* Parse SIP request line. */
+static void int_parse_req_line( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_request_line *req_line)
+{
+ pj_str_t token;
+
+ pj_scan_get( scanner, &pconst.pjsip_TOKEN_SPEC, &token);
+ pjsip_method_init_np( &req_line->method, &token);
+
+ req_line->uri = int_parse_uri(scanner, pool, PJ_TRUE);
+ parse_sip_version(scanner);
+ pj_scan_get_newline( scanner );
+}
+
+/* Parse status line. */
+static void int_parse_status_line( pj_scanner *scanner,
+ pjsip_status_line *status_line)
+{
+ pj_str_t token;
+
+ parse_sip_version(scanner);
+ pj_scan_get( scanner, &pconst.pjsip_DIGIT_SPEC, &token);
+ status_line->code = pj_strtoul(&token);
+ if (*scanner->curptr != '\r' && *scanner->curptr != '\n')
+ pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &status_line->reason);
+ else
+ status_line->reason.slen=0, status_line->reason.ptr=NULL;
+ pj_scan_get_newline( scanner );
+}
+
+
+/*
+ * Public API to parse SIP status line.
+ */
+PJ_DEF(pj_status_t) pjsip_parse_status_line( char *buf, pj_size_t size,
+ pjsip_status_line *status_line)
+{
+ pj_scanner scanner;
+ PJ_USE_EXCEPTION;
+
+ pj_bzero(status_line, sizeof(*status_line));
+ pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER,
+ &on_syntax_error);
+
+ PJ_TRY {
+ int_parse_status_line(&scanner, status_line);
+ }
+ PJ_CATCH_ANY {
+ /* Tolerate the error if it is caused only by missing newline */
+ if (status_line->code == 0 && status_line->reason.slen == 0) {
+ pj_scan_fini(&scanner);
+ return PJSIP_EINVALIDMSG;
+ }
+ }
+ PJ_END;
+
+ pj_scan_fini(&scanner);
+ return PJ_SUCCESS;
+}
+
+
+/* Parse ending of header. */
+static void parse_hdr_end( pj_scanner *scanner )
+{
+ if (pj_scan_is_eof(scanner)) {
+ ; /* Do nothing. */
+ } else if (*scanner->curptr == '&') {
+ pj_scan_get_char(scanner);
+ } else {
+ pj_scan_get_newline(scanner);
+ }
+}
+
+/* Parse ending of header. */
+PJ_DEF(void) pjsip_parse_end_hdr_imp( pj_scanner *scanner )
+{
+ parse_hdr_end(scanner);
+}
+
+/* Parse generic array header. */
+static void parse_generic_array_hdr( pjsip_generic_array_hdr *hdr,
+ pj_scanner *scanner)
+{
+ /* Some header fields allow empty elements in the value:
+ * Accept, Allow, Supported
+ */
+ if (pj_scan_is_eof(scanner) ||
+ *scanner->curptr == '\r' || *scanner->curptr == '\n')
+ {
+ goto end;
+ }
+
+ if (hdr->count >= PJ_ARRAY_SIZE(hdr->values)) {
+ /* Too many elements */
+ on_syntax_error(scanner);
+ return;
+ }
+
+ pj_scan_get( scanner, &pconst.pjsip_NOT_COMMA_OR_NEWLINE,
+ &hdr->values[hdr->count]);
+ hdr->count++;
+
+ while (*scanner->curptr == ',') {
+ pj_scan_get_char(scanner);
+ pj_scan_get( scanner, &pconst.pjsip_NOT_COMMA_OR_NEWLINE,
+ &hdr->values[hdr->count]);
+ hdr->count++;
+
+ if (hdr->count >= PJSIP_GENERIC_ARRAY_MAX_COUNT)
+ break;
+ }
+
+end:
+ parse_hdr_end(scanner);
+}
+
+/* Parse generic string header. */
+static void parse_generic_string_hdr( pjsip_generic_string_hdr *hdr,
+ pjsip_parse_ctx *ctx)
+{
+ pj_scanner *scanner = ctx->scanner;
+
+ hdr->hvalue.slen = 0;
+
+ /* header may be mangled hence the loop */
+ while (pj_cis_match(&pconst.pjsip_NOT_NEWLINE, *scanner->curptr)) {
+ pj_str_t next, tmp;
+
+ pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &hdr->hvalue);
+ if (pj_scan_is_eof(scanner) || IS_NEWLINE(*scanner->curptr))
+ break;
+ /* mangled, get next fraction */
+ pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &next);
+ /* concatenate */
+ tmp.ptr = (char*)pj_pool_alloc(ctx->pool,
+ hdr->hvalue.slen + next.slen + 2);
+ tmp.slen = 0;
+ pj_strcpy(&tmp, &hdr->hvalue);
+ pj_strcat2(&tmp, " ");
+ pj_strcat(&tmp, &next);
+ tmp.ptr[tmp.slen] = '\0';
+
+ hdr->hvalue = tmp;
+ }
+
+ parse_hdr_end(scanner);
+}
+
+/* Parse generic integer header. */
+static void parse_generic_int_hdr( pjsip_generic_int_hdr *hdr,
+ pj_scanner *scanner )
+{
+ pj_str_t tmp;
+ pj_scan_get( scanner, &pconst.pjsip_DIGIT_SPEC, &tmp);
+ hdr->ivalue = pj_strtoul(&tmp);
+ parse_hdr_end(scanner);
+}
+
+
+/* Parse Accept header. */
+static pjsip_hdr* parse_hdr_accept(pjsip_parse_ctx *ctx)
+{
+ pjsip_accept_hdr *accept = pjsip_accept_hdr_create(ctx->pool);
+ parse_generic_array_hdr(accept, ctx->scanner);
+ return (pjsip_hdr*)accept;
+}
+
+/* Parse Allow header. */
+static pjsip_hdr* parse_hdr_allow(pjsip_parse_ctx *ctx)
+{
+ pjsip_allow_hdr *allow = pjsip_allow_hdr_create(ctx->pool);
+ parse_generic_array_hdr(allow, ctx->scanner);
+ return (pjsip_hdr*)allow;
+}
+
+/* Parse Call-ID header. */
+static pjsip_hdr* parse_hdr_call_id(pjsip_parse_ctx *ctx)
+{
+ pjsip_cid_hdr *hdr = pjsip_cid_hdr_create(ctx->pool);
+ pj_scan_get( ctx->scanner, &pconst.pjsip_NOT_NEWLINE, &hdr->id);
+ parse_hdr_end(ctx->scanner);
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.cid = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse and interpret Contact param. */
+static void int_parse_contact_param( pjsip_contact_hdr *hdr,
+ pj_scanner *scanner,
+ pj_pool_t *pool)
+{
+ while ( *scanner->curptr == ';' ) {
+ pj_str_t pname, pvalue;
+
+ int_parse_param( scanner, pool, &pname, &pvalue, 0);
+ if (!parser_stricmp(pname, pconst.pjsip_Q_STR) && pvalue.slen) {
+ char *dot_pos = (char*) pj_memchr(pvalue.ptr, '.', pvalue.slen);
+ if (!dot_pos) {
+ hdr->q1000 = pj_strtoul(&pvalue) * 1000;
+ } else {
+ pj_str_t tmp = pvalue;
+
+ tmp.slen = dot_pos - pvalue.ptr;
+ hdr->q1000 = pj_strtoul(&tmp) * 1000;
+
+ pvalue.slen = (pvalue.ptr+pvalue.slen) - (dot_pos+1);
+ pvalue.ptr = dot_pos + 1;
+ hdr->q1000 += pj_strtoul_mindigit(&pvalue, 3);
+ }
+ } else if (!parser_stricmp(pname, pconst.pjsip_EXPIRES_STR) && pvalue.slen) {
+ hdr->expires = pj_strtoul(&pvalue);
+
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = pname;
+ p->value = pvalue;
+ pj_list_insert_before(&hdr->other_param, p);
+ }
+ }
+}
+
+/* Parse Contact header. */
+static pjsip_hdr* parse_hdr_contact( pjsip_parse_ctx *ctx )
+{
+ pjsip_contact_hdr *first = NULL;
+ pj_scanner *scanner = ctx->scanner;
+
+ do {
+ pjsip_contact_hdr *hdr = pjsip_contact_hdr_create(ctx->pool);
+ if (first == NULL)
+ first = hdr;
+ else
+ pj_list_insert_before(first, hdr);
+
+ if (*scanner->curptr == '*') {
+ pj_scan_get_char(scanner);
+ hdr->star = 1;
+
+ } else {
+ hdr->star = 0;
+ hdr->uri = int_parse_uri_or_name_addr(scanner, ctx->pool,
+ PJSIP_PARSE_URI_AS_NAMEADDR |
+ PJSIP_PARSE_URI_IN_FROM_TO_HDR);
+
+ int_parse_contact_param(hdr, scanner, ctx->pool);
+ }
+
+ if (*scanner->curptr != ',')
+ break;
+
+ pj_scan_get_char(scanner);
+
+ } while (1);
+
+ parse_hdr_end(scanner);
+
+ return (pjsip_hdr*)first;
+}
+
+/* Parse Content-Length header. */
+static pjsip_hdr* parse_hdr_content_len( pjsip_parse_ctx *ctx )
+{
+ pj_str_t digit;
+ pjsip_clen_hdr *hdr;
+
+ hdr = pjsip_clen_hdr_create(ctx->pool);
+ pj_scan_get(ctx->scanner, &pconst.pjsip_DIGIT_SPEC, &digit);
+ hdr->len = pj_strtoul(&digit);
+ parse_hdr_end(ctx->scanner);
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.clen = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Content-Type header. */
+static pjsip_hdr* parse_hdr_content_type( pjsip_parse_ctx *ctx )
+{
+ pjsip_ctype_hdr *hdr;
+ pj_scanner *scanner = ctx->scanner;
+
+ hdr = pjsip_ctype_hdr_create(ctx->pool);
+
+ /* Parse media type and subtype. */
+ pj_scan_get(scanner, &pconst.pjsip_TOKEN_SPEC, &hdr->media.type);
+ pj_scan_get_char(scanner);
+ pj_scan_get(scanner, &pconst.pjsip_TOKEN_SPEC, &hdr->media.subtype);
+
+ /* Parse media parameters */
+ while (*scanner->curptr == ';') {
+ pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+ int_parse_param(scanner, ctx->pool, &param->name, &param->value, 0);
+ pj_list_push_back(&hdr->media.param, param);
+ }
+
+ parse_hdr_end(ctx->scanner);
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.ctype = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse CSeq header. */
+static pjsip_hdr* parse_hdr_cseq( pjsip_parse_ctx *ctx )
+{
+ pj_str_t cseq, method;
+ pjsip_cseq_hdr *hdr;
+
+ hdr = pjsip_cseq_hdr_create(ctx->pool);
+ pj_scan_get( ctx->scanner, &pconst.pjsip_DIGIT_SPEC, &cseq);
+ hdr->cseq = pj_strtoul(&cseq);
+
+ pj_scan_get( ctx->scanner, &pconst.pjsip_TOKEN_SPEC, &method);
+ pjsip_method_init_np(&hdr->method, &method);
+
+ parse_hdr_end( ctx->scanner );
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.cseq = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Expires header. */
+static pjsip_hdr* parse_hdr_expires(pjsip_parse_ctx *ctx)
+{
+ pjsip_expires_hdr *hdr = pjsip_expires_hdr_create(ctx->pool, 0);
+ parse_generic_int_hdr(hdr, ctx->scanner);
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse From: or To: header. */
+static void parse_hdr_fromto( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pjsip_from_hdr *hdr)
+{
+ hdr->uri = int_parse_uri_or_name_addr(scanner, pool,
+ PJSIP_PARSE_URI_AS_NAMEADDR |
+ PJSIP_PARSE_URI_IN_FROM_TO_HDR);
+
+ while ( *scanner->curptr == ';' ) {
+ pj_str_t pname, pvalue;
+
+ int_parse_param( scanner, pool, &pname, &pvalue, 0);
+
+ if (!parser_stricmp(pname, pconst.pjsip_TAG_STR)) {
+ hdr->tag = pvalue;
+
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = pname;
+ p->value = pvalue;
+ pj_list_insert_before(&hdr->other_param, p);
+ }
+ }
+
+ parse_hdr_end(scanner);
+}
+
+/* Parse From: header. */
+static pjsip_hdr* parse_hdr_from( pjsip_parse_ctx *ctx )
+{
+ pjsip_from_hdr *hdr = pjsip_from_hdr_create(ctx->pool);
+ parse_hdr_fromto(ctx->scanner, ctx->pool, hdr);
+ if (ctx->rdata)
+ ctx->rdata->msg_info.from = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Require: header. */
+static pjsip_hdr* parse_hdr_require( pjsip_parse_ctx *ctx )
+{
+ pjsip_require_hdr *hdr;
+ pj_bool_t new_hdr = (ctx->rdata==NULL ||
+ ctx->rdata->msg_info.require == NULL);
+
+ if (ctx->rdata && ctx->rdata->msg_info.require) {
+ hdr = ctx->rdata->msg_info.require;
+ } else {
+ hdr = pjsip_require_hdr_create(ctx->pool);
+ if (ctx->rdata)
+ ctx->rdata->msg_info.require = hdr;
+ }
+
+ parse_generic_array_hdr(hdr, ctx->scanner);
+
+ return new_hdr ? (pjsip_hdr*)hdr : NULL;
+}
+
+/* Parse Retry-After: header. */
+static pjsip_hdr* parse_hdr_retry_after(pjsip_parse_ctx *ctx)
+{
+ pjsip_retry_after_hdr *hdr;
+ pj_scanner *scanner = ctx->scanner;
+ pj_str_t tmp;
+
+ hdr = pjsip_retry_after_hdr_create(ctx->pool, 0);
+
+ pj_scan_get(scanner, &pconst.pjsip_DIGIT_SPEC, &tmp);
+ hdr->ivalue = pj_strtoul(&tmp);
+
+ while (!pj_scan_is_eof(scanner) && *scanner->curptr!='\r' &&
+ *scanner->curptr!='\n')
+ {
+ if (*scanner->curptr=='(') {
+ pj_scan_get_quote(scanner, '(', ')', &hdr->comment);
+ /* Trim the leading and ending parens */
+ hdr->comment.ptr++;
+ hdr->comment.slen -= 2;
+ } else if (*scanner->curptr==';') {
+ pjsip_param *prm = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+ int_parse_param(scanner, ctx->pool, &prm->name, &prm->value, 0);
+ pj_list_push_back(&hdr->param, prm);
+ }
+ }
+
+ parse_hdr_end(scanner);
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Supported: header. */
+static pjsip_hdr* parse_hdr_supported(pjsip_parse_ctx *ctx)
+{
+ pjsip_supported_hdr *hdr;
+ pj_bool_t new_hdr = (ctx->rdata==NULL ||
+ ctx->rdata->msg_info.supported == NULL);
+
+ if (ctx->rdata && ctx->rdata->msg_info.supported) {
+ hdr = ctx->rdata->msg_info.supported;
+ } else {
+ hdr = pjsip_supported_hdr_create(ctx->pool);
+ if (ctx->rdata)
+ ctx->rdata->msg_info.supported = hdr;
+ }
+
+ parse_generic_array_hdr(hdr, ctx->scanner);
+ return new_hdr ? (pjsip_hdr*)hdr : NULL;
+}
+
+/* Parse To: header. */
+static pjsip_hdr* parse_hdr_to( pjsip_parse_ctx *ctx )
+{
+ pjsip_to_hdr *hdr = pjsip_to_hdr_create(ctx->pool);
+ parse_hdr_fromto(ctx->scanner, ctx->pool, hdr);
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.to = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Unsupported: header. */
+static pjsip_hdr* parse_hdr_unsupported(pjsip_parse_ctx *ctx)
+{
+ pjsip_unsupported_hdr *hdr = pjsip_unsupported_hdr_create(ctx->pool);
+ parse_generic_array_hdr(hdr, ctx->scanner);
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse and interpret Via parameters. */
+static void int_parse_via_param( pjsip_via_hdr *hdr, pj_scanner *scanner,
+ pj_pool_t *pool)
+{
+ while ( *scanner->curptr == ';' ) {
+ pj_str_t pname, pvalue;
+
+ //Parse with PARAM_CHAR instead, to allow IPv6
+ //No, back to using int_parse_param() for the "`" character!
+ //int_parse_param( scanner, pool, &pname, &pvalue, 0);
+ //parse_param_imp(scanner, pool, &pname, &pvalue,
+ // &pconst.pjsip_TOKEN_SPEC,
+ // &pconst.pjsip_TOKEN_SPEC_ESC, 0);
+ //int_parse_param(scanner, pool, &pname, &pvalue, 0);
+ // This should be the correct one:
+ // added special spec for Via parameter, basically token plus
+ // ":" to allow IPv6 address in the received param.
+ pj_scan_get_char(scanner);
+ parse_param_imp(scanner, pool, &pname, &pvalue,
+ &pconst.pjsip_VIA_PARAM_SPEC,
+ &pconst.pjsip_VIA_PARAM_SPEC_ESC,
+ 0);
+
+ if (!parser_stricmp(pname, pconst.pjsip_BRANCH_STR) && pvalue.slen) {
+ hdr->branch_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_TTL_STR) && pvalue.slen) {
+ hdr->ttl_param = pj_strtoul(&pvalue);
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_MADDR_STR) && pvalue.slen) {
+ hdr->maddr_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_RECEIVED_STR) && pvalue.slen) {
+ hdr->recvd_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_RPORT_STR)) {
+ if (pvalue.slen)
+ hdr->rport_param = pj_strtoul(&pvalue);
+ else
+ hdr->rport_param = 0;
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = pname;
+ p->value = pvalue;
+ pj_list_insert_before(&hdr->other_param, p);
+ }
+ }
+}
+
+/* Parse Max-Forwards header. */
+static pjsip_hdr* parse_hdr_max_forwards( pjsip_parse_ctx *ctx )
+{
+ pjsip_max_fwd_hdr *hdr;
+ hdr = pjsip_max_fwd_hdr_create(ctx->pool, 0);
+ parse_generic_int_hdr(hdr, ctx->scanner);
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.max_fwd = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Min-Expires header. */
+static pjsip_hdr* parse_hdr_min_expires(pjsip_parse_ctx *ctx)
+{
+ pjsip_min_expires_hdr *hdr;
+ hdr = pjsip_min_expires_hdr_create(ctx->pool, 0);
+ parse_generic_int_hdr(hdr, ctx->scanner);
+ return (pjsip_hdr*)hdr;
+}
+
+
+/* Parse Route: or Record-Route: header. */
+static void parse_hdr_rr_route( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_routing_hdr *hdr )
+{
+ pjsip_name_addr *temp=int_parse_name_addr(scanner, pool);
+
+ pj_memcpy(&hdr->name_addr, temp, sizeof(*temp));
+
+ while (*scanner->curptr == ';') {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ int_parse_param(scanner, pool, &p->name, &p->value, 0);
+ pj_list_insert_before(&hdr->other_param, p);
+ }
+}
+
+/* Parse Record-Route header. */
+static pjsip_hdr* parse_hdr_rr( pjsip_parse_ctx *ctx)
+{
+ pjsip_rr_hdr *first = NULL;
+ pj_scanner *scanner = ctx->scanner;
+
+ do {
+ pjsip_rr_hdr *hdr = pjsip_rr_hdr_create(ctx->pool);
+ if (!first) {
+ first = hdr;
+ } else {
+ pj_list_insert_before(first, hdr);
+ }
+ parse_hdr_rr_route(scanner, ctx->pool, hdr);
+ if (*scanner->curptr == ',') {
+ pj_scan_get_char(scanner);
+ } else {
+ break;
+ }
+ } while (1);
+ parse_hdr_end(scanner);
+
+ if (ctx->rdata && ctx->rdata->msg_info.record_route==NULL)
+ ctx->rdata->msg_info.record_route = first;
+
+ return (pjsip_hdr*)first;
+}
+
+/* Parse Route: header. */
+static pjsip_hdr* parse_hdr_route( pjsip_parse_ctx *ctx )
+{
+ pjsip_route_hdr *first = NULL;
+ pj_scanner *scanner = ctx->scanner;
+
+ do {
+ pjsip_route_hdr *hdr = pjsip_route_hdr_create(ctx->pool);
+ if (!first) {
+ first = hdr;
+ } else {
+ pj_list_insert_before(first, hdr);
+ }
+ parse_hdr_rr_route(scanner, ctx->pool, hdr);
+ if (*scanner->curptr == ',') {
+ pj_scan_get_char(scanner);
+ } else {
+ break;
+ }
+ } while (1);
+ parse_hdr_end(scanner);
+
+ if (ctx->rdata && ctx->rdata->msg_info.route==NULL)
+ ctx->rdata->msg_info.route = first;
+
+ return (pjsip_hdr*)first;
+}
+
+/* Parse Via: header. */
+static pjsip_hdr* parse_hdr_via( pjsip_parse_ctx *ctx )
+{
+ pjsip_via_hdr *first = NULL;
+ pj_scanner *scanner = ctx->scanner;
+
+ do {
+ pjsip_via_hdr *hdr = pjsip_via_hdr_create(ctx->pool);
+ if (!first)
+ first = hdr;
+ else
+ pj_list_insert_before(first, hdr);
+
+ parse_sip_version(scanner);
+ if (pj_scan_get_char(scanner) != '/')
+ on_syntax_error(scanner);
+
+ pj_scan_get( scanner, &pconst.pjsip_TOKEN_SPEC, &hdr->transport);
+ int_parse_host(scanner, &hdr->sent_by.host);
+
+ if (*scanner->curptr==':') {
+ pj_str_t digit;
+ pj_scan_get_char(scanner);
+ pj_scan_get(scanner, &pconst.pjsip_DIGIT_SPEC, &digit);
+ hdr->sent_by.port = pj_strtoul(&digit);
+ }
+
+ int_parse_via_param(hdr, scanner, ctx->pool);
+
+ if (*scanner->curptr == '(') {
+ pj_scan_get_char(scanner);
+ pj_scan_get_until_ch( scanner, ')', &hdr->comment);
+ pj_scan_get_char( scanner );
+ }
+
+ if (*scanner->curptr != ',')
+ break;
+
+ pj_scan_get_char(scanner);
+
+ } while (1);
+
+ parse_hdr_end(scanner);
+
+ if (ctx->rdata && ctx->rdata->msg_info.via == NULL)
+ ctx->rdata->msg_info.via = first;
+
+ return (pjsip_hdr*)first;
+}
+
+/* Parse generic header. */
+static pjsip_hdr* parse_hdr_generic_string( pjsip_parse_ctx *ctx )
+{
+ pjsip_generic_string_hdr *hdr;
+
+ hdr = pjsip_generic_string_hdr_create(ctx->pool, NULL, NULL);
+ parse_generic_string_hdr(hdr, ctx);
+ return (pjsip_hdr*)hdr;
+
+}
+
+/* Public function to parse a header value. */
+PJ_DEF(void*) pjsip_parse_hdr( pj_pool_t *pool, const pj_str_t *hname,
+ char *buf, pj_size_t size, int *parsed_len )
+{
+ pj_scanner scanner;
+ pjsip_hdr *hdr = NULL;
+ pjsip_parse_ctx context;
+ PJ_USE_EXCEPTION;
+
+ pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER,
+ &on_syntax_error);
+
+ context.scanner = &scanner;
+ context.pool = pool;
+ context.rdata = NULL;
+
+ PJ_TRY {
+ pjsip_parse_hdr_func *handler = find_handler(hname);
+ if (handler) {
+ hdr = (*handler)(&context);
+ } else {
+ hdr = parse_hdr_generic_string(&context);
+ hdr->type = PJSIP_H_OTHER;
+ pj_strdup(pool, &hdr->name, hname);
+ hdr->sname = hdr->name;
+ }
+
+ }
+ PJ_CATCH_ANY {
+ hdr = NULL;
+ }
+ PJ_END
+
+ if (parsed_len) {
+ *parsed_len = (scanner.curptr - scanner.begin);
+ }
+
+ pj_scan_fini(&scanner);
+
+ return hdr;
+}
+
+/* Parse multiple header lines */
+PJ_DEF(pj_status_t) pjsip_parse_headers( pj_pool_t *pool, char *input,
+ pj_size_t size, pjsip_hdr *hlist,
+ unsigned options)
+{
+ enum { STOP_ON_ERROR = 1 };
+ pj_scanner scanner;
+ pjsip_parse_ctx ctx;
+ pj_str_t hname;
+ PJ_USE_EXCEPTION;
+
+ pj_scan_init(&scanner, input, size, PJ_SCAN_AUTOSKIP_WS_HEADER,
+ &on_syntax_error);
+
+ pj_bzero(&ctx, sizeof(ctx));
+ ctx.scanner = &scanner;
+ ctx.pool = pool;
+
+retry_parse:
+ PJ_TRY
+ {
+ /* Parse headers. */
+ do {
+ pjsip_parse_hdr_func * handler;
+ pjsip_hdr *hdr = NULL;
+
+ /* Init hname just in case parsing fails.
+ * Ref: PROTOS #2412
+ */
+ hname.slen = 0;
+
+ /* Get hname. */
+ pj_scan_get( &scanner, &pconst.pjsip_TOKEN_SPEC, &hname);
+ if (pj_scan_get_char( &scanner ) != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ /* Find handler. */
+ handler = find_handler(&hname);
+
+ /* Call the handler if found.
+ * If no handler is found, then treat the header as generic
+ * hname/hvalue pair.
+ */
+ if (handler) {
+ hdr = (*handler)(&ctx);
+ } else {
+ hdr = parse_hdr_generic_string(&ctx);
+ hdr->name = hdr->sname = hname;
+ }
+
+ /* Single parse of header line can produce multiple headers.
+ * For example, if one Contact: header contains Contact list
+ * separated by comma, then these Contacts will be split into
+ * different Contact headers.
+ * So here we must insert list instead of just insert one header.
+ */
+ if (hdr)
+ pj_list_insert_nodes_before(hlist, hdr);
+
+ /* Parse until EOF or an empty line is found. */
+ } while (!pj_scan_is_eof(&scanner) && !IS_NEWLINE(*scanner.curptr));
+
+ /* If empty line is found, eat it. */
+ if (!pj_scan_is_eof(&scanner)) {
+ if (IS_NEWLINE(*scanner.curptr)) {
+ pj_scan_get_newline(&scanner);
+ }
+ }
+ }
+ PJ_CATCH_ANY
+ {
+ PJ_LOG(4,(THIS_FILE, "Error parsing header: '%.*s' line %d col %d",
+ (int)hname.slen, hname.ptr, scanner.line,
+ pj_scan_get_col(&scanner)));
+
+ /* Exception was thrown during parsing. */
+ if ((options & STOP_ON_ERROR) == STOP_ON_ERROR) {
+ pj_scan_fini(&scanner);
+ return PJSIP_EINVALIDHDR;
+ }
+
+ /* Skip until newline, and parse next header. */
+ if (!pj_scan_is_eof(&scanner)) {
+ /* Skip until next line.
+ * Watch for header continuation.
+ */
+ do {
+ pj_scan_skip_line(&scanner);
+ } while (IS_SPACE(*scanner.curptr));
+ }
+
+ /* Restore flag. Flag may be set in int_parse_sip_url() */
+ scanner.skip_ws = PJ_SCAN_AUTOSKIP_WS_HEADER;
+
+ /* Continue parse next header, if any. */
+ if (!pj_scan_is_eof(&scanner) && !IS_NEWLINE(*scanner.curptr)) {
+ goto retry_parse;
+ }
+
+ }
+ PJ_END;
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsip/sip_parser_wrap.cpp b/pjsip/src/pjsip/sip_parser_wrap.cpp
new file mode 100644
index 0000000..21f9718
--- /dev/null
+++ b/pjsip/src/pjsip/sip_parser_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_parser_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_parser.c"
diff --git a/pjsip/src/pjsip/sip_resolve.c b/pjsip/src/pjsip/sip_resolve.c
new file mode 100644
index 0000000..f5efc1e
--- /dev/null
+++ b/pjsip/src/pjsip/sip_resolve.c
@@ -0,0 +1,520 @@
+/* $Id: sip_resolve.c 4108 2012-04-27 01:32:12Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_resolve.h>
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_errno.h>
+#include <pjlib-util/errno.h>
+#include <pjlib-util/srv_resolver.h>
+#include <pj/addr_resolv.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "sip_resolve.c"
+
+#define ADDR_MAX_COUNT 8
+
+struct naptr_target
+{
+ pj_str_t res_type; /**< e.g. "_sip._udp" */
+ pj_str_t name; /**< Domain name. */
+ pjsip_transport_type_e type; /**< Transport type. */
+ unsigned order; /**< Order */
+ unsigned pref; /**< Preference. */
+};
+
+struct query
+{
+ char *objname;
+
+ pj_dns_type query_type;
+ void *token;
+ pjsip_resolver_callback *cb;
+ pj_dns_async_query *object;
+ pj_status_t last_error;
+
+ /* Original request: */
+ struct {
+ pjsip_host_info target;
+ unsigned def_port;
+ } req;
+
+ /* NAPTR records: */
+ unsigned naptr_cnt;
+ struct naptr_target naptr[8];
+};
+
+
+struct pjsip_resolver_t
+{
+ pj_dns_resolver *res;
+};
+
+
+static void srv_resolver_cb(void *user_data,
+ pj_status_t status,
+ const pj_dns_srv_record *rec);
+static void dns_a_callback(void *user_data,
+ pj_status_t status,
+ pj_dns_parsed_packet *response);
+
+
+/*
+ * Public API to create the resolver.
+ */
+PJ_DEF(pj_status_t) pjsip_resolver_create( pj_pool_t *pool,
+ pjsip_resolver_t **p_res)
+{
+ pjsip_resolver_t *resolver;
+
+ PJ_ASSERT_RETURN(pool && p_res, PJ_EINVAL);
+ resolver = PJ_POOL_ZALLOC_T(pool, pjsip_resolver_t);
+ *p_res = resolver;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Public API to set the DNS resolver instance for the SIP resolver.
+ */
+PJ_DEF(pj_status_t) pjsip_resolver_set_resolver(pjsip_resolver_t *res,
+ pj_dns_resolver *dns_res)
+{
+#if PJSIP_HAS_RESOLVER
+ res->res = dns_res;
+ return PJ_SUCCESS;
+#else
+ PJ_UNUSED_ARG(res);
+ PJ_UNUSED_ARG(dns_res);
+ pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)");
+ return PJ_EINVALIDOP;
+#endif
+}
+
+
+/*
+ * Public API to get the internal DNS resolver.
+ */
+PJ_DEF(pj_dns_resolver*) pjsip_resolver_get_resolver(pjsip_resolver_t *res)
+{
+ return res->res;
+}
+
+
+/*
+ * Public API to create destroy the resolver
+ */
+PJ_DEF(void) pjsip_resolver_destroy(pjsip_resolver_t *resolver)
+{
+ if (resolver->res) {
+#if PJSIP_HAS_RESOLVER
+ pj_dns_resolver_destroy(resolver->res, PJ_FALSE);
+#endif
+ resolver->res = NULL;
+ }
+}
+
+/*
+ * Internal:
+ * determine if an address is a valid IP address, and if it is,
+ * return the IP version (4 or 6).
+ */
+static int get_ip_addr_ver(const pj_str_t *host)
+{
+ pj_in_addr dummy;
+ pj_in6_addr dummy6;
+
+ /* First check with inet_aton() */
+ if (pj_inet_aton(host, &dummy) > 0)
+ return 4;
+
+ /* Then check if this is an IPv6 address */
+ if (pj_inet_pton(pj_AF_INET6(), host, &dummy6) == PJ_SUCCESS)
+ return 6;
+
+ /* Not an IP address */
+ return 0;
+}
+
+
+/*
+ * This is the main function for performing server resolution.
+ */
+PJ_DEF(void) pjsip_resolve( pjsip_resolver_t *resolver,
+ pj_pool_t *pool,
+ const pjsip_host_info *target,
+ void *token,
+ pjsip_resolver_callback *cb)
+{
+ pjsip_server_addresses svr_addr;
+ pj_status_t status = PJ_SUCCESS;
+ int ip_addr_ver;
+ struct query *query;
+ pjsip_transport_type_e type = target->type;
+
+ /* Is it IP address or hostname? And if it's an IP, which version? */
+ ip_addr_ver = get_ip_addr_ver(&target->addr.host);
+
+ /* Set the transport type if not explicitly specified.
+ * RFC 3263 section 4.1 specify rules to set up this.
+ */
+ if (type == PJSIP_TRANSPORT_UNSPECIFIED) {
+ if (ip_addr_ver || (target->addr.port != 0)) {
+#if PJ_HAS_TCP
+ if (target->flag & PJSIP_TRANSPORT_SECURE)
+ {
+ type = PJSIP_TRANSPORT_TLS;
+ } else if (target->flag & PJSIP_TRANSPORT_RELIABLE)
+ {
+ type = PJSIP_TRANSPORT_TCP;
+ } else
+#endif
+ {
+ type = PJSIP_TRANSPORT_UDP;
+ }
+ } else {
+ /* No type or explicit port is specified, and the address is
+ * not IP address.
+ * In this case, full NAPTR resolution must be performed.
+ * But we don't support it (yet).
+ */
+#if PJ_HAS_TCP
+ if (target->flag & PJSIP_TRANSPORT_SECURE)
+ {
+ type = PJSIP_TRANSPORT_TLS;
+ } else if (target->flag & PJSIP_TRANSPORT_RELIABLE)
+ {
+ type = PJSIP_TRANSPORT_TCP;
+ } else
+#endif
+ {
+ type = PJSIP_TRANSPORT_UDP;
+ }
+ }
+
+ /* Add IPv6 flag for IPv6 address */
+ if (ip_addr_ver == 6)
+ type = (pjsip_transport_type_e)((int)type + PJSIP_TRANSPORT_IPV6);
+ }
+
+
+ /* If target is an IP address, or if resolver is not configured,
+ * we can just finish the resolution now using pj_gethostbyname()
+ */
+ if (ip_addr_ver || resolver->res == NULL) {
+ char addr_str[PJ_INET6_ADDRSTRLEN+10];
+ pj_uint16_t srv_port;
+
+ if (ip_addr_ver != 0) {
+ /* Target is an IP address, no need to resolve */
+ if (ip_addr_ver == 4) {
+ pj_sockaddr_init(pj_AF_INET(), &svr_addr.entry[0].addr,
+ NULL, 0);
+ pj_inet_aton(&target->addr.host,
+ &svr_addr.entry[0].addr.ipv4.sin_addr);
+ } else {
+ pj_sockaddr_init(pj_AF_INET6(), &svr_addr.entry[0].addr,
+ NULL, 0);
+ pj_inet_pton(pj_AF_INET6(), &target->addr.host,
+ &svr_addr.entry[0].addr.ipv6.sin6_addr);
+ }
+ } else {
+ pj_addrinfo ai;
+ unsigned count;
+ int af;
+
+ PJ_LOG(5,(THIS_FILE,
+ "DNS resolver not available, target '%.*s:%d' type=%s "
+ "will be resolved with getaddrinfo()",
+ target->addr.host.slen,
+ target->addr.host.ptr,
+ target->addr.port,
+ pjsip_transport_get_type_name(target->type)));
+
+ if (type & PJSIP_TRANSPORT_IPV6) {
+ af = pj_AF_INET6();
+ } else {
+ af = pj_AF_INET();
+ }
+
+ /* Resolve */
+ count = 1;
+ status = pj_getaddrinfo(af, &target->addr.host, &count, &ai);
+ if (status != PJ_SUCCESS) {
+ /* "Normalize" error to PJ_ERESOLVE. This is a special error
+ * because it will be translated to SIP status 502 by
+ * sip_transaction.c
+ */
+ status = PJ_ERESOLVE;
+ goto on_error;
+ }
+
+ svr_addr.entry[0].addr.addr.sa_family = (pj_uint16_t)af;
+ pj_memcpy(&svr_addr.entry[0].addr, &ai.ai_addr,
+ sizeof(pj_sockaddr));
+ }
+
+ /* Set the port number */
+ if (target->addr.port == 0) {
+ srv_port = (pj_uint16_t)
+ pjsip_transport_get_default_port_for_type(type);
+ } else {
+ srv_port = (pj_uint16_t)target->addr.port;
+ }
+ pj_sockaddr_set_port(&svr_addr.entry[0].addr, srv_port);
+
+ /* Call the callback. */
+ PJ_LOG(5,(THIS_FILE,
+ "Target '%.*s:%d' type=%s resolved to "
+ "'%s' type=%s (%s)",
+ (int)target->addr.host.slen,
+ target->addr.host.ptr,
+ target->addr.port,
+ pjsip_transport_get_type_name(target->type),
+ pj_sockaddr_print(&svr_addr.entry[0].addr, addr_str,
+ sizeof(addr_str), 3),
+ pjsip_transport_get_type_name(type),
+ pjsip_transport_get_type_desc(type)));
+ svr_addr.count = 1;
+ svr_addr.entry[0].priority = 0;
+ svr_addr.entry[0].weight = 0;
+ svr_addr.entry[0].type = type;
+ svr_addr.entry[0].addr_len = pj_sockaddr_get_len(&svr_addr.entry[0].addr);
+ (*cb)(status, token, &svr_addr);
+
+ /* Done. */
+ return;
+ }
+
+ /* Target is not an IP address so we need to resolve it. */
+#if PJSIP_HAS_RESOLVER
+
+ /* Build the query state */
+ query = PJ_POOL_ZALLOC_T(pool, struct query);
+ query->objname = THIS_FILE;
+ query->token = token;
+ query->cb = cb;
+ query->req.target = *target;
+ pj_strdup(pool, &query->req.target.addr.host, &target->addr.host);
+
+ /* If port is not specified, start with SRV resolution
+ * (should be with NAPTR, but we'll do that later)
+ */
+ PJ_TODO(SUPPORT_DNS_NAPTR);
+
+ /* Build dummy NAPTR entry */
+ query->naptr_cnt = 1;
+ pj_bzero(&query->naptr[0], sizeof(query->naptr[0]));
+ query->naptr[0].order = 0;
+ query->naptr[0].pref = 0;
+ query->naptr[0].type = type;
+ pj_strdup(pool, &query->naptr[0].name, &target->addr.host);
+
+
+ /* Start DNS SRV or A resolution, depending on whether port is specified */
+ if (target->addr.port == 0) {
+ query->query_type = PJ_DNS_TYPE_SRV;
+
+ query->req.def_port = 5060;
+
+ if (type == PJSIP_TRANSPORT_TLS) {
+ query->naptr[0].res_type = pj_str("_sips._tcp.");
+ query->req.def_port = 5061;
+ } else if (type == PJSIP_TRANSPORT_TCP)
+ query->naptr[0].res_type = pj_str("_sip._tcp.");
+ else if (type == PJSIP_TRANSPORT_UDP)
+ query->naptr[0].res_type = pj_str("_sip._udp.");
+ else {
+ pj_assert(!"Unknown transport type");
+ query->naptr[0].res_type = pj_str("_sip._udp.");
+
+ }
+
+ } else {
+ /* Otherwise if port is specified, start with A (or AAAA) host
+ * resolution
+ */
+ query->query_type = PJ_DNS_TYPE_A;
+ query->naptr[0].res_type.slen = 0;
+ query->req.def_port = target->addr.port;
+ }
+
+ /* Start the asynchronous query */
+ PJ_LOG(5, (query->objname,
+ "Starting async DNS %s query: target=%.*s%.*s, transport=%s, "
+ "port=%d",
+ pj_dns_get_type_name(query->query_type),
+ (int)query->naptr[0].res_type.slen,
+ query->naptr[0].res_type.ptr,
+ (int)query->naptr[0].name.slen, query->naptr[0].name.ptr,
+ pjsip_transport_get_type_name(target->type),
+ target->addr.port));
+
+ if (query->query_type == PJ_DNS_TYPE_SRV) {
+
+ status = pj_dns_srv_resolve(&query->naptr[0].name,
+ &query->naptr[0].res_type,
+ query->req.def_port, pool, resolver->res,
+ PJ_TRUE, query, &srv_resolver_cb, NULL);
+
+ } else if (query->query_type == PJ_DNS_TYPE_A) {
+
+ status = pj_dns_resolver_start_query(resolver->res,
+ &query->naptr[0].name,
+ PJ_DNS_TYPE_A, 0,
+ &dns_a_callback,
+ query, &query->object);
+
+ } else {
+ pj_assert(!"Unexpected");
+ status = PJ_EBUG;
+ }
+
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ return;
+
+#else /* PJSIP_HAS_RESOLVER */
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(query);
+ PJ_UNUSED_ARG(srv_name);
+#endif /* PJSIP_HAS_RESOLVER */
+
+on_error:
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ PJ_LOG(4,(THIS_FILE, "Failed to resolve '%.*s'. Err=%d (%s)",
+ (int)target->addr.host.slen,
+ target->addr.host.ptr,
+ status,
+ pj_strerror(status,errmsg,sizeof(errmsg)).ptr));
+ (*cb)(status, token, NULL);
+ return;
+ }
+}
+
+#if PJSIP_HAS_RESOLVER
+
+/*
+ * This callback is called when target is resolved with DNS A query.
+ */
+static void dns_a_callback(void *user_data,
+ pj_status_t status,
+ pj_dns_parsed_packet *pkt)
+{
+ struct query *query = (struct query*) user_data;
+ pjsip_server_addresses srv;
+ pj_dns_a_record rec;
+ unsigned i;
+
+ rec.addr_count = 0;
+
+ /* Parse the response */
+ if (status == PJ_SUCCESS) {
+ status = pj_dns_parse_a_response(pkt, &rec);
+ }
+
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ /* Log error */
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(query->objname, "DNS A record resolution failed: %s",
+ errmsg));
+
+ /* Call the callback */
+ (*query->cb)(status, query->token, NULL);
+ return;
+ }
+
+ /* Build server addresses and call callback */
+ srv.count = 0;
+ for (i=0; i<rec.addr_count; ++i) {
+ srv.entry[srv.count].type = query->naptr[0].type;
+ srv.entry[srv.count].priority = 0;
+ srv.entry[srv.count].weight = 0;
+ srv.entry[srv.count].addr_len = sizeof(pj_sockaddr_in);
+ pj_sockaddr_in_init(&srv.entry[srv.count].addr.ipv4,
+ 0, (pj_uint16_t)query->req.def_port);
+ srv.entry[srv.count].addr.ipv4.sin_addr.s_addr =
+ rec.addr[i].s_addr;
+
+ ++srv.count;
+ }
+
+ /* Call the callback */
+ (*query->cb)(PJ_SUCCESS, query->token, &srv);
+}
+
+
+/* Callback to be called by DNS SRV resolution */
+static void srv_resolver_cb(void *user_data,
+ pj_status_t status,
+ const pj_dns_srv_record *rec)
+{
+ struct query *query = (struct query*) user_data;
+ pjsip_server_addresses srv;
+ unsigned i;
+
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ /* Log error */
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(query->objname, "DNS A record resolution failed: %s",
+ errmsg));
+
+ /* Call the callback */
+ (*query->cb)(status, query->token, NULL);
+ return;
+ }
+
+ /* Build server addresses and call callback */
+ srv.count = 0;
+ for (i=0; i<rec->count; ++i) {
+ unsigned j;
+
+ for (j=0; j<rec->entry[i].server.addr_count; ++j) {
+ srv.entry[srv.count].type = query->naptr[0].type;
+ srv.entry[srv.count].priority = rec->entry[i].priority;
+ srv.entry[srv.count].weight = rec->entry[i].weight;
+ srv.entry[srv.count].addr_len = sizeof(pj_sockaddr_in);
+ pj_sockaddr_in_init(&srv.entry[srv.count].addr.ipv4,
+ 0, (pj_uint16_t)rec->entry[i].port);
+ srv.entry[srv.count].addr.ipv4.sin_addr.s_addr =
+ rec->entry[i].server.addr[j].s_addr;
+
+ ++srv.count;
+ }
+ }
+
+ /* Call the callback */
+ (*query->cb)(PJ_SUCCESS, query->token, &srv);
+}
+
+#endif /* PJSIP_HAS_RESOLVER */
+
diff --git a/pjsip/src/pjsip/sip_tel_uri.c b/pjsip/src/pjsip/sip_tel_uri.c
new file mode 100644
index 0000000..4120ae0
--- /dev/null
+++ b/pjsip/src/pjsip/sip_tel_uri.c
@@ -0,0 +1,448 @@
+/* $Id: sip_tel_uri.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_tel_uri.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/print_util.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+#include <pj/ctype.h>
+#include <pj/except.h>
+#include <pjlib-util/string.h>
+#include <pjlib-util/scanner.h>
+
+#define ALPHA
+#define DIGITS "0123456789"
+#define HEX "aAbBcCdDeEfF"
+#define HEX_DIGITS DIGITS HEX
+#define VISUAL_SEP "-.()"
+#define PHONE_DIGITS DIGITS VISUAL_SEP
+#define GLOBAL_DIGITS "+" PHONE_DIGITS
+#define LOCAL_DIGITS HEX_DIGITS "*#" VISUAL_SEP
+#define NUMBER_SPEC LOCAL_DIGITS GLOBAL_DIGITS
+#define PHONE_CONTEXT ALPHA GLOBAL_DIGITS
+//#define RESERVED ";/?:@&=+$,"
+#define RESERVED "/:@&$,+"
+#define MARK "-_.!~*'()"
+#define UNRESERVED ALPHA DIGITS MARK
+#define ESCAPED "%"
+#define URIC RESERVED UNRESERVED ESCAPED "[]+"
+#define PARAM_UNRESERVED "[]/:&+$"
+#define PARAM_CHAR PARAM_UNRESERVED UNRESERVED ESCAPED
+
+static pj_cis_buf_t cis_buf;
+static pj_cis_t pjsip_TEL_NUMBER_SPEC;
+static pj_cis_t pjsip_TEL_EXT_VALUE_SPEC;
+static pj_cis_t pjsip_TEL_PHONE_CONTEXT_SPEC;
+static pj_cis_t pjsip_TEL_URIC_SPEC;
+static pj_cis_t pjsip_TEL_VISUAL_SEP_SPEC;
+static pj_cis_t pjsip_TEL_PNAME_SPEC;
+static pj_cis_t pjsip_TEL_PVALUE_SPEC;
+static pj_cis_t pjsip_TEL_PVALUE_SPEC_ESC;
+static pj_cis_t pjsip_TEL_PARSING_PVALUE_SPEC;
+static pj_cis_t pjsip_TEL_PARSING_PVALUE_SPEC_ESC;
+
+static pj_str_t pjsip_ISUB_STR = { "isub", 4 };
+static pj_str_t pjsip_EXT_STR = { "ext", 3 };
+static pj_str_t pjsip_PH_CTX_STR = { "phone-context", 13 };
+
+
+static const pj_str_t *tel_uri_get_scheme( const pjsip_tel_uri* );
+static void *tel_uri_get_uri( pjsip_tel_uri* );
+static pj_ssize_t tel_uri_print( pjsip_uri_context_e context,
+ const pjsip_tel_uri *url,
+ char *buf, pj_size_t size);
+static int tel_uri_cmp( pjsip_uri_context_e context,
+ const pjsip_tel_uri *url1, const pjsip_tel_uri *url2);
+static pjsip_tel_uri* tel_uri_clone(pj_pool_t *pool, const pjsip_tel_uri *rhs);
+static void* tel_uri_parse( pj_scanner *scanner, pj_pool_t *pool,
+ pj_bool_t parse_params);
+
+typedef const pj_str_t* (*P_GET_SCHEME)(const void*);
+typedef void* (*P_GET_URI)(void*);
+typedef pj_ssize_t (*P_PRINT_URI)(pjsip_uri_context_e,const void *,
+ char*,pj_size_t);
+typedef int (*P_CMP_URI)(pjsip_uri_context_e, const void*,
+ const void*);
+typedef void* (*P_CLONE)(pj_pool_t*, const void*);
+
+static pjsip_uri_vptr tel_uri_vptr =
+{
+ (P_GET_SCHEME) &tel_uri_get_scheme,
+ (P_GET_URI) &tel_uri_get_uri,
+ (P_PRINT_URI) &tel_uri_print,
+ (P_CMP_URI) &tel_uri_cmp,
+ (P_CLONE) &tel_uri_clone
+};
+
+
+PJ_DEF(pjsip_tel_uri*) pjsip_tel_uri_create(pj_pool_t *pool)
+{
+ pjsip_tel_uri *uri = PJ_POOL_ZALLOC_T(pool, pjsip_tel_uri);
+ uri->vptr = &tel_uri_vptr;
+ pj_list_init(&uri->other_param);
+ return uri;
+}
+
+
+static const pj_str_t *tel_uri_get_scheme( const pjsip_tel_uri *uri )
+{
+ PJ_UNUSED_ARG(uri);
+ return &pjsip_parser_const()->pjsip_TEL_STR;
+}
+
+static void *tel_uri_get_uri( pjsip_tel_uri *uri )
+{
+ return uri;
+}
+
+
+pj_status_t pjsip_tel_uri_subsys_init(void)
+{
+ pj_status_t status;
+
+ pj_cis_buf_init(&cis_buf);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_EXT_VALUE_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_str(&pjsip_TEL_EXT_VALUE_SPEC, PHONE_DIGITS);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_NUMBER_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_str(&pjsip_TEL_NUMBER_SPEC, NUMBER_SPEC);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_VISUAL_SEP_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_str(&pjsip_TEL_VISUAL_SEP_SPEC, VISUAL_SEP);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_PHONE_CONTEXT_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_alpha(&pjsip_TEL_PHONE_CONTEXT_SPEC);
+ pj_cis_add_num(&pjsip_TEL_PHONE_CONTEXT_SPEC);
+ pj_cis_add_str(&pjsip_TEL_PHONE_CONTEXT_SPEC, PHONE_CONTEXT);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_URIC_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_alpha(&pjsip_TEL_URIC_SPEC);
+ pj_cis_add_num(&pjsip_TEL_URIC_SPEC);
+ pj_cis_add_str(&pjsip_TEL_URIC_SPEC, URIC);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_PNAME_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_alpha(&pjsip_TEL_PNAME_SPEC);
+ pj_cis_add_num(&pjsip_TEL_PNAME_SPEC);
+ pj_cis_add_str(&pjsip_TEL_PNAME_SPEC, "-");
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_PVALUE_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_alpha(&pjsip_TEL_PVALUE_SPEC);
+ pj_cis_add_num(&pjsip_TEL_PVALUE_SPEC);
+ pj_cis_add_str(&pjsip_TEL_PVALUE_SPEC, PARAM_CHAR);
+
+ status = pj_cis_dup(&pjsip_TEL_PVALUE_SPEC_ESC, &pjsip_TEL_PVALUE_SPEC);
+ pj_cis_del_str(&pjsip_TEL_PVALUE_SPEC_ESC, "%");
+
+ status = pj_cis_dup(&pjsip_TEL_PARSING_PVALUE_SPEC, &pjsip_TEL_URIC_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_cis(&pjsip_TEL_PARSING_PVALUE_SPEC, &pjsip_TEL_PVALUE_SPEC);
+ pj_cis_add_str(&pjsip_TEL_PARSING_PVALUE_SPEC, "=");
+
+ status = pj_cis_dup(&pjsip_TEL_PARSING_PVALUE_SPEC_ESC,
+ &pjsip_TEL_PARSING_PVALUE_SPEC);
+ pj_cis_del_str(&pjsip_TEL_PARSING_PVALUE_SPEC_ESC, "%");
+
+ status = pjsip_register_uri_parser("tel", &tel_uri_parse);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+ return PJ_SUCCESS;
+}
+
+/* Print tel: URI */
+static pj_ssize_t tel_uri_print( pjsip_uri_context_e context,
+ const pjsip_tel_uri *uri,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf+size;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ PJ_UNUSED_ARG(context);
+
+ /* Print scheme. */
+ copy_advance(buf, pc->pjsip_TEL_STR);
+ *buf++ = ':';
+
+ /* Print number. */
+ copy_advance_escape(buf, uri->number, pjsip_TEL_NUMBER_SPEC);
+
+ /* ISDN sub-address or extension must appear first. */
+
+ /* Extension param. */
+ copy_advance_pair_escape(buf, ";ext=", 5, uri->ext_param,
+ pjsip_TEL_EXT_VALUE_SPEC);
+
+ /* ISDN sub-address. */
+ copy_advance_pair_escape(buf, ";isub=", 6, uri->isub_param,
+ pjsip_TEL_URIC_SPEC);
+
+ /* Followed by phone context, if present. */
+ copy_advance_pair_escape(buf, ";phone-context=", 15, uri->context,
+ pjsip_TEL_PHONE_CONTEXT_SPEC);
+
+
+ /* Print other parameters. */
+ printed = pjsip_param_print_on(&uri->other_param, buf, (endbuf-buf),
+ &pjsip_TEL_PNAME_SPEC,
+ &pjsip_TEL_PVALUE_SPEC, ';');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return (buf-startbuf);
+}
+
+/* Compare two numbers, according to RFC 3966:
+ * - both must be either local or global numbers.
+ * - The 'global-number-digits' and the 'local-number-digits' must be
+ * equal, after removing all visual separators.
+ */
+PJ_DEF(int) pjsip_tel_nb_cmp(const pj_str_t *number1, const pj_str_t *number2)
+{
+ const char *s1 = number1->ptr,
+ *e1 = number1->ptr + number1->slen,
+ *s2 = number2->ptr,
+ *e2 = number2->ptr + number2->slen;
+
+ /* Compare each number, ignoreing visual separators. */
+ while (s1!=e1 && s2!=e2) {
+ int diff;
+
+ if (pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s1)) {
+ ++s1;
+ continue;
+ }
+ if (pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s2)) {
+ ++s2;
+ continue;
+ }
+
+ diff = pj_tolower(*s1) - pj_tolower(*s2);
+ if (!diff) {
+ ++s1, ++s2;
+ continue;
+ } else
+ return diff;
+ }
+
+ /* Exhaust remaining visual separators. */
+ while (s1!=e1 && pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s1))
+ ++s1;
+ while (s2!=e2 && pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s2))
+ ++s2;
+
+ if (s1==e1 && s2==e2)
+ return 0;
+ else if (s1==e1)
+ return -1;
+ else
+ return 1;
+}
+
+/* Compare two tel: URI */
+static int tel_uri_cmp( pjsip_uri_context_e context,
+ const pjsip_tel_uri *url1, const pjsip_tel_uri *url2)
+{
+ int result;
+
+ PJ_UNUSED_ARG(context);
+
+ /* Scheme must match. */
+ if (url1->vptr != url2->vptr)
+ return -1;
+
+ /* Compare number. */
+ result = pjsip_tel_nb_cmp(&url1->number, &url2->number);
+ if (result != 0)
+ return result;
+
+ /* Compare phone-context as hostname or as as global nb. */
+ if (url1->context.slen) {
+ if (*url1->context.ptr != '+')
+ result = pj_stricmp(&url1->context, &url2->context);
+ else
+ result = pjsip_tel_nb_cmp(&url1->context, &url2->context);
+
+ if (result != 0)
+ return result;
+
+ } else if (url2->context.slen)
+ return -1;
+
+ /* Compare extension. */
+ if (url1->ext_param.slen) {
+ result = pjsip_tel_nb_cmp(&url1->ext_param, &url2->ext_param);
+ if (result != 0)
+ return result;
+ }
+
+ /* Compare isub bytes by bytes. */
+ if (url1->isub_param.slen) {
+ result = pj_stricmp(&url1->isub_param, &url2->isub_param);
+ if (result != 0)
+ return result;
+ }
+
+ /* Other parameters are compared regardless of the order.
+ * If one URI has parameter not found in the other URI, the URIs are
+ * not equal.
+ */
+ if (url1->other_param.next != &url1->other_param) {
+ const pjsip_param *p1, *p2;
+ int cnt1 = 0, cnt2 = 0;
+
+ p1 = url1->other_param.next;
+ while (p1 != &url1->other_param) {
+ p2 = pjsip_param_cfind(&url2->other_param, &p1->name);
+ if (!p2 )
+ return 1;
+
+ result = pj_stricmp(&p1->value, &p2->value);
+ if (result != 0)
+ return result;
+
+ p1 = p1->next;
+ ++cnt1;
+ }
+
+ p2 = url2->other_param.next;
+ while (p2 != &url2->other_param)
+ ++cnt2, p2 = p2->next;
+
+ if (cnt1 < cnt2)
+ return -1;
+ else if (cnt1 > cnt2)
+ return 1;
+
+ } else if (url2->other_param.next != &url2->other_param)
+ return -1;
+
+ /* Equal. */
+ return 0;
+}
+
+/* Clone tel: URI */
+static pjsip_tel_uri* tel_uri_clone(pj_pool_t *pool, const pjsip_tel_uri *rhs)
+{
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ pj_strdup(pool, &uri->number, &rhs->number);
+ pj_strdup(pool, &uri->context, &rhs->context);
+ pj_strdup(pool, &uri->ext_param, &rhs->ext_param);
+ pj_strdup(pool, &uri->isub_param, &rhs->isub_param);
+ pjsip_param_clone(pool, &uri->other_param, &rhs->other_param);
+
+ return uri;
+}
+
+/* Parse tel: URI
+ * THis actually returns (pjsip_tel_uri *) type.
+ */
+static void* tel_uri_parse( pj_scanner *scanner, pj_pool_t *pool,
+ pj_bool_t parse_params)
+{
+ pjsip_tel_uri *uri;
+ pj_str_t token;
+ int skip_ws = scanner->skip_ws;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ scanner->skip_ws = 0;
+
+ /* Parse scheme. */
+ pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &token);
+ if (pj_scan_get_char(scanner) != ':')
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ if (pj_stricmp_alnum(&token, &pc->pjsip_TEL_STR) != 0)
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+
+ /* Create URI */
+ uri = pjsip_tel_uri_create(pool);
+
+ /* Get the phone number. */
+#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
+ pj_scan_get_unescape(scanner, &pjsip_TEL_NUMBER_SPEC, &uri->number);
+#else
+ pj_scan_get(scanner, &pjsip_TEL_NUMBER_SPEC, &uri->number);
+ uri->number = pj_str_unescape(pool, &uri->number);
+#endif
+
+ /* Get all parameters. */
+ if (parse_params && *scanner->curptr==';') {
+ pj_str_t pname, pvalue;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ do {
+ /* Eat the ';' separator. */
+ pj_scan_get_char(scanner);
+
+ /* Get pname. */
+ pj_scan_get(scanner, &pc->pjsip_PARAM_CHAR_SPEC, &pname);
+
+ if (*scanner->curptr == '=') {
+ pj_scan_get_char(scanner);
+
+# if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
+ pj_scan_get_unescape(scanner,
+ &pjsip_TEL_PARSING_PVALUE_SPEC_ESC,
+ &pvalue);
+# else
+ pj_scan_get(scanner, &pjsip_TEL_PARSING_PVALUE_SPEC,
+ &pvalue);
+ pvalue = pj_str_unescape(pool, &pvalue);
+# endif
+
+ } else {
+ pvalue.slen = 0;
+ pvalue.ptr = NULL;
+ }
+
+ /* Save the parameters. */
+ if (pj_stricmp_alnum(&pname, &pjsip_ISUB_STR)==0) {
+ uri->isub_param = pvalue;
+ } else if (pj_stricmp_alnum(&pname, &pjsip_EXT_STR)==0) {
+ uri->ext_param = pvalue;
+ } else if (pj_stricmp_alnum(&pname, &pjsip_PH_CTX_STR)==0) {
+ uri->context = pvalue;
+ } else {
+ pjsip_param *param = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ param->name = pname;
+ param->value = pvalue;
+ pj_list_insert_before(&uri->other_param, param);
+ }
+
+ } while (*scanner->curptr==';');
+ }
+
+ scanner->skip_ws = skip_ws;
+ pj_scan_skip_whitespace(scanner);
+ return uri;
+}
+
diff --git a/pjsip/src/pjsip/sip_tel_uri_wrap.cpp b/pjsip/src/pjsip/sip_tel_uri_wrap.cpp
new file mode 100644
index 0000000..d13fb52
--- /dev/null
+++ b/pjsip/src/pjsip/sip_tel_uri_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_tel_uri_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_tel_uri.c"
diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c
new file mode 100644
index 0000000..4b3dd12
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transaction.c
@@ -0,0 +1,3277 @@
+/* $Id: sip_transaction.c 4165 2012-06-14 09:04:20Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_event.h>
+#include <pjlib-util/errno.h>
+#include <pj/hash.h>
+#include <pj/pool.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+
+#define THIS_FILE "sip_transaction.c"
+
+#if 0
+#define TSX_TRACE_(expr) PJ_LOG(3,expr)
+#else
+#define TSX_TRACE_(expr)
+#endif
+
+/* When this macro is set, transaction will keep the hashed value
+ * so that future lookup (to unregister transaction) does not need
+ * to recalculate the hash again. It should gains a little bit of
+ * performance, so generally we'd want this.
+ */
+#define PRECALC_HASH
+
+
+/* Defined in sip_util_statefull.c */
+extern pjsip_module mod_stateful_util;
+
+
+/*****************************************************************************
+ **
+ ** Declarations and static variable definitions section.
+ **
+ *****************************************************************************
+ **/
+/* Prototypes. */
+static pj_status_t mod_tsx_layer_load(pjsip_endpoint *endpt);
+static pj_status_t mod_tsx_layer_start(void);
+static pj_status_t mod_tsx_layer_stop(void);
+static pj_status_t mod_tsx_layer_unload(void);
+static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata);
+static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata);
+
+/* Transaction layer module definition. */
+static struct mod_tsx_layer
+{
+ struct pjsip_module mod;
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pj_mutex_t *mutex;
+ pj_hash_table_t *htable;
+} mod_tsx_layer =
+{ {
+ NULL, NULL, /* List's prev and next. */
+ { "mod-tsx-layer", 13 }, /* Module name. */
+ -1, /* Module ID */
+ PJSIP_MOD_PRIORITY_TSX_LAYER, /* Priority. */
+ mod_tsx_layer_load, /* load(). */
+ mod_tsx_layer_start, /* start() */
+ mod_tsx_layer_stop, /* stop() */
+ mod_tsx_layer_unload, /* unload() */
+ mod_tsx_layer_on_rx_request, /* on_rx_request() */
+ mod_tsx_layer_on_rx_response, /* on_rx_response() */
+ NULL
+ }
+};
+
+/* Thread Local Storage ID for transaction lock */
+static long pjsip_tsx_lock_tls_id;
+
+/* Transaction state names */
+static const char *state_str[] =
+{
+ "Null",
+ "Calling",
+ "Trying",
+ "Proceeding",
+ "Completed",
+ "Confirmed",
+ "Terminated",
+ "Destroyed",
+};
+
+/* Role names */
+static const char *role_name[] =
+{
+ "UAC",
+ "UAS"
+};
+
+/* Transport flag. */
+enum
+{
+ TSX_HAS_PENDING_TRANSPORT = 1,
+ TSX_HAS_PENDING_RESCHED = 2,
+ TSX_HAS_PENDING_SEND = 4,
+ TSX_HAS_PENDING_DESTROY = 8,
+ TSX_HAS_RESOLVED_SERVER = 16,
+};
+
+/* Transaction lock. */
+typedef struct tsx_lock_data {
+ struct tsx_lock_data *prev;
+ pjsip_transaction *tsx;
+ int is_alive;
+} tsx_lock_data;
+
+
+/* Timer timeout value constants */
+static pj_time_val t1_timer_val = { PJSIP_T1_TIMEOUT/1000,
+ PJSIP_T1_TIMEOUT%1000 };
+static pj_time_val t2_timer_val = { PJSIP_T2_TIMEOUT/1000,
+ PJSIP_T2_TIMEOUT%1000 };
+static pj_time_val t4_timer_val = { PJSIP_T4_TIMEOUT/1000,
+ PJSIP_T4_TIMEOUT%1000 };
+static pj_time_val td_timer_val = { PJSIP_TD_TIMEOUT/1000,
+ PJSIP_TD_TIMEOUT%1000 };
+static pj_time_val timeout_timer_val = { (64*PJSIP_T1_TIMEOUT)/1000,
+ (64*PJSIP_T1_TIMEOUT)%1000 };
+
+#define TIMER_INACTIVE 0
+#define TIMER_ACTIVE 1
+
+
+/* Prototypes. */
+static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck);
+static pj_status_t unlock_tsx( pjsip_transaction *tsx,
+ struct tsx_lock_data *lck);
+static pj_status_t tsx_on_state_null( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_calling( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_trying( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_proceeding_uac( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_completed_uac( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_confirmed( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_terminated( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_destroyed( pjsip_transaction *tsx,
+ pjsip_event *event);
+static void tsx_timer_callback( pj_timer_heap_t *theap,
+ pj_timer_entry *entry);
+static void tsx_tp_state_callback(
+ pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info);
+static pj_status_t tsx_create( pjsip_module *tsx_user,
+ pjsip_transaction **p_tsx);
+static pj_status_t tsx_destroy( pjsip_transaction *tsx );
+static void tsx_resched_retransmission( pjsip_transaction *tsx );
+static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched);
+static int tsx_send_msg( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata);
+static void tsx_update_transport( pjsip_transaction *tsx,
+ pjsip_transport *tp);
+
+
+/* State handlers for UAC, indexed by state */
+static int (*tsx_state_handler_uac[PJSIP_TSX_STATE_MAX])(pjsip_transaction *,
+ pjsip_event *) =
+{
+ &tsx_on_state_null,
+ &tsx_on_state_calling,
+ NULL,
+ &tsx_on_state_proceeding_uac,
+ &tsx_on_state_completed_uac,
+ &tsx_on_state_confirmed,
+ &tsx_on_state_terminated,
+ &tsx_on_state_destroyed,
+};
+
+/* State handlers for UAS */
+static int (*tsx_state_handler_uas[PJSIP_TSX_STATE_MAX])(pjsip_transaction *,
+ pjsip_event *) =
+{
+ &tsx_on_state_null,
+ NULL,
+ &tsx_on_state_trying,
+ &tsx_on_state_proceeding_uas,
+ &tsx_on_state_completed_uas,
+ &tsx_on_state_confirmed,
+ &tsx_on_state_terminated,
+ &tsx_on_state_destroyed,
+};
+
+/*****************************************************************************
+ **
+ ** Utilities
+ **
+ *****************************************************************************
+ */
+/*
+ * Get transaction state name.
+ */
+PJ_DEF(const char *) pjsip_tsx_state_str(pjsip_tsx_state_e state)
+{
+ return state_str[state];
+}
+
+/*
+ * Get the role name.
+ */
+PJ_DEF(const char *) pjsip_role_name(pjsip_role_e role)
+{
+ return role_name[role];
+}
+
+
+/*
+ * Create transaction key for RFC2543 compliant messages, which don't have
+ * unique branch parameter in the top most Via header.
+ *
+ * INVITE requests matches a transaction if the following attributes
+ * match the original request:
+ * - Request-URI
+ * - To tag
+ * - From tag
+ * - Call-ID
+ * - CSeq
+ * - top Via header
+ *
+ * CANCEL matching is done similarly as INVITE, except:
+ * - CSeq method will differ
+ * - To tag is not matched.
+ *
+ * ACK matching is done similarly, except that:
+ * - method of the CSeq will differ,
+ * - To tag is matched to the response sent by the server transaction.
+ *
+ * The transaction key is constructed from the common components of above
+ * components. Additional comparison is needed to fully match a transaction.
+ */
+static pj_status_t create_tsx_key_2543( pj_pool_t *pool,
+ pj_str_t *str,
+ pjsip_role_e role,
+ const pjsip_method *method,
+ const pjsip_rx_data *rdata )
+{
+#define SEPARATOR '$'
+ char *key, *p, *end;
+ int len;
+ pj_size_t len_required;
+ pjsip_uri *req_uri;
+ pj_str_t *host;
+
+ PJ_ASSERT_RETURN(pool && str && method && rdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(rdata->msg_info.via, PJSIP_EMISSINGHDR);
+ PJ_ASSERT_RETURN(rdata->msg_info.cseq, PJSIP_EMISSINGHDR);
+ PJ_ASSERT_RETURN(rdata->msg_info.from, PJSIP_EMISSINGHDR);
+
+ host = &rdata->msg_info.via->sent_by.host;
+ req_uri = (pjsip_uri*)rdata->msg_info.msg->line.req.uri;
+
+ /* Calculate length required. */
+ len_required = 9 + /* CSeq number */
+ rdata->msg_info.from->tag.slen + /* From tag. */
+ rdata->msg_info.cid->id.slen + /* Call-ID */
+ host->slen + /* Via host. */
+ 9 + /* Via port. */
+ 16; /* Separator+Allowance. */
+ key = p = (char*) pj_pool_alloc(pool, len_required);
+ end = p + len_required;
+
+ /* Add role. */
+ *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's');
+ *p++ = SEPARATOR;
+
+ /* Add method, except when method is INVITE or ACK. */
+ if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) {
+ pj_memcpy(p, method->name.ptr, method->name.slen);
+ p += method->name.slen;
+ *p++ = '$';
+ }
+
+ /* Add CSeq (only the number). */
+ len = pj_utoa(rdata->msg_info.cseq->cseq, p);
+ p += len;
+ *p++ = SEPARATOR;
+
+ /* Add From tag. */
+ len = rdata->msg_info.from->tag.slen;
+ pj_memcpy( p, rdata->msg_info.from->tag.ptr, len);
+ p += len;
+ *p++ = SEPARATOR;
+
+ /* Add Call-ID. */
+ len = rdata->msg_info.cid->id.slen;
+ pj_memcpy( p, rdata->msg_info.cid->id.ptr, len );
+ p += len;
+ *p++ = SEPARATOR;
+
+ /* Add top Via header.
+ * We don't really care whether the port contains the real port (because
+ * it can be omited if default port is used). Anyway this function is
+ * only used to match request retransmission, and we expect that the
+ * request retransmissions will contain the same port.
+ */
+ pj_memcpy(p, host->ptr, host->slen);
+ p += host->slen;
+ *p++ = ':';
+
+ len = pj_utoa(rdata->msg_info.via->sent_by.port, p);
+ p += len;
+ *p++ = SEPARATOR;
+
+ *p++ = '\0';
+
+ /* Done. */
+ str->ptr = key;
+ str->slen = p-key;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create transaction key for RFC3161 compliant system.
+ */
+static pj_status_t create_tsx_key_3261( pj_pool_t *pool,
+ pj_str_t *key,
+ pjsip_role_e role,
+ const pjsip_method *method,
+ const pj_str_t *branch)
+{
+ char *p;
+
+ PJ_ASSERT_RETURN(pool && key && method && branch, PJ_EINVAL);
+
+ p = key->ptr = (char*)
+ pj_pool_alloc(pool, branch->slen + method->name.slen + 4 );
+
+ /* Add role. */
+ *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's');
+ *p++ = SEPARATOR;
+
+ /* Add method, except when method is INVITE or ACK. */
+ if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) {
+ pj_memcpy(p, method->name.ptr, method->name.slen);
+ p += method->name.slen;
+ *p++ = '$';
+ }
+
+ /* Add branch ID. */
+ pj_memcpy(p, branch->ptr, branch->slen);
+ p += branch->slen;
+
+ /* Set length */
+ key->slen = p - key->ptr;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create key from the incoming data, to be used to search the transaction
+ * in the transaction hash table.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_create_key( pj_pool_t *pool, pj_str_t *key,
+ pjsip_role_e role,
+ const pjsip_method *method,
+ const pjsip_rx_data *rdata)
+{
+ pj_str_t rfc3261_branch = {PJSIP_RFC3261_BRANCH_ID,
+ PJSIP_RFC3261_BRANCH_LEN};
+
+
+ /* Get the branch parameter in the top-most Via.
+ * If branch parameter is started with "z9hG4bK", then the message was
+ * generated by agent compliant with RFC3261. Otherwise, it will be
+ * handled as RFC2543.
+ */
+ const pj_str_t *branch = &rdata->msg_info.via->branch_param;
+
+ if (pj_strncmp(branch,&rfc3261_branch,PJSIP_RFC3261_BRANCH_LEN)==0) {
+
+ /* Create transaction key. */
+ return create_tsx_key_3261(pool, key, role, method, branch);
+
+ } else {
+ /* Create the key for the message. This key will be matched up
+ * with the transaction key. For RFC2563 transactions, the
+ * transaction key was created by the same function, so it will
+ * match the message.
+ */
+ return create_tsx_key_2543( pool, key, role, method, rdata );
+ }
+}
+
+/*****************************************************************************
+ **
+ ** Transaction layer module
+ **
+ *****************************************************************************
+ **/
+/*
+ * Create transaction layer module and registers it to the endpoint.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_layer_init_module(pjsip_endpoint *endpt)
+{
+ pj_pool_t *pool;
+ pj_status_t status;
+
+
+ PJ_ASSERT_RETURN(mod_tsx_layer.endpt==NULL, PJ_EINVALIDOP);
+
+ /* Initialize timer values */
+ t1_timer_val.sec = pjsip_cfg()->tsx.t1 / 1000;
+ t1_timer_val.msec = pjsip_cfg()->tsx.t1 % 1000;
+ t2_timer_val.sec = pjsip_cfg()->tsx.t2 / 1000;
+ t2_timer_val.msec = pjsip_cfg()->tsx.t2 % 1000;
+ t4_timer_val.sec = pjsip_cfg()->tsx.t4 / 1000;
+ t4_timer_val.msec = pjsip_cfg()->tsx.t4 % 1000;
+ td_timer_val.sec = pjsip_cfg()->tsx.td / 1000;
+ td_timer_val.msec = pjsip_cfg()->tsx.td % 1000;
+ /* Changed the initialization below to use td_timer_val instead, to enable
+ * customization to the timeout value.
+ */
+ //timeout_timer_val.sec = (64 * pjsip_cfg()->tsx.t1) / 1000;
+ //timeout_timer_val.msec = (64 * pjsip_cfg()->tsx.t1) % 1000;
+ timeout_timer_val = td_timer_val;
+
+ /* Initialize TLS ID for transaction lock. */
+ status = pj_thread_local_alloc(&pjsip_tsx_lock_tls_id);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_thread_local_set(pjsip_tsx_lock_tls_id, NULL);
+
+ /*
+ * Initialize transaction layer structure.
+ */
+
+ /* Create pool for the module. */
+ pool = pjsip_endpt_create_pool(endpt, "tsxlayer",
+ PJSIP_POOL_TSX_LAYER_LEN,
+ PJSIP_POOL_TSX_LAYER_INC );
+ if (!pool)
+ return PJ_ENOMEM;
+
+
+ /* Initialize some attributes. */
+ mod_tsx_layer.pool = pool;
+ mod_tsx_layer.endpt = endpt;
+
+
+ /* Create hash table. */
+ mod_tsx_layer.htable = pj_hash_create( pool, pjsip_cfg()->tsx.max_count );
+ if (!mod_tsx_layer.htable) {
+ pjsip_endpt_release_pool(endpt, pool);
+ return PJ_ENOMEM;
+ }
+
+ /* Create mutex. */
+ status = pj_mutex_create_recursive(pool, "tsxlayer", &mod_tsx_layer.mutex);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_release_pool(endpt, pool);
+ return status;
+ }
+
+ /*
+ * Register transaction layer module to endpoint.
+ */
+ status = pjsip_endpt_register_module( endpt, &mod_tsx_layer.mod );
+ if (status != PJ_SUCCESS) {
+ pj_mutex_destroy(mod_tsx_layer.mutex);
+ pjsip_endpt_release_pool(endpt, pool);
+ return status;
+ }
+
+ /* Register mod_stateful_util module (sip_util_statefull.c) */
+ status = pjsip_endpt_register_module(endpt, &mod_stateful_util);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the instance of transaction layer module.
+ */
+PJ_DEF(pjsip_module*) pjsip_tsx_layer_instance(void)
+{
+ return &mod_tsx_layer.mod;
+}
+
+
+/*
+ * Unregister and destroy transaction layer module.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_layer_destroy(void)
+{
+ /* Are we registered? */
+ PJ_ASSERT_RETURN(mod_tsx_layer.endpt!=NULL, PJ_EINVALIDOP);
+
+ /* Unregister from endpoint.
+ * Clean-ups will be done in the unload() module callback.
+ */
+ return pjsip_endpt_unregister_module( mod_tsx_layer.endpt,
+ &mod_tsx_layer.mod);
+}
+
+
+/*
+ * Register the transaction to the hash table.
+ */
+static pj_status_t mod_tsx_layer_register_tsx( pjsip_transaction *tsx)
+{
+ pj_assert(tsx->transaction_key.slen != 0);
+
+ /* Lock hash table mutex. */
+ pj_mutex_lock(mod_tsx_layer.mutex);
+
+ /* Check if no transaction with the same key exists.
+ * Do not use PJ_ASSERT_RETURN since it evaluates the expression
+ * twice!
+ */
+ if(pj_hash_get(mod_tsx_layer.htable,
+ tsx->transaction_key.ptr,
+ tsx->transaction_key.slen,
+ NULL))
+ {
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+ PJ_LOG(2,(THIS_FILE,
+ "Unable to register %.*s transaction (key exists)",
+ (int)tsx->method.name.slen,
+ tsx->method.name.ptr));
+ return PJ_EEXISTS;
+ }
+
+ TSX_TRACE_((THIS_FILE,
+ "Transaction %p registered with hkey=0x%p and key=%.*s",
+ tsx, tsx->hashed_key, tsx->transaction_key.slen,
+ tsx->transaction_key.ptr));
+
+ /* Register the transaction to the hash table. */
+#ifdef PRECALC_HASH
+ pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen, tsx->hashed_key, tsx);
+#else
+ pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen, 0, tsx);
+#endif
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Unregister the transaction from the hash table.
+ */
+static void mod_tsx_layer_unregister_tsx( pjsip_transaction *tsx)
+{
+ if (mod_tsx_layer.mod.id == -1) {
+ /* The transaction layer has been unregistered. This could happen
+ * if the transaction was pending on transport and the application
+ * is shutdown. See http://trac.pjsip.org/repos/ticket/1033. In
+ * this case just do nothing.
+ */
+ return;
+ }
+
+ pj_assert(tsx->transaction_key.slen != 0);
+ //pj_assert(tsx->state != PJSIP_TSX_STATE_NULL);
+
+ /* Lock hash table mutex. */
+ pj_mutex_lock(mod_tsx_layer.mutex);
+
+ /* Register the transaction to the hash table. */
+#ifdef PRECALC_HASH
+ pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen, tsx->hashed_key, NULL);
+#else
+ pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen, 0, NULL);
+#endif
+
+ TSX_TRACE_((THIS_FILE,
+ "Transaction %p unregistered, hkey=0x%p and key=%.*s",
+ tsx, tsx->hashed_key, tsx->transaction_key.slen,
+ tsx->transaction_key.ptr));
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+}
+
+
+/*
+ * Retrieve the current number of transactions currently registered in
+ * the hash table.
+ */
+PJ_DEF(unsigned) pjsip_tsx_layer_get_tsx_count(void)
+{
+ unsigned count;
+
+ /* Are we registered? */
+ PJ_ASSERT_RETURN(mod_tsx_layer.endpt!=NULL, 0);
+
+ pj_mutex_lock(mod_tsx_layer.mutex);
+ count = pj_hash_count(mod_tsx_layer.htable);
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+
+ return count;
+}
+
+
+/*
+ * Find a transaction.
+ */
+PJ_DEF(pjsip_transaction*) pjsip_tsx_layer_find_tsx( const pj_str_t *key,
+ pj_bool_t lock )
+{
+ pjsip_transaction *tsx;
+ pj_uint32_t hval = 0;
+
+ pj_mutex_lock(mod_tsx_layer.mutex);
+ tsx = (pjsip_transaction*)
+ pj_hash_get( mod_tsx_layer.htable, key->ptr, key->slen, &hval );
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+
+ TSX_TRACE_((THIS_FILE,
+ "Finding tsx with hkey=0x%p and key=%.*s: found %p",
+ hval, key->slen, key->ptr, tsx));
+
+ /* Race condition!
+ * Transaction may gets deleted before we have chance to lock it.
+ */
+ PJ_TODO(FIX_RACE_CONDITION_HERE);
+ if (tsx && lock)
+ pj_mutex_lock(tsx->mutex);
+
+ return tsx;
+}
+
+
+/* This module callback is called when module is being loaded by
+ * endpoint. It does nothing for this module.
+ */
+static pj_status_t mod_tsx_layer_load(pjsip_endpoint *endpt)
+{
+ PJ_UNUSED_ARG(endpt);
+ return PJ_SUCCESS;
+}
+
+
+/* This module callback is called when module is being started by
+ * endpoint. It does nothing for this module.
+ */
+static pj_status_t mod_tsx_layer_start(void)
+{
+ return PJ_SUCCESS;
+}
+
+
+/* This module callback is called when module is being stopped by
+ * endpoint.
+ */
+static pj_status_t mod_tsx_layer_stop(void)
+{
+ pj_hash_iterator_t it_buf, *it;
+
+ PJ_LOG(4,(THIS_FILE, "Stopping transaction layer module"));
+
+ pj_mutex_lock(mod_tsx_layer.mutex);
+
+ /* Destroy all transactions. */
+ it = pj_hash_first(mod_tsx_layer.htable, &it_buf);
+ while (it) {
+ pjsip_transaction *tsx = (pjsip_transaction*)
+ pj_hash_this(mod_tsx_layer.htable, it);
+ pj_hash_iterator_t *next = pj_hash_next(mod_tsx_layer.htable, it);
+ if (tsx) {
+ pjsip_tsx_terminate(tsx, PJSIP_SC_SERVICE_UNAVAILABLE);
+ mod_tsx_layer_unregister_tsx(tsx);
+ tsx_destroy(tsx);
+ }
+ it = next;
+ }
+
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+
+ PJ_LOG(4,(THIS_FILE, "Stopped transaction layer module"));
+
+ return PJ_SUCCESS;
+}
+
+
+/* Destroy this module */
+static void tsx_layer_destroy(pjsip_endpoint *endpt)
+{
+ PJ_UNUSED_ARG(endpt);
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(mod_tsx_layer.mutex);
+
+ /* Release pool. */
+ pjsip_endpt_release_pool(mod_tsx_layer.endpt, mod_tsx_layer.pool);
+
+ /* Free TLS */
+ pj_thread_local_free(pjsip_tsx_lock_tls_id);
+
+ /* Mark as unregistered. */
+ mod_tsx_layer.endpt = NULL;
+
+ PJ_LOG(4,(THIS_FILE, "Transaction layer module destroyed"));
+}
+
+
+/* This module callback is called when module is being unloaded by
+ * endpoint.
+ */
+static pj_status_t mod_tsx_layer_unload(void)
+{
+ /* Only self destroy when there's no transaction in the table.
+ * Transaction may refuse to destroy when it has pending
+ * transmission. If we destroy the module now, application will
+ * crash when the pending transaction finally got error response
+ * from transport and when it tries to unregister itself.
+ */
+ if (pj_hash_count(mod_tsx_layer.htable) != 0) {
+ if (pjsip_endpt_atexit(mod_tsx_layer.endpt, &tsx_layer_destroy) !=
+ PJ_SUCCESS)
+ {
+ PJ_LOG(3,(THIS_FILE, "Failed to register transaction layer "
+ "module destroy."));
+ }
+ return PJ_EBUSY;
+ }
+
+ tsx_layer_destroy(mod_tsx_layer.endpt);
+
+ return PJ_SUCCESS;
+}
+
+
+/* This module callback is called when endpoint has received an
+ * incoming request message.
+ */
+static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata)
+{
+ pj_str_t key;
+ pj_uint32_t hval = 0;
+ pjsip_transaction *tsx;
+
+ pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAS,
+ &rdata->msg_info.cseq->method, rdata);
+
+ /* Find transaction. */
+ pj_mutex_lock( mod_tsx_layer.mutex );
+
+ tsx = (pjsip_transaction*)
+ pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval );
+
+
+ TSX_TRACE_((THIS_FILE,
+ "Finding tsx for request, hkey=0x%p and key=%.*s, found %p",
+ hval, key.slen, key.ptr, tsx));
+
+
+ if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+ /* Transaction not found.
+ * Reject the request so that endpoint passes the request to
+ * upper layer modules.
+ */
+ pj_mutex_unlock( mod_tsx_layer.mutex);
+ return PJ_FALSE;
+ }
+
+ /* Unlock hash table. */
+ pj_mutex_unlock( mod_tsx_layer.mutex );
+
+ /* Race condition!
+ * Transaction may gets deleted before we have chance to lock it
+ * in pjsip_tsx_recv_msg().
+ */
+ PJ_TODO(FIX_RACE_CONDITION_HERE);
+
+ /* Pass the message to the transaction. */
+ pjsip_tsx_recv_msg(tsx, rdata );
+
+ return PJ_TRUE;
+}
+
+
+/* This module callback is called when endpoint has received an
+ * incoming response message.
+ */
+static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata)
+{
+ pj_str_t key;
+ pj_uint32_t hval = 0;
+ pjsip_transaction *tsx;
+
+ pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAC,
+ &rdata->msg_info.cseq->method, rdata);
+
+ /* Find transaction. */
+ pj_mutex_lock( mod_tsx_layer.mutex );
+
+ tsx = (pjsip_transaction*)
+ pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval );
+
+
+ TSX_TRACE_((THIS_FILE,
+ "Finding tsx for response, hkey=0x%p and key=%.*s, found %p",
+ hval, key.slen, key.ptr, tsx));
+
+
+ if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+ /* Transaction not found.
+ * Reject the request so that endpoint passes the request to
+ * upper layer modules.
+ */
+ pj_mutex_unlock( mod_tsx_layer.mutex);
+ return PJ_FALSE;
+ }
+
+ /* Unlock hash table. */
+ pj_mutex_unlock( mod_tsx_layer.mutex );
+
+ /* Race condition!
+ * Transaction may gets deleted before we have chance to lock it
+ * in pjsip_tsx_recv_msg().
+ */
+ PJ_TODO(FIX_RACE_CONDITION_HERE);
+
+ /* Pass the message to the transaction. */
+ pjsip_tsx_recv_msg(tsx, rdata );
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Get transaction instance in the rdata.
+ */
+PJ_DEF(pjsip_transaction*) pjsip_rdata_get_tsx( pjsip_rx_data *rdata )
+{
+ return (pjsip_transaction*)
+ rdata->endpt_info.mod_data[mod_tsx_layer.mod.id];
+}
+
+
+/*
+ * Dump transaction layer.
+ */
+PJ_DEF(void) pjsip_tsx_layer_dump(pj_bool_t detail)
+{
+#if PJ_LOG_MAX_LEVEL >= 3
+ pj_hash_iterator_t itbuf, *it;
+
+ /* Lock mutex. */
+ pj_mutex_lock(mod_tsx_layer.mutex);
+
+ PJ_LOG(3, (THIS_FILE, "Dumping transaction table:"));
+ PJ_LOG(3, (THIS_FILE, " Total %d transactions",
+ pj_hash_count(mod_tsx_layer.htable)));
+
+ if (detail) {
+ it = pj_hash_first(mod_tsx_layer.htable, &itbuf);
+ if (it == NULL) {
+ PJ_LOG(3, (THIS_FILE, " - none - "));
+ } else {
+ while (it != NULL) {
+ pjsip_transaction *tsx = (pjsip_transaction*)
+ pj_hash_this(mod_tsx_layer.htable,it);
+
+ PJ_LOG(3, (THIS_FILE, " %s %s|%d|%s",
+ tsx->obj_name,
+ (tsx->last_tx?
+ pjsip_tx_data_get_info(tsx->last_tx):
+ "none"),
+ tsx->status_code,
+ pjsip_tsx_state_str(tsx->state)));
+
+ it = pj_hash_next(mod_tsx_layer.htable, it);
+ }
+ }
+ }
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+#endif
+}
+
+/*****************************************************************************
+ **
+ ** Transaction
+ **
+ *****************************************************************************
+ **/
+/*
+ * Lock transaction and set the value of Thread Local Storage.
+ */
+static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck)
+{
+ struct tsx_lock_data *prev_data;
+
+ pj_mutex_lock(tsx->mutex);
+ prev_data = (struct tsx_lock_data *)
+ pj_thread_local_get(pjsip_tsx_lock_tls_id);
+ lck->prev = prev_data;
+ lck->tsx = tsx;
+ lck->is_alive = 1;
+ pj_thread_local_set(pjsip_tsx_lock_tls_id, lck);
+}
+
+
+/*
+ * Unlock transaction.
+ * This will selectively unlock the mutex ONLY IF the transaction has not been
+ * destroyed. The function knows whether the transaction has been destroyed
+ * because when transaction is destroyed the is_alive flag for the transaction
+ * will be set to zero.
+ */
+static pj_status_t unlock_tsx( pjsip_transaction *tsx,
+ struct tsx_lock_data *lck)
+{
+ pj_assert( (void*)pj_thread_local_get(pjsip_tsx_lock_tls_id) == lck);
+ pj_assert( lck->tsx == tsx );
+ pj_thread_local_set(pjsip_tsx_lock_tls_id, lck->prev);
+ if (lck->is_alive)
+ pj_mutex_unlock(tsx->mutex);
+
+ return lck->is_alive ? PJ_SUCCESS : PJSIP_ETSXDESTROYED;
+}
+
+
+/* Lock transaction for accessing the timeout timer only. */
+static void lock_timer(pjsip_transaction *tsx)
+{
+ pj_mutex_lock(tsx->mutex_b);
+}
+
+/* Unlock timer */
+static void unlock_timer(pjsip_transaction *tsx)
+{
+ pj_mutex_unlock(tsx->mutex_b);
+}
+
+/* Create and initialize basic transaction structure.
+ * This function is called by both UAC and UAS creation.
+ */
+static pj_status_t tsx_create( pjsip_module *tsx_user,
+ pjsip_transaction **p_tsx)
+{
+ pj_pool_t *pool;
+ pjsip_transaction *tsx;
+ pj_status_t status;
+
+ pool = pjsip_endpt_create_pool( mod_tsx_layer.endpt, "tsx",
+ PJSIP_POOL_TSX_LEN, PJSIP_POOL_TSX_INC );
+ if (!pool)
+ return PJ_ENOMEM;
+
+ tsx = PJ_POOL_ZALLOC_T(pool, pjsip_transaction);
+ tsx->pool = pool;
+ tsx->tsx_user = tsx_user;
+ tsx->endpt = mod_tsx_layer.endpt;
+
+ pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name),
+ "tsx%p", tsx);
+ pj_memcpy(pool->obj_name, tsx->obj_name, sizeof(pool->obj_name));
+
+ tsx->handle_200resp = 1;
+ tsx->retransmit_timer.id = 0;
+ tsx->retransmit_timer.user_data = tsx;
+ tsx->retransmit_timer.cb = &tsx_timer_callback;
+ tsx->timeout_timer.id = 0;
+ tsx->timeout_timer.user_data = tsx;
+ tsx->timeout_timer.cb = &tsx_timer_callback;
+
+ status = pj_mutex_create_recursive(pool, tsx->obj_name, &tsx->mutex);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_release_pool(mod_tsx_layer.endpt, pool);
+ return status;
+ }
+
+ status = pj_mutex_create_simple(pool, tsx->obj_name, &tsx->mutex_b);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_destroy(tsx->mutex);
+ pjsip_endpt_release_pool(mod_tsx_layer.endpt, pool);
+ return status;
+ }
+
+ *p_tsx = tsx;
+ return PJ_SUCCESS;
+}
+
+
+/* Destroy transaction. */
+static pj_status_t tsx_destroy( pjsip_transaction *tsx )
+{
+ struct tsx_lock_data *lck;
+
+ /* Release the transport */
+ tsx_update_transport(tsx, NULL);
+
+ /* Decrement reference counter in transport selector */
+ pjsip_tpselector_dec_ref(&tsx->tp_sel);
+
+ /* Free last transmitted message. */
+ if (tsx->last_tx) {
+ pjsip_tx_data_dec_ref( tsx->last_tx );
+ tsx->last_tx = NULL;
+ }
+ /* Cancel timeout timer. */
+ if (tsx->timeout_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer.id = 0;
+ }
+ /* Cancel retransmission timer. */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* Clear some pending flags. */
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED | TSX_HAS_PENDING_SEND);
+
+ /* Refuse to destroy transaction if it has pending resolving. */
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_DESTROY;
+ tsx->tsx_user = NULL;
+ PJ_LOG(4,(tsx->obj_name, "Will destroy later because transport is "
+ "in progress"));
+ return PJ_EBUSY;
+ }
+
+ /* Clear TLS, so that mutex will not be unlocked */
+ lck = (struct tsx_lock_data*) pj_thread_local_get(pjsip_tsx_lock_tls_id);
+ while (lck) {
+ if (lck->tsx == tsx) {
+ lck->is_alive = 0;
+ }
+ lck = lck->prev;
+ }
+
+ pj_mutex_destroy(tsx->mutex_b);
+ pj_mutex_destroy(tsx->mutex);
+
+ PJ_LOG(5,(tsx->obj_name, "Transaction destroyed!"));
+
+ pjsip_endpt_release_pool(tsx->endpt, tsx->pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Callback when timer expires.
+ */
+static void tsx_timer_callback( pj_timer_heap_t *theap, pj_timer_entry *entry)
+{
+ pjsip_event event;
+ pjsip_transaction *tsx = (pjsip_transaction*) entry->user_data;
+ struct tsx_lock_data lck;
+
+ PJ_UNUSED_ARG(theap);
+
+ entry->id = 0;
+
+ PJ_LOG(5,(tsx->obj_name, "%s timer event",
+ (entry==&tsx->retransmit_timer ? "Retransmit":"Timeout")));
+ pj_log_push_indent();
+
+
+ PJSIP_EVENT_INIT_TIMER(event, entry);
+
+ /* Dispatch event to transaction. */
+ lock_tsx(tsx, &lck);
+ (*tsx->state_handler)(tsx, &event);
+ unlock_tsx(tsx, &lck);
+
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Set transaction state, and inform TU about the transaction state change.
+ */
+static void tsx_set_state( pjsip_transaction *tsx,
+ pjsip_tsx_state_e state,
+ pjsip_event_id_e event_src_type,
+ void *event_src )
+{
+ pjsip_tsx_state_e prev_state = tsx->state;
+
+ /* New state must be greater than previous state */
+ pj_assert(state >= tsx->state);
+
+ PJ_LOG(5, (tsx->obj_name, "State changed from %s to %s, event=%s",
+ state_str[tsx->state], state_str[state],
+ pjsip_event_str(event_src_type)));
+ pj_log_push_indent();
+
+ /* Change state. */
+ tsx->state = state;
+
+ /* Update the state handlers. */
+ if (tsx->role == PJSIP_ROLE_UAC) {
+ tsx->state_handler = tsx_state_handler_uac[state];
+ } else {
+ tsx->state_handler = tsx_state_handler_uas[state];
+ }
+
+ /* Before informing TU about state changed, inform TU about
+ * rx event.
+ */
+ if (event_src_type==PJSIP_EVENT_RX_MSG && tsx->tsx_user) {
+ pjsip_rx_data *rdata = (pjsip_rx_data*) event_src;
+
+ pj_assert(rdata != NULL);
+
+ if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG &&
+ tsx->tsx_user->on_rx_response)
+ {
+ (*tsx->tsx_user->on_rx_response)(rdata);
+ }
+
+ }
+
+ /* Inform TU about state changed. */
+ if (tsx->tsx_user && tsx->tsx_user->on_tsx_state) {
+ pjsip_event e;
+ PJSIP_EVENT_INIT_TSX_STATE(e, tsx, event_src_type, event_src,
+ prev_state);
+ (*tsx->tsx_user->on_tsx_state)(tsx, &e);
+ }
+
+
+ /* When the transaction is terminated, release transport, and free the
+ * saved last transmitted message.
+ */
+ if (state == PJSIP_TSX_STATE_TERMINATED) {
+ pj_time_val timeout = {0, 0};
+
+ /* If we're still waiting for a message to be sent.. */
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ /* Disassociate ourselves from the outstanding transmit data
+ * so that when the send callback is called we will be able
+ * to ignore that (otherwise we'll get assertion, see
+ * http://trac.pjsip.org/repos/ticket/1033)
+ */
+ if (tsx->pending_tx) {
+ tsx->pending_tx->mod_data[mod_tsx_layer.mod.id] = NULL;
+ tsx->pending_tx = NULL;
+ }
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT);
+ }
+
+ lock_timer(tsx);
+
+ /* Cancel timeout timer. */
+ if (tsx->timeout_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer.id = 0;
+ }
+
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+
+ unlock_timer(tsx);
+
+ } else if (state == PJSIP_TSX_STATE_DESTROYED) {
+
+ /* Unregister transaction. */
+ mod_tsx_layer_unregister_tsx(tsx);
+
+ /* Destroy transaction. */
+ tsx_destroy(tsx);
+ }
+
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Create, initialize, and register UAC transaction.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_create_uac( pjsip_module *tsx_user,
+ pjsip_tx_data *tdata,
+ pjsip_transaction **p_tsx)
+{
+ pjsip_transaction *tsx;
+ pjsip_msg *msg;
+ pjsip_cseq_hdr *cseq;
+ pjsip_via_hdr *via;
+ pjsip_host_info dst_info;
+ struct tsx_lock_data lck;
+ pj_status_t status;
+
+ /* Validate arguments. */
+ PJ_ASSERT_RETURN(tdata && tdata->msg && p_tsx, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Method MUST NOT be ACK! */
+ PJ_ASSERT_RETURN(tdata->msg->line.req.method.id != PJSIP_ACK_METHOD,
+ PJ_EINVALIDOP);
+
+ /* Keep shortcut */
+ msg = tdata->msg;
+
+ /* Make sure CSeq header is present. */
+ cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL);
+ if (!cseq) {
+ pj_assert(!"CSeq header not present in outgoing message!");
+ return PJSIP_EMISSINGHDR;
+ }
+
+
+ /* Create transaction instance. */
+ status = tsx_create( tsx_user, &tsx);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Lock transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Role is UAC. */
+ tsx->role = PJSIP_ROLE_UAC;
+
+ /* Save method. */
+ pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method);
+
+ /* Save CSeq. */
+ tsx->cseq = cseq->cseq;
+
+ /* Generate Via header if it doesn't exist. */
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_VIA, NULL);
+ if (via == NULL) {
+ via = pjsip_via_hdr_create(tdata->pool);
+ pjsip_msg_insert_first_hdr(msg, (pjsip_hdr*) via);
+ }
+
+ /* Generate branch parameter if it doesn't exist. */
+ if (via->branch_param.slen == 0) {
+ pj_str_t tmp;
+ via->branch_param.ptr = (char*)
+ pj_pool_alloc(tsx->pool, PJSIP_MAX_BRANCH_LEN);
+ via->branch_param.slen = PJSIP_MAX_BRANCH_LEN;
+ pj_memcpy(via->branch_param.ptr, PJSIP_RFC3261_BRANCH_ID,
+ PJSIP_RFC3261_BRANCH_LEN);
+ tmp.ptr = via->branch_param.ptr + PJSIP_RFC3261_BRANCH_LEN + 2;
+ *(tmp.ptr-2) = 80; *(tmp.ptr-1) = 106;
+ pj_generate_unique_string( &tmp );
+
+ /* Save branch parameter. */
+ tsx->branch = via->branch_param;
+
+ } else {
+ /* Copy branch parameter. */
+ pj_strdup(tsx->pool, &tsx->branch, &via->branch_param);
+ }
+
+ /* Generate transaction key. */
+ create_tsx_key_3261( tsx->pool, &tsx->transaction_key,
+ PJSIP_ROLE_UAC, &tsx->method,
+ &via->branch_param);
+
+ /* Calculate hashed key value. */
+#ifdef PRECALC_HASH
+ tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen);
+#endif
+
+ PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen,
+ tsx->transaction_key.ptr));
+
+ /* Begin with State_Null.
+ * Manually set-up the state becase we don't want to call the callback.
+ */
+ tsx->state = PJSIP_TSX_STATE_NULL;
+ tsx->state_handler = &tsx_on_state_null;
+
+ /* Save the message. */
+ tsx->last_tx = tdata;
+ pjsip_tx_data_add_ref(tsx->last_tx);
+
+ /* Determine whether reliable transport should be used initially.
+ * This will be updated whenever transport has changed.
+ */
+ status = pjsip_get_request_dest(tdata, &dst_info);
+ if (status != PJ_SUCCESS) {
+ unlock_tsx(tsx, &lck);
+ tsx_destroy(tsx);
+ return status;
+ }
+ tsx->is_reliable = (dst_info.flag & PJSIP_TRANSPORT_RELIABLE);
+
+ /* Register transaction to hash table. */
+ status = mod_tsx_layer_register_tsx(tsx);
+ if (status != PJ_SUCCESS) {
+ /* The assertion is removed by #1090:
+ pj_assert(!"Bug in branch_param generator (i.e. not unique)");
+ */
+ unlock_tsx(tsx, &lck);
+ tsx_destroy(tsx);
+ return status;
+ }
+
+
+ /* Unlock transaction and return. */
+ unlock_tsx(tsx, &lck);
+
+ pj_log_push_indent();
+ PJ_LOG(5,(tsx->obj_name, "Transaction created for %s",
+ pjsip_tx_data_get_info(tdata)));
+ pj_log_pop_indent();
+
+ *p_tsx = tsx;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create, initialize, and register UAS transaction.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_create_uas( pjsip_module *tsx_user,
+ pjsip_rx_data *rdata,
+ pjsip_transaction **p_tsx)
+{
+ pjsip_transaction *tsx;
+ pjsip_msg *msg;
+ pj_str_t *branch;
+ pjsip_cseq_hdr *cseq;
+ pj_status_t status;
+ struct tsx_lock_data lck;
+
+ /* Validate arguments. */
+ PJ_ASSERT_RETURN(rdata && rdata->msg_info.msg && p_tsx, PJ_EINVAL);
+
+ /* Keep shortcut to message */
+ msg = rdata->msg_info.msg;
+
+ /* Make sure this is a request message. */
+ PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG);
+
+ /* Make sure method is not ACK */
+ PJ_ASSERT_RETURN(msg->line.req.method.id != PJSIP_ACK_METHOD,
+ PJ_EINVALIDOP);
+
+ /* Make sure CSeq header is present. */
+ cseq = rdata->msg_info.cseq;
+ if (!cseq)
+ return PJSIP_EMISSINGHDR;
+
+ /* Make sure Via header is present. */
+ if (rdata->msg_info.via == NULL)
+ return PJSIP_EMISSINGHDR;
+
+ /* Check that method in CSeq header match request method.
+ * Reference: PROTOS #1922
+ */
+ if (pjsip_method_cmp(&msg->line.req.method,
+ &rdata->msg_info.cseq->method) != 0)
+ {
+ PJ_LOG(4,(THIS_FILE, "Error: CSeq header contains different "
+ "method than the request line"));
+ return PJSIP_EINVALIDHDR;
+ }
+
+ /*
+ * Create transaction instance.
+ */
+ status = tsx_create( tsx_user, &tsx);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Lock transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Role is UAS */
+ tsx->role = PJSIP_ROLE_UAS;
+
+ /* Save method. */
+ pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method);
+
+ /* Save CSeq */
+ tsx->cseq = cseq->cseq;
+
+ /* Get transaction key either from branch for RFC3261 message, or
+ * create transaction key.
+ */
+ status = pjsip_tsx_create_key(tsx->pool, &tsx->transaction_key,
+ PJSIP_ROLE_UAS, &tsx->method, rdata);
+ if (status != PJ_SUCCESS) {
+ unlock_tsx(tsx, &lck);
+ tsx_destroy(tsx);
+ return status;
+ }
+
+ /* Calculate hashed key value. */
+#ifdef PRECALC_HASH
+ tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen);
+#endif
+
+ /* Duplicate branch parameter for transaction. */
+ branch = &rdata->msg_info.via->branch_param;
+ pj_strdup(tsx->pool, &tsx->branch, branch);
+
+ PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen,
+ tsx->transaction_key.ptr));
+
+
+ /* Begin with state NULL.
+ * Manually set-up the state becase we don't want to call the callback.
+ */
+ tsx->state = PJSIP_TSX_STATE_NULL;
+ tsx->state_handler = &tsx_on_state_null;
+
+ /* Get response address. */
+ status = pjsip_get_response_addr( tsx->pool, rdata, &tsx->res_addr );
+ if (status != PJ_SUCCESS) {
+ unlock_tsx(tsx, &lck);
+ tsx_destroy(tsx);
+ return status;
+ }
+
+ /* If it's decided that we should use current transport, keep the
+ * transport.
+ */
+ if (tsx->res_addr.transport) {
+ tsx_update_transport(tsx, tsx->res_addr.transport);
+ pj_memcpy(&tsx->addr, &tsx->res_addr.addr, tsx->res_addr.addr_len);
+ tsx->addr_len = tsx->res_addr.addr_len;
+ tsx->is_reliable = PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport);
+ } else {
+ tsx->is_reliable =
+ (tsx->res_addr.dst_host.flag & PJSIP_TRANSPORT_RELIABLE);
+ }
+
+
+ /* Register the transaction. */
+ status = mod_tsx_layer_register_tsx(tsx);
+ if (status != PJ_SUCCESS) {
+ unlock_tsx(tsx, &lck);
+ tsx_destroy(tsx);
+ return status;
+ }
+
+ /* Put this transaction in rdata's mod_data. */
+ rdata->endpt_info.mod_data[mod_tsx_layer.mod.id] = tsx;
+
+ /* Unlock transaction and return. */
+ unlock_tsx(tsx, &lck);
+
+ pj_log_push_indent();
+ PJ_LOG(5,(tsx->obj_name, "Transaction created for %s",
+ pjsip_rx_data_get_info(rdata)));
+ pj_log_pop_indent();
+
+
+ *p_tsx = tsx;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Bind transaction to a specific transport/listener.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_set_transport(pjsip_transaction *tsx,
+ const pjsip_tpselector *sel)
+{
+ struct tsx_lock_data lck;
+
+ /* Must be UAC transaction */
+ PJ_ASSERT_RETURN(tsx && sel, PJ_EINVAL);
+
+ /* Start locking the transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Decrement reference counter of previous transport selector */
+ pjsip_tpselector_dec_ref(&tsx->tp_sel);
+
+ /* Copy transport selector structure .*/
+ pj_memcpy(&tsx->tp_sel, sel, sizeof(*sel));
+
+ /* Increment reference counter */
+ pjsip_tpselector_add_ref(&tsx->tp_sel);
+
+ /* Unlock transaction. */
+ unlock_tsx(tsx, &lck);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set transaction status code and reason.
+ */
+static void tsx_set_status_code(pjsip_transaction *tsx,
+ int code, const pj_str_t *reason)
+{
+ tsx->status_code = code;
+ if (reason)
+ pj_strdup(tsx->pool, &tsx->status_text, reason);
+ else
+ tsx->status_text = *pjsip_get_status_text(code);
+}
+
+
+/*
+ * Forcely terminate transaction.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_terminate( pjsip_transaction *tsx, int code )
+{
+ struct tsx_lock_data lck;
+
+ PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL);
+
+ PJ_LOG(5,(tsx->obj_name, "Request to terminate transaction"));
+
+ PJ_ASSERT_RETURN(code >= 200, PJ_EINVAL);
+
+ if (tsx->state >= PJSIP_TSX_STATE_TERMINATED)
+ return PJ_SUCCESS;
+
+ pj_log_push_indent();
+
+ lock_tsx(tsx, &lck);
+ tsx_set_status_code(tsx, code, NULL);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, PJSIP_EVENT_USER, NULL);
+ unlock_tsx(tsx, &lck);
+
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Cease retransmission on the UAC transaction. The UAC transaction is
+ * still considered running, and it will complete when either final
+ * response is received or the transaction times out.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_stop_retransmit(pjsip_transaction *tsx)
+{
+ struct tsx_lock_data lck;
+
+ PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tsx->role == PJSIP_ROLE_UAC &&
+ tsx->method.id == PJSIP_INVITE_METHOD,
+ PJ_EINVALIDOP);
+
+ PJ_LOG(5,(tsx->obj_name, "Request to stop retransmission"));
+
+ pj_log_push_indent();
+
+ lock_tsx(tsx, &lck);
+ /* Cancel retransmission timer. */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+ unlock_tsx(tsx, &lck);
+
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Start a timer to terminate transaction after the specified time
+ * has elapsed.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_set_timeout( pjsip_transaction *tsx,
+ unsigned millisec)
+{
+ pj_time_val timeout;
+
+ PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tsx->role == PJSIP_ROLE_UAC &&
+ tsx->method.id == PJSIP_INVITE_METHOD,
+ PJ_EINVALIDOP);
+
+ /* Note: must not call lock_tsx() as that would introduce deadlock.
+ * See #1121.
+ */
+ lock_timer(tsx);
+
+ /* Transaction should normally not have final response, but as
+ * #1121 says there is a (tolerable) window of race condition
+ * where this might happen.
+ */
+ if (tsx->status_code >= 200 && tsx->timeout_timer.id != 0) {
+ /* Timeout is already set */
+ unlock_timer(tsx);
+ return PJ_EEXISTS;
+ }
+
+ if (tsx->timeout_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer.id = 0;
+ }
+
+ timeout.sec = 0;
+ timeout.msec = millisec;
+ pj_time_val_normalize(&timeout);
+
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+
+
+ unlock_timer(tsx);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This function is called by TU to send a message.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_send_msg( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata )
+{
+ pjsip_event event;
+ struct tsx_lock_data lck;
+ pj_status_t status;
+
+ if (tdata == NULL)
+ tdata = tsx->last_tx;
+
+ PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP);
+
+ PJ_LOG(5,(tsx->obj_name, "Sending %s in state %s",
+ pjsip_tx_data_get_info(tdata),
+ state_str[tsx->state]));
+ pj_log_push_indent();
+
+ PJSIP_EVENT_INIT_TX_MSG(event, tdata);
+
+ /* Dispatch to transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Set transport selector to tdata */
+ pjsip_tx_data_set_transport(tdata, &tsx->tp_sel);
+
+ /* Dispatch to state handler */
+ status = (*tsx->state_handler)(tsx, &event);
+
+ unlock_tsx(tsx, &lck);
+
+ /* Only decrement reference counter when it returns success.
+ * (This is the specification from the .PDF design document).
+ */
+ if (status == PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ }
+
+ pj_log_pop_indent();
+
+ return status;
+}
+
+
+/*
+ * This function is called by endpoint when incoming message for the
+ * transaction is received.
+ */
+PJ_DEF(void) pjsip_tsx_recv_msg( pjsip_transaction *tsx,
+ pjsip_rx_data *rdata)
+{
+ pjsip_event event;
+ struct tsx_lock_data lck;
+ pj_status_t status;
+
+ PJ_LOG(5,(tsx->obj_name, "Incoming %s in state %s",
+ pjsip_rx_data_get_info(rdata), state_str[tsx->state]));
+ pj_log_push_indent();
+
+ /* Put the transaction in the rdata's mod_data. */
+ rdata->endpt_info.mod_data[mod_tsx_layer.mod.id] = tsx;
+
+ /* Init event. */
+ PJSIP_EVENT_INIT_RX_MSG(event, rdata);
+
+ /* Dispatch to transaction. */
+ lock_tsx(tsx, &lck);
+ status = (*tsx->state_handler)(tsx, &event);
+ unlock_tsx(tsx, &lck);
+
+ pj_log_pop_indent();
+}
+
+
+/* Callback called by send message framework */
+static void send_msg_callback( pjsip_send_state *send_state,
+ pj_ssize_t sent, pj_bool_t *cont )
+{
+ pjsip_transaction *tsx = (pjsip_transaction*) send_state->token;
+ pjsip_tx_data *tdata = send_state->tdata;
+ struct tsx_lock_data lck;
+
+ /* Check if transaction has cancelled itself from this transmit
+ * notification (https://trac.pjsip.org/repos/ticket/1033).
+ * Also check if the transaction layer itself may have been shutdown
+ * (https://trac.pjsip.org/repos/ticket/1535)
+ */
+ if (mod_tsx_layer.mod.id < 0 ||
+ tdata->mod_data[mod_tsx_layer.mod.id] == NULL)
+ {
+ *cont = PJ_FALSE;
+ return;
+ }
+
+ /* Reset */
+ tdata->mod_data[mod_tsx_layer.mod.id] = NULL;
+ tsx->pending_tx = NULL;
+
+ lock_tsx(tsx, &lck);
+
+ if (sent > 0) {
+ /* Successfully sent! */
+ pj_assert(send_state->cur_transport != NULL);
+
+ if (tsx->transport != send_state->cur_transport) {
+ /* Update transport. */
+ tsx_update_transport(tsx, send_state->cur_transport);
+
+ /* Update remote address. */
+ tsx->addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len;
+ pj_memcpy(&tsx->addr,
+ &tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr,
+ tsx->addr_len);
+
+ /* Update is_reliable flag. */
+ tsx->is_reliable = PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport);
+ }
+
+ /* Clear pending transport flag. */
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT);
+
+ /* Mark that we have resolved the addresses. */
+ tsx->transport_flag |= TSX_HAS_RESOLVED_SERVER;
+
+ /* Pending destroy? */
+ if (tsx->transport_flag & TSX_HAS_PENDING_DESTROY) {
+ tsx_set_state( tsx, PJSIP_TSX_STATE_DESTROYED,
+ PJSIP_EVENT_UNKNOWN, NULL );
+ unlock_tsx(tsx, &lck);
+ return;
+ }
+
+ /* Need to transmit a message? */
+ if (tsx->transport_flag & TSX_HAS_PENDING_SEND) {
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_SEND);
+ tsx_send_msg(tsx, tsx->last_tx);
+ }
+
+ /* Need to reschedule retransmission? */
+ if (tsx->transport_flag & TSX_HAS_PENDING_RESCHED) {
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED);
+
+ /* Only update when transport turns out to be unreliable. */
+ if (!tsx->is_reliable) {
+ tsx_resched_retransmission(tsx);
+ }
+ }
+
+ } else {
+ /* Failed to send! */
+ pj_assert(sent != 0);
+
+ /* If transaction is using the same transport as the failed one,
+ * release the transport.
+ */
+ if (send_state->cur_transport==tsx->transport)
+ tsx_update_transport(tsx, NULL);
+
+ /* Also stop processing if transaction has been flagged with
+ * pending destroy (http://trac.pjsip.org/repos/ticket/906)
+ */
+ if ((!*cont) || (tsx->transport_flag & TSX_HAS_PENDING_DESTROY)) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pjsip_status_code sc;
+ pj_str_t err;
+
+ tsx->transport_err = -sent;
+
+ err =pj_strerror(-sent, errmsg, sizeof(errmsg));
+
+ PJ_LOG(2,(tsx->obj_name,
+ "Failed to send %s! err=%d (%s)",
+ pjsip_tx_data_get_info(send_state->tdata), -sent,
+ errmsg));
+
+ /* Clear pending transport flag. */
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT);
+
+ /* Mark that we have resolved the addresses. */
+ tsx->transport_flag |= TSX_HAS_RESOLVED_SERVER;
+
+ /* Server resolution error is now mapped to 502 instead of 503,
+ * since with 503 normally client should try again.
+ * See http://trac.pjsip.org/repos/ticket/870
+ */
+ if (-sent==PJ_ERESOLVE || -sent==PJLIB_UTIL_EDNS_NXDOMAIN)
+ sc = PJSIP_SC_BAD_GATEWAY;
+ else
+ sc = PJSIP_SC_TSX_TRANSPORT_ERROR;
+
+ /* Terminate transaction, if it's not already terminated. */
+ tsx_set_status_code(tsx, sc, &err);
+ if (tsx->state != PJSIP_TSX_STATE_TERMINATED &&
+ tsx->state != PJSIP_TSX_STATE_DESTROYED)
+ {
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, send_state->tdata);
+ }
+ /* Don't forget to destroy if we have pending destroy flag
+ * (http://trac.pjsip.org/repos/ticket/906)
+ */
+ else if (tsx->transport_flag & TSX_HAS_PENDING_DESTROY)
+ {
+ tsx_set_state( tsx, PJSIP_TSX_STATE_DESTROYED,
+ PJSIP_EVENT_TRANSPORT_ERROR, send_state->tdata);
+ }
+
+ } else {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ PJ_LOG(2,(tsx->obj_name,
+ "Temporary failure in sending %s, "
+ "will try next server. Err=%d (%s)",
+ pjsip_tx_data_get_info(send_state->tdata), -sent,
+ pj_strerror(-sent, errmsg, sizeof(errmsg)).ptr));
+
+ /* Reset retransmission count */
+ tsx->retransmit_count = 0;
+
+ /* And reset timeout timer */
+ if (tsx->timeout_timer.id) {
+ lock_timer(tsx);
+
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer.id = TIMER_INACTIVE;
+
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout_timer_val);
+
+ unlock_timer(tsx);
+ }
+
+ /* Put again pending tdata */
+ tdata->mod_data[mod_tsx_layer.mod.id] = tsx;
+ tsx->pending_tx = tdata;
+ }
+ }
+
+ unlock_tsx(tsx, &lck);
+}
+
+
+/* Transport callback. */
+static void transport_callback(void *token, pjsip_tx_data *tdata,
+ pj_ssize_t sent)
+{
+ if (sent < 0) {
+ pjsip_transaction *tsx = (pjsip_transaction*) token;
+ struct tsx_lock_data lck;
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t err;
+
+ tsx->transport_err = -sent;
+
+ err = pj_strerror(-sent, errmsg, sizeof(errmsg));
+
+ PJ_LOG(2,(tsx->obj_name, "Transport failed to send %s! Err=%d (%s)",
+ pjsip_tx_data_get_info(tdata), -sent, errmsg));
+
+ lock_tsx(tsx, &lck);
+
+ /* Release transport. */
+ tsx_update_transport(tsx, NULL);
+
+ /* Terminate transaction. */
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, tdata );
+
+ unlock_tsx(tsx, &lck);
+ }
+}
+
+
+/*
+ * Callback when transport state changes.
+ */
+static void tsx_tp_state_callback( pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info)
+{
+ PJ_UNUSED_ARG(tp);
+
+ if (state == PJSIP_TP_STATE_DISCONNECTED) {
+ pjsip_transaction *tsx;
+ struct tsx_lock_data lck;
+
+ pj_assert(tp && info && info->user_data);
+
+ tsx = (pjsip_transaction*)info->user_data;
+
+ lock_tsx(tsx, &lck);
+
+ /* Terminate transaction when transport disconnected */
+ if (tsx->state < PJSIP_TSX_STATE_TERMINATED) {
+ pj_str_t err;
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ err = pj_strerror(info->status, errmsg, sizeof(errmsg));
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, NULL);
+ }
+
+ unlock_tsx(tsx, &lck);
+ }
+}
+
+
+/*
+ * Send message to the transport.
+ */
+static pj_status_t tsx_send_msg( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(tsx && tdata, PJ_EINVAL);
+
+ /* Send later if transport is still pending. */
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_SEND;
+ return PJ_SUCCESS;
+ }
+
+ /* If we have the transport, send the message using that transport.
+ * Otherwise perform full transport resolution.
+ */
+ if (tsx->transport) {
+ status = pjsip_transport_send( tsx->transport, tdata, &tsx->addr,
+ tsx->addr_len, tsx,
+ &transport_callback);
+ if (status == PJ_EPENDING)
+ status = PJ_SUCCESS;
+
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ PJ_LOG(2,(tsx->obj_name,
+ "Error sending %s: Err=%d (%s)",
+ pjsip_tx_data_get_info(tdata), status,
+ pj_strerror(status, errmsg, sizeof(errmsg)).ptr));
+
+ /* On error, release transport to force using full transport
+ * resolution procedure.
+ */
+ tsx_update_transport(tsx, NULL);
+
+ tsx->addr_len = 0;
+ tsx->res_addr.transport = NULL;
+ tsx->res_addr.addr_len = 0;
+ } else {
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* We are here because we don't have transport, or we failed to send
+ * the message using existing transport. If we haven't resolved the
+ * server before, then begin the long process of resolving the server
+ * and send the message with possibly new server.
+ */
+ pj_assert(status != PJ_SUCCESS || tsx->transport == NULL);
+
+ /* If we have resolved the server, we treat the error as permanent error.
+ * Terminate transaction with transport error failure.
+ */
+ if (tsx->transport_flag & TSX_HAS_RESOLVED_SERVER) {
+
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t err;
+
+ if (status == PJ_SUCCESS) {
+ pj_assert(!"Unexpected status!");
+ status = PJ_EUNKNOWN;
+ }
+
+ /* We have resolved the server!.
+ * Treat this as permanent transport error.
+ */
+ err = pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(2,(tsx->obj_name,
+ "Transport error, terminating transaction. "
+ "Err=%d (%s)",
+ status, errmsg));
+
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, NULL );
+
+ return status;
+ }
+
+ /* Must add reference counter because the send request functions
+ * decrement the reference counter.
+ */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Also attach ourselves to the transmit data so that we'll be able
+ * to unregister ourselves from the send notification of this
+ * transmit data.
+ */
+ tdata->mod_data[mod_tsx_layer.mod.id] = tsx;
+ tsx->pending_tx = tdata;
+
+ /* Begin resolving destination etc to send the message. */
+ if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+
+ tsx->transport_flag |= TSX_HAS_PENDING_TRANSPORT;
+ status = pjsip_endpt_send_request_stateless(tsx->endpt, tdata, tsx,
+ &send_msg_callback);
+ if (status == PJ_EPENDING)
+ status = PJ_SUCCESS;
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ tdata->mod_data[mod_tsx_layer.mod.id] = NULL;
+ tsx->pending_tx = NULL;
+ }
+
+ /* Check if transaction is terminated. */
+ if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TERMINATED)
+ status = tsx->transport_err;
+
+ } else {
+
+ tsx->transport_flag |= TSX_HAS_PENDING_TRANSPORT;
+ status = pjsip_endpt_send_response( tsx->endpt, &tsx->res_addr,
+ tdata, tsx,
+ &send_msg_callback);
+ if (status == PJ_EPENDING)
+ status = PJ_SUCCESS;
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ tdata->mod_data[mod_tsx_layer.mod.id] = NULL;
+ tsx->pending_tx = NULL;
+ }
+
+ /* Check if transaction is terminated. */
+ if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TERMINATED)
+ status = tsx->transport_err;
+
+ }
+
+
+ return status;
+}
+
+
+/*
+ * Manually retransmit the last messagewithout updating the transaction state.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_retransmit_no_state(pjsip_transaction *tsx,
+ pjsip_tx_data *tdata)
+{
+ struct tsx_lock_data lck;
+ pj_status_t status;
+
+ lock_tsx(tsx, &lck);
+ if (tdata == NULL) {
+ tdata = tsx->last_tx;
+ }
+ status = tsx_send_msg(tsx, tdata);
+ unlock_tsx(tsx, &lck);
+
+ /* Only decrement reference counter when it returns success.
+ * (This is the specification from the .PDF design document).
+ */
+ if (status == PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ }
+
+ return status;
+}
+
+
+/*
+ * Retransmit last message sent.
+ */
+static void tsx_resched_retransmission( pjsip_transaction *tsx )
+{
+ pj_uint32_t msec_time;
+
+ pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0);
+
+ if (tsx->role==PJSIP_ROLE_UAC && tsx->status_code >= 100)
+ msec_time = pjsip_cfg()->tsx.t2;
+ else
+ msec_time = (1 << (tsx->retransmit_count)) * pjsip_cfg()->tsx.t1;
+
+ if (tsx->role == PJSIP_ROLE_UAC) {
+ pj_assert(tsx->status_code < 200);
+ /* Retransmission for non-INVITE transaction caps-off at T2 */
+ if (msec_time > pjsip_cfg()->tsx.t2 &&
+ tsx->method.id != PJSIP_INVITE_METHOD)
+ {
+ msec_time = pjsip_cfg()->tsx.t2;
+ }
+ } else {
+ /* For UAS, this can be retransmission of 2xx response for INVITE
+ * or non-100 1xx response.
+ */
+ if (tsx->status_code < 200) {
+ /* non-100 1xx retransmission is at 60 seconds */
+ msec_time = PJSIP_TSX_1XX_RETRANS_DELAY * 1000;
+ } else {
+ /* Retransmission of INVITE final response also caps-off at T2 */
+ pj_assert(tsx->status_code >= 200);
+ if (msec_time > pjsip_cfg()->tsx.t2)
+ msec_time = pjsip_cfg()->tsx.t2;
+ }
+ }
+
+ if (msec_time != 0) {
+ pj_time_val timeout;
+
+ timeout.sec = msec_time / 1000;
+ timeout.msec = msec_time % 1000;
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->retransmit_timer,
+ &timeout);
+ }
+}
+
+/*
+ * Retransmit last message sent.
+ */
+static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tsx->last_tx!=NULL, PJ_EBUG);
+
+ PJ_LOG(5,(tsx->obj_name, "Retransmiting %s, count=%d, restart?=%d",
+ pjsip_tx_data_get_info(tsx->last_tx),
+ tsx->retransmit_count, resched));
+
+ ++tsx->retransmit_count;
+
+ /* Restart timer T1 first before sending the message to ensure that
+ * retransmission timer is not engaged when loop transport is used.
+ */
+ if (resched) {
+ pj_assert(tsx->state != PJSIP_TSX_STATE_CONFIRMED);
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
+ } else {
+ tsx_resched_retransmission(tsx);
+ }
+ }
+
+ status = tsx_send_msg( tsx, tsx->last_tx);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static void tsx_update_transport( pjsip_transaction *tsx,
+ pjsip_transport *tp)
+{
+ pj_assert(tsx);
+
+ if (tsx->transport) {
+ pjsip_transport_remove_state_listener(tsx->transport,
+ tsx->tp_st_key, tsx);
+ pjsip_transport_dec_ref( tsx->transport );
+ tsx->transport = NULL;
+ }
+
+ if (tp) {
+ tsx->transport = tp;
+ pjsip_transport_add_ref(tp);
+ pjsip_transport_add_state_listener(tp, &tsx_tp_state_callback, tsx,
+ &tsx->tp_st_key);
+ }
+}
+
+
+/*
+ * Handler for events in state Null.
+ */
+static pj_status_t tsx_on_state_null( pjsip_transaction *tsx,
+ pjsip_event *event )
+{
+ pj_status_t status;
+
+ pj_assert(tsx->state == PJSIP_TSX_STATE_NULL);
+
+ if (tsx->role == PJSIP_ROLE_UAS) {
+
+ /* Set state to Trying. */
+ pj_assert(event->type == PJSIP_EVENT_RX_MSG &&
+ event->body.rx_msg.rdata->msg_info.msg->type ==
+ PJSIP_REQUEST_MSG);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TRYING, PJSIP_EVENT_RX_MSG,
+ event->body.rx_msg.rdata);
+
+ } else {
+ pjsip_tx_data *tdata;
+
+ /* Must be transmit event.
+ * You may got this assertion when using loop transport with delay
+ * set to zero. That would cause on_rx_response() callback to be
+ * called before tsx_send_msg() has completed.
+ */
+ PJ_ASSERT_RETURN(event->type == PJSIP_EVENT_TX_MSG, PJ_EBUG);
+
+ /* Get the txdata */
+ tdata = event->body.tx_msg.tdata;
+
+ /* Save the message for retransmission. */
+ if (tsx->last_tx && tsx->last_tx != tdata) {
+ pjsip_tx_data_dec_ref(tsx->last_tx);
+ tsx->last_tx = NULL;
+ }
+ if (tsx->last_tx != tdata) {
+ tsx->last_tx = tdata;
+ pjsip_tx_data_add_ref(tdata);
+ }
+
+ /* Send the message. */
+ status = tsx_send_msg( tsx, tdata);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Start Timer B (or called timer F for non-INVITE) for transaction
+ * timeout.
+ */
+ lock_timer(tsx);
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout_timer_val);
+ unlock_timer(tsx);
+
+ /* Start Timer A (or timer E) for retransmission only if unreliable
+ * transport is being used.
+ */
+ if (!tsx->is_reliable) {
+ tsx->retransmit_count = 0;
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
+ } else {
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt, &tsx->retransmit_timer,
+ &t1_timer_val);
+ }
+ }
+
+ /* Move state. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_CALLING,
+ PJSIP_EVENT_TX_MSG, tdata);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * State Calling is for UAC after it sends request but before any responses
+ * is received.
+ */
+static pj_status_t tsx_on_state_calling( pjsip_transaction *tsx,
+ pjsip_event *event )
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_CALLING);
+ pj_assert(tsx->role == PJSIP_ROLE_UAC);
+
+ if (event->type == PJSIP_EVENT_TIMER &&
+ event->body.timer.entry == &tsx->retransmit_timer)
+ {
+ pj_status_t status;
+
+ /* Retransmit the request. */
+ status = tsx_retransmit( tsx, 1 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ } else if (event->type == PJSIP_EVENT_TIMER &&
+ event->body.timer.entry == &tsx->timeout_timer)
+ {
+
+ /* Cancel retransmission timer. */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED);
+
+ /* Set status code */
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL);
+
+ /* Inform TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer);
+
+ /* Transaction is destroyed */
+ //return PJSIP_ETSXDESTROYED;
+
+ } else if (event->type == PJSIP_EVENT_RX_MSG) {
+ pjsip_msg *msg;
+ int code;
+
+ /* Get message instance */
+ msg = event->body.rx_msg.rdata->msg_info.msg;
+
+ /* Better be a response message. */
+ if (msg->type != PJSIP_RESPONSE_MSG)
+ return PJSIP_ENOTRESPONSEMSG;
+
+ code = msg->line.status.code;
+
+ /* If the response is final, cancel both retransmission and timeout
+ * timer.
+ */
+ if (code >= 200) {
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ if (tsx->timeout_timer.id != 0) {
+ lock_timer(tsx);
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer.id = 0;
+ unlock_timer(tsx);
+ }
+
+ } else {
+ /* Cancel retransmit timer (for non-INVITE transaction, the
+ * retransmit timer will be rescheduled at T2.
+ */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* For provisional response, only cancel retransmit when this
+ * is an INVITE transaction. For non-INVITE, section 17.1.2.1
+ * of RFC 3261 says that:
+ * - retransmit timer is set to T2
+ * - timeout timer F is not deleted.
+ */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+ /* Cancel timeout timer */
+ lock_timer(tsx);
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ unlock_timer(tsx);
+
+ } else {
+ if (!tsx->is_reliable) {
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt,
+ &tsx->retransmit_timer,
+ &t2_timer_val);
+ }
+ }
+ }
+
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED);
+
+
+ /* Discard retransmission message if it is not INVITE.
+ * The INVITE tdata is needed in case we have to generate ACK for
+ * the final response.
+ */
+ /* Keep last_tx for authorization. */
+ //blp: always keep last_tx until transaction is destroyed
+ //code = msg->line.status.code;
+ //if (tsx->method.id != PJSIP_INVITE_METHOD && code!=401 && code!=407) {
+ // pjsip_tx_data_dec_ref(tsx->last_tx);
+ // tsx->last_tx = NULL;
+ //}
+
+ /* Processing is similar to state Proceeding. */
+ tsx_on_state_proceeding_uac( tsx, event);
+
+ } else {
+ pj_assert(!"Unexpected event");
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * State Trying is for UAS after it received request but before any responses
+ * is sent.
+ * Note: this is different than RFC3261, which can use Trying state for
+ * non-INVITE client transaction (bug in RFC?).
+ */
+static pj_status_t tsx_on_state_trying( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_status_t status;
+
+ pj_assert(tsx->state == PJSIP_TSX_STATE_TRYING);
+
+ /* This state is only for UAS */
+ pj_assert(tsx->role == PJSIP_ROLE_UAS);
+
+ /* Better be transmission of response message.
+ * If we've got request retransmission, this means that the TU hasn't
+ * transmitted any responses within 500 ms, which is not allowed. If
+ * this happens, just ignore the event (we couldn't retransmit last
+ * response because we haven't sent any!).
+ */
+ if (event->type != PJSIP_EVENT_TX_MSG) {
+ return PJ_SUCCESS;
+ }
+
+ /* The rest of the processing of the event is exactly the same as in
+ * "Proceeding" state.
+ */
+ status = tsx_on_state_proceeding_uas( tsx, event);
+
+ /* Inform the TU of the state transision if state is still State_Trying */
+ if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TRYING) {
+
+ tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING,
+ PJSIP_EVENT_TX_MSG, event->body.tx_msg.tdata);
+
+ }
+
+ return status;
+}
+
+
+/*
+ * Handler for events in Proceeding for UAS
+ * This state happens after the TU sends provisional response.
+ */
+static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_PROCEEDING ||
+ tsx->state == PJSIP_TSX_STATE_TRYING);
+
+ /* This state is only for UAS. */
+ pj_assert(tsx->role == PJSIP_ROLE_UAS);
+
+ /* Receive request retransmission. */
+ if (event->type == PJSIP_EVENT_RX_MSG) {
+
+ pj_status_t status;
+
+ /* Must have last response sent. */
+ PJ_ASSERT_RETURN(tsx->last_tx != NULL, PJ_EBUG);
+
+ /* Send last response */
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_SEND;
+ } else {
+ status = tsx_send_msg(tsx, tsx->last_tx);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ } else if (event->type == PJSIP_EVENT_TX_MSG ) {
+ pjsip_tx_data *tdata = event->body.tx_msg.tdata;
+ pj_status_t status;
+
+ /* The TU sends response message to the request. Save this message so
+ * that we can retransmit the last response in case we receive request
+ * retransmission.
+ */
+ pjsip_msg *msg = tdata->msg;
+
+ /* This can only be a response message. */
+ PJ_ASSERT_RETURN(msg->type==PJSIP_RESPONSE_MSG, PJSIP_ENOTRESPONSEMSG);
+
+ /* Update last status */
+ tsx_set_status_code(tsx, msg->line.status.code,
+ &msg->line.status.reason);
+
+ /* Discard the saved last response (it will be updated later as
+ * necessary).
+ */
+ if (tsx->last_tx && tsx->last_tx != tdata) {
+ pjsip_tx_data_dec_ref( tsx->last_tx );
+ tsx->last_tx = NULL;
+ }
+
+ /* Send the message. */
+ status = tsx_send_msg(tsx, tdata);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ // Update To tag header for RFC2543 transaction.
+ // TODO:
+
+ /* Update transaction state */
+ if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 100)) {
+
+ if (tsx->last_tx != tdata) {
+ tsx->last_tx = tdata;
+ pjsip_tx_data_add_ref( tdata );
+ }
+
+ tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING,
+ PJSIP_EVENT_TX_MSG, tdata );
+
+ /* Retransmit provisional response every 1 minute if this is
+ * an INVITE provisional response greater than 100.
+ */
+ if (PJSIP_TSX_1XX_RETRANS_DELAY > 0 &&
+ tsx->method.id==PJSIP_INVITE_METHOD && tsx->status_code>100)
+ {
+
+ /* Stop 1xx retransmission timer, if any */
+ if (tsx->retransmit_timer.id) {
+ pjsip_endpt_cancel_timer(tsx->endpt,
+ &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* Schedule retransmission */
+ tsx->retransmit_count = 0;
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
+ } else {
+ pj_time_val delay = {PJSIP_TSX_1XX_RETRANS_DELAY, 0};
+
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt,
+ &tsx->retransmit_timer,
+ &delay);
+ }
+ }
+
+ } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
+
+ /* Stop 1xx retransmission timer, if any */
+ if (tsx->retransmit_timer.id) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->handle_200resp==0) {
+
+ /* 2xx class message is not saved, because retransmission
+ * is handled by TU.
+ */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TX_MSG, tdata );
+
+ /* Transaction is destroyed. */
+ //return PJSIP_ETSXDESTROYED;
+
+ } else {
+ pj_time_val timeout;
+
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ tsx->retransmit_count = 0;
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
+ } else {
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt,
+ &tsx->retransmit_timer,
+ &t1_timer_val);
+ }
+ }
+
+ /* Save last response sent for retransmission when request
+ * retransmission is received.
+ */
+ if (tsx->last_tx != tdata) {
+ tsx->last_tx = tdata;
+ pjsip_tx_data_add_ref(tdata);
+ }
+
+ /* Setup timeout timer: */
+
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+ /* Start Timer H at 64*T1 for INVITE server transaction,
+ * regardless of transport.
+ */
+ timeout = timeout_timer_val;
+
+ } else if (!tsx->is_reliable) {
+
+ /* For non-INVITE, start timer J at 64*T1 for unreliable
+ * transport.
+ */
+ timeout = timeout_timer_val;
+
+ } else {
+
+ /* Transaction terminates immediately for non-INVITE when
+ * reliable transport is used.
+ */
+ timeout.sec = timeout.msec = 0;
+ }
+
+ lock_timer(tsx);
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+ unlock_timer(tsx);
+
+ /* Set state to "Completed" */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_TX_MSG, tdata );
+ }
+
+ } else if (tsx->status_code >= 300) {
+
+ /* Stop 1xx retransmission timer, if any */
+ if (tsx->retransmit_timer.id) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* 3xx-6xx class message causes transaction to move to
+ * "Completed" state.
+ */
+ if (tsx->last_tx != tdata) {
+ tsx->last_tx = tdata;
+ pjsip_tx_data_add_ref( tdata );
+ }
+
+ /* For INVITE, start timer H for transaction termination
+ * regardless whether transport is reliable or not.
+ * For non-INVITE, start timer J with the value of 64*T1 for
+ * non-reliable transports, and zero for reliable transports.
+ */
+ lock_timer(tsx);
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ /* Start timer H for INVITE */
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer,
+ &timeout_timer_val);
+ } else if (!tsx->is_reliable) {
+ /* Start timer J on 64*T1 seconds for non-INVITE */
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer,
+ &timeout_timer_val);
+ } else {
+ /* Start timer J on zero seconds for non-INVITE */
+ pj_time_val zero_time = { 0, 0 };
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer,
+ &zero_time);
+ }
+ unlock_timer(tsx);
+
+ /* For INVITE, if unreliable transport is used, retransmission
+ * timer G will be scheduled (retransmission).
+ */
+ if (!tsx->is_reliable) {
+ pjsip_cseq_hdr *cseq = (pjsip_cseq_hdr*)
+ pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ,
+ NULL);
+ if (cseq->method.id == PJSIP_INVITE_METHOD) {
+ tsx->retransmit_count = 0;
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
+ } else {
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt,
+ &tsx->retransmit_timer,
+ &t1_timer_val);
+ }
+ }
+ }
+
+ /* Inform TU */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_TX_MSG, tdata );
+
+ } else {
+ pj_assert(0);
+ }
+
+
+ } else if (event->type == PJSIP_EVENT_TIMER &&
+ event->body.timer.entry == &tsx->retransmit_timer) {
+
+ /* Retransmission timer elapsed. */
+ pj_status_t status;
+
+ /* Must not be triggered while transport is pending. */
+ pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0);
+
+ /* Must have last response to retransmit. */
+ pj_assert(tsx->last_tx != NULL);
+
+ /* Retransmit the last response. */
+ status = tsx_retransmit( tsx, 1 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ } else if (event->type == PJSIP_EVENT_TIMER &&
+ event->body.timer.entry == &tsx->timeout_timer) {
+
+ /* Timeout timer. should not happen? */
+ pj_assert(!"Should not happen(?)");
+
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL);
+
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer);
+
+ return PJ_EBUG;
+
+ } else {
+ pj_assert(!"Unexpected event");
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in Proceeding for UAC
+ * This state happens after provisional response(s) has been received from
+ * UAS.
+ */
+static pj_status_t tsx_on_state_proceeding_uac(pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+
+ pj_assert(tsx->state == PJSIP_TSX_STATE_PROCEEDING ||
+ tsx->state == PJSIP_TSX_STATE_CALLING);
+
+ if (event->type != PJSIP_EVENT_TIMER) {
+ pjsip_msg *msg;
+
+ /* Must be incoming response, because we should not retransmit
+ * request once response has been received.
+ */
+ pj_assert(event->type == PJSIP_EVENT_RX_MSG);
+ if (event->type != PJSIP_EVENT_RX_MSG) {
+ return PJ_EINVALIDOP;
+ }
+
+ msg = event->body.rx_msg.rdata->msg_info.msg;
+
+ /* Must be a response message. */
+ if (msg->type != PJSIP_RESPONSE_MSG) {
+ pj_assert(!"Expecting response message!");
+ return PJSIP_ENOTRESPONSEMSG;
+ }
+
+ tsx_set_status_code(tsx, msg->line.status.code,
+ &msg->line.status.reason);
+
+ } else {
+ if (event->body.timer.entry == &tsx->retransmit_timer) {
+ /* Retransmit message. */
+ pj_status_t status;
+
+ status = tsx_retransmit( tsx, 1 );
+
+ return status;
+
+ } else {
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL);
+ }
+ }
+
+ if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 100)) {
+
+ /* Inform the message to TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+
+ } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code,200)) {
+
+ /* Stop timeout timer B/F. */
+ lock_timer(tsx);
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+ unlock_timer(tsx);
+
+ /* For INVITE, the state moves to Terminated state (because ACK is
+ * handled in TU). For non-INVITE, state moves to Completed.
+ */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+ //return PJSIP_ETSXDESTROYED;
+
+ } else {
+ pj_time_val timeout;
+
+ /* For unreliable transport, start timer D (for INVITE) or
+ * timer K for non-INVITE. */
+ if (!tsx->is_reliable) {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ timeout = td_timer_val;
+ } else {
+ timeout = t4_timer_val;
+ }
+ } else {
+ timeout.sec = timeout.msec = 0;
+ }
+ lock_timer(tsx);
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+ unlock_timer(tsx);
+
+ /* Cancel retransmission timer */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* Move state to Completed, inform TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+ }
+
+ } else if (event->type == PJSIP_EVENT_TIMER &&
+ event->body.timer.entry == &tsx->timeout_timer) {
+
+ /* Inform TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer);
+
+
+ } else if (tsx->status_code >= 300 && tsx->status_code <= 699) {
+
+
+#if 0
+ /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
+ /*
+ * This is the old code; it's broken for authentication.
+ */
+ pj_time_val timeout;
+ pj_status_t status;
+
+ /* Stop timer B. */
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+
+ /* Generate and send ACK for INVITE. */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ pjsip_tx_data *ack;
+
+ status = pjsip_endpt_create_ack( tsx->endpt, tsx->last_tx,
+ event->body.rx_msg.rdata,
+ &ack);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (ack != tsx->last_tx) {
+ pjsip_tx_data_dec_ref(tsx->last_tx);
+ tsx->last_tx = ack;
+ }
+
+ status = tsx_send_msg( tsx, tsx->last_tx);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+ }
+
+ /* Start Timer D with TD/T4 timer if unreliable transport is used. */
+ if (!tsx->is_reliable) {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ timeout = td_timer_val;
+ } else {
+ timeout = t4_timer_val;
+ }
+ } else {
+ timeout.sec = timeout.msec = 0;
+ }
+ tsx->timeout->timer.id = TSX_TIMER_TIMEOUT;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout);
+
+ /* Inform TU.
+ * blp: You might be tempted to move this notification before
+ * sending ACK, but I think you shouldn't. Better set-up
+ * everything before calling tsx_user's callback to avoid
+ * mess up.
+ */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+
+ /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
+#endif
+
+ /* New code, taken from 0.2.9.x branch */
+ pj_time_val timeout;
+ pjsip_tx_data *ack_tdata = NULL;
+
+ /* Cancel retransmission timer */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* Stop timer B. */
+ lock_timer(tsx);
+ tsx->timeout_timer.id = 0;
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+ unlock_timer(tsx);
+
+ /* Generate and send ACK (for INVITE) */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ pj_status_t status;
+
+ status = pjsip_endpt_create_ack( tsx->endpt, tsx->last_tx,
+ event->body.rx_msg.rdata,
+ &ack_tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = tsx_send_msg( tsx, ack_tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Inform TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata);
+
+ /* Generate and send ACK for INVITE. */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ if (ack_tdata != tsx->last_tx) {
+ pjsip_tx_data_dec_ref(tsx->last_tx);
+ tsx->last_tx = ack_tdata;
+
+ /* This is a bug.
+ tsx_send_msg() does NOT decrement tdata's reference counter,
+ so if we add the reference counter here, tdata will have
+ reference counter 2, causing it to leak.
+ pjsip_tx_data_add_ref(ack_tdata);
+ */
+ }
+ }
+
+ /* Start Timer D with TD/T4 timer if unreliable transport is used. */
+ /* Note: tsx->transport may be NULL! */
+ if (!tsx->is_reliable) {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ timeout = td_timer_val;
+ } else {
+ timeout = t4_timer_val;
+ }
+ } else {
+ timeout.sec = timeout.msec = 0;
+ }
+ lock_timer(tsx);
+ /* In the short period above timer may have been inserted
+ * by set_timeout() (by CANCEL). Cancel it if necessary. See:
+ * https://trac.pjsip.org/repos/ticket/1374
+ */
+ if (tsx->timeout_timer.id)
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout);
+ unlock_timer(tsx);
+
+ } else {
+ // Shouldn't happen because there's no timer for this state.
+ pj_assert(!"Unexpected event");
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in Completed state for UAS
+ */
+static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_COMPLETED);
+
+ if (event->type == PJSIP_EVENT_RX_MSG) {
+ pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg;
+
+ /* This must be a request message retransmission. */
+ if (msg->type != PJSIP_REQUEST_MSG)
+ return PJSIP_ENOTREQUESTMSG;
+
+ /* On receive request retransmission, retransmit last response. */
+ if (msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ pj_status_t status;
+
+ status = tsx_retransmit( tsx, 0 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ } else {
+ pj_time_val timeout;
+
+ /* Process incoming ACK request. */
+
+ /* Verify that this is an INVITE transaction */
+ if (tsx->method.id != PJSIP_INVITE_METHOD) {
+ PJ_LOG(2, (tsx->obj_name,
+ "Received illegal ACK for %.*s transaction",
+ (int)tsx->method.name.slen,
+ tsx->method.name.ptr));
+ return PJSIP_EINVALIDMETHOD;
+ }
+
+ /* Cease retransmission. */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED);
+
+ /* Reschedule timeout timer. */
+ lock_timer(tsx);
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+
+ /* Timer I is T4 timer for unreliable transports, and
+ * zero seconds for reliable transports.
+ */
+ if (!tsx->is_reliable) {
+ timeout.sec = 0;
+ timeout.msec = 0;
+ } else {
+ timeout.sec = t4_timer_val.sec;
+ timeout.msec = t4_timer_val.msec;
+ }
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+ unlock_timer(tsx);
+
+ /* Move state to "Confirmed" */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_CONFIRMED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+ }
+
+ } else if (event->type == PJSIP_EVENT_TIMER) {
+
+ if (event->body.timer.entry == &tsx->retransmit_timer) {
+ /* Retransmit message. */
+ pj_status_t status;
+
+ status = tsx_retransmit( tsx, 1 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ } else {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+ /* For INVITE, this means that ACK was never received.
+ * Set state to Terminated, and inform TU.
+ */
+
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL);
+
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer );
+
+ //return PJSIP_ETSXDESTROYED;
+
+ } else {
+ /* Transaction terminated, it can now be deleted. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer );
+ //return PJSIP_ETSXDESTROYED;
+ }
+ }
+
+ } else {
+ /* Ignore request to transmit. */
+ PJ_ASSERT_RETURN(event->type == PJSIP_EVENT_TX_MSG &&
+ event->body.tx_msg.tdata == tsx->last_tx,
+ PJ_EINVALIDOP);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in Completed state for UAC transaction.
+ */
+static pj_status_t tsx_on_state_completed_uac( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_COMPLETED);
+
+ if (event->type == PJSIP_EVENT_TIMER) {
+ /* Must be the timeout timer. */
+ pj_assert(event->body.timer.entry == &tsx->timeout_timer);
+
+ /* Move to Terminated state. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, event->body.timer.entry );
+
+ /* Transaction has been destroyed. */
+ //return PJSIP_ETSXDESTROYED;
+
+ } else if (event->type == PJSIP_EVENT_RX_MSG) {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ /* On received of final response retransmission, retransmit the ACK.
+ * TU doesn't need to be informed.
+ */
+ pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg;
+ pj_assert(msg->type == PJSIP_RESPONSE_MSG);
+ if (msg->type==PJSIP_RESPONSE_MSG &&
+ msg->line.status.code >= 200)
+ {
+ pj_status_t status;
+
+ status = tsx_retransmit( tsx, 0 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+ } else {
+ /* Very late retransmission of privisional response. */
+ pj_assert( msg->type == PJSIP_RESPONSE_MSG );
+ }
+ } else {
+ /* Just drop the response. */
+ }
+
+ } else {
+ pj_assert(!"Unexpected event");
+ return PJ_EINVALIDOP;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in state Confirmed.
+ */
+static pj_status_t tsx_on_state_confirmed( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_CONFIRMED);
+
+ /* This state is only for UAS for INVITE. */
+ pj_assert(tsx->role == PJSIP_ROLE_UAS);
+ pj_assert(tsx->method.id == PJSIP_INVITE_METHOD);
+
+ /* Absorb any ACK received. */
+ if (event->type == PJSIP_EVENT_RX_MSG) {
+
+ pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg;
+
+ /* Only expecting request message. */
+ if (msg->type != PJSIP_REQUEST_MSG)
+ return PJSIP_ENOTREQUESTMSG;
+
+ /* Must be an ACK request or a late INVITE retransmission. */
+ pj_assert(msg->line.req.method.id == PJSIP_ACK_METHOD ||
+ msg->line.req.method.id == PJSIP_INVITE_METHOD);
+
+ } else if (event->type == PJSIP_EVENT_TIMER) {
+ /* Must be from timeout_timer_. */
+ pj_assert(event->body.timer.entry == &tsx->timeout_timer);
+
+ /* Move to Terminated state. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer );
+
+ /* Transaction has been destroyed. */
+ //return PJSIP_ETSXDESTROYED;
+
+ } else {
+ pj_assert(!"Unexpected event");
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in state Terminated.
+ */
+static pj_status_t tsx_on_state_terminated( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_TERMINATED);
+
+ /* Ignore events other than timer. This used to be an assertion but
+ * events may genuinely arrive at this state.
+ */
+ if (event->type != PJSIP_EVENT_TIMER) {
+ return PJ_EIGNORED;
+ }
+
+ /* Destroy this transaction */
+ tsx_set_state(tsx, PJSIP_TSX_STATE_DESTROYED,
+ event->type, event->body.user.user1 );
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in state Destroyed.
+ * Shouldn't happen!
+ */
+static pj_status_t tsx_on_state_destroyed(pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ PJ_UNUSED_ARG(tsx);
+ PJ_UNUSED_ARG(event);
+
+ // See https://trac.pjsip.org/repos/ticket/1432
+ //pj_assert(!"Not expecting any events!!");
+
+ return PJ_EIGNORED;
+}
+
diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c
new file mode 100644
index 0000000..4d8b77e
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport.c
@@ -0,0 +1,1942 @@
+/* $Id: sip_transport.c 4094 2012-04-26 09:31:00Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_private.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_module.h>
+#include <pj/except.h>
+#include <pj/os.h>
+#include <pj/log.h>
+#include <pj/ioqueue.h>
+#include <pj/hash.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/list.h>
+
+
+#define THIS_FILE "sip_transport.c"
+
+#if 0
+# define TRACE_(x) PJ_LOG(5,x)
+
+static const char *addr_string(const pj_sockaddr_t *addr)
+{
+ static char str[PJ_INET6_ADDRSTRLEN];
+ pj_inet_ntop(((const pj_sockaddr*)addr)->addr.sa_family,
+ pj_sockaddr_get_addr(addr),
+ str, sizeof(str));
+ return str;
+}
+#else
+# define TRACE_(x)
+#endif
+
+/* Prototype. */
+static pj_status_t mod_on_tx_msg(pjsip_tx_data *tdata);
+
+/* This module has sole purpose to print transmit data to contigous buffer
+ * before actually transmitted to the wire.
+ */
+static pjsip_module mod_msg_print =
+{
+ NULL, NULL, /* prev and next */
+ { "mod-msg-print", 13}, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ NULL, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ &mod_on_tx_msg, /* on_tx_request() */
+ &mod_on_tx_msg, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+/*
+ * Transport manager.
+ */
+struct pjsip_tpmgr
+{
+ pj_hash_table_t *table;
+ pj_lock_t *lock;
+ pjsip_endpoint *endpt;
+ pjsip_tpfactory factory_list;
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ pj_atomic_t *tdata_counter;
+#endif
+ void (*on_rx_msg)(pjsip_endpoint*, pj_status_t, pjsip_rx_data*);
+ pj_status_t (*on_tx_msg)(pjsip_endpoint*, pjsip_tx_data*);
+ pjsip_tp_state_callback tp_state_cb;
+};
+
+
+/* Transport state listener list type */
+typedef struct tp_state_listener
+{
+ PJ_DECL_LIST_MEMBER(struct tp_state_listener);
+
+ pjsip_tp_state_callback cb;
+ void *user_data;
+} tp_state_listener;
+
+
+/*
+ * Transport data.
+ */
+typedef struct transport_data
+{
+ /* Transport listeners */
+ tp_state_listener st_listeners;
+ tp_state_listener st_listeners_empty;
+} transport_data;
+
+
+/*****************************************************************************
+ *
+ * GENERAL TRANSPORT (NAMES, TYPES, ETC.)
+ *
+ *****************************************************************************/
+
+/*
+ * Transport names.
+ */
+struct transport_names_t
+{
+ pjsip_transport_type_e type; /* Transport type */
+ pj_uint16_t port; /* Default port number */
+ pj_str_t name; /* Id tag */
+ const char *description; /* Longer description */
+ unsigned flag; /* Flags */
+ char name_buf[16]; /* For user's transport */
+} transport_names[16] =
+{
+ {
+ PJSIP_TRANSPORT_UNSPECIFIED,
+ 0,
+ {"Unspecified", 11},
+ "Unspecified",
+ 0
+ },
+ {
+ PJSIP_TRANSPORT_UDP,
+ 5060,
+ {"UDP", 3},
+ "UDP transport",
+ PJSIP_TRANSPORT_DATAGRAM
+ },
+ {
+ PJSIP_TRANSPORT_TCP,
+ 5060,
+ {"TCP", 3},
+ "TCP transport",
+ PJSIP_TRANSPORT_RELIABLE
+ },
+ {
+ PJSIP_TRANSPORT_TLS,
+ 5061,
+ {"TLS", 3},
+ "TLS transport",
+ PJSIP_TRANSPORT_RELIABLE | PJSIP_TRANSPORT_SECURE
+ },
+ {
+ PJSIP_TRANSPORT_SCTP,
+ 5060,
+ {"SCTP", 4},
+ "SCTP transport",
+ PJSIP_TRANSPORT_RELIABLE
+ },
+ {
+ PJSIP_TRANSPORT_LOOP,
+ 15060,
+ {"LOOP", 4},
+ "Loopback transport",
+ PJSIP_TRANSPORT_RELIABLE
+ },
+ {
+ PJSIP_TRANSPORT_LOOP_DGRAM,
+ 15060,
+ {"LOOP-DGRAM", 10},
+ "Loopback datagram transport",
+ PJSIP_TRANSPORT_DATAGRAM
+ },
+ {
+ PJSIP_TRANSPORT_UDP6,
+ 5060,
+ {"UDP", 3},
+ "UDP IPv6 transport",
+ PJSIP_TRANSPORT_DATAGRAM
+ },
+ {
+ PJSIP_TRANSPORT_TCP6,
+ 5060,
+ {"TCP", 3},
+ "TCP IPv6 transport",
+ PJSIP_TRANSPORT_RELIABLE
+ },
+};
+
+static void tp_state_callback(pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info);
+
+
+struct transport_names_t *get_tpname(pjsip_transport_type_e type)
+{
+ unsigned i;
+ for (i=0; i<PJ_ARRAY_SIZE(transport_names); ++i) {
+ if (transport_names[i].type == type)
+ return &transport_names[i];
+ }
+ pj_assert(!"Invalid transport type!");
+ return NULL;
+}
+
+
+
+/*
+ * Register new transport type to PJSIP.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_register_type( unsigned tp_flag,
+ const char *tp_name,
+ int def_port,
+ int *p_tp_type)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(tp_flag && tp_name && def_port, PJ_EINVAL);
+ PJ_ASSERT_RETURN(pj_ansi_strlen(tp_name) <
+ PJ_ARRAY_SIZE(transport_names[0].name_buf),
+ PJ_ENAMETOOLONG);
+
+ for (i=1; i<PJ_ARRAY_SIZE(transport_names); ++i) {
+ if (transport_names[i].type == 0)
+ break;
+ }
+
+ if (i == PJ_ARRAY_SIZE(transport_names))
+ return PJ_ETOOMANY;
+
+ transport_names[i].type = (pjsip_transport_type_e)i;
+ transport_names[i].port = (pj_uint16_t)def_port;
+ pj_ansi_strcpy(transport_names[i].name_buf, tp_name);
+ transport_names[i].name = pj_str(transport_names[i].name_buf);
+ transport_names[i].flag = tp_flag;
+
+ if (p_tp_type)
+ *p_tp_type = i;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get transport type from name.
+ */
+PJ_DEF(pjsip_transport_type_e) pjsip_transport_get_type_from_name(const pj_str_t *name)
+{
+ unsigned i;
+
+ if (name->slen == 0)
+ return PJSIP_TRANSPORT_UNSPECIFIED;
+
+ /* Get transport type from name. */
+ for (i=0; i<PJ_ARRAY_SIZE(transport_names); ++i) {
+ if (pj_stricmp(name, &transport_names[i].name) == 0) {
+ return transport_names[i].type;
+ }
+ }
+
+ pj_assert(!"Invalid transport name");
+ return PJSIP_TRANSPORT_UNSPECIFIED;
+}
+
+
+/*
+ * Get the transport type for the specified flags.
+ */
+PJ_DEF(pjsip_transport_type_e) pjsip_transport_get_type_from_flag(unsigned flag)
+{
+ unsigned i;
+
+ /* Get the transport type for the specified flags. */
+ for (i=0; i<PJ_ARRAY_SIZE(transport_names); ++i) {
+ if (transport_names[i].flag == flag) {
+ return transport_names[i].type;
+ }
+ }
+
+ pj_assert(!"Invalid transport type");
+ return PJSIP_TRANSPORT_UNSPECIFIED;
+}
+
+/*
+ * Get the socket address family of a given transport type.
+ */
+PJ_DEF(int) pjsip_transport_type_get_af(pjsip_transport_type_e type)
+{
+ if (type & PJSIP_TRANSPORT_IPV6)
+ return pj_AF_INET6();
+ else
+ return pj_AF_INET();
+}
+
+PJ_DEF(unsigned) pjsip_transport_get_flag_from_type(pjsip_transport_type_e type)
+{
+ /* Return transport flag. */
+ return get_tpname(type)->flag;
+}
+
+/*
+ * Get the default SIP port number for the specified type.
+ */
+PJ_DEF(int) pjsip_transport_get_default_port_for_type(pjsip_transport_type_e type)
+{
+ /* Return the port. */
+ return get_tpname(type)->port;
+}
+
+/*
+ * Get transport name.
+ */
+PJ_DEF(const char*) pjsip_transport_get_type_name(pjsip_transport_type_e type)
+{
+ /* Return the name. */
+ return get_tpname(type)->name.ptr;
+}
+
+/*
+ * Get transport description.
+ */
+PJ_DEF(const char*) pjsip_transport_get_type_desc(pjsip_transport_type_e type)
+{
+ /* Return the description. */
+ return get_tpname(type)->description;
+}
+
+
+/*****************************************************************************
+ *
+ * TRANSPORT SELECTOR
+ *
+ *****************************************************************************/
+
+/*
+ * Add transport/listener reference in the selector.
+ */
+PJ_DEF(void) pjsip_tpselector_add_ref(pjsip_tpselector *sel)
+{
+ if (sel->type == PJSIP_TPSELECTOR_TRANSPORT && sel->u.transport != NULL)
+ pjsip_transport_add_ref(sel->u.transport);
+ else if (sel->type == PJSIP_TPSELECTOR_LISTENER && sel->u.listener != NULL)
+ ; /* Hmm.. looks like we don't have reference counter for listener */
+}
+
+
+/*
+ * Decrement transport/listener reference in the selector.
+ */
+PJ_DEF(void) pjsip_tpselector_dec_ref(pjsip_tpselector *sel)
+{
+ if (sel->type == PJSIP_TPSELECTOR_TRANSPORT && sel->u.transport != NULL)
+ pjsip_transport_dec_ref(sel->u.transport);
+ else if (sel->type == PJSIP_TPSELECTOR_LISTENER && sel->u.listener != NULL)
+ ; /* Hmm.. looks like we don't have reference counter for listener */
+}
+
+
+/*****************************************************************************
+ *
+ * TRANSMIT DATA BUFFER MANIPULATION.
+ *
+ *****************************************************************************/
+
+/*
+ * Create new transmit buffer.
+ */
+PJ_DEF(pj_status_t) pjsip_tx_data_create( pjsip_tpmgr *mgr,
+ pjsip_tx_data **p_tdata )
+{
+ pj_pool_t *pool;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(mgr && p_tdata, PJ_EINVAL);
+
+ pool = pjsip_endpt_create_pool( mgr->endpt, "tdta%p",
+ PJSIP_POOL_LEN_TDATA,
+ PJSIP_POOL_INC_TDATA );
+ if (!pool)
+ return PJ_ENOMEM;
+
+ tdata = PJ_POOL_ZALLOC_T(pool, pjsip_tx_data);
+ tdata->pool = pool;
+ tdata->mgr = mgr;
+ pj_memcpy(tdata->obj_name, pool->obj_name, PJ_MAX_OBJ_NAME);
+
+ status = pj_atomic_create(tdata->pool, 0, &tdata->ref_cnt);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_release_pool( mgr->endpt, tdata->pool );
+ return status;
+ }
+
+ //status = pj_lock_create_simple_mutex(pool, "tdta%p", &tdata->lock);
+ status = pj_lock_create_null_mutex(pool, "tdta%p", &tdata->lock);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_release_pool( mgr->endpt, tdata->pool );
+ return status;
+ }
+
+ pj_ioqueue_op_key_init(&tdata->op_key.key, sizeof(tdata->op_key.key));
+
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ pj_atomic_inc( tdata->mgr->tdata_counter );
+#endif
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add reference to tx buffer.
+ */
+PJ_DEF(void) pjsip_tx_data_add_ref( pjsip_tx_data *tdata )
+{
+ pj_atomic_inc(tdata->ref_cnt);
+}
+
+/*
+ * Decrease transport data reference, destroy it when the reference count
+ * reaches zero.
+ */
+PJ_DEF(pj_status_t) pjsip_tx_data_dec_ref( pjsip_tx_data *tdata )
+{
+ pj_assert( pj_atomic_get(tdata->ref_cnt) > 0);
+ if (pj_atomic_dec_and_get(tdata->ref_cnt) <= 0) {
+ PJ_LOG(5,(tdata->obj_name, "Destroying txdata %s",
+ pjsip_tx_data_get_info(tdata)));
+ pjsip_tpselector_dec_ref(&tdata->tp_sel);
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ pj_atomic_dec( tdata->mgr->tdata_counter );
+#endif
+ pj_atomic_destroy( tdata->ref_cnt );
+ pj_lock_destroy( tdata->lock );
+ pjsip_endpt_release_pool( tdata->mgr->endpt, tdata->pool );
+ return PJSIP_EBUFDESTROYED;
+ } else {
+ return PJ_SUCCESS;
+ }
+}
+
+/*
+ * Invalidate the content of the print buffer to force the message to be
+ * re-printed when sent.
+ */
+PJ_DEF(void) pjsip_tx_data_invalidate_msg( pjsip_tx_data *tdata )
+{
+ tdata->buf.cur = tdata->buf.start;
+ tdata->info = NULL;
+}
+
+/*
+ * Print the SIP message to transmit data buffer's internal buffer.
+ */
+PJ_DEF(pj_status_t) pjsip_tx_data_encode(pjsip_tx_data *tdata)
+{
+ /* Allocate buffer if necessary. */
+ if (tdata->buf.start == NULL) {
+ PJ_USE_EXCEPTION;
+
+ PJ_TRY {
+ tdata->buf.start = (char*)
+ pj_pool_alloc(tdata->pool, PJSIP_MAX_PKT_LEN);
+ }
+ PJ_CATCH_ANY {
+ return PJ_ENOMEM;
+ }
+ PJ_END
+
+ tdata->buf.cur = tdata->buf.start;
+ tdata->buf.end = tdata->buf.start + PJSIP_MAX_PKT_LEN;
+ }
+
+ /* Do we need to reprint? */
+ if (!pjsip_tx_data_is_valid(tdata)) {
+ pj_ssize_t size;
+
+ size = pjsip_msg_print( tdata->msg, tdata->buf.start,
+ tdata->buf.end - tdata->buf.start);
+ if (size < 0) {
+ return PJSIP_EMSGTOOLONG;
+ }
+ pj_assert(size != 0);
+ tdata->buf.cur[size] = '\0';
+ tdata->buf.cur += size;
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_bool_t) pjsip_tx_data_is_valid( pjsip_tx_data *tdata )
+{
+ return tdata->buf.cur != tdata->buf.start;
+}
+
+static char *get_msg_info(pj_pool_t *pool, const char *obj_name,
+ const pjsip_msg *msg)
+{
+ char info_buf[128], *info;
+ const pjsip_cseq_hdr *cseq;
+ int len;
+
+ cseq = (const pjsip_cseq_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL);
+ PJ_ASSERT_RETURN(cseq != NULL, "INVALID MSG");
+
+ if (msg->type == PJSIP_REQUEST_MSG) {
+ len = pj_ansi_snprintf(info_buf, sizeof(info_buf),
+ "Request msg %.*s/cseq=%d (%s)",
+ (int)msg->line.req.method.name.slen,
+ msg->line.req.method.name.ptr,
+ cseq->cseq, obj_name);
+ } else {
+ len = pj_ansi_snprintf(info_buf, sizeof(info_buf),
+ "Response msg %d/%.*s/cseq=%d (%s)",
+ msg->line.status.code,
+ (int)cseq->method.name.slen,
+ cseq->method.name.ptr,
+ cseq->cseq, obj_name);
+ }
+
+ if (len < 1 || len >= (int)sizeof(info_buf)) {
+ return (char*)obj_name;
+ }
+
+ info = (char*) pj_pool_alloc(pool, len+1);
+ pj_memcpy(info, info_buf, len+1);
+
+ return info;
+}
+
+PJ_DEF(char*) pjsip_tx_data_get_info( pjsip_tx_data *tdata )
+{
+ /* tdata->info may be assigned by application so if it exists
+ * just return it.
+ */
+ if (tdata->info)
+ return tdata->info;
+
+ if (tdata==NULL || tdata->msg==NULL)
+ return "NULL";
+
+ pj_lock_acquire(tdata->lock);
+ tdata->info = get_msg_info(tdata->pool, tdata->obj_name, tdata->msg);
+ pj_lock_release(tdata->lock);
+
+ return tdata->info;
+}
+
+PJ_DEF(pj_status_t) pjsip_tx_data_set_transport(pjsip_tx_data *tdata,
+ const pjsip_tpselector *sel)
+{
+ PJ_ASSERT_RETURN(tdata && sel, PJ_EINVAL);
+
+ pj_lock_acquire(tdata->lock);
+
+ pjsip_tpselector_dec_ref(&tdata->tp_sel);
+
+ pj_memcpy(&tdata->tp_sel, sel, sizeof(*sel));
+ pjsip_tpselector_add_ref(&tdata->tp_sel);
+
+ pj_lock_release(tdata->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(char*) pjsip_rx_data_get_info(pjsip_rx_data *rdata)
+{
+ char obj_name[PJ_MAX_OBJ_NAME];
+
+ PJ_ASSERT_RETURN(rdata->msg_info.msg, "INVALID MSG");
+
+ if (rdata->msg_info.info)
+ return rdata->msg_info.info;
+
+ pj_ansi_strcpy(obj_name, "rdata");
+ pj_ansi_snprintf(obj_name+5, sizeof(obj_name)-5, "%p", rdata);
+
+ rdata->msg_info.info = get_msg_info(rdata->tp_info.pool, obj_name,
+ rdata->msg_info.msg);
+ return rdata->msg_info.info;
+}
+
+/*****************************************************************************
+ *
+ * TRANSPORT KEY
+ *
+ *****************************************************************************/
+
+
+/*****************************************************************************
+ *
+ * TRANSPORT
+ *
+ *****************************************************************************/
+
+static void transport_send_callback(pjsip_transport *transport,
+ void *token,
+ pj_ssize_t size)
+{
+ pjsip_tx_data *tdata = (pjsip_tx_data*) token;
+
+ PJ_UNUSED_ARG(transport);
+
+ /* Mark pending off so that app can resend/reuse txdata from inside
+ * the callback.
+ */
+ tdata->is_pending = 0;
+
+ /* Call callback, if any. */
+ if (tdata->cb) {
+ (*tdata->cb)(tdata->token, tdata, size);
+ }
+
+ /* Decrement reference count. */
+ pjsip_tx_data_dec_ref(tdata);
+}
+
+/* This function is called by endpoint for on_tx_request() and on_tx_response()
+ * notification.
+ */
+static pj_status_t mod_on_tx_msg(pjsip_tx_data *tdata)
+{
+ return pjsip_tx_data_encode(tdata);
+}
+
+/*
+ * Send a SIP message using the specified transport.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_send( pjsip_transport *tr,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *addr,
+ int addr_len,
+ void *token,
+ pjsip_tp_send_callback cb)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tr && tdata && addr, PJ_EINVAL);
+
+ /* Is it currently being sent? */
+ if (tdata->is_pending) {
+ pj_assert(!"Invalid operation step!");
+ PJ_LOG(2,(THIS_FILE, "Unable to send %s: message is pending",
+ pjsip_tx_data_get_info(tdata)));
+ return PJSIP_EPENDINGTX;
+ }
+
+ /* Add reference to prevent deletion, and to cancel idle timer if
+ * it's running.
+ */
+ pjsip_transport_add_ref(tr);
+
+ /* Fill in tp_info. */
+ tdata->tp_info.transport = tr;
+ pj_memcpy(&tdata->tp_info.dst_addr, addr, addr_len);
+ tdata->tp_info.dst_addr_len = addr_len;
+
+ pj_inet_ntop(((pj_sockaddr*)addr)->addr.sa_family,
+ pj_sockaddr_get_addr(addr),
+ tdata->tp_info.dst_name,
+ sizeof(tdata->tp_info.dst_name));
+ tdata->tp_info.dst_port = pj_sockaddr_get_port(addr);
+
+ /* Distribute to modules.
+ * When the message reach mod_msg_print, the contents of the message will
+ * be "printed" to contiguous buffer.
+ */
+ if (tr->tpmgr->on_tx_msg) {
+ status = (*tr->tpmgr->on_tx_msg)(tr->endpt, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_transport_dec_ref(tr);
+ return status;
+ }
+ }
+
+ /* Save callback data. */
+ tdata->token = token;
+ tdata->cb = cb;
+
+ /* Add reference counter. */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Mark as pending. */
+ tdata->is_pending = 1;
+
+ /* Send to transport. */
+ status = (*tr->send_msg)(tr, tdata, addr, addr_len, (void*)tdata,
+ &transport_send_callback);
+
+ if (status != PJ_EPENDING) {
+ tdata->is_pending = 0;
+ pjsip_tx_data_dec_ref(tdata);
+ }
+
+ pjsip_transport_dec_ref(tr);
+ return status;
+}
+
+
+/* send_raw() callback */
+static void send_raw_callback(pjsip_transport *transport,
+ void *token,
+ pj_ssize_t size)
+{
+ pjsip_tx_data *tdata = (pjsip_tx_data*) token;
+
+ /* Mark pending off so that app can resend/reuse txdata from inside
+ * the callback.
+ */
+ tdata->is_pending = 0;
+
+ /* Call callback, if any. */
+ if (tdata->cb) {
+ (*tdata->cb)(tdata->token, tdata, size);
+ }
+
+ /* Decrement tdata reference count. */
+ pjsip_tx_data_dec_ref(tdata);
+
+ /* Decrement transport reference count */
+ pjsip_transport_dec_ref(transport);
+}
+
+
+/* Send raw data */
+PJ_DEF(pj_status_t) pjsip_tpmgr_send_raw(pjsip_tpmgr *mgr,
+ pjsip_transport_type_e tp_type,
+ const pjsip_tpselector *sel,
+ pjsip_tx_data *tdata,
+ const void *raw_data,
+ pj_size_t data_len,
+ const pj_sockaddr_t *addr,
+ int addr_len,
+ void *token,
+ pjsip_tp_send_callback cb)
+{
+ pjsip_transport *tr;
+ pj_status_t status;
+
+ /* Acquire the transport */
+ status = pjsip_tpmgr_acquire_transport(mgr, tp_type, addr, addr_len,
+ sel, &tr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create transmit data buffer if one is not specified */
+ if (tdata == NULL) {
+ status = pjsip_endpt_create_tdata(tr->endpt, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_transport_dec_ref(tr);
+ return status;
+ }
+
+ tdata->info = "raw";
+
+ /* Add reference counter. */
+ pjsip_tx_data_add_ref(tdata);
+ }
+
+ /* Allocate buffer */
+ if (tdata->buf.start == NULL ||
+ (tdata->buf.end - tdata->buf.start) < (int)data_len)
+ {
+ /* Note: data_len may be zero, so allocate +1 */
+ tdata->buf.start = (char*) pj_pool_alloc(tdata->pool, data_len+1);
+ tdata->buf.end = tdata->buf.start + data_len + 1;
+ }
+
+ /* Copy data, if any! (application may send zero len packet) */
+ if (data_len) {
+ pj_memcpy(tdata->buf.start, raw_data, data_len);
+ }
+ tdata->buf.cur = tdata->buf.start + data_len;
+
+ /* Save callback data. */
+ tdata->token = token;
+ tdata->cb = cb;
+
+ /* Mark as pending. */
+ tdata->is_pending = 1;
+
+ /* Send to transport */
+ status = tr->send_msg(tr, tdata, addr, addr_len,
+ tdata, &send_raw_callback);
+
+ if (status != PJ_EPENDING) {
+ /* callback will not be called, so destroy tdata now. */
+ pjsip_tx_data_dec_ref(tdata);
+ pjsip_transport_dec_ref(tr);
+ }
+
+ return status;
+}
+
+
+static void transport_idle_callback(pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pjsip_transport *tp = (pjsip_transport*) entry->user_data;
+ pj_assert(tp != NULL);
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ entry->id = PJ_FALSE;
+ pjsip_transport_destroy(tp);
+}
+
+/*
+ * Add ref.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_add_ref( pjsip_transport *tp )
+{
+ PJ_ASSERT_RETURN(tp != NULL, PJ_EINVAL);
+
+ if (pj_atomic_inc_and_get(tp->ref_cnt) == 1) {
+ pj_lock_acquire(tp->tpmgr->lock);
+ /* Verify again. */
+ if (pj_atomic_get(tp->ref_cnt) == 1) {
+ if (tp->idle_timer.id != PJ_FALSE) {
+ pjsip_endpt_cancel_timer(tp->tpmgr->endpt, &tp->idle_timer);
+ tp->idle_timer.id = PJ_FALSE;
+ }
+ }
+ pj_lock_release(tp->tpmgr->lock);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Dec ref.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_dec_ref( pjsip_transport *tp )
+{
+ PJ_ASSERT_RETURN(tp != NULL, PJ_EINVAL);
+
+ pj_assert(pj_atomic_get(tp->ref_cnt) > 0);
+
+ if (pj_atomic_dec_and_get(tp->ref_cnt) == 0) {
+ pj_lock_acquire(tp->tpmgr->lock);
+ /* Verify again. Do not register timer if the transport is
+ * being destroyed.
+ */
+ if (pj_atomic_get(tp->ref_cnt) == 0 && !tp->is_destroying) {
+ pj_time_val delay;
+
+ /* If transport is in graceful shutdown, then this is the
+ * last user who uses the transport. Schedule to destroy the
+ * transport immediately. Otherwise schedule idle timer.
+ */
+ if (tp->is_shutdown) {
+ delay.sec = delay.msec = 0;
+ } else {
+ delay.sec = (tp->dir==PJSIP_TP_DIR_OUTGOING) ?
+ PJSIP_TRANSPORT_IDLE_TIME :
+ PJSIP_TRANSPORT_SERVER_IDLE_TIME;
+ delay.msec = 0;
+ }
+
+ pj_assert(tp->idle_timer.id == 0);
+ tp->idle_timer.id = PJ_TRUE;
+ pjsip_endpt_schedule_timer(tp->tpmgr->endpt, &tp->idle_timer,
+ &delay);
+ }
+ pj_lock_release(tp->tpmgr->lock);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Register a transport.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_register( pjsip_tpmgr *mgr,
+ pjsip_transport *tp )
+{
+ int key_len;
+ pj_uint32_t hval;
+ void *entry;
+
+ /* Init. */
+ tp->tpmgr = mgr;
+ pj_bzero(&tp->idle_timer, sizeof(tp->idle_timer));
+ tp->idle_timer.user_data = tp;
+ tp->idle_timer.cb = &transport_idle_callback;
+
+ /*
+ * Register to hash table (see Trac ticket #42).
+ */
+ key_len = sizeof(tp->key.type) + tp->addr_len;
+ pj_lock_acquire(mgr->lock);
+
+ /* If entry already occupied, unregister previous entry */
+ hval = 0;
+ entry = pj_hash_get(mgr->table, &tp->key, key_len, &hval);
+ if (entry != NULL)
+ pj_hash_set(NULL, mgr->table, &tp->key, key_len, hval, NULL);
+
+ /* Register new entry */
+ pj_hash_set(tp->pool, mgr->table, &tp->key, key_len, hval, tp);
+
+ pj_lock_release(mgr->lock);
+
+ TRACE_((THIS_FILE,"Transport %s registered: type=%s, remote=%s:%d",
+ tp->obj_name,
+ pjsip_transport_get_type_name(tp->key.type),
+ addr_string(&tp->key.rem_addr),
+ pj_sockaddr_get_port(&tp->key.rem_addr)));
+
+ return PJ_SUCCESS;
+}
+
+/* Force destroy transport (e.g. during transport manager shutdown. */
+static pj_status_t destroy_transport( pjsip_tpmgr *mgr,
+ pjsip_transport *tp )
+{
+ int key_len;
+ pj_uint32_t hval;
+ void *entry;
+
+ TRACE_((THIS_FILE, "Transport %s is being destroyed", tp->obj_name));
+
+ pj_lock_acquire(tp->lock);
+ pj_lock_acquire(mgr->lock);
+
+ tp->is_destroying = PJ_TRUE;
+
+ /*
+ * Unregister timer, if any.
+ */
+ //pj_assert(tp->idle_timer.id == PJ_FALSE);
+ if (tp->idle_timer.id != PJ_FALSE) {
+ pjsip_endpt_cancel_timer(mgr->endpt, &tp->idle_timer);
+ tp->idle_timer.id = PJ_FALSE;
+ }
+
+ /*
+ * Unregister from hash table (see Trac ticket #42).
+ */
+ key_len = sizeof(tp->key.type) + tp->addr_len;
+ hval = 0;
+ entry = pj_hash_get(mgr->table, &tp->key, key_len, &hval);
+ if (entry == (void*)tp)
+ pj_hash_set(NULL, mgr->table, &tp->key, key_len, hval, NULL);
+
+ pj_lock_release(mgr->lock);
+
+ /* Destroy. */
+ return tp->destroy(tp);
+}
+
+
+/*
+ * Start graceful shutdown procedure for this transport.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_shutdown(pjsip_transport *tp)
+{
+ pjsip_tpmgr *mgr;
+ pj_status_t status;
+
+ TRACE_((THIS_FILE, "Transport %s shutting down", tp->obj_name));
+
+ pj_lock_acquire(tp->lock);
+
+ mgr = tp->tpmgr;
+ pj_lock_acquire(mgr->lock);
+
+ /* Do nothing if transport is being shutdown already */
+ if (tp->is_shutdown) {
+ pj_lock_release(tp->lock);
+ pj_lock_release(mgr->lock);
+ return PJ_SUCCESS;
+ }
+
+ status = PJ_SUCCESS;
+
+ /* Instruct transport to shutdown itself */
+ if (tp->do_shutdown)
+ status = tp->do_shutdown(tp);
+
+ if (status == PJ_SUCCESS)
+ tp->is_shutdown = PJ_TRUE;
+
+ /* If transport reference count is zero, start timer count-down */
+ if (pj_atomic_get(tp->ref_cnt) == 0) {
+ pjsip_transport_add_ref(tp);
+ pjsip_transport_dec_ref(tp);
+ }
+
+ pj_lock_release(tp->lock);
+ pj_lock_release(mgr->lock);
+
+ return status;
+}
+
+
+/**
+ * Unregister transport.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_destroy( pjsip_transport *tp)
+{
+ /* Must have no user. */
+ PJ_ASSERT_RETURN(pj_atomic_get(tp->ref_cnt) == 0, PJSIP_EBUSY);
+
+ /* Destroy. */
+ return destroy_transport(tp->tpmgr, tp);
+}
+
+
+
+/*****************************************************************************
+ *
+ * TRANSPORT FACTORY
+ *
+ *****************************************************************************/
+
+
+PJ_DEF(pj_status_t) pjsip_tpmgr_register_tpfactory( pjsip_tpmgr *mgr,
+ pjsip_tpfactory *tpf)
+{
+ pjsip_tpfactory *p;
+ pj_status_t status;
+
+ pj_lock_acquire(mgr->lock);
+
+ /* Check that no factory with the same type has been registered. */
+ status = PJ_SUCCESS;
+ for (p=mgr->factory_list.next; p!=&mgr->factory_list; p=p->next) {
+ if (p->type == tpf->type) {
+ status = PJSIP_ETYPEEXISTS;
+ break;
+ }
+ if (p == tpf) {
+ status = PJ_EEXISTS;
+ break;
+ }
+ }
+
+ if (status != PJ_SUCCESS) {
+ pj_lock_release(mgr->lock);
+ return status;
+ }
+
+ pj_list_insert_before(&mgr->factory_list, tpf);
+
+ pj_lock_release(mgr->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Unregister factory.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_unregister_tpfactory( pjsip_tpmgr *mgr,
+ pjsip_tpfactory *tpf)
+{
+ pj_lock_acquire(mgr->lock);
+
+ pj_assert(pj_list_find_node(&mgr->factory_list, tpf) == tpf);
+ pj_list_erase(tpf);
+
+ pj_lock_release(mgr->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ *
+ * TRANSPORT MANAGER
+ *
+ *****************************************************************************/
+
+/*
+ * Create a new transport manager.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_create( pj_pool_t *pool,
+ pjsip_endpoint *endpt,
+ pjsip_rx_callback rx_cb,
+ pjsip_tx_callback tx_cb,
+ pjsip_tpmgr **p_mgr)
+{
+ pjsip_tpmgr *mgr;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && endpt && rx_cb && p_mgr, PJ_EINVAL);
+
+ /* Register mod_msg_print module. */
+ status = pjsip_endpt_register_module(endpt, &mod_msg_print);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create and initialize transport manager. */
+ mgr = PJ_POOL_ZALLOC_T(pool, pjsip_tpmgr);
+ mgr->endpt = endpt;
+ mgr->on_rx_msg = rx_cb;
+ mgr->on_tx_msg = tx_cb;
+ pj_list_init(&mgr->factory_list);
+
+ mgr->table = pj_hash_create(pool, PJSIP_TPMGR_HTABLE_SIZE);
+ if (!mgr->table)
+ return PJ_ENOMEM;
+
+ status = pj_lock_create_recursive_mutex(pool, "tmgr%p", &mgr->lock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ status = pj_atomic_create(pool, 0, &mgr->tdata_counter);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif
+
+ /* Set transport state callback */
+ status = pjsip_tpmgr_set_state_cb(mgr, &tp_state_callback);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ PJ_LOG(5, (THIS_FILE, "Transport manager created."));
+
+ *p_mgr = mgr;
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Find out the appropriate local address info (IP address and port) to
+ * advertise in Contact header based on the remote address to be
+ * contacted. The local address info would be the address name of the
+ * transport or listener which will be used to send the request.
+ *
+ * In this implementation, it will only select the transport based on
+ * the transport type in the request.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_find_local_addr( pjsip_tpmgr *tpmgr,
+ pj_pool_t *pool,
+ pjsip_transport_type_e type,
+ const pjsip_tpselector *sel,
+ pj_str_t *ip_addr,
+ int *port)
+{
+ pj_status_t status = PJSIP_EUNSUPTRANSPORT;
+ unsigned flag;
+
+ /* Sanity checks */
+ PJ_ASSERT_RETURN(tpmgr && pool && ip_addr && port, PJ_EINVAL);
+
+ ip_addr->slen = 0;
+ *port = 0;
+
+ flag = pjsip_transport_get_flag_from_type(type);
+
+ if (sel && sel->type == PJSIP_TPSELECTOR_TRANSPORT &&
+ sel->u.transport)
+ {
+ pj_strdup(pool, ip_addr, &sel->u.transport->local_name.host);
+ *port = sel->u.transport->local_name.port;
+ status = PJ_SUCCESS;
+
+ } else if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER &&
+ sel->u.listener)
+ {
+ pj_strdup(pool, ip_addr, &sel->u.listener->addr_name.host);
+ *port = sel->u.listener->addr_name.port;
+ status = PJ_SUCCESS;
+
+ } else if ((flag & PJSIP_TRANSPORT_DATAGRAM) != 0) {
+
+ pj_sockaddr remote;
+ int addr_len;
+ pjsip_transport *tp;
+
+ pj_bzero(&remote, sizeof(remote));
+ if (type & PJSIP_TRANSPORT_IPV6) {
+ addr_len = sizeof(pj_sockaddr_in6);
+ remote.addr.sa_family = pj_AF_INET6();
+ } else {
+ addr_len = sizeof(pj_sockaddr_in);
+ remote.addr.sa_family = pj_AF_INET();
+ }
+
+ status = pjsip_tpmgr_acquire_transport(tpmgr, type, &remote,
+ addr_len, NULL, &tp);
+
+ if (status == PJ_SUCCESS) {
+ pj_strdup(pool, ip_addr, &tp->local_name.host);
+ *port = tp->local_name.port;
+ status = PJ_SUCCESS;
+
+ pjsip_transport_dec_ref(tp);
+ }
+
+ } else {
+ /* For connection oriented transport, enum the factories */
+ pjsip_tpfactory *f;
+
+ pj_lock_acquire(tpmgr->lock);
+
+ f = tpmgr->factory_list.next;
+ while (f != &tpmgr->factory_list) {
+ if (f->type == type)
+ break;
+ f = f->next;
+ }
+
+ if (f != &tpmgr->factory_list) {
+ pj_strdup(pool, ip_addr, &f->addr_name.host);
+ *port = f->addr_name.port;
+ status = PJ_SUCCESS;
+ }
+ pj_lock_release(tpmgr->lock);
+ }
+
+ return status;
+}
+
+/*
+ * Return number of transports currently registered to the transport
+ * manager.
+ */
+PJ_DEF(unsigned) pjsip_tpmgr_get_transport_count(pjsip_tpmgr *mgr)
+{
+ pj_hash_iterator_t itr_val;
+ pj_hash_iterator_t *itr;
+ int nr_of_transports = 0;
+
+ pj_lock_acquire(mgr->lock);
+
+ itr = pj_hash_first(mgr->table, &itr_val);
+ while (itr) {
+ nr_of_transports++;
+ itr = pj_hash_next(mgr->table, itr);
+ }
+
+ pj_lock_release(mgr->lock);
+
+ return nr_of_transports;
+}
+
+/*
+ * pjsip_tpmgr_destroy()
+ *
+ * Destroy transport manager.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_destroy( pjsip_tpmgr *mgr )
+{
+ pj_hash_iterator_t itr_val;
+ pj_hash_iterator_t *itr;
+ pjsip_tpfactory *factory;
+ pjsip_endpoint *endpt = mgr->endpt;
+
+ PJ_LOG(5, (THIS_FILE, "Destroying transport manager"));
+
+ pj_lock_acquire(mgr->lock);
+
+ /*
+ * Destroy all transports.
+ */
+ itr = pj_hash_first(mgr->table, &itr_val);
+ while (itr != NULL) {
+ pj_hash_iterator_t *next;
+ pjsip_transport *transport;
+
+ transport = (pjsip_transport*) pj_hash_this(mgr->table, itr);
+
+ next = pj_hash_next(mgr->table, itr);
+
+ destroy_transport(mgr, transport);
+
+ itr = next;
+ }
+
+ /*
+ * Destroy all factories/listeners.
+ */
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+ pjsip_tpfactory *next = factory->next;
+
+ factory->destroy(factory);
+
+ factory = next;
+ }
+
+ pj_lock_release(mgr->lock);
+ pj_lock_destroy(mgr->lock);
+
+ /* Unregister mod_msg_print. */
+ if (mod_msg_print.id != -1) {
+ pjsip_endpt_unregister_module(endpt, &mod_msg_print);
+ }
+
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ /* If you encounter assert error on this line, it means there are
+ * leakings in transmit data (i.e. some transmit data have not been
+ * destroyed).
+ */
+ //pj_assert(pj_atomic_get(mgr->tdata_counter) == 0);
+ if (pj_atomic_get(mgr->tdata_counter) != 0) {
+ PJ_LOG(3,(THIS_FILE, "Warning: %d transmit buffer(s) not freed!",
+ pj_atomic_get(mgr->tdata_counter)));
+ }
+#endif
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * pjsip_tpmgr_receive_packet()
+ *
+ * Called by tranports when they receive a new packet.
+ */
+PJ_DEF(pj_ssize_t) pjsip_tpmgr_receive_packet( pjsip_tpmgr *mgr,
+ pjsip_rx_data *rdata)
+{
+ pjsip_transport *tr = rdata->tp_info.transport;
+
+ char *current_pkt;
+ pj_size_t remaining_len;
+ pj_size_t total_processed = 0;
+
+ /* Check size. */
+ pj_assert(rdata->pkt_info.len > 0);
+ if (rdata->pkt_info.len <= 0)
+ return -1;
+
+ current_pkt = rdata->pkt_info.packet;
+ remaining_len = rdata->pkt_info.len;
+
+ /* Must NULL terminate buffer. This is the requirement of the
+ * parser etc.
+ */
+ current_pkt[remaining_len] = '\0';
+
+ /* Process all message fragments. */
+ while (remaining_len > 0) {
+
+ pjsip_msg *msg;
+ char *p, *end;
+ char saved;
+ pj_size_t msg_fragment_size;
+
+ /* Skip leading newlines as pjsip_find_msg() currently can't
+ * handle leading newlines.
+ */
+ for (p=current_pkt, end=p+remaining_len; p!=end; ++p) {
+ if (*p != '\r' && *p != '\n')
+ break;
+ }
+ if (p!=current_pkt) {
+ remaining_len -= (p - current_pkt);
+ total_processed += (p - current_pkt);
+ current_pkt = p;
+ if (remaining_len == 0) {
+ return total_processed;
+ }
+ }
+
+ /* Initialize default fragment size. */
+ msg_fragment_size = remaining_len;
+
+ /* Clear and init msg_info in rdata.
+ * Endpoint might inspect the values there when we call the callback
+ * to report some errors.
+ */
+ pj_bzero(&rdata->msg_info, sizeof(rdata->msg_info));
+ pj_list_init(&rdata->msg_info.parse_err);
+ rdata->msg_info.msg_buf = current_pkt;
+ rdata->msg_info.len = remaining_len;
+
+ /* For TCP transport, check if the whole message has been received. */
+ if ((tr->flag & PJSIP_TRANSPORT_DATAGRAM) == 0) {
+ pj_status_t msg_status;
+ msg_status = pjsip_find_msg(current_pkt, remaining_len, PJ_FALSE,
+ &msg_fragment_size);
+ if (msg_status != PJ_SUCCESS) {
+ if (remaining_len == PJSIP_MAX_PKT_LEN) {
+ mgr->on_rx_msg(mgr->endpt, PJSIP_ERXOVERFLOW, rdata);
+ /* Exhaust all data. */
+ return rdata->pkt_info.len;
+ } else {
+ /* Not enough data in packet. */
+ return total_processed;
+ }
+ }
+ }
+
+ /* Update msg_info. */
+ rdata->msg_info.len = msg_fragment_size;
+
+ /* Null terminate packet */
+ saved = current_pkt[msg_fragment_size];
+ current_pkt[msg_fragment_size] = '\0';
+
+ /* Parse the message. */
+ rdata->msg_info.msg = msg =
+ pjsip_parse_rdata( current_pkt, msg_fragment_size, rdata);
+
+ /* Restore null termination */
+ current_pkt[msg_fragment_size] = saved;
+
+ /* Check for parsing syntax error */
+ if (msg==NULL || !pj_list_empty(&rdata->msg_info.parse_err)) {
+ pjsip_parser_err_report *err;
+ char buf[128];
+ pj_str_t tmp;
+
+ /* Gather syntax error information */
+ tmp.ptr = buf; tmp.slen = 0;
+ err = rdata->msg_info.parse_err.next;
+ while (err != &rdata->msg_info.parse_err) {
+ int len;
+ len = pj_ansi_snprintf(tmp.ptr+tmp.slen, sizeof(buf)-tmp.slen,
+ ": %s exception when parsing '%.*s' "
+ "header on line %d col %d",
+ pj_exception_id_name(err->except_code),
+ (int)err->hname.slen, err->hname.ptr,
+ err->line, err->col);
+ if (len > 0 && len < (int) (sizeof(buf)-tmp.slen)) {
+ tmp.slen += len;
+ }
+ err = err->next;
+ }
+
+ /* Only print error message if there's error.
+ * Sometimes we receive blank packets (packets with only CRLF)
+ * which were sent to keep NAT bindings.
+ */
+ if (tmp.slen) {
+ PJ_LOG(1, (THIS_FILE,
+ "Error processing %d bytes packet from %s %s:%d %.*s:\n"
+ "%.*s\n"
+ "-- end of packet.",
+ msg_fragment_size,
+ rdata->tp_info.transport->type_name,
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ (int)tmp.slen, tmp.ptr,
+ (int)msg_fragment_size,
+ rdata->msg_info.msg_buf));
+ }
+
+ goto finish_process_fragment;
+ }
+
+ /* Perform basic header checking. */
+ if (rdata->msg_info.cid == NULL ||
+ rdata->msg_info.cid->id.slen == 0 ||
+ rdata->msg_info.from == NULL ||
+ rdata->msg_info.to == NULL ||
+ rdata->msg_info.via == NULL ||
+ rdata->msg_info.cseq == NULL)
+ {
+ mgr->on_rx_msg(mgr->endpt, PJSIP_EMISSINGHDR, rdata);
+ goto finish_process_fragment;
+ }
+
+ /* For request: */
+ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) {
+ /* always add received parameter to the via. */
+ pj_strdup2(rdata->tp_info.pool,
+ &rdata->msg_info.via->recvd_param,
+ rdata->pkt_info.src_name);
+
+ /* RFC 3581:
+ * If message contains "rport" param, put the received port there.
+ */
+ if (rdata->msg_info.via->rport_param == 0) {
+ rdata->msg_info.via->rport_param = rdata->pkt_info.src_port;
+ }
+ } else {
+ /* Drop malformed responses */
+ if (rdata->msg_info.msg->line.status.code < 100 ||
+ rdata->msg_info.msg->line.status.code >= 700)
+ {
+ mgr->on_rx_msg(mgr->endpt, PJSIP_EINVALIDSTATUS, rdata);
+ goto finish_process_fragment;
+ }
+ }
+
+ /* Drop response message if it has more than one Via.
+ */
+ /* This is wrong. Proxy DOES receive responses with multiple
+ * Via headers! Thanks Aldo <acampi at deis.unibo.it> for pointing
+ * this out.
+
+ if (msg->type == PJSIP_RESPONSE_MSG) {
+ pjsip_hdr *hdr;
+ hdr = (pjsip_hdr*)rdata->msg_info.via->next;
+ if (hdr != &msg->hdr) {
+ hdr = pjsip_msg_find_hdr(msg, PJSIP_H_VIA, hdr);
+ if (hdr) {
+ mgr->on_rx_msg(mgr->endpt, PJSIP_EMULTIPLEVIA, rdata);
+ goto finish_process_fragment;
+ }
+ }
+ }
+ */
+
+ /* Call the transport manager's upstream message callback.
+ */
+ mgr->on_rx_msg(mgr->endpt, PJ_SUCCESS, rdata);
+
+
+finish_process_fragment:
+ total_processed += msg_fragment_size;
+ current_pkt += msg_fragment_size;
+ remaining_len -= msg_fragment_size;
+
+ } /* while (rdata->pkt_info.len > 0) */
+
+
+ return total_processed;
+}
+
+
+/*
+ * pjsip_tpmgr_acquire_transport()
+ *
+ * Get transport suitable to communicate to remote. Create a new one
+ * if necessary.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr,
+ pjsip_transport_type_e type,
+ const pj_sockaddr_t *remote,
+ int addr_len,
+ const pjsip_tpselector *sel,
+ pjsip_transport **tp)
+{
+ return pjsip_tpmgr_acquire_transport2(mgr, type, remote, addr_len, sel,
+ NULL, tp);
+}
+
+/*
+ * pjsip_tpmgr_acquire_transport2()
+ *
+ * Get transport suitable to communicate to remote. Create a new one
+ * if necessary.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr,
+ pjsip_transport_type_e type,
+ const pj_sockaddr_t *remote,
+ int addr_len,
+ const pjsip_tpselector *sel,
+ pjsip_tx_data *tdata,
+ pjsip_transport **tp)
+{
+ pjsip_tpfactory *factory;
+ pj_status_t status;
+
+ TRACE_((THIS_FILE,"Acquiring transport type=%s, remote=%s:%d",
+ pjsip_transport_get_type_name(type),
+ addr_string(remote),
+ pj_sockaddr_get_port(remote)));
+
+ pj_lock_acquire(mgr->lock);
+
+ /* If transport is specified, then just use it if it is suitable
+ * for the destination.
+ */
+ if (sel && sel->type == PJSIP_TPSELECTOR_TRANSPORT &&
+ sel->u.transport)
+ {
+ pjsip_transport *seltp = sel->u.transport;
+
+ /* See if the transport is (not) suitable */
+ if (seltp->key.type != type) {
+ pj_lock_release(mgr->lock);
+ return PJSIP_ETPNOTSUITABLE;
+ }
+
+ /* We could also verify that the destination address is reachable
+ * from this transport (i.e. both are equal), but if application
+ * has requested a specific transport to be used, assume that
+ * it knows what to do.
+ *
+ * In other words, I don't think destination verification is a good
+ * idea for now.
+ */
+
+ /* Transport looks to be suitable to use, so just use it. */
+ pjsip_transport_add_ref(seltp);
+ pj_lock_release(mgr->lock);
+ *tp = seltp;
+
+ TRACE_((THIS_FILE, "Transport %s acquired", seltp->obj_name));
+ return PJ_SUCCESS;
+
+
+ } else if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER &&
+ sel->u.listener)
+ {
+ /* Application has requested that a specific listener is to
+ * be used. In this case, skip transport hash table lookup.
+ */
+
+ /* Verify that the listener type matches the destination type */
+ if (sel->u.listener->type != type) {
+ pj_lock_release(mgr->lock);
+ return PJSIP_ETPNOTSUITABLE;
+ }
+
+ /* We'll use this listener to create transport */
+ factory = sel->u.listener;
+
+ } else {
+
+ /*
+ * This is the "normal" flow, where application doesn't specify
+ * specific transport/listener to be used to send message to.
+ * In this case, lookup the transport from the hash table.
+ */
+ pjsip_transport_key key;
+ int key_len;
+ pjsip_transport *transport;
+
+ pj_bzero(&key, sizeof(key));
+ key_len = sizeof(key.type) + addr_len;
+
+ /* First try to get exact destination. */
+ key.type = type;
+ pj_memcpy(&key.rem_addr, remote, addr_len);
+
+ transport = (pjsip_transport*)
+ pj_hash_get(mgr->table, &key, key_len, NULL);
+
+ if (transport == NULL) {
+ unsigned flag = pjsip_transport_get_flag_from_type(type);
+ const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote;
+
+
+ /* Ignore address for loop transports. */
+ if (type == PJSIP_TRANSPORT_LOOP ||
+ type == PJSIP_TRANSPORT_LOOP_DGRAM)
+ {
+ pj_sockaddr *addr = &key.rem_addr;
+
+ pj_bzero(addr, addr_len);
+ key_len = sizeof(key.type) + addr_len;
+ transport = (pjsip_transport*)
+ pj_hash_get(mgr->table, &key, key_len, NULL);
+ }
+ /* For datagram transports, try lookup with zero address.
+ */
+ else if (flag & PJSIP_TRANSPORT_DATAGRAM)
+ {
+ pj_sockaddr *addr = &key.rem_addr;
+
+ pj_bzero(addr, addr_len);
+ addr->addr.sa_family = remote_addr->addr.sa_family;
+
+ key_len = sizeof(key.type) + addr_len;
+ transport = (pjsip_transport*)
+ pj_hash_get(mgr->table, &key, key_len, NULL);
+ }
+ }
+
+ if (transport!=NULL && !transport->is_shutdown) {
+ /*
+ * Transport found!
+ */
+ pjsip_transport_add_ref(transport);
+ pj_lock_release(mgr->lock);
+ *tp = transport;
+
+ TRACE_((THIS_FILE, "Transport %s acquired", transport->obj_name));
+ return PJ_SUCCESS;
+ }
+
+ /*
+ * Transport not found!
+ * Find factory that can create such transport.
+ */
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+ if (factory->type == type)
+ break;
+ factory = factory->next;
+ }
+
+ if (factory == &mgr->factory_list) {
+ /* No factory can create the transport! */
+ pj_lock_release(mgr->lock);
+ TRACE_((THIS_FILE, "No suitable factory was found either"));
+ return PJSIP_EUNSUPTRANSPORT;
+ }
+ }
+
+ TRACE_((THIS_FILE, "Creating new transport from factory"));
+
+ /* Request factory to create transport. */
+ if (factory->create_transport2) {
+ status = factory->create_transport2(factory, mgr, mgr->endpt,
+ (const pj_sockaddr*) remote,
+ addr_len, tdata, tp);
+ } else {
+ status = factory->create_transport(factory, mgr, mgr->endpt,
+ (const pj_sockaddr*) remote,
+ addr_len, tp);
+ }
+ if (status == PJ_SUCCESS) {
+ PJ_ASSERT_ON_FAIL(tp!=NULL,
+ {pj_lock_release(mgr->lock); return PJ_EBUG;});
+ pjsip_transport_add_ref(*tp);
+ }
+ pj_lock_release(mgr->lock);
+ return status;
+}
+
+/**
+ * Dump transport info.
+ */
+PJ_DEF(void) pjsip_tpmgr_dump_transports(pjsip_tpmgr *mgr)
+{
+#if PJ_LOG_MAX_LEVEL >= 3
+ pj_hash_iterator_t itr_val;
+ pj_hash_iterator_t *itr;
+ pjsip_tpfactory *factory;
+
+ pj_lock_acquire(mgr->lock);
+
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ PJ_LOG(3,(THIS_FILE, " Outstanding transmit buffers: %d",
+ pj_atomic_get(mgr->tdata_counter)));
+#endif
+
+ PJ_LOG(3, (THIS_FILE, " Dumping listeners:"));
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+ PJ_LOG(3, (THIS_FILE, " %s %s:%.*s:%d",
+ factory->obj_name,
+ factory->type_name,
+ (int)factory->addr_name.host.slen,
+ factory->addr_name.host.ptr,
+ (int)factory->addr_name.port));
+ factory = factory->next;
+ }
+
+ itr = pj_hash_first(mgr->table, &itr_val);
+ if (itr) {
+ PJ_LOG(3, (THIS_FILE, " Dumping transports:"));
+
+ do {
+ pjsip_transport *t = (pjsip_transport*)
+ pj_hash_this(mgr->table, itr);
+
+ PJ_LOG(3, (THIS_FILE, " %s %s (refcnt=%d%s)",
+ t->obj_name,
+ t->info,
+ pj_atomic_get(t->ref_cnt),
+ (t->idle_timer.id ? " [idle]" : "")));
+
+ itr = pj_hash_next(mgr->table, itr);
+ } while (itr);
+ }
+
+ pj_lock_release(mgr->lock);
+#else
+ PJ_UNUSED_ARG(mgr);
+#endif
+}
+
+/**
+ * Set callback of global transport state notification.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_set_state_cb(pjsip_tpmgr *mgr,
+ pjsip_tp_state_callback cb)
+{
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ mgr->tp_state_cb = cb;
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Get callback of global transport state notification.
+ */
+PJ_DEF(pjsip_tp_state_callback) pjsip_tpmgr_get_state_cb(
+ const pjsip_tpmgr *mgr)
+{
+ PJ_ASSERT_RETURN(mgr, NULL);
+
+ return mgr->tp_state_cb;
+}
+
+
+/**
+ * Allocate and init transport data.
+ */
+static void init_tp_data(pjsip_transport *tp)
+{
+ transport_data *tp_data;
+
+ pj_assert(tp && !tp->data);
+
+ tp_data = PJ_POOL_ZALLOC_T(tp->pool, transport_data);
+ pj_list_init(&tp_data->st_listeners);
+ pj_list_init(&tp_data->st_listeners_empty);
+ tp->data = tp_data;
+}
+
+
+static void tp_state_callback(pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info)
+{
+ transport_data *tp_data;
+
+ pj_lock_acquire(tp->lock);
+
+ tp_data = (transport_data*)tp->data;
+
+ /* Notify the transport state listeners, if any. */
+ if (!tp_data || pj_list_empty(&tp_data->st_listeners)) {
+ goto on_return;
+ } else {
+ pjsip_transport_state_info st_info;
+ tp_state_listener *st_listener = tp_data->st_listeners.next;
+
+ /* As we need to put the user data into the transport state info,
+ * let's use a copy of transport state info.
+ */
+ pj_memcpy(&st_info, info, sizeof(st_info));
+ while (st_listener != &tp_data->st_listeners) {
+ st_info.user_data = st_listener->user_data;
+ (*st_listener->cb)(tp, state, &st_info);
+
+ st_listener = st_listener->next;
+ }
+ }
+
+on_return:
+ pj_lock_release(tp->lock);
+}
+
+
+/**
+ * Add a listener to the specified transport for transport state notification.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_add_state_listener (
+ pjsip_transport *tp,
+ pjsip_tp_state_callback cb,
+ void *user_data,
+ pjsip_tp_state_listener_key **key)
+{
+ transport_data *tp_data;
+ tp_state_listener *entry;
+
+ PJ_ASSERT_RETURN(tp && cb && key, PJ_EINVAL);
+
+ pj_lock_acquire(tp->lock);
+
+ /* Init transport data, if it hasn't */
+ if (!tp->data)
+ init_tp_data(tp);
+
+ tp_data = (transport_data*)tp->data;
+
+ /* Init the new listener entry. Use available empty slot, if any,
+ * otherwise allocate it using the transport pool.
+ */
+ if (!pj_list_empty(&tp_data->st_listeners_empty)) {
+ entry = tp_data->st_listeners_empty.next;
+ pj_list_erase(entry);
+ } else {
+ entry = PJ_POOL_ZALLOC_T(tp->pool, tp_state_listener);
+ }
+ entry->cb = cb;
+ entry->user_data = user_data;
+
+ /* Add the new listener entry to the listeners list */
+ pj_list_push_back(&tp_data->st_listeners, entry);
+
+ *key = entry;
+
+ pj_lock_release(tp->lock);
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Remove a listener from the specified transport for transport state
+ * notification.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_remove_state_listener (
+ pjsip_transport *tp,
+ pjsip_tp_state_listener_key *key,
+ const void *user_data)
+{
+ transport_data *tp_data;
+ tp_state_listener *entry;
+
+ PJ_ASSERT_RETURN(tp && key, PJ_EINVAL);
+
+ pj_lock_acquire(tp->lock);
+
+ tp_data = (transport_data*)tp->data;
+
+ /* Transport data is NULL or no registered listener? */
+ if (!tp_data || pj_list_empty(&tp_data->st_listeners)) {
+ pj_lock_release(tp->lock);
+ return PJ_ENOTFOUND;
+ }
+
+ entry = (tp_state_listener*)key;
+
+ /* Validate the user data */
+ if (entry->user_data != user_data) {
+ pj_assert(!"Invalid transport state listener key");
+ pj_lock_release(tp->lock);
+ return PJ_EBUG;
+ }
+
+ /* Reset the entry and move it to the empty list */
+ entry->cb = NULL;
+ entry->user_data = NULL;
+ pj_list_erase(entry);
+ pj_list_push_back(&tp_data->st_listeners_empty, entry);
+
+ pj_lock_release(tp->lock);
+
+ return PJ_SUCCESS;
+}
diff --git a/pjsip/src/pjsip/sip_transport_loop.c b/pjsip/src/pjsip/sip_transport_loop.c
new file mode 100644
index 0000000..498b529
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_loop.c
@@ -0,0 +1,509 @@
+/* $Id: sip_transport_loop.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_transport_loop.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pj/pool.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/lock.h>
+#include <pj/assert.h>
+#include <pj/compat/socket.h>
+
+
+#define ADDR_LOOP "128.0.0.1"
+#define ADDR_LOOP_DGRAM "129.0.0.1"
+
+
+/** This structure describes incoming packet. */
+struct recv_list
+{
+ PJ_DECL_LIST_MEMBER(struct recv_list);
+ pjsip_rx_data rdata;
+};
+
+/** This structure is used to keep delayed send failure. */
+struct send_list
+{
+ PJ_DECL_LIST_MEMBER(struct send_list);
+ pj_time_val sent_time;
+ pj_ssize_t sent;
+ pjsip_tx_data *tdata;
+ void *token;
+ void (*callback)(pjsip_transport*, void*, pj_ssize_t);
+};
+
+/** This structure describes the loop transport. */
+struct loop_transport
+{
+ pjsip_transport base;
+ pj_pool_t *pool;
+ pj_thread_t *thread;
+ pj_bool_t thread_quit_flag;
+ pj_bool_t discard;
+ int fail_mode;
+ unsigned recv_delay;
+ unsigned send_delay;
+ struct recv_list recv_list;
+ struct send_list send_list;
+};
+
+
+/* Helper function to create "incoming" packet */
+struct recv_list *create_incoming_packet( struct loop_transport *loop,
+ pjsip_tx_data *tdata )
+{
+ pj_pool_t *pool;
+ struct recv_list *pkt;
+
+ pool = pjsip_endpt_create_pool(loop->base.endpt, "rdata",
+ PJSIP_POOL_RDATA_LEN,
+ PJSIP_POOL_RDATA_INC+5);
+ if (!pool)
+ return NULL;
+
+ pkt = PJ_POOL_ZALLOC_T(pool, struct recv_list);
+
+ /* Initialize rdata. */
+ pkt->rdata.tp_info.pool = pool;
+ pkt->rdata.tp_info.transport = &loop->base;
+
+ /* Copy the packet. */
+ pj_memcpy(pkt->rdata.pkt_info.packet, tdata->buf.start,
+ tdata->buf.cur - tdata->buf.start);
+ pkt->rdata.pkt_info.len = tdata->buf.cur - tdata->buf.start;
+
+ /* the source address */
+ pkt->rdata.pkt_info.src_addr.addr.sa_family = pj_AF_INET();
+
+ /* "Source address" info. */
+ pkt->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in);
+ if (loop->base.key.type == PJSIP_TRANSPORT_LOOP) {
+ pj_ansi_strcpy(pkt->rdata.pkt_info.src_name, ADDR_LOOP);
+ } else {
+ pj_ansi_strcpy(pkt->rdata.pkt_info.src_name, ADDR_LOOP_DGRAM);
+ }
+ pkt->rdata.pkt_info.src_port = loop->base.local_name.port;
+
+ /* When do we need to "deliver" this packet. */
+ pj_gettimeofday(&pkt->rdata.pkt_info.timestamp);
+ pkt->rdata.pkt_info.timestamp.msec += loop->recv_delay;
+ pj_time_val_normalize(&pkt->rdata.pkt_info.timestamp);
+
+ /* Done. */
+
+ return pkt;
+}
+
+
+/* Helper function to add pending notification callback. */
+static pj_status_t add_notification( struct loop_transport *loop,
+ pjsip_tx_data *tdata,
+ pj_ssize_t sent,
+ void *token,
+ void (*callback)(pjsip_transport*,
+ void*, pj_ssize_t))
+{
+ struct send_list *sent_status;
+
+ pjsip_tx_data_add_ref(tdata);
+ pj_lock_acquire(tdata->lock);
+ sent_status = PJ_POOL_ALLOC_T(tdata->pool, struct send_list);
+ pj_lock_release(tdata->lock);
+
+ sent_status->sent = sent;
+ sent_status->tdata = tdata;
+ sent_status->token = token;
+ sent_status->callback = callback;
+
+ pj_gettimeofday(&sent_status->sent_time);
+ sent_status->sent_time.msec += loop->send_delay;
+ pj_time_val_normalize(&sent_status->sent_time);
+
+ pj_lock_acquire(loop->base.lock);
+ pj_list_push_back(&loop->send_list, sent_status);
+ pj_lock_release(loop->base.lock);
+
+ return PJ_SUCCESS;
+}
+
+/* Handler for sending outgoing message; called by transport manager. */
+static pj_status_t loop_send_msg( pjsip_transport *tp,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback cb)
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+ struct recv_list *recv_pkt;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ PJ_UNUSED_ARG(rem_addr);
+ PJ_UNUSED_ARG(addr_len);
+
+
+ /* Need to send failure? */
+ if (loop->fail_mode) {
+ if (loop->send_delay == 0) {
+ return PJ_STATUS_FROM_OS(OSERR_ECONNRESET);
+ } else {
+ add_notification(loop, tdata, -PJ_STATUS_FROM_OS(OSERR_ECONNRESET),
+ token, cb);
+
+ return PJ_EPENDING;
+ }
+ }
+
+ /* Discard any packets? */
+ if (loop->discard)
+ return PJ_SUCCESS;
+
+ /* Create rdata for the "incoming" packet. */
+ recv_pkt = create_incoming_packet(loop, tdata);
+ if (!recv_pkt)
+ return PJ_ENOMEM;
+
+ /* If delay is not configured, deliver this packet now! */
+ if (loop->recv_delay == 0) {
+ pj_ssize_t size_eaten;
+
+ size_eaten = pjsip_tpmgr_receive_packet( loop->base.tpmgr,
+ &recv_pkt->rdata);
+ pj_assert(size_eaten == recv_pkt->rdata.pkt_info.len);
+
+ pjsip_endpt_release_pool(loop->base.endpt,
+ recv_pkt->rdata.tp_info.pool);
+
+ } else {
+ /* Otherwise if delay is configured, add the "packet" to the
+ * receive list to be processed by worker thread.
+ */
+ pj_lock_acquire(loop->base.lock);
+ pj_list_push_back(&loop->recv_list, recv_pkt);
+ pj_lock_release(loop->base.lock);
+ }
+
+ if (loop->send_delay != 0) {
+ add_notification(loop, tdata, tdata->buf.cur - tdata->buf.start,
+ token, cb);
+ return PJ_EPENDING;
+ } else {
+ return PJ_SUCCESS;
+ }
+}
+
+/* Handler to destroy the transport; called by transport manager */
+static pj_status_t loop_destroy(pjsip_transport *tp)
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ loop->thread_quit_flag = 1;
+ /* Unlock transport mutex before joining thread. */
+ pj_lock_release(tp->lock);
+ pj_thread_join(loop->thread);
+ pj_thread_destroy(loop->thread);
+
+ /* Clear pending send notifications. */
+ while (!pj_list_empty(&loop->send_list)) {
+ struct send_list *node = loop->send_list.next;
+ /* Notify callback. */
+ if (node->callback) {
+ (*node->callback)(&loop->base, node->token, -PJSIP_ESHUTDOWN);
+ }
+ pj_list_erase(node);
+ pjsip_tx_data_dec_ref(node->tdata);
+ }
+
+ /* Clear "incoming" packets in the queue. */
+ while (!pj_list_empty(&loop->recv_list)) {
+ struct recv_list *node = loop->recv_list.next;
+ pj_list_erase(node);
+ pjsip_endpt_release_pool(loop->base.endpt,
+ node->rdata.tp_info.pool);
+ }
+
+ /* Self destruct.. heheh.. */
+ pj_lock_destroy(loop->base.lock);
+ pj_atomic_destroy(loop->base.ref_cnt);
+ pjsip_endpt_release_pool(loop->base.endpt, loop->base.pool);
+
+ return PJ_SUCCESS;
+}
+
+/* Worker thread for loop transport. */
+static int loop_transport_worker_thread(void *arg)
+{
+ struct loop_transport *loop = (struct loop_transport*) arg;
+ struct recv_list r;
+ struct send_list s;
+
+ pj_list_init(&r);
+ pj_list_init(&s);
+
+ while (!loop->thread_quit_flag) {
+ pj_time_val now;
+
+ pj_thread_sleep(1);
+ pj_gettimeofday(&now);
+
+ pj_lock_acquire(loop->base.lock);
+
+ /* Move expired send notification to local list. */
+ while (!pj_list_empty(&loop->send_list)) {
+ struct send_list *node = loop->send_list.next;
+
+ /* Break when next node time is greater than now. */
+ if (PJ_TIME_VAL_GTE(node->sent_time, now))
+ break;
+
+ /* Delete this from the list. */
+ pj_list_erase(node);
+
+ /* Add to local list. */
+ pj_list_push_back(&s, node);
+ }
+
+ /* Move expired "incoming" packet to local list. */
+ while (!pj_list_empty(&loop->recv_list)) {
+ struct recv_list *node = loop->recv_list.next;
+
+ /* Break when next node time is greater than now. */
+ if (PJ_TIME_VAL_GTE(node->rdata.pkt_info.timestamp, now))
+ break;
+
+ /* Delete this from the list. */
+ pj_list_erase(node);
+
+ /* Add to local list. */
+ pj_list_push_back(&r, node);
+
+ }
+
+ pj_lock_release(loop->base.lock);
+
+ /* Process send notification and incoming packet notification
+ * without holding down the loop's mutex.
+ */
+ while (!pj_list_empty(&s)) {
+ struct send_list *node = s.next;
+
+ pj_list_erase(node);
+
+ /* Notify callback. */
+ if (node->callback) {
+ (*node->callback)(&loop->base, node->token, node->sent);
+ }
+
+ /* Decrement tdata reference counter. */
+ pjsip_tx_data_dec_ref(node->tdata);
+ }
+
+ /* Process "incoming" packet. */
+ while (!pj_list_empty(&r)) {
+ struct recv_list *node = r.next;
+ pj_ssize_t size_eaten;
+
+ pj_list_erase(node);
+
+ /* Notify transport manager about the "incoming packet" */
+ size_eaten = pjsip_tpmgr_receive_packet(loop->base.tpmgr,
+ &node->rdata);
+
+ /* Must "eat" all the packets. */
+ pj_assert(size_eaten == node->rdata.pkt_info.len);
+
+ /* Done. */
+ pjsip_endpt_release_pool(loop->base.endpt,
+ node->rdata.tp_info.pool);
+ }
+ }
+
+ return 0;
+}
+
+
+/* Start loop transport. */
+PJ_DEF(pj_status_t) pjsip_loop_start( pjsip_endpoint *endpt,
+ pjsip_transport **transport)
+{
+ pj_pool_t *pool;
+ struct loop_transport *loop;
+ pj_status_t status;
+
+ /* Create pool. */
+ pool = pjsip_endpt_create_pool(endpt, "loop", 4000, 4000);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ /* Create the loop structure. */
+ loop = PJ_POOL_ZALLOC_T(pool, struct loop_transport);
+
+ /* Initialize transport properties. */
+ pj_ansi_snprintf(loop->base.obj_name, sizeof(loop->base.obj_name),
+ "loop%p", loop);
+ loop->base.pool = pool;
+ status = pj_atomic_create(pool, 0, &loop->base.ref_cnt);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ status = pj_lock_create_recursive_mutex(pool, "loop", &loop->base.lock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ loop->base.key.type = PJSIP_TRANSPORT_LOOP_DGRAM;
+ //loop->base.key.rem_addr.sa_family = pj_AF_INET();
+ loop->base.type_name = "LOOP-DGRAM";
+ loop->base.info = "LOOP-DGRAM";
+ loop->base.flag = PJSIP_TRANSPORT_DATAGRAM;
+ loop->base.local_name.host = pj_str(ADDR_LOOP_DGRAM);
+ loop->base.local_name.port =
+ pjsip_transport_get_default_port_for_type((pjsip_transport_type_e)
+ loop->base.key.type);
+ loop->base.addr_len = sizeof(pj_sockaddr_in);
+ loop->base.dir = PJSIP_TP_DIR_NONE;
+ loop->base.endpt = endpt;
+ loop->base.tpmgr = pjsip_endpt_get_tpmgr(endpt);
+ loop->base.send_msg = &loop_send_msg;
+ loop->base.destroy = &loop_destroy;
+
+ pj_list_init(&loop->recv_list);
+ pj_list_init(&loop->send_list);
+
+ /* Create worker thread. */
+ status = pj_thread_create(pool, "loop",
+ &loop_transport_worker_thread, loop, 0,
+ PJ_THREAD_SUSPENDED, &loop->thread);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Register to transport manager. */
+ status = pjsip_transport_register( loop->base.tpmgr, &loop->base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Start the thread. */
+ status = pj_thread_resume(loop->thread);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /*
+ * Done.
+ */
+
+ if (transport)
+ *transport = &loop->base;
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (loop->base.lock)
+ pj_lock_destroy(loop->base.lock);
+ if (loop->thread)
+ pj_thread_destroy(loop->thread);
+ if (loop->base.ref_cnt)
+ pj_atomic_destroy(loop->base.ref_cnt);
+ pjsip_endpt_release_pool(endpt, loop->pool);
+ return status;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_loop_set_discard( pjsip_transport *tp,
+ pj_bool_t discard,
+ pj_bool_t *prev_value )
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ if (prev_value)
+ *prev_value = loop->discard;
+ loop->discard = discard;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_loop_set_failure( pjsip_transport *tp,
+ int fail_flag,
+ int *prev_value )
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ if (prev_value)
+ *prev_value = loop->fail_mode;
+ loop->fail_mode = fail_flag;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_loop_set_recv_delay( pjsip_transport *tp,
+ unsigned delay,
+ unsigned *prev_value)
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ if (prev_value)
+ *prev_value = loop->recv_delay;
+ loop->recv_delay = delay;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_loop_set_send_callback_delay( pjsip_transport *tp,
+ unsigned delay,
+ unsigned *prev_value)
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ if (prev_value)
+ *prev_value = loop->send_delay;
+ loop->send_delay = delay;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_loop_set_delay( pjsip_transport *tp, unsigned delay )
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ loop->recv_delay = delay;
+ loop->send_delay = delay;
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsip/sip_transport_tcp.c b/pjsip/src/pjsip/sip_transport_tcp.c
new file mode 100644
index 0000000..141434b
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_tcp.c
@@ -0,0 +1,1417 @@
+/* $Id: sip_transport_tcp.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_transport_tcp.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pj/compat/socket.h>
+#include <pj/addr_resolv.h>
+#include <pj/activesock.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+/* Only declare the API if PJ_HAS_TCP is true */
+#if defined(PJ_HAS_TCP) && PJ_HAS_TCP!=0
+
+
+#define THIS_FILE "sip_transport_tcp.c"
+
+#define MAX_ASYNC_CNT 16
+#define POOL_LIS_INIT 512
+#define POOL_LIS_INC 512
+#define POOL_TP_INIT 512
+#define POOL_TP_INC 512
+
+struct tcp_listener;
+struct tcp_transport;
+
+
+/*
+ * This is the TCP listener, which is a "descendant" of pjsip_tpfactory (the
+ * SIP transport factory).
+ */
+struct tcp_listener
+{
+ pjsip_tpfactory factory;
+ pj_bool_t is_registered;
+ pjsip_endpoint *endpt;
+ pjsip_tpmgr *tpmgr;
+ pj_activesock_t *asock;
+ pj_qos_type qos_type;
+ pj_qos_params qos_params;
+};
+
+
+/*
+ * This structure is used to keep delayed transmit operation in a list.
+ * A delayed transmission occurs when application sends tx_data when
+ * the TCP connect/establishment is still in progress. These delayed
+ * transmission will be "flushed" once the socket is connected (either
+ * successfully or with errors).
+ */
+struct delayed_tdata
+{
+ PJ_DECL_LIST_MEMBER(struct delayed_tdata);
+ pjsip_tx_data_op_key *tdata_op_key;
+};
+
+
+/*
+ * This structure describes the TCP transport, and it's descendant of
+ * pjsip_transport.
+ */
+struct tcp_transport
+{
+ pjsip_transport base;
+ pj_bool_t is_server;
+
+ /* Do not save listener instance in the transport, because
+ * listener might be destroyed during transport's lifetime.
+ * See http://trac.pjsip.org/repos/ticket/491
+ struct tcp_listener *listener;
+ */
+
+ pj_bool_t is_registered;
+ pj_bool_t is_closing;
+ pj_status_t close_reason;
+ pj_sock_t sock;
+ pj_activesock_t *asock;
+ pj_bool_t has_pending_connect;
+
+ /* Keep-alive timer. */
+ pj_timer_entry ka_timer;
+ pj_time_val last_activity;
+ pjsip_tx_data_op_key ka_op_key;
+ pj_str_t ka_pkt;
+
+ /* TCP transport can only have one rdata!
+ * Otherwise chunks of incoming PDU may be received on different
+ * buffer.
+ */
+ pjsip_rx_data rdata;
+
+ /* Pending transmission list. */
+ struct delayed_tdata delayed_list;
+};
+
+
+/****************************************************************************
+ * PROTOTYPES
+ */
+
+/* This callback is called when pending accept() operation completes. */
+static pj_bool_t on_accept_complete(pj_activesock_t *asock,
+ pj_sock_t newsock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len);
+
+/* This callback is called by transport manager to destroy listener */
+static pj_status_t lis_destroy(pjsip_tpfactory *factory);
+
+/* This callback is called by transport manager to create transport */
+static pj_status_t lis_create_transport(pjsip_tpfactory *factory,
+ pjsip_tpmgr *mgr,
+ pjsip_endpoint *endpt,
+ const pj_sockaddr *rem_addr,
+ int addr_len,
+ pjsip_transport **transport);
+
+/* Common function to create and initialize transport */
+static pj_status_t tcp_create(struct tcp_listener *listener,
+ pj_pool_t *pool,
+ pj_sock_t sock, pj_bool_t is_server,
+ const pj_sockaddr_in *local,
+ const pj_sockaddr_in *remote,
+ struct tcp_transport **p_tcp);
+
+
+static void tcp_perror(const char *sender, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
+}
+
+
+static void sockaddr_to_host_port( pj_pool_t *pool,
+ pjsip_host_port *host_port,
+ const pj_sockaddr_in *addr )
+{
+ host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4);
+ pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 2);
+ host_port->host.slen = pj_ansi_strlen(host_port->host.ptr);
+ host_port->port = pj_sockaddr_get_port(addr);
+}
+
+
+static void tcp_init_shutdown(struct tcp_transport *tcp, pj_status_t status)
+{
+ pjsip_tp_state_callback state_cb;
+
+ if (tcp->close_reason == PJ_SUCCESS)
+ tcp->close_reason = status;
+
+ if (tcp->base.is_shutdown)
+ return;
+
+ /* Prevent immediate transport destroy by application, as transport
+ * state notification callback may be stacked and transport instance
+ * must remain valid at any point in the callback.
+ */
+ pjsip_transport_add_ref(&tcp->base);
+
+ /* Notify application of transport disconnected state */
+ state_cb = pjsip_tpmgr_get_state_cb(tcp->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+
+ pj_bzero(&state_info, sizeof(state_info));
+ state_info.status = tcp->close_reason;
+ (*state_cb)(&tcp->base, PJSIP_TP_STATE_DISCONNECTED, &state_info);
+ }
+
+ /* We can not destroy the transport since high level objects may
+ * still keep reference to this transport. So we can only
+ * instruct transport manager to gracefully start the shutdown
+ * procedure for this transport.
+ */
+ pjsip_transport_shutdown(&tcp->base);
+
+ /* Now, it is ok to destroy the transport. */
+ pjsip_transport_dec_ref(&tcp->base);
+}
+
+
+/*
+ * Initialize pjsip_tcp_transport_cfg structure with default values.
+ */
+PJ_DEF(void) pjsip_tcp_transport_cfg_default(pjsip_tcp_transport_cfg *cfg,
+ int af)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+ cfg->af = af;
+ pj_sockaddr_init(cfg->af, &cfg->bind_addr, NULL, 0);
+ cfg->async_cnt = 1;
+}
+
+
+/****************************************************************************
+ * The TCP listener/transport factory.
+ */
+
+/*
+ * This is the public API to create, initialize, register, and start the
+ * TCP listener.
+ */
+PJ_DEF(pj_status_t) pjsip_tcp_transport_start3(
+ pjsip_endpoint *endpt,
+ const pjsip_tcp_transport_cfg *cfg,
+ pjsip_tpfactory **p_factory
+ )
+{
+ pj_pool_t *pool;
+ pj_sock_t sock = PJ_INVALID_SOCKET;
+ struct tcp_listener *listener;
+ pj_activesock_cfg asock_cfg;
+ pj_activesock_cb listener_cb;
+ pj_sockaddr *listener_addr;
+ int addr_len;
+ pj_status_t status;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(endpt && cfg->async_cnt, PJ_EINVAL);
+
+ /* Verify that address given in a_name (if any) is valid */
+ if (cfg->addr_name.host.slen) {
+ pj_sockaddr tmp;
+
+ status = pj_sockaddr_init(cfg->af, &tmp, &cfg->addr_name.host,
+ (pj_uint16_t)cfg->addr_name.port);
+ if (status != PJ_SUCCESS || !pj_sockaddr_has_addr(&tmp) ||
+ (cfg->af==pj_AF_INET() &&
+ tmp.ipv4.sin_addr.s_addr==PJ_INADDR_NONE))
+ {
+ /* Invalid address */
+ return PJ_EINVAL;
+ }
+ }
+
+ pool = pjsip_endpt_create_pool(endpt, "tcplis", POOL_LIS_INIT,
+ POOL_LIS_INC);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+
+ listener = PJ_POOL_ZALLOC_T(pool, struct tcp_listener);
+ listener->factory.pool = pool;
+ listener->factory.type = PJSIP_TRANSPORT_TCP;
+ listener->factory.type_name = "tcp";
+ listener->factory.flag =
+ pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TCP);
+ listener->qos_type = cfg->qos_type;
+ pj_memcpy(&listener->qos_params, &cfg->qos_params,
+ sizeof(cfg->qos_params));
+
+ pj_ansi_strcpy(listener->factory.obj_name, "tcplis");
+
+ status = pj_lock_create_recursive_mutex(pool, "tcplis",
+ &listener->factory.lock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Create socket */
+ status = pj_sock_socket(cfg->af, pj_SOCK_STREAM(), 0, &sock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Apply QoS, if specified */
+ status = pj_sock_apply_qos2(sock, cfg->qos_type, &cfg->qos_params,
+ 2, listener->factory.obj_name,
+ "SIP TCP listener socket");
+
+ /* Bind socket */
+ listener_addr = &listener->factory.local_addr;
+ pj_sockaddr_cp(listener_addr, &cfg->bind_addr);
+
+ status = pj_sock_bind(sock, listener_addr,
+ pj_sockaddr_get_len(listener_addr));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Retrieve the bound address */
+ addr_len = pj_sockaddr_get_len(listener_addr);
+ status = pj_sock_getsockname(sock, listener_addr, &addr_len);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* If published host/IP is specified, then use that address as the
+ * listener advertised address.
+ */
+ if (cfg->addr_name.host.slen) {
+ /* Copy the address */
+ listener->factory.addr_name = cfg->addr_name;
+ pj_strdup(listener->factory.pool, &listener->factory.addr_name.host,
+ &cfg->addr_name.host);
+ listener->factory.addr_name.port = cfg->addr_name.port;
+
+ } else {
+ /* No published address is given, use the bound address */
+
+ /* If the address returns 0.0.0.0, use the default
+ * interface address as the transport's address.
+ */
+ if (!pj_sockaddr_has_addr(listener_addr)) {
+ pj_sockaddr hostip;
+
+ status = pj_gethostip(pj_AF_INET(), &hostip);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pj_memcpy(pj_sockaddr_get_addr(listener_addr),
+ pj_sockaddr_get_addr(&hostip),
+ pj_sockaddr_get_addr_len(&hostip));
+ }
+
+ /* Save the address name */
+ sockaddr_to_host_port(listener->factory.pool,
+ &listener->factory.addr_name,
+ (pj_sockaddr_in*)listener_addr);
+ }
+
+ /* If port is zero, get the bound port */
+ if (listener->factory.addr_name.port == 0) {
+ listener->factory.addr_name.port = pj_sockaddr_get_port(listener_addr);
+ }
+
+ pj_ansi_snprintf(listener->factory.obj_name,
+ sizeof(listener->factory.obj_name),
+ "tcplis:%d", listener->factory.addr_name.port);
+
+
+ /* Start listening to the address */
+ status = pj_sock_listen(sock, PJSIP_TCP_TRANSPORT_BACKLOG);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Create active socket */
+ pj_activesock_cfg_default(&asock_cfg);
+ if (cfg->async_cnt > MAX_ASYNC_CNT)
+ asock_cfg.async_cnt = MAX_ASYNC_CNT;
+ else
+ asock_cfg.async_cnt = cfg->async_cnt;
+
+ pj_bzero(&listener_cb, sizeof(listener_cb));
+ listener_cb.on_accept_complete = &on_accept_complete;
+ status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), &asock_cfg,
+ pjsip_endpt_get_ioqueue(endpt),
+ &listener_cb, listener,
+ &listener->asock);
+
+ /* Register to transport manager */
+ listener->endpt = endpt;
+ listener->tpmgr = pjsip_endpt_get_tpmgr(endpt);
+ listener->factory.create_transport = lis_create_transport;
+ listener->factory.destroy = lis_destroy;
+ listener->is_registered = PJ_TRUE;
+ status = pjsip_tpmgr_register_tpfactory(listener->tpmgr,
+ &listener->factory);
+ if (status != PJ_SUCCESS) {
+ listener->is_registered = PJ_FALSE;
+ goto on_error;
+ }
+
+ /* Start pending accept() operations */
+ status = pj_activesock_start_accept(listener->asock, pool);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ PJ_LOG(4,(listener->factory.obj_name,
+ "SIP TCP listener ready for incoming connections at %.*s:%d",
+ (int)listener->factory.addr_name.host.slen,
+ listener->factory.addr_name.host.ptr,
+ listener->factory.addr_name.port));
+
+ /* Return the pointer to user */
+ if (p_factory) *p_factory = &listener->factory;
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (listener->asock==NULL && sock!=PJ_INVALID_SOCKET)
+ pj_sock_close(sock);
+ lis_destroy(&listener->factory);
+ return status;
+}
+
+
+/*
+ * This is the public API to create, initialize, register, and start the
+ * TCP listener.
+ */
+PJ_DEF(pj_status_t) pjsip_tcp_transport_start2(pjsip_endpoint *endpt,
+ const pj_sockaddr_in *local,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_tpfactory **p_factory)
+{
+ pjsip_tcp_transport_cfg cfg;
+
+ pjsip_tcp_transport_cfg_default(&cfg, pj_AF_INET());
+
+ if (local)
+ pj_sockaddr_cp(&cfg.bind_addr, local);
+ else
+ pj_sockaddr_init(cfg.af, &cfg.bind_addr, NULL, 0);
+
+ if (a_name)
+ pj_memcpy(&cfg.addr_name, a_name, sizeof(*a_name));
+
+ if (async_cnt)
+ cfg.async_cnt = async_cnt;
+
+ return pjsip_tcp_transport_start3(endpt, &cfg, p_factory);
+}
+
+
+/*
+ * This is the public API to create, initialize, register, and start the
+ * TCP listener.
+ */
+PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt,
+ const pj_sockaddr_in *local,
+ unsigned async_cnt,
+ pjsip_tpfactory **p_factory)
+{
+ return pjsip_tcp_transport_start2(endpt, local, NULL, async_cnt, p_factory);
+}
+
+
+/* This callback is called by transport manager to destroy listener */
+static pj_status_t lis_destroy(pjsip_tpfactory *factory)
+{
+ struct tcp_listener *listener = (struct tcp_listener *)factory;
+
+ if (listener->is_registered) {
+ pjsip_tpmgr_unregister_tpfactory(listener->tpmgr, &listener->factory);
+ listener->is_registered = PJ_FALSE;
+ }
+
+ if (listener->asock) {
+ pj_activesock_close(listener->asock);
+ listener->asock = NULL;
+ }
+
+ if (listener->factory.lock) {
+ pj_lock_destroy(listener->factory.lock);
+ listener->factory.lock = NULL;
+ }
+
+ if (listener->factory.pool) {
+ pj_pool_t *pool = listener->factory.pool;
+
+ PJ_LOG(4,(listener->factory.obj_name, "SIP TCP listener destroyed"));
+
+ listener->factory.pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/***************************************************************************/
+/*
+ * TCP Transport
+ */
+
+/*
+ * Prototypes.
+ */
+/* Called by transport manager to send message */
+static pj_status_t tcp_send_msg(pjsip_transport *transport,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback callback);
+
+/* Called by transport manager to shutdown */
+static pj_status_t tcp_shutdown(pjsip_transport *transport);
+
+/* Called by transport manager to destroy transport */
+static pj_status_t tcp_destroy_transport(pjsip_transport *transport);
+
+/* Utility to destroy transport */
+static pj_status_t tcp_destroy(pjsip_transport *transport,
+ pj_status_t reason);
+
+/* Callback on incoming data */
+static pj_bool_t on_data_read(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder);
+
+/* Callback when packet is sent */
+static pj_bool_t on_data_sent(pj_activesock_t *asock,
+ pj_ioqueue_op_key_t *send_key,
+ pj_ssize_t sent);
+
+/* Callback when connect completes */
+static pj_bool_t on_connect_complete(pj_activesock_t *asock,
+ pj_status_t status);
+
+/* TCP keep-alive timer callback */
+static void tcp_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e);
+
+/*
+ * Common function to create TCP transport, called when pending accept() and
+ * pending connect() complete.
+ */
+static pj_status_t tcp_create( struct tcp_listener *listener,
+ pj_pool_t *pool,
+ pj_sock_t sock, pj_bool_t is_server,
+ const pj_sockaddr_in *local,
+ const pj_sockaddr_in *remote,
+ struct tcp_transport **p_tcp)
+{
+ struct tcp_transport *tcp;
+ pj_ioqueue_t *ioqueue;
+ pj_activesock_cfg asock_cfg;
+ pj_activesock_cb tcp_callback;
+ const pj_str_t ka_pkt = PJSIP_TCP_KEEP_ALIVE_DATA;
+ pj_status_t status;
+
+
+ PJ_ASSERT_RETURN(sock != PJ_INVALID_SOCKET, PJ_EINVAL);
+
+
+ if (pool == NULL) {
+ pool = pjsip_endpt_create_pool(listener->endpt, "tcp",
+ POOL_TP_INIT, POOL_TP_INC);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+ }
+
+ /*
+ * Create and initialize basic transport structure.
+ */
+ tcp = PJ_POOL_ZALLOC_T(pool, struct tcp_transport);
+ tcp->is_server = is_server;
+ tcp->sock = sock;
+ /*tcp->listener = listener;*/
+ pj_list_init(&tcp->delayed_list);
+ tcp->base.pool = pool;
+
+ pj_ansi_snprintf(tcp->base.obj_name, PJ_MAX_OBJ_NAME,
+ (is_server ? "tcps%p" :"tcpc%p"), tcp);
+
+ status = pj_atomic_create(pool, 0, &tcp->base.ref_cnt);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ status = pj_lock_create_recursive_mutex(pool, "tcp", &tcp->base.lock);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ tcp->base.key.type = PJSIP_TRANSPORT_TCP;
+ pj_memcpy(&tcp->base.key.rem_addr, remote, sizeof(pj_sockaddr_in));
+ tcp->base.type_name = "tcp";
+ tcp->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TCP);
+
+ tcp->base.info = (char*) pj_pool_alloc(pool, 64);
+ pj_ansi_snprintf(tcp->base.info, 64, "TCP to %s:%d",
+ pj_inet_ntoa(remote->sin_addr),
+ (int)pj_ntohs(remote->sin_port));
+
+ tcp->base.addr_len = sizeof(pj_sockaddr_in);
+ pj_memcpy(&tcp->base.local_addr, local, sizeof(pj_sockaddr_in));
+ sockaddr_to_host_port(pool, &tcp->base.local_name, local);
+ sockaddr_to_host_port(pool, &tcp->base.remote_name, remote);
+ tcp->base.dir = is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING;
+
+ tcp->base.endpt = listener->endpt;
+ tcp->base.tpmgr = listener->tpmgr;
+ tcp->base.send_msg = &tcp_send_msg;
+ tcp->base.do_shutdown = &tcp_shutdown;
+ tcp->base.destroy = &tcp_destroy_transport;
+
+
+ /* Create active socket */
+ pj_activesock_cfg_default(&asock_cfg);
+ asock_cfg.async_cnt = 1;
+
+ pj_bzero(&tcp_callback, sizeof(tcp_callback));
+ tcp_callback.on_data_read = &on_data_read;
+ tcp_callback.on_data_sent = &on_data_sent;
+ tcp_callback.on_connect_complete = &on_connect_complete;
+
+ ioqueue = pjsip_endpt_get_ioqueue(listener->endpt);
+ status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), &asock_cfg,
+ ioqueue, &tcp_callback, tcp, &tcp->asock);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Register transport to transport manager */
+ status = pjsip_transport_register(listener->tpmgr, &tcp->base);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ tcp->is_registered = PJ_TRUE;
+
+ /* Initialize keep-alive timer */
+ tcp->ka_timer.user_data = (void*)tcp;
+ tcp->ka_timer.cb = &tcp_keep_alive_timer;
+ pj_ioqueue_op_key_init(&tcp->ka_op_key.key, sizeof(pj_ioqueue_op_key_t));
+ pj_strdup(tcp->base.pool, &tcp->ka_pkt, &ka_pkt);
+
+ /* Done setting up basic transport. */
+ *p_tcp = tcp;
+
+ PJ_LOG(4,(tcp->base.obj_name, "TCP %s transport created",
+ (tcp->is_server ? "server" : "client")));
+
+ return PJ_SUCCESS;
+
+on_error:
+ tcp_destroy(&tcp->base, status);
+ return status;
+}
+
+
+/* Flush all delayed transmision once the socket is connected. */
+static void tcp_flush_pending_tx(struct tcp_transport *tcp)
+{
+ pj_lock_acquire(tcp->base.lock);
+ while (!pj_list_empty(&tcp->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pjsip_tx_data *tdata;
+ pj_ioqueue_op_key_t *op_key;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ pending_tx = tcp->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ tdata = pending_tx->tdata_op_key->tdata;
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ /* send! */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_activesock_send(tcp->asock, op_key, tdata->buf.start,
+ &size, 0);
+ if (status != PJ_EPENDING) {
+ on_data_sent(tcp->asock, op_key, size);
+ }
+
+ }
+ pj_lock_release(tcp->base.lock);
+}
+
+
+/* Called by transport manager to destroy transport */
+static pj_status_t tcp_destroy_transport(pjsip_transport *transport)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*)transport;
+
+ /* Transport would have been unregistered by now since this callback
+ * is called by transport manager.
+ */
+ tcp->is_registered = PJ_FALSE;
+
+ return tcp_destroy(transport, tcp->close_reason);
+}
+
+
+/* Destroy TCP transport */
+static pj_status_t tcp_destroy(pjsip_transport *transport,
+ pj_status_t reason)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*)transport;
+
+ if (tcp->close_reason == 0)
+ tcp->close_reason = reason;
+
+ if (tcp->is_registered) {
+ tcp->is_registered = PJ_FALSE;
+ pjsip_transport_destroy(transport);
+
+ /* pjsip_transport_destroy will recursively call this function
+ * again.
+ */
+ return PJ_SUCCESS;
+ }
+
+ /* Mark transport as closing */
+ tcp->is_closing = PJ_TRUE;
+
+ /* Stop keep-alive timer. */
+ if (tcp->ka_timer.id) {
+ pjsip_endpt_cancel_timer(tcp->base.endpt, &tcp->ka_timer);
+ tcp->ka_timer.id = PJ_FALSE;
+ }
+
+ /* Cancel all delayed transmits */
+ while (!pj_list_empty(&tcp->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pj_ioqueue_op_key_t *op_key;
+
+ pending_tx = tcp->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ on_data_sent(tcp->asock, op_key, -reason);
+ }
+
+ if (tcp->rdata.tp_info.pool) {
+ pj_pool_release(tcp->rdata.tp_info.pool);
+ tcp->rdata.tp_info.pool = NULL;
+ }
+
+ if (tcp->asock) {
+ pj_activesock_close(tcp->asock);
+ tcp->asock = NULL;
+ tcp->sock = PJ_INVALID_SOCKET;
+ } else if (tcp->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(tcp->sock);
+ tcp->sock = PJ_INVALID_SOCKET;
+ }
+
+ if (tcp->base.lock) {
+ pj_lock_destroy(tcp->base.lock);
+ tcp->base.lock = NULL;
+ }
+
+ if (tcp->base.ref_cnt) {
+ pj_atomic_destroy(tcp->base.ref_cnt);
+ tcp->base.ref_cnt = NULL;
+ }
+
+ if (tcp->base.pool) {
+ pj_pool_t *pool;
+
+ if (reason != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(reason, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(tcp->base.obj_name,
+ "TCP transport destroyed with reason %d: %s",
+ reason, errmsg));
+
+ } else {
+
+ PJ_LOG(4,(tcp->base.obj_name,
+ "TCP transport destroyed normally"));
+
+ }
+
+ pool = tcp->base.pool;
+ tcp->base.pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This utility function creates receive data buffers and start
+ * asynchronous recv() operations from the socket. It is called after
+ * accept() or connect() operation complete.
+ */
+static pj_status_t tcp_start_read(struct tcp_transport *tcp)
+{
+ pj_pool_t *pool;
+ pj_ssize_t size;
+ pj_sockaddr_in *rem_addr;
+ void *readbuf[1];
+ pj_status_t status;
+
+ /* Init rdata */
+ pool = pjsip_endpt_create_pool(tcp->base.endpt,
+ "rtd%p",
+ PJSIP_POOL_RDATA_LEN,
+ PJSIP_POOL_RDATA_INC);
+ if (!pool) {
+ tcp_perror(tcp->base.obj_name, "Unable to create pool", PJ_ENOMEM);
+ return PJ_ENOMEM;
+ }
+
+ tcp->rdata.tp_info.pool = pool;
+
+ tcp->rdata.tp_info.transport = &tcp->base;
+ tcp->rdata.tp_info.tp_data = tcp;
+ tcp->rdata.tp_info.op_key.rdata = &tcp->rdata;
+ pj_ioqueue_op_key_init(&tcp->rdata.tp_info.op_key.op_key,
+ sizeof(pj_ioqueue_op_key_t));
+
+ tcp->rdata.pkt_info.src_addr = tcp->base.key.rem_addr;
+ tcp->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in);
+ rem_addr = (pj_sockaddr_in*) &tcp->base.key.rem_addr;
+ pj_ansi_strcpy(tcp->rdata.pkt_info.src_name,
+ pj_inet_ntoa(rem_addr->sin_addr));
+ tcp->rdata.pkt_info.src_port = pj_ntohs(rem_addr->sin_port);
+
+ size = sizeof(tcp->rdata.pkt_info.packet);
+ readbuf[0] = tcp->rdata.pkt_info.packet;
+ status = pj_activesock_start_read2(tcp->asock, tcp->base.pool, size,
+ readbuf, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ PJ_LOG(4, (tcp->base.obj_name,
+ "pj_activesock_start_read() error, status=%d",
+ status));
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* This callback is called by transport manager for the TCP factory
+ * to create outgoing transport to the specified destination.
+ */
+static pj_status_t lis_create_transport(pjsip_tpfactory *factory,
+ pjsip_tpmgr *mgr,
+ pjsip_endpoint *endpt,
+ const pj_sockaddr *rem_addr,
+ int addr_len,
+ pjsip_transport **p_transport)
+{
+ struct tcp_listener *listener;
+ struct tcp_transport *tcp;
+ pj_sock_t sock;
+ pj_sockaddr_in local_addr;
+ pj_status_t status;
+
+ /* Sanity checks */
+ PJ_ASSERT_RETURN(factory && mgr && endpt && rem_addr &&
+ addr_len && p_transport, PJ_EINVAL);
+
+ /* Check that address is a sockaddr_in */
+ PJ_ASSERT_RETURN(rem_addr->addr.sa_family == pj_AF_INET() &&
+ addr_len == sizeof(pj_sockaddr_in), PJ_EINVAL);
+
+
+ listener = (struct tcp_listener*)factory;
+
+
+ /* Create socket */
+ status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Apply QoS, if specified */
+ status = pj_sock_apply_qos2(sock, listener->qos_type,
+ &listener->qos_params,
+ 2, listener->factory.obj_name,
+ "outgoing SIP TCP socket");
+
+ /* Bind to any port */
+ status = pj_sock_bind_in(sock, 0, 0);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ /* Get the local port */
+ addr_len = sizeof(pj_sockaddr_in);
+ status = pj_sock_getsockname(sock, &local_addr, &addr_len);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ /* Initially set the address from the listener's address */
+ local_addr.sin_addr.s_addr =
+ ((pj_sockaddr_in*)&listener->factory.local_addr)->sin_addr.s_addr;
+
+ /* Create the transport descriptor */
+ status = tcp_create(listener, NULL, sock, PJ_FALSE, &local_addr,
+ (pj_sockaddr_in*)rem_addr, &tcp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Start asynchronous connect() operation */
+ tcp->has_pending_connect = PJ_TRUE;
+ status = pj_activesock_start_connect(tcp->asock, tcp->base.pool, rem_addr,
+ sizeof(pj_sockaddr_in));
+ if (status == PJ_SUCCESS) {
+ on_connect_complete(tcp->asock, PJ_SUCCESS);
+ } else if (status != PJ_EPENDING) {
+ tcp_destroy(&tcp->base, status);
+ return status;
+ }
+
+ if (tcp->has_pending_connect) {
+ /* Update (again) local address, just in case local address currently
+ * set is different now that asynchronous connect() is started.
+ */
+ addr_len = sizeof(pj_sockaddr_in);
+ if (pj_sock_getsockname(sock, &local_addr, &addr_len)==PJ_SUCCESS) {
+ pj_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tcp->base.local_addr;
+
+ /* Some systems (like old Win32 perhaps) may not set local address
+ * properly before socket is fully connected.
+ */
+ if (tp_addr->sin_addr.s_addr != local_addr.sin_addr.s_addr &&
+ local_addr.sin_addr.s_addr != 0)
+ {
+ tp_addr->sin_addr.s_addr = local_addr.sin_addr.s_addr;
+ tp_addr->sin_port = local_addr.sin_port;
+ sockaddr_to_host_port(tcp->base.pool, &tcp->base.local_name,
+ &local_addr);
+ }
+ }
+
+ PJ_LOG(4,(tcp->base.obj_name,
+ "TCP transport %.*s:%d is connecting to %.*s:%d...",
+ (int)tcp->base.local_name.host.slen,
+ tcp->base.local_name.host.ptr,
+ tcp->base.local_name.port,
+ (int)tcp->base.remote_name.host.slen,
+ tcp->base.remote_name.host.ptr,
+ tcp->base.remote_name.port));
+ }
+
+ /* Done */
+ *p_transport = &tcp->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This callback is called by active socket when pending accept() operation
+ * has completed.
+ */
+static pj_bool_t on_accept_complete(pj_activesock_t *asock,
+ pj_sock_t sock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len)
+{
+ struct tcp_listener *listener;
+ struct tcp_transport *tcp;
+ char addr[PJ_INET6_ADDRSTRLEN+10];
+ pjsip_tp_state_callback state_cb;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(src_addr_len);
+
+ listener = (struct tcp_listener*) pj_activesock_get_user_data(asock);
+
+ PJ_ASSERT_RETURN(sock != PJ_INVALID_SOCKET, PJ_TRUE);
+
+ PJ_LOG(4,(listener->factory.obj_name,
+ "TCP listener %.*s:%d: got incoming TCP connection "
+ "from %s, sock=%d",
+ (int)listener->factory.addr_name.host.slen,
+ listener->factory.addr_name.host.ptr,
+ listener->factory.addr_name.port,
+ pj_sockaddr_print(src_addr, addr, sizeof(addr), 3),
+ sock));
+
+ /* Apply QoS, if specified */
+ status = pj_sock_apply_qos2(sock, listener->qos_type,
+ &listener->qos_params,
+ 2, listener->factory.obj_name,
+ "incoming SIP TCP socket");
+
+ /*
+ * Incoming connection!
+ * Create TCP transport for the new socket.
+ */
+ status = tcp_create( listener, NULL, sock, PJ_TRUE,
+ (const pj_sockaddr_in*)&listener->factory.local_addr,
+ (const pj_sockaddr_in*)src_addr, &tcp);
+ if (status == PJ_SUCCESS) {
+ status = tcp_start_read(tcp);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(tcp->base.obj_name, "New transport cancelled"));
+ tcp_destroy(&tcp->base, status);
+ } else {
+ /* Start keep-alive timer */
+ if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) {
+ pj_time_val delay = {PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0};
+ pjsip_endpt_schedule_timer(listener->endpt,
+ &tcp->ka_timer,
+ &delay);
+ tcp->ka_timer.id = PJ_TRUE;
+ pj_gettimeofday(&tcp->last_activity);
+ }
+
+ /* Notify application of transport state accepted */
+ state_cb = pjsip_tpmgr_get_state_cb(tcp->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+
+ pj_bzero(&state_info, sizeof(state_info));
+ (*state_cb)(&tcp->base, PJSIP_TP_STATE_CONNECTED, &state_info);
+ }
+ }
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Callback from ioqueue when packet is sent.
+ */
+static pj_bool_t on_data_sent(pj_activesock_t *asock,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_sent)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*)
+ pj_activesock_get_user_data(asock);
+ pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key;
+
+ /* Note that op_key may be the op_key from keep-alive, thus
+ * it will not have tdata etc.
+ */
+
+ tdata_op_key->tdata = NULL;
+
+ if (tdata_op_key->callback) {
+ /*
+ * Notify sip_transport.c that packet has been sent.
+ */
+ if (bytes_sent == 0)
+ bytes_sent = -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+
+ tdata_op_key->callback(&tcp->base, tdata_op_key->token, bytes_sent);
+
+ /* Mark last activity time */
+ pj_gettimeofday(&tcp->last_activity);
+
+ }
+
+ /* Check for error/closure */
+ if (bytes_sent <= 0) {
+ pj_status_t status;
+
+ PJ_LOG(5,(tcp->base.obj_name, "TCP send() error, sent=%d",
+ bytes_sent));
+
+ status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) :
+ -bytes_sent;
+
+ tcp_init_shutdown(tcp, status);
+
+ return PJ_FALSE;
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * This callback is called by transport manager to send SIP message
+ */
+static pj_status_t tcp_send_msg(pjsip_transport *transport,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback callback)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*)transport;
+ pj_ssize_t size;
+ pj_bool_t delayed = PJ_FALSE;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL);
+
+ /* Check that there's no pending operation associated with the tdata */
+ PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX);
+
+ /* Check the address is supported */
+ PJ_ASSERT_RETURN(rem_addr && addr_len==sizeof(pj_sockaddr_in), PJ_EINVAL);
+
+
+
+ /* Init op key. */
+ tdata->op_key.tdata = tdata;
+ tdata->op_key.token = token;
+ tdata->op_key.callback = callback;
+
+ /* If asynchronous connect() has not completed yet, just put the
+ * transmit data in the pending transmission list since we can not
+ * use the socket yet.
+ */
+ if (tcp->has_pending_connect) {
+
+ /*
+ * Looks like connect() is still in progress. Check again (this time
+ * with holding the lock) to be sure.
+ */
+ pj_lock_acquire(tcp->base.lock);
+
+ if (tcp->has_pending_connect) {
+ struct delayed_tdata *delayed_tdata;
+
+ /*
+ * connect() is still in progress. Put the transmit data to
+ * the delayed list.
+ */
+ delayed_tdata = PJ_POOL_ALLOC_T(tdata->pool,
+ struct delayed_tdata);
+ delayed_tdata->tdata_op_key = &tdata->op_key;
+
+ pj_list_push_back(&tcp->delayed_list, delayed_tdata);
+ status = PJ_EPENDING;
+
+ /* Prevent pj_ioqueue_send() to be called below */
+ delayed = PJ_TRUE;
+ }
+
+ pj_lock_release(tcp->base.lock);
+ }
+
+ if (!delayed) {
+ /*
+ * Transport is ready to go. Send the packet to ioqueue to be
+ * sent asynchronously.
+ */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_activesock_send(tcp->asock,
+ (pj_ioqueue_op_key_t*)&tdata->op_key,
+ tdata->buf.start, &size, 0);
+
+ if (status != PJ_EPENDING) {
+ /* Not pending (could be immediate success or error) */
+ tdata->op_key.tdata = NULL;
+
+ /* Shutdown transport on closure/errors */
+ if (size <= 0) {
+
+ PJ_LOG(5,(tcp->base.obj_name, "TCP send() error, sent=%d",
+ size));
+
+ if (status == PJ_SUCCESS)
+ status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+
+ tcp_init_shutdown(tcp, status);
+ }
+ }
+ }
+
+ return status;
+}
+
+
+/*
+ * This callback is called by transport manager to shutdown transport.
+ */
+static pj_status_t tcp_shutdown(pjsip_transport *transport)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*)transport;
+
+ /* Stop keep-alive timer. */
+ if (tcp->ka_timer.id) {
+ pjsip_endpt_cancel_timer(tcp->base.endpt, &tcp->ka_timer);
+ tcp->ka_timer.id = PJ_FALSE;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Callback from ioqueue that an incoming data is received from the socket.
+ */
+static pj_bool_t on_data_read(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder)
+{
+ enum { MAX_IMMEDIATE_PACKET = 10 };
+ struct tcp_transport *tcp;
+ pjsip_rx_data *rdata;
+
+ PJ_UNUSED_ARG(data);
+
+ tcp = (struct tcp_transport*) pj_activesock_get_user_data(asock);
+ rdata = &tcp->rdata;
+
+ /* Don't do anything if transport is closing. */
+ if (tcp->is_closing) {
+ tcp->is_closing++;
+ return PJ_FALSE;
+ }
+
+ /* Houston, we have packet! Report the packet to transport manager
+ * to be parsed.
+ */
+ if (status == PJ_SUCCESS) {
+ pj_size_t size_eaten;
+
+ /* Mark this as an activity */
+ pj_gettimeofday(&tcp->last_activity);
+
+ pj_assert((void*)rdata->pkt_info.packet == data);
+
+ /* Init pkt_info part. */
+ rdata->pkt_info.len = size;
+ rdata->pkt_info.zero = 0;
+ pj_gettimeofday(&rdata->pkt_info.timestamp);
+
+ /* Report to transport manager.
+ * The transport manager will tell us how many bytes of the packet
+ * have been processed (as valid SIP message).
+ */
+ size_eaten =
+ pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr,
+ rdata);
+
+ pj_assert(size_eaten <= (pj_size_t)rdata->pkt_info.len);
+
+ /* Move unprocessed data to the front of the buffer */
+ *remainder = size - size_eaten;
+ if (*remainder > 0 && *remainder != size) {
+ pj_memmove(rdata->pkt_info.packet,
+ rdata->pkt_info.packet + size_eaten,
+ *remainder);
+ }
+
+ } else {
+
+ /* Transport is closed */
+ PJ_LOG(4,(tcp->base.obj_name, "TCP connection closed"));
+
+ tcp_init_shutdown(tcp, status);
+
+ return PJ_FALSE;
+
+ }
+
+ /* Reset pool. */
+ pj_pool_reset(rdata->tp_info.pool);
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Callback from ioqueue when asynchronous connect() operation completes.
+ */
+static pj_bool_t on_connect_complete(pj_activesock_t *asock,
+ pj_status_t status)
+{
+ struct tcp_transport *tcp;
+ pj_sockaddr_in addr;
+ int addrlen;
+ pjsip_tp_state_callback state_cb;
+
+ tcp = (struct tcp_transport*) pj_activesock_get_user_data(asock);
+
+ /* Mark that pending connect() operation has completed. */
+ tcp->has_pending_connect = PJ_FALSE;
+
+ /* Check connect() status */
+ if (status != PJ_SUCCESS) {
+
+ tcp_perror(tcp->base.obj_name, "TCP connect() error", status);
+
+ /* Cancel all delayed transmits */
+ while (!pj_list_empty(&tcp->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pj_ioqueue_op_key_t *op_key;
+
+ pending_tx = tcp->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ on_data_sent(tcp->asock, op_key, -status);
+ }
+
+ tcp_init_shutdown(tcp, status);
+ return PJ_FALSE;
+ }
+
+ PJ_LOG(4,(tcp->base.obj_name,
+ "TCP transport %.*s:%d is connected to %.*s:%d",
+ (int)tcp->base.local_name.host.slen,
+ tcp->base.local_name.host.ptr,
+ tcp->base.local_name.port,
+ (int)tcp->base.remote_name.host.slen,
+ tcp->base.remote_name.host.ptr,
+ tcp->base.remote_name.port));
+
+
+ /* Update (again) local address, just in case local address currently
+ * set is different now that the socket is connected (could happen
+ * on some systems, like old Win32 probably?).
+ */
+ addrlen = sizeof(pj_sockaddr_in);
+ if (pj_sock_getsockname(tcp->sock, &addr, &addrlen)==PJ_SUCCESS) {
+ pj_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tcp->base.local_addr;
+
+ if (pj_sockaddr_has_addr(&addr) &&
+ tp_addr->sin_addr.s_addr != addr.sin_addr.s_addr)
+ {
+ tp_addr->sin_addr.s_addr = addr.sin_addr.s_addr;
+ tp_addr->sin_port = addr.sin_port;
+ sockaddr_to_host_port(tcp->base.pool, &tcp->base.local_name,
+ tp_addr);
+ }
+ }
+
+ /* Start pending read */
+ status = tcp_start_read(tcp);
+ if (status != PJ_SUCCESS) {
+ tcp_init_shutdown(tcp, status);
+ return PJ_FALSE;
+ }
+
+ /* Notify application of transport state connected */
+ state_cb = pjsip_tpmgr_get_state_cb(tcp->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+
+ pj_bzero(&state_info, sizeof(state_info));
+ (*state_cb)(&tcp->base, PJSIP_TP_STATE_CONNECTED, &state_info);
+ }
+
+ /* Flush all pending send operations */
+ tcp_flush_pending_tx(tcp);
+
+ /* Start keep-alive timer */
+ if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) {
+ pj_time_val delay = { PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0 };
+ pjsip_endpt_schedule_timer(tcp->base.endpt, &tcp->ka_timer,
+ &delay);
+ tcp->ka_timer.id = PJ_TRUE;
+ pj_gettimeofday(&tcp->last_activity);
+ }
+
+ return PJ_TRUE;
+}
+
+/* Transport keep-alive timer callback */
+static void tcp_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*) e->user_data;
+ pj_time_val delay;
+ pj_time_val now;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(th);
+
+ tcp->ka_timer.id = PJ_TRUE;
+
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, tcp->last_activity);
+
+ if (now.sec > 0 && now.sec < PJSIP_TCP_KEEP_ALIVE_INTERVAL) {
+ /* There has been activity, so don't send keep-alive */
+ delay.sec = PJSIP_TCP_KEEP_ALIVE_INTERVAL - now.sec;
+ delay.msec = 0;
+
+ pjsip_endpt_schedule_timer(tcp->base.endpt, &tcp->ka_timer,
+ &delay);
+ tcp->ka_timer.id = PJ_TRUE;
+ return;
+ }
+
+ PJ_LOG(5,(tcp->base.obj_name, "Sending %d byte(s) keep-alive to %.*s:%d",
+ (int)tcp->ka_pkt.slen, (int)tcp->base.remote_name.host.slen,
+ tcp->base.remote_name.host.ptr,
+ tcp->base.remote_name.port));
+
+ /* Send the data */
+ size = tcp->ka_pkt.slen;
+ status = pj_activesock_send(tcp->asock, &tcp->ka_op_key.key,
+ tcp->ka_pkt.ptr, &size, 0);
+
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ tcp_perror(tcp->base.obj_name,
+ "Error sending keep-alive packet", status);
+ tcp_init_shutdown(tcp, status);
+ return;
+ }
+
+ /* Register next keep-alive */
+ delay.sec = PJSIP_TCP_KEEP_ALIVE_INTERVAL;
+ delay.msec = 0;
+
+ pjsip_endpt_schedule_timer(tcp->base.endpt, &tcp->ka_timer,
+ &delay);
+ tcp->ka_timer.id = PJ_TRUE;
+}
+
+
+#endif /* PJ_HAS_TCP */
+
diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c
new file mode 100644
index 0000000..878b6db
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_tls.c
@@ -0,0 +1,1610 @@
+/* $Id: sip_transport_tls.c 4146 2012-05-30 06:35:59Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <pjsip/sip_transport_tls.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pj/compat/socket.h>
+#include <pj/addr_resolv.h>
+#include <pj/ssl_sock.h>
+#include <pj/assert.h>
+#include <pj/hash.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
+
+#define THIS_FILE "sip_transport_tls.c"
+
+#define MAX_ASYNC_CNT 16
+#define POOL_LIS_INIT 512
+#define POOL_LIS_INC 512
+#define POOL_TP_INIT 512
+#define POOL_TP_INC 512
+
+struct tls_listener;
+struct tls_transport;
+
+/*
+ * Definition of TLS/SSL transport listener, and it's descendant of
+ * pjsip_tpfactory.
+ */
+struct tls_listener
+{
+ pjsip_tpfactory factory;
+ pj_bool_t is_registered;
+ pjsip_endpoint *endpt;
+ pjsip_tpmgr *tpmgr;
+ pj_ssl_sock_t *ssock;
+ pj_ssl_cert_t *cert;
+ pjsip_tls_setting tls_setting;
+};
+
+
+/*
+ * This structure is used to keep delayed transmit operation in a list.
+ * A delayed transmission occurs when application sends tx_data when
+ * the TLS connect/establishment is still in progress. These delayed
+ * transmission will be "flushed" once the socket is connected (either
+ * successfully or with errors).
+ */
+struct delayed_tdata
+{
+ PJ_DECL_LIST_MEMBER(struct delayed_tdata);
+ pjsip_tx_data_op_key *tdata_op_key;
+};
+
+
+/*
+ * TLS/SSL transport, and it's descendant of pjsip_transport.
+ */
+struct tls_transport
+{
+ pjsip_transport base;
+ pj_bool_t is_server;
+ pj_str_t remote_name;
+
+ pj_bool_t is_registered;
+ pj_bool_t is_closing;
+ pj_status_t close_reason;
+ pj_ssl_sock_t *ssock;
+ pj_bool_t has_pending_connect;
+ pj_bool_t verify_server;
+
+ /* Keep-alive timer. */
+ pj_timer_entry ka_timer;
+ pj_time_val last_activity;
+ pjsip_tx_data_op_key ka_op_key;
+ pj_str_t ka_pkt;
+
+ /* TLS transport can only have one rdata!
+ * Otherwise chunks of incoming PDU may be received on different
+ * buffer.
+ */
+ pjsip_rx_data rdata;
+
+ /* Pending transmission list. */
+ struct delayed_tdata delayed_list;
+};
+
+
+/****************************************************************************
+ * PROTOTYPES
+ */
+
+/* This callback is called when pending accept() operation completes. */
+static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock,
+ pj_ssl_sock_t *new_ssock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len);
+
+/* Callback on incoming data */
+static pj_bool_t on_data_read(pj_ssl_sock_t *ssock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder);
+
+/* Callback when packet is sent */
+static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock,
+ pj_ioqueue_op_key_t *send_key,
+ pj_ssize_t sent);
+
+/* This callback is called by transport manager to destroy listener */
+static pj_status_t lis_destroy(pjsip_tpfactory *factory);
+
+/* This callback is called by transport manager to create transport */
+static pj_status_t lis_create_transport(pjsip_tpfactory *factory,
+ pjsip_tpmgr *mgr,
+ pjsip_endpoint *endpt,
+ const pj_sockaddr *rem_addr,
+ int addr_len,
+ pjsip_tx_data *tdata,
+ pjsip_transport **transport);
+
+/* Common function to create and initialize transport */
+static pj_status_t tls_create(struct tls_listener *listener,
+ pj_pool_t *pool,
+ pj_ssl_sock_t *ssock,
+ pj_bool_t is_server,
+ const pj_sockaddr_in *local,
+ const pj_sockaddr_in *remote,
+ const pj_str_t *remote_name,
+ struct tls_transport **p_tls);
+
+
+static void tls_perror(const char *sender, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
+}
+
+
+static void sockaddr_to_host_port( pj_pool_t *pool,
+ pjsip_host_port *host_port,
+ const pj_sockaddr_in *addr )
+{
+ host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4);
+ pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 2);
+ host_port->host.slen = pj_ansi_strlen(host_port->host.ptr);
+ host_port->port = pj_sockaddr_get_port(addr);
+}
+
+
+static void tls_init_shutdown(struct tls_transport *tls, pj_status_t status)
+{
+ pjsip_tp_state_callback state_cb;
+
+ if (tls->close_reason == PJ_SUCCESS)
+ tls->close_reason = status;
+
+ if (tls->base.is_shutdown)
+ return;
+
+ /* Prevent immediate transport destroy by application, as transport
+ * state notification callback may be stacked and transport instance
+ * must remain valid at any point in the callback.
+ */
+ pjsip_transport_add_ref(&tls->base);
+
+ /* Notify application of transport disconnected state */
+ state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+ pjsip_tls_state_info tls_info;
+ pj_ssl_sock_info ssl_info;
+
+ /* Init transport state info */
+ pj_bzero(&state_info, sizeof(state_info));
+ state_info.status = tls->close_reason;
+
+ if (tls->ssock &&
+ pj_ssl_sock_get_info(tls->ssock, &ssl_info) == PJ_SUCCESS)
+ {
+ pj_bzero(&tls_info, sizeof(tls_info));
+ tls_info.ssl_sock_info = &ssl_info;
+ state_info.ext_info = &tls_info;
+ }
+
+ (*state_cb)(&tls->base, PJSIP_TP_STATE_DISCONNECTED, &state_info);
+ }
+
+ /* We can not destroy the transport since high level objects may
+ * still keep reference to this transport. So we can only
+ * instruct transport manager to gracefully start the shutdown
+ * procedure for this transport.
+ */
+ pjsip_transport_shutdown(&tls->base);
+
+ /* Now, it is ok to destroy the transport. */
+ pjsip_transport_dec_ref(&tls->base);
+}
+
+
+/****************************************************************************
+ * The TLS listener/transport factory.
+ */
+
+/*
+ * This is the public API to create, initialize, register, and start the
+ * TLS listener.
+ */
+PJ_DEF(pj_status_t) pjsip_tls_transport_start (pjsip_endpoint *endpt,
+ const pjsip_tls_setting *opt,
+ const pj_sockaddr_in *local,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_tpfactory **p_factory)
+{
+ pj_pool_t *pool;
+ struct tls_listener *listener;
+ pj_ssl_sock_param ssock_param;
+ pj_sockaddr_in *listener_addr;
+ pj_bool_t has_listener;
+ pj_status_t status;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL);
+
+ /* Verify that address given in a_name (if any) is valid */
+ if (a_name && a_name->host.slen) {
+ pj_sockaddr_in tmp;
+
+ status = pj_sockaddr_in_init(&tmp, &a_name->host,
+ (pj_uint16_t)a_name->port);
+ if (status != PJ_SUCCESS || tmp.sin_addr.s_addr == PJ_INADDR_ANY ||
+ tmp.sin_addr.s_addr == PJ_INADDR_NONE)
+ {
+ /* Invalid address */
+ return PJ_EINVAL;
+ }
+ }
+
+ pool = pjsip_endpt_create_pool(endpt, "tlslis", POOL_LIS_INIT,
+ POOL_LIS_INC);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+ listener = PJ_POOL_ZALLOC_T(pool, struct tls_listener);
+ listener->factory.pool = pool;
+ listener->factory.type = PJSIP_TRANSPORT_TLS;
+ listener->factory.type_name = "tls";
+ listener->factory.flag =
+ pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS);
+
+ pj_ansi_strcpy(listener->factory.obj_name, "tlslis");
+
+ if (opt)
+ pjsip_tls_setting_copy(pool, &listener->tls_setting, opt);
+ else
+ pjsip_tls_setting_default(&listener->tls_setting);
+
+ status = pj_lock_create_recursive_mutex(pool, "tlslis",
+ &listener->factory.lock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (async_cnt > MAX_ASYNC_CNT)
+ async_cnt = MAX_ASYNC_CNT;
+
+ /* Build SSL socket param */
+ pj_ssl_sock_param_default(&ssock_param);
+ ssock_param.cb.on_accept_complete = &on_accept_complete;
+ ssock_param.cb.on_data_read = &on_data_read;
+ ssock_param.cb.on_data_sent = &on_data_sent;
+ ssock_param.async_cnt = async_cnt;
+ ssock_param.ioqueue = pjsip_endpt_get_ioqueue(endpt);
+ ssock_param.require_client_cert = listener->tls_setting.require_client_cert;
+ ssock_param.timeout = listener->tls_setting.timeout;
+ ssock_param.user_data = listener;
+ ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket
+ * due to verification error */
+ if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN;
+ if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN;
+ ssock_param.ciphers_num = listener->tls_setting.ciphers_num;
+ ssock_param.ciphers = listener->tls_setting.ciphers;
+ ssock_param.qos_type = listener->tls_setting.qos_type;
+ ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error;
+ pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params,
+ sizeof(ssock_param.qos_params));
+
+ has_listener = PJ_FALSE;
+
+ switch(listener->tls_setting.method) {
+ case PJSIP_TLSV1_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_TLS1;
+ break;
+ case PJSIP_SSLV2_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL2;
+ break;
+ case PJSIP_SSLV3_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL3;
+ break;
+ case PJSIP_SSLV23_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL23;
+ break;
+ default:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_DEFAULT;
+ break;
+ }
+
+ /* Create SSL socket */
+ status = pj_ssl_sock_create(pool, &ssock_param, &listener->ssock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ listener_addr = (pj_sockaddr_in*)&listener->factory.local_addr;
+ if (local) {
+ pj_sockaddr_cp((pj_sockaddr_t*)listener_addr,
+ (const pj_sockaddr_t*)local);
+ } else {
+ pj_sockaddr_in_init(listener_addr, NULL, 0);
+ }
+
+ /* Check if certificate/CA list for SSL socket is set */
+ if (listener->tls_setting.cert_file.slen ||
+ listener->tls_setting.ca_list_file.slen)
+ {
+ status = pj_ssl_cert_load_from_files(pool,
+ &listener->tls_setting.ca_list_file,
+ &listener->tls_setting.cert_file,
+ &listener->tls_setting.privkey_file,
+ &listener->tls_setting.password,
+ &listener->cert);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_ssl_sock_set_certificate(listener->ssock, pool,
+ listener->cert);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Start accepting incoming connections. Note that some TLS/SSL backends
+ * may not support for SSL socket server.
+ */
+ has_listener = PJ_FALSE;
+
+ status = pj_ssl_sock_start_accept(listener->ssock, pool,
+ (pj_sockaddr_t*)listener_addr,
+ pj_sockaddr_get_len((pj_sockaddr_t*)listener_addr));
+ if (status == PJ_SUCCESS || status == PJ_EPENDING) {
+ pj_ssl_sock_info info;
+ has_listener = PJ_TRUE;
+
+ /* Retrieve the bound address */
+ status = pj_ssl_sock_get_info(listener->ssock, &info);
+ if (status == PJ_SUCCESS)
+ pj_sockaddr_cp(listener_addr, (pj_sockaddr_t*)&info.local_addr);
+ } else if (status != PJ_ENOTSUP) {
+ goto on_error;
+ }
+
+ /* If published host/IP is specified, then use that address as the
+ * listener advertised address.
+ */
+ if (a_name && a_name->host.slen) {
+ /* Copy the address */
+ listener->factory.addr_name = *a_name;
+ pj_strdup(listener->factory.pool, &listener->factory.addr_name.host,
+ &a_name->host);
+ listener->factory.addr_name.port = a_name->port;
+
+ } else {
+ /* No published address is given, use the bound address */
+
+ /* If the address returns 0.0.0.0, use the default
+ * interface address as the transport's address.
+ */
+ if (listener_addr->sin_addr.s_addr == 0) {
+ pj_sockaddr hostip;
+
+ status = pj_gethostip(pj_AF_INET(), &hostip);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ listener_addr->sin_addr.s_addr = hostip.ipv4.sin_addr.s_addr;
+ }
+
+ /* Save the address name */
+ sockaddr_to_host_port(listener->factory.pool,
+ &listener->factory.addr_name, listener_addr);
+ }
+
+ /* If port is zero, get the bound port */
+ if (listener->factory.addr_name.port == 0) {
+ listener->factory.addr_name.port = pj_ntohs(listener_addr->sin_port);
+ }
+
+ pj_ansi_snprintf(listener->factory.obj_name,
+ sizeof(listener->factory.obj_name),
+ "tlslis:%d", listener->factory.addr_name.port);
+
+ /* Register to transport manager */
+ listener->endpt = endpt;
+ listener->tpmgr = pjsip_endpt_get_tpmgr(endpt);
+ listener->factory.create_transport2 = lis_create_transport;
+ listener->factory.destroy = lis_destroy;
+ listener->is_registered = PJ_TRUE;
+ status = pjsip_tpmgr_register_tpfactory(listener->tpmgr,
+ &listener->factory);
+ if (status != PJ_SUCCESS) {
+ listener->is_registered = PJ_FALSE;
+ goto on_error;
+ }
+
+ if (has_listener) {
+ PJ_LOG(4,(listener->factory.obj_name,
+ "SIP TLS listener is ready for incoming connections "
+ "at %.*s:%d",
+ (int)listener->factory.addr_name.host.slen,
+ listener->factory.addr_name.host.ptr,
+ listener->factory.addr_name.port));
+ } else {
+ PJ_LOG(4,(listener->factory.obj_name, "SIP TLS is ready "
+ "(client only)"));
+ }
+
+ /* Return the pointer to user */
+ if (p_factory) *p_factory = &listener->factory;
+
+ return PJ_SUCCESS;
+
+on_error:
+ lis_destroy(&listener->factory);
+ return status;
+}
+
+
+/* This callback is called by transport manager to destroy listener */
+static pj_status_t lis_destroy(pjsip_tpfactory *factory)
+{
+ struct tls_listener *listener = (struct tls_listener *)factory;
+
+ if (listener->is_registered) {
+ pjsip_tpmgr_unregister_tpfactory(listener->tpmgr, &listener->factory);
+ listener->is_registered = PJ_FALSE;
+ }
+
+ if (listener->ssock) {
+ pj_ssl_sock_close(listener->ssock);
+ listener->ssock = NULL;
+ }
+
+ if (listener->factory.lock) {
+ pj_lock_destroy(listener->factory.lock);
+ listener->factory.lock = NULL;
+ }
+
+ if (listener->factory.pool) {
+ pj_pool_t *pool = listener->factory.pool;
+
+ PJ_LOG(4,(listener->factory.obj_name, "SIP TLS listener destroyed"));
+
+ listener->factory.pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/***************************************************************************/
+/*
+ * TLS Transport
+ */
+
+/*
+ * Prototypes.
+ */
+/* Called by transport manager to send message */
+static pj_status_t tls_send_msg(pjsip_transport *transport,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback callback);
+
+/* Called by transport manager to shutdown */
+static pj_status_t tls_shutdown(pjsip_transport *transport);
+
+/* Called by transport manager to destroy transport */
+static pj_status_t tls_destroy_transport(pjsip_transport *transport);
+
+/* Utility to destroy transport */
+static pj_status_t tls_destroy(pjsip_transport *transport,
+ pj_status_t reason);
+
+/* Callback when connect completes */
+static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock,
+ pj_status_t status);
+
+/* TLS keep-alive timer callback */
+static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e);
+
+/*
+ * Common function to create TLS transport, called when pending accept() and
+ * pending connect() complete.
+ */
+static pj_status_t tls_create( struct tls_listener *listener,
+ pj_pool_t *pool,
+ pj_ssl_sock_t *ssock,
+ pj_bool_t is_server,
+ const pj_sockaddr_in *local,
+ const pj_sockaddr_in *remote,
+ const pj_str_t *remote_name,
+ struct tls_transport **p_tls)
+{
+ struct tls_transport *tls;
+ const pj_str_t ka_pkt = PJSIP_TLS_KEEP_ALIVE_DATA;
+ pj_status_t status;
+
+
+ PJ_ASSERT_RETURN(listener && ssock && local && remote && p_tls, PJ_EINVAL);
+
+
+ if (pool == NULL) {
+ pool = pjsip_endpt_create_pool(listener->endpt, "tls",
+ POOL_TP_INIT, POOL_TP_INC);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+ }
+
+ /*
+ * Create and initialize basic transport structure.
+ */
+ tls = PJ_POOL_ZALLOC_T(pool, struct tls_transport);
+ tls->is_server = is_server;
+ tls->verify_server = listener->tls_setting.verify_server;
+ pj_list_init(&tls->delayed_list);
+ tls->base.pool = pool;
+
+ pj_ansi_snprintf(tls->base.obj_name, PJ_MAX_OBJ_NAME,
+ (is_server ? "tlss%p" :"tlsc%p"), tls);
+
+ status = pj_atomic_create(pool, 0, &tls->base.ref_cnt);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ status = pj_lock_create_recursive_mutex(pool, "tls", &tls->base.lock);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ if (remote_name)
+ pj_strdup(pool, &tls->remote_name, remote_name);
+
+ tls->base.key.type = PJSIP_TRANSPORT_TLS;
+ pj_memcpy(&tls->base.key.rem_addr, remote, sizeof(pj_sockaddr_in));
+ tls->base.type_name = "tls";
+ tls->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS);
+
+ tls->base.info = (char*) pj_pool_alloc(pool, 64);
+ pj_ansi_snprintf(tls->base.info, 64, "TLS to %s:%d",
+ pj_inet_ntoa(remote->sin_addr),
+ (int)pj_ntohs(remote->sin_port));
+
+ tls->base.addr_len = sizeof(pj_sockaddr_in);
+ tls->base.dir = is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING;
+
+ /* Set initial local address */
+ if (!pj_sockaddr_has_addr(local)) {
+ pj_sockaddr_cp(&tls->base.local_addr,
+ &listener->factory.local_addr);
+ } else {
+ pj_sockaddr_cp(&tls->base.local_addr, local);
+ }
+
+ sockaddr_to_host_port(pool, &tls->base.local_name,
+ (pj_sockaddr_in*)&tls->base.local_addr);
+ if (tls->remote_name.slen) {
+ tls->base.remote_name.host = tls->remote_name;
+ tls->base.remote_name.port = pj_sockaddr_in_get_port(remote);
+ } else {
+ sockaddr_to_host_port(pool, &tls->base.remote_name, remote);
+ }
+
+ tls->base.endpt = listener->endpt;
+ tls->base.tpmgr = listener->tpmgr;
+ tls->base.send_msg = &tls_send_msg;
+ tls->base.do_shutdown = &tls_shutdown;
+ tls->base.destroy = &tls_destroy_transport;
+
+ tls->ssock = ssock;
+
+ /* Register transport to transport manager */
+ status = pjsip_transport_register(listener->tpmgr, &tls->base);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ tls->is_registered = PJ_TRUE;
+
+ /* Initialize keep-alive timer */
+ tls->ka_timer.user_data = (void*)tls;
+ tls->ka_timer.cb = &tls_keep_alive_timer;
+ pj_ioqueue_op_key_init(&tls->ka_op_key.key, sizeof(pj_ioqueue_op_key_t));
+ pj_strdup(tls->base.pool, &tls->ka_pkt, &ka_pkt);
+
+ /* Done setting up basic transport. */
+ *p_tls = tls;
+
+ PJ_LOG(4,(tls->base.obj_name, "TLS %s transport created",
+ (tls->is_server ? "server" : "client")));
+
+ return PJ_SUCCESS;
+
+on_error:
+ tls_destroy(&tls->base, status);
+ return status;
+}
+
+
+/* Flush all delayed transmision once the socket is connected. */
+static void tls_flush_pending_tx(struct tls_transport *tls)
+{
+ pj_lock_acquire(tls->base.lock);
+ while (!pj_list_empty(&tls->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pjsip_tx_data *tdata;
+ pj_ioqueue_op_key_t *op_key;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ pending_tx = tls->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ tdata = pending_tx->tdata_op_key->tdata;
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ /* send! */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_ssl_sock_send(tls->ssock, op_key, tdata->buf.start,
+ &size, 0);
+
+ if (status != PJ_EPENDING) {
+ on_data_sent(tls->ssock, op_key, size);
+ }
+ }
+ pj_lock_release(tls->base.lock);
+}
+
+
+/* Called by transport manager to destroy transport */
+static pj_status_t tls_destroy_transport(pjsip_transport *transport)
+{
+ struct tls_transport *tls = (struct tls_transport*)transport;
+
+ /* Transport would have been unregistered by now since this callback
+ * is called by transport manager.
+ */
+ tls->is_registered = PJ_FALSE;
+
+ return tls_destroy(transport, tls->close_reason);
+}
+
+
+/* Destroy TLS transport */
+static pj_status_t tls_destroy(pjsip_transport *transport,
+ pj_status_t reason)
+{
+ struct tls_transport *tls = (struct tls_transport*)transport;
+
+ if (tls->close_reason == 0)
+ tls->close_reason = reason;
+
+ if (tls->is_registered) {
+ tls->is_registered = PJ_FALSE;
+ pjsip_transport_destroy(transport);
+
+ /* pjsip_transport_destroy will recursively call this function
+ * again.
+ */
+ return PJ_SUCCESS;
+ }
+
+ /* Mark transport as closing */
+ tls->is_closing = PJ_TRUE;
+
+ /* Stop keep-alive timer. */
+ if (tls->ka_timer.id) {
+ pjsip_endpt_cancel_timer(tls->base.endpt, &tls->ka_timer);
+ tls->ka_timer.id = PJ_FALSE;
+ }
+
+ /* Cancel all delayed transmits */
+ while (!pj_list_empty(&tls->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pj_ioqueue_op_key_t *op_key;
+
+ pending_tx = tls->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ on_data_sent(tls->ssock, op_key, -reason);
+ }
+
+ if (tls->rdata.tp_info.pool) {
+ pj_pool_release(tls->rdata.tp_info.pool);
+ tls->rdata.tp_info.pool = NULL;
+ }
+
+ if (tls->ssock) {
+ pj_ssl_sock_close(tls->ssock);
+ tls->ssock = NULL;
+ }
+ if (tls->base.lock) {
+ pj_lock_destroy(tls->base.lock);
+ tls->base.lock = NULL;
+ }
+
+ if (tls->base.ref_cnt) {
+ pj_atomic_destroy(tls->base.ref_cnt);
+ tls->base.ref_cnt = NULL;
+ }
+
+ if (tls->base.pool) {
+ pj_pool_t *pool;
+
+ if (reason != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(reason, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(tls->base.obj_name,
+ "TLS transport destroyed with reason %d: %s",
+ reason, errmsg));
+
+ } else {
+
+ PJ_LOG(4,(tls->base.obj_name,
+ "TLS transport destroyed normally"));
+
+ }
+
+ pool = tls->base.pool;
+ tls->base.pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This utility function creates receive data buffers and start
+ * asynchronous recv() operations from the socket. It is called after
+ * accept() or connect() operation complete.
+ */
+static pj_status_t tls_start_read(struct tls_transport *tls)
+{
+ pj_pool_t *pool;
+ pj_ssize_t size;
+ pj_sockaddr_in *rem_addr;
+ void *readbuf[1];
+ pj_status_t status;
+
+ /* Init rdata */
+ pool = pjsip_endpt_create_pool(tls->base.endpt,
+ "rtd%p",
+ PJSIP_POOL_RDATA_LEN,
+ PJSIP_POOL_RDATA_INC);
+ if (!pool) {
+ tls_perror(tls->base.obj_name, "Unable to create pool", PJ_ENOMEM);
+ return PJ_ENOMEM;
+ }
+
+ tls->rdata.tp_info.pool = pool;
+
+ tls->rdata.tp_info.transport = &tls->base;
+ tls->rdata.tp_info.tp_data = tls;
+ tls->rdata.tp_info.op_key.rdata = &tls->rdata;
+ pj_ioqueue_op_key_init(&tls->rdata.tp_info.op_key.op_key,
+ sizeof(pj_ioqueue_op_key_t));
+
+ tls->rdata.pkt_info.src_addr = tls->base.key.rem_addr;
+ tls->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in);
+ rem_addr = (pj_sockaddr_in*) &tls->base.key.rem_addr;
+ pj_ansi_strcpy(tls->rdata.pkt_info.src_name,
+ pj_inet_ntoa(rem_addr->sin_addr));
+ tls->rdata.pkt_info.src_port = pj_ntohs(rem_addr->sin_port);
+
+ size = sizeof(tls->rdata.pkt_info.packet);
+ readbuf[0] = tls->rdata.pkt_info.packet;
+ status = pj_ssl_sock_start_read2(tls->ssock, tls->base.pool, size,
+ readbuf, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ PJ_LOG(4, (tls->base.obj_name,
+ "pj_ssl_sock_start_read() error, status=%d",
+ status));
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* This callback is called by transport manager for the TLS factory
+ * to create outgoing transport to the specified destination.
+ */
+static pj_status_t lis_create_transport(pjsip_tpfactory *factory,
+ pjsip_tpmgr *mgr,
+ pjsip_endpoint *endpt,
+ const pj_sockaddr *rem_addr,
+ int addr_len,
+ pjsip_tx_data *tdata,
+ pjsip_transport **p_transport)
+{
+ struct tls_listener *listener;
+ struct tls_transport *tls;
+ pj_pool_t *pool;
+ pj_ssl_sock_t *ssock;
+ pj_ssl_sock_param ssock_param;
+ pj_sockaddr_in local_addr;
+ pj_str_t remote_name;
+ pj_status_t status;
+
+ /* Sanity checks */
+ PJ_ASSERT_RETURN(factory && mgr && endpt && rem_addr &&
+ addr_len && p_transport, PJ_EINVAL);
+
+ /* Check that address is a sockaddr_in */
+ PJ_ASSERT_RETURN(rem_addr->addr.sa_family == pj_AF_INET() &&
+ addr_len == sizeof(pj_sockaddr_in), PJ_EINVAL);
+
+
+ listener = (struct tls_listener*)factory;
+
+ pool = pjsip_endpt_create_pool(listener->endpt, "tls",
+ POOL_TP_INIT, POOL_TP_INC);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ /* Get remote host name from tdata */
+ if (tdata)
+ remote_name = tdata->dest_info.name;
+ else
+ pj_bzero(&remote_name, sizeof(remote_name));
+
+ /* Build SSL socket param */
+ pj_ssl_sock_param_default(&ssock_param);
+ ssock_param.cb.on_connect_complete = &on_connect_complete;
+ ssock_param.cb.on_data_read = &on_data_read;
+ ssock_param.cb.on_data_sent = &on_data_sent;
+ ssock_param.async_cnt = 1;
+ ssock_param.ioqueue = pjsip_endpt_get_ioqueue(listener->endpt);
+ ssock_param.server_name = remote_name;
+ ssock_param.timeout = listener->tls_setting.timeout;
+ ssock_param.user_data = NULL; /* pending, must be set later */
+ ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket
+ * due to verification error */
+ if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN;
+ if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN;
+ ssock_param.ciphers_num = listener->tls_setting.ciphers_num;
+ ssock_param.ciphers = listener->tls_setting.ciphers;
+ ssock_param.qos_type = listener->tls_setting.qos_type;
+ ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error;
+ pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params,
+ sizeof(ssock_param.qos_params));
+
+ switch(listener->tls_setting.method) {
+ case PJSIP_TLSV1_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_TLS1;
+ break;
+ case PJSIP_SSLV2_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL2;
+ break;
+ case PJSIP_SSLV3_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL3;
+ break;
+ case PJSIP_SSLV23_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL23;
+ break;
+ default:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_DEFAULT;
+ break;
+ }
+
+ status = pj_ssl_sock_create(pool, &ssock_param, &ssock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Apply SSL certificate */
+ if (listener->cert) {
+ status = pj_ssl_sock_set_certificate(ssock, pool, listener->cert);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Initially set bind address to PJ_INADDR_ANY port 0 */
+ pj_sockaddr_in_init(&local_addr, NULL, 0);
+
+ /* Create the transport descriptor */
+ status = tls_create(listener, pool, ssock, PJ_FALSE, &local_addr,
+ (pj_sockaddr_in*)rem_addr, &remote_name, &tls);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set the "pending" SSL socket user data */
+ pj_ssl_sock_set_user_data(tls->ssock, tls);
+
+ /* Start asynchronous connect() operation */
+ tls->has_pending_connect = PJ_TRUE;
+ status = pj_ssl_sock_start_connect(tls->ssock, tls->base.pool,
+ (pj_sockaddr_t*)&local_addr,
+ (pj_sockaddr_t*)rem_addr,
+ addr_len);
+ if (status == PJ_SUCCESS) {
+ on_connect_complete(tls->ssock, PJ_SUCCESS);
+ } else if (status != PJ_EPENDING) {
+ tls_destroy(&tls->base, status);
+ return status;
+ }
+
+ if (tls->has_pending_connect) {
+ pj_ssl_sock_info info;
+
+ /* Update local address, just in case local address currently set is
+ * different now that asynchronous connect() is started.
+ */
+
+ /* Retrieve the bound address */
+ status = pj_ssl_sock_get_info(tls->ssock, &info);
+ if (status == PJ_SUCCESS) {
+ pj_uint16_t new_port;
+
+ new_port = pj_sockaddr_get_port((pj_sockaddr_t*)&info.local_addr);
+
+ if (pj_sockaddr_has_addr((pj_sockaddr_t*)&info.local_addr)) {
+ /* Update sockaddr */
+ pj_sockaddr_cp((pj_sockaddr_t*)&tls->base.local_addr,
+ (pj_sockaddr_t*)&info.local_addr);
+ } else if (new_port && new_port != pj_sockaddr_get_port(
+ (pj_sockaddr_t*)&tls->base.local_addr))
+ {
+ /* Update port only */
+ pj_sockaddr_set_port(&tls->base.local_addr,
+ new_port);
+ }
+
+ sockaddr_to_host_port(tls->base.pool, &tls->base.local_name,
+ (pj_sockaddr_in*)&tls->base.local_addr);
+ }
+
+ PJ_LOG(4,(tls->base.obj_name,
+ "TLS transport %.*s:%d is connecting to %.*s:%d...",
+ (int)tls->base.local_name.host.slen,
+ tls->base.local_name.host.ptr,
+ tls->base.local_name.port,
+ (int)tls->base.remote_name.host.slen,
+ tls->base.remote_name.host.ptr,
+ tls->base.remote_name.port));
+ }
+
+ /* Done */
+ *p_transport = &tls->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This callback is called by SSL socket when pending accept() operation
+ * has completed.
+ */
+static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock,
+ pj_ssl_sock_t *new_ssock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len)
+{
+ struct tls_listener *listener;
+ struct tls_transport *tls;
+ pj_ssl_sock_info ssl_info;
+ char addr[PJ_INET6_ADDRSTRLEN+10];
+ pjsip_tp_state_callback state_cb;
+ pj_bool_t is_shutdown;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(src_addr_len);
+
+ listener = (struct tls_listener*) pj_ssl_sock_get_user_data(ssock);
+
+ PJ_ASSERT_RETURN(new_ssock, PJ_TRUE);
+
+ PJ_LOG(4,(listener->factory.obj_name,
+ "TLS listener %.*s:%d: got incoming TLS connection "
+ "from %s, sock=%d",
+ (int)listener->factory.addr_name.host.slen,
+ listener->factory.addr_name.host.ptr,
+ listener->factory.addr_name.port,
+ pj_sockaddr_print(src_addr, addr, sizeof(addr), 3),
+ new_ssock));
+
+ /* Retrieve SSL socket info, close the socket if this is failed
+ * as the SSL socket info availability is rather critical here.
+ */
+ status = pj_ssl_sock_get_info(new_ssock, &ssl_info);
+ if (status != PJ_SUCCESS) {
+ pj_ssl_sock_close(new_ssock);
+ return PJ_TRUE;
+ }
+
+ /*
+ * Incoming connection!
+ * Create TLS transport for the new socket.
+ */
+ status = tls_create( listener, NULL, new_ssock, PJ_TRUE,
+ (const pj_sockaddr_in*)&listener->factory.local_addr,
+ (const pj_sockaddr_in*)src_addr, NULL, &tls);
+
+ if (status != PJ_SUCCESS)
+ return PJ_TRUE;
+
+ /* Set the "pending" SSL socket user data */
+ pj_ssl_sock_set_user_data(new_ssock, tls);
+
+ /* Prevent immediate transport destroy as application may access it
+ * (getting info, etc) in transport state notification callback.
+ */
+ pjsip_transport_add_ref(&tls->base);
+
+ /* If there is verification error and verification is mandatory, shutdown
+ * and destroy the transport.
+ */
+ if (ssl_info.verify_status && listener->tls_setting.verify_client) {
+ if (tls->close_reason == PJ_SUCCESS)
+ tls->close_reason = PJSIP_TLS_ECERTVERIF;
+ pjsip_transport_shutdown(&tls->base);
+ }
+
+ /* Notify transport state to application */
+ state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+ pjsip_tls_state_info tls_info;
+ pjsip_transport_state tp_state;
+
+ /* Init transport state info */
+ pj_bzero(&tls_info, sizeof(tls_info));
+ pj_bzero(&state_info, sizeof(state_info));
+ tls_info.ssl_sock_info = &ssl_info;
+ state_info.ext_info = &tls_info;
+
+ /* Set transport state based on verification status */
+ if (ssl_info.verify_status && listener->tls_setting.verify_client)
+ {
+ tp_state = PJSIP_TP_STATE_DISCONNECTED;
+ state_info.status = PJSIP_TLS_ECERTVERIF;
+ } else {
+ tp_state = PJSIP_TP_STATE_CONNECTED;
+ state_info.status = PJ_SUCCESS;
+ }
+
+ (*state_cb)(&tls->base, tp_state, &state_info);
+ }
+
+ /* Release transport reference. If transport is shutting down, it may
+ * get destroyed here.
+ */
+ is_shutdown = tls->base.is_shutdown;
+ pjsip_transport_dec_ref(&tls->base);
+ if (is_shutdown)
+ return PJ_TRUE;
+
+
+ status = tls_start_read(tls);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(tls->base.obj_name, "New transport cancelled"));
+ tls_init_shutdown(tls, status);
+ tls_destroy(&tls->base, status);
+ } else {
+ /* Start keep-alive timer */
+ if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+ pj_time_val delay = {PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0};
+ pjsip_endpt_schedule_timer(listener->endpt,
+ &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+ pj_gettimeofday(&tls->last_activity);
+ }
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Callback from ioqueue when packet is sent.
+ */
+static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_sent)
+{
+ struct tls_transport *tls = (struct tls_transport*)
+ pj_ssl_sock_get_user_data(ssock);
+ pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key;
+
+ /* Note that op_key may be the op_key from keep-alive, thus
+ * it will not have tdata etc.
+ */
+
+ tdata_op_key->tdata = NULL;
+
+ if (tdata_op_key->callback) {
+ /*
+ * Notify sip_transport.c that packet has been sent.
+ */
+ if (bytes_sent == 0)
+ bytes_sent = -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+
+ tdata_op_key->callback(&tls->base, tdata_op_key->token, bytes_sent);
+
+ /* Mark last activity time */
+ pj_gettimeofday(&tls->last_activity);
+
+ }
+
+ /* Check for error/closure */
+ if (bytes_sent <= 0) {
+ pj_status_t status;
+
+ PJ_LOG(5,(tls->base.obj_name, "TLS send() error, sent=%d",
+ bytes_sent));
+
+ status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) :
+ -bytes_sent;
+
+ tls_init_shutdown(tls, status);
+
+ return PJ_FALSE;
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * This callback is called by transport manager to send SIP message
+ */
+static pj_status_t tls_send_msg(pjsip_transport *transport,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback callback)
+{
+ struct tls_transport *tls = (struct tls_transport*)transport;
+ pj_ssize_t size;
+ pj_bool_t delayed = PJ_FALSE;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL);
+
+ /* Check that there's no pending operation associated with the tdata */
+ PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX);
+
+ /* Check the address is supported */
+ PJ_ASSERT_RETURN(rem_addr && addr_len==sizeof(pj_sockaddr_in), PJ_EINVAL);
+
+
+
+ /* Init op key. */
+ tdata->op_key.tdata = tdata;
+ tdata->op_key.token = token;
+ tdata->op_key.callback = callback;
+
+ /* If asynchronous connect() has not completed yet, just put the
+ * transmit data in the pending transmission list since we can not
+ * use the socket yet.
+ */
+ if (tls->has_pending_connect) {
+
+ /*
+ * Looks like connect() is still in progress. Check again (this time
+ * with holding the lock) to be sure.
+ */
+ pj_lock_acquire(tls->base.lock);
+
+ if (tls->has_pending_connect) {
+ struct delayed_tdata *delayed_tdata;
+
+ /*
+ * connect() is still in progress. Put the transmit data to
+ * the delayed list.
+ */
+ delayed_tdata = PJ_POOL_ALLOC_T(tdata->pool,
+ struct delayed_tdata);
+ delayed_tdata->tdata_op_key = &tdata->op_key;
+
+ pj_list_push_back(&tls->delayed_list, delayed_tdata);
+ status = PJ_EPENDING;
+
+ /* Prevent pj_ioqueue_send() to be called below */
+ delayed = PJ_TRUE;
+ }
+
+ pj_lock_release(tls->base.lock);
+ }
+
+ if (!delayed) {
+ /*
+ * Transport is ready to go. Send the packet to ioqueue to be
+ * sent asynchronously.
+ */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_ssl_sock_send(tls->ssock,
+ (pj_ioqueue_op_key_t*)&tdata->op_key,
+ tdata->buf.start, &size, 0);
+
+ if (status != PJ_EPENDING) {
+ /* Not pending (could be immediate success or error) */
+ tdata->op_key.tdata = NULL;
+
+ /* Shutdown transport on closure/errors */
+ if (size <= 0) {
+
+ PJ_LOG(5,(tls->base.obj_name, "TLS send() error, sent=%d",
+ size));
+
+ if (status == PJ_SUCCESS)
+ status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+
+ tls_init_shutdown(tls, status);
+ }
+ }
+ }
+
+ return status;
+}
+
+
+/*
+ * This callback is called by transport manager to shutdown transport.
+ */
+static pj_status_t tls_shutdown(pjsip_transport *transport)
+{
+ struct tls_transport *tls = (struct tls_transport*)transport;
+
+ /* Stop keep-alive timer. */
+ if (tls->ka_timer.id) {
+ pjsip_endpt_cancel_timer(tls->base.endpt, &tls->ka_timer);
+ tls->ka_timer.id = PJ_FALSE;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Callback from ioqueue that an incoming data is received from the socket.
+ */
+static pj_bool_t on_data_read(pj_ssl_sock_t *ssock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder)
+{
+ enum { MAX_IMMEDIATE_PACKET = 10 };
+ struct tls_transport *tls;
+ pjsip_rx_data *rdata;
+
+ PJ_UNUSED_ARG(data);
+
+ tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock);
+ rdata = &tls->rdata;
+
+ /* Don't do anything if transport is closing. */
+ if (tls->is_closing) {
+ tls->is_closing++;
+ return PJ_FALSE;
+ }
+
+ /* Houston, we have packet! Report the packet to transport manager
+ * to be parsed.
+ */
+ if (status == PJ_SUCCESS) {
+ pj_size_t size_eaten;
+
+ /* Mark this as an activity */
+ pj_gettimeofday(&tls->last_activity);
+
+ pj_assert((void*)rdata->pkt_info.packet == data);
+
+ /* Init pkt_info part. */
+ rdata->pkt_info.len = size;
+ rdata->pkt_info.zero = 0;
+ pj_gettimeofday(&rdata->pkt_info.timestamp);
+
+ /* Report to transport manager.
+ * The transport manager will tell us how many bytes of the packet
+ * have been processed (as valid SIP message).
+ */
+ size_eaten =
+ pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr,
+ rdata);
+
+ pj_assert(size_eaten <= (pj_size_t)rdata->pkt_info.len);
+
+ /* Move unprocessed data to the front of the buffer */
+ *remainder = size - size_eaten;
+ if (*remainder > 0 && *remainder != size) {
+ pj_memmove(rdata->pkt_info.packet,
+ rdata->pkt_info.packet + size_eaten,
+ *remainder);
+ }
+
+ } else {
+
+ /* Transport is closed */
+ PJ_LOG(4,(tls->base.obj_name, "TLS connection closed"));
+
+ tls_init_shutdown(tls, status);
+
+ return PJ_FALSE;
+
+ }
+
+ /* Reset pool. */
+ pj_pool_reset(rdata->tp_info.pool);
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Callback from ioqueue when asynchronous connect() operation completes.
+ */
+static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock,
+ pj_status_t status)
+{
+ struct tls_transport *tls;
+ pj_ssl_sock_info ssl_info;
+ pj_sockaddr_in addr, *tp_addr;
+ pjsip_tp_state_callback state_cb;
+ pj_bool_t is_shutdown;
+
+ tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock);
+
+ /* Check connect() status */
+ if (status != PJ_SUCCESS) {
+
+ tls_perror(tls->base.obj_name, "TLS connect() error", status);
+
+ /* Cancel all delayed transmits */
+ while (!pj_list_empty(&tls->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pj_ioqueue_op_key_t *op_key;
+
+ pending_tx = tls->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ on_data_sent(tls->ssock, op_key, -status);
+ }
+
+ goto on_error;
+ }
+
+ /* Retrieve SSL socket info, shutdown the transport if this is failed
+ * as the SSL socket info availability is rather critical here.
+ */
+ status = pj_ssl_sock_get_info(tls->ssock, &ssl_info);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Update (again) local address, just in case local address currently
+ * set is different now that the socket is connected (could happen
+ * on some systems, like old Win32 probably?).
+ */
+ tp_addr = (pj_sockaddr_in*)&tls->base.local_addr;
+ pj_sockaddr_cp((pj_sockaddr_t*)&addr,
+ (pj_sockaddr_t*)&ssl_info.local_addr);
+ if (tp_addr->sin_addr.s_addr != addr.sin_addr.s_addr) {
+ tp_addr->sin_addr.s_addr = addr.sin_addr.s_addr;
+ tp_addr->sin_port = addr.sin_port;
+ sockaddr_to_host_port(tls->base.pool, &tls->base.local_name,
+ tp_addr);
+ }
+
+ /* Server identity verification based on server certificate. */
+ if (ssl_info.remote_cert_info->version) {
+ pj_str_t *remote_name;
+ pj_ssl_cert_info *serv_cert = ssl_info.remote_cert_info;
+ pj_bool_t matched = PJ_FALSE;
+ unsigned i;
+
+ /* Remote name may be hostname or IP address */
+ if (tls->remote_name.slen)
+ remote_name = &tls->remote_name;
+ else
+ remote_name = &tls->base.remote_name.host;
+
+ /* Start matching remote name with SubjectAltName fields of
+ * server certificate.
+ */
+ for (i = 0; i < serv_cert->subj_alt_name.cnt && !matched; ++i) {
+ pj_str_t *cert_name = &serv_cert->subj_alt_name.entry[i].name;
+
+ switch (serv_cert->subj_alt_name.entry[i].type) {
+ case PJ_SSL_CERT_NAME_DNS:
+ case PJ_SSL_CERT_NAME_IP:
+ matched = !pj_stricmp(remote_name, cert_name);
+ break;
+ case PJ_SSL_CERT_NAME_URI:
+ if (pj_strnicmp2(cert_name, "sip:", 4) == 0 ||
+ pj_strnicmp2(cert_name, "sips:", 5) == 0)
+ {
+ pj_str_t host_part;
+ char *p;
+
+ p = pj_strchr(cert_name, ':') + 1;
+ pj_strset(&host_part, p, cert_name->slen -
+ (p - cert_name->ptr));
+ matched = !pj_stricmp(remote_name, &host_part);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* When still not matched or no SubjectAltName fields in server
+ * certificate, try with Common Name of Subject field.
+ */
+ if (!matched) {
+ matched = !pj_stricmp(remote_name, &serv_cert->subject.cn);
+ }
+
+ if (!matched)
+ ssl_info.verify_status |= PJ_SSL_CERT_EIDENTITY_NOT_MATCH;
+ }
+
+ /* Prevent immediate transport destroy as application may access it
+ * (getting info, etc) in transport state notification callback.
+ */
+ pjsip_transport_add_ref(&tls->base);
+
+ /* If there is verification error and verification is mandatory, shutdown
+ * and destroy the transport.
+ */
+ if (ssl_info.verify_status && tls->verify_server) {
+ if (tls->close_reason == PJ_SUCCESS)
+ tls->close_reason = PJSIP_TLS_ECERTVERIF;
+ pjsip_transport_shutdown(&tls->base);
+ }
+
+ /* Notify transport state to application */
+ state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+ pjsip_tls_state_info tls_info;
+ pjsip_transport_state tp_state;
+
+ /* Init transport state info */
+ pj_bzero(&state_info, sizeof(state_info));
+ pj_bzero(&tls_info, sizeof(tls_info));
+ state_info.ext_info = &tls_info;
+ tls_info.ssl_sock_info = &ssl_info;
+
+ /* Set transport state based on verification status */
+ if (ssl_info.verify_status && tls->verify_server)
+ {
+ tp_state = PJSIP_TP_STATE_DISCONNECTED;
+ state_info.status = PJSIP_TLS_ECERTVERIF;
+ } else {
+ tp_state = PJSIP_TP_STATE_CONNECTED;
+ state_info.status = PJ_SUCCESS;
+ }
+
+ (*state_cb)(&tls->base, tp_state, &state_info);
+ }
+
+ /* Release transport reference. If transport is shutting down, it may
+ * get destroyed here.
+ */
+ is_shutdown = tls->base.is_shutdown;
+ pjsip_transport_dec_ref(&tls->base);
+ if (is_shutdown)
+ return PJ_FALSE;
+
+
+ /* Mark that pending connect() operation has completed. */
+ tls->has_pending_connect = PJ_FALSE;
+
+ PJ_LOG(4,(tls->base.obj_name,
+ "TLS transport %.*s:%d is connected to %.*s:%d",
+ (int)tls->base.local_name.host.slen,
+ tls->base.local_name.host.ptr,
+ tls->base.local_name.port,
+ (int)tls->base.remote_name.host.slen,
+ tls->base.remote_name.host.ptr,
+ tls->base.remote_name.port));
+
+ /* Start pending read */
+ status = tls_start_read(tls);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Flush all pending send operations */
+ tls_flush_pending_tx(tls);
+
+ /* Start keep-alive timer */
+ if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+ pj_time_val delay = { PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0 };
+ pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+ pj_gettimeofday(&tls->last_activity);
+ }
+
+ return PJ_TRUE;
+
+on_error:
+ tls_init_shutdown(tls, status);
+
+ return PJ_FALSE;
+}
+
+
+/* Transport keep-alive timer callback */
+static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e)
+{
+ struct tls_transport *tls = (struct tls_transport*) e->user_data;
+ pj_time_val delay;
+ pj_time_val now;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(th);
+
+ tls->ka_timer.id = PJ_TRUE;
+
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, tls->last_activity);
+
+ if (now.sec > 0 && now.sec < PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+ /* There has been activity, so don't send keep-alive */
+ delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL - now.sec;
+ delay.msec = 0;
+
+ pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+ return;
+ }
+
+ PJ_LOG(5,(tls->base.obj_name, "Sending %d byte(s) keep-alive to %.*s:%d",
+ (int)tls->ka_pkt.slen, (int)tls->base.remote_name.host.slen,
+ tls->base.remote_name.host.ptr,
+ tls->base.remote_name.port));
+
+ /* Send the data */
+ size = tls->ka_pkt.slen;
+ status = pj_ssl_sock_send(tls->ssock, &tls->ka_op_key.key,
+ tls->ka_pkt.ptr, &size, 0);
+
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ tls_perror(tls->base.obj_name,
+ "Error sending keep-alive packet", status);
+
+ tls_init_shutdown(tls, status);
+ return;
+ }
+
+ /* Register next keep-alive */
+ delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL;
+ delay.msec = 0;
+
+ pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+}
+
+#endif /* PJSIP_HAS_TLS_TRANSPORT */
diff --git a/pjsip/src/pjsip/sip_transport_udp.c b/pjsip/src/pjsip/sip_transport_udp.c
new file mode 100644
index 0000000..60b3175
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_udp.c
@@ -0,0 +1,1095 @@
+/* $Id: sip_transport_udp.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_transport_udp.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pj/addr_resolv.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/sock.h>
+#include <pj/compat/socket.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "sip_transport_udp.c"
+
+/**
+ * These are the target values for socket send and receive buffer sizes,
+ * respectively. They will be applied to UDP socket with setsockopt().
+ * When transport failed to set these size, it will decrease it until
+ * sufficiently large number has been successfully set.
+ *
+ * The buffer size is important, especially in WinXP/2000 machines.
+ * Basicly the lower the size, the more packets will be lost (dropped?)
+ * when we're sending (receiving?) packets in large volumes.
+ *
+ * The figure here is taken based on my experiment on WinXP/2000 machine,
+ * and with this value, the rate of dropped packet is about 8% when
+ * sending 1800 requests simultaneously (percentage taken as average
+ * after 50K requests or so).
+ *
+ * More experiments are needed probably.
+ */
+/* 2010/01/14
+ * Too many people complained about seeing "Error setting SNDBUF" log,
+ * so lets just remove this. People who want to have SNDBUF set can
+ * still do so by declaring these two macros in config_site.h
+ */
+#ifndef PJSIP_UDP_SO_SNDBUF_SIZE
+/*# define PJSIP_UDP_SO_SNDBUF_SIZE (24*1024*1024)*/
+# define PJSIP_UDP_SO_SNDBUF_SIZE 0
+#endif
+
+#ifndef PJSIP_UDP_SO_RCVBUF_SIZE
+/*# define PJSIP_UDP_SO_RCVBUF_SIZE (24*1024*1024)*/
+# define PJSIP_UDP_SO_RCVBUF_SIZE 0
+#endif
+
+
+/* Struct udp_transport "inherits" struct pjsip_transport */
+struct udp_transport
+{
+ pjsip_transport base;
+ pj_sock_t sock;
+ pj_ioqueue_key_t *key;
+ int rdata_cnt;
+ pjsip_rx_data **rdata;
+ int is_closing;
+ pj_bool_t is_paused;
+};
+
+
+/*
+ * Initialize transport's receive buffer from the specified pool.
+ */
+static void init_rdata(struct udp_transport *tp, unsigned rdata_index,
+ pj_pool_t *pool, pjsip_rx_data **p_rdata)
+{
+ pjsip_rx_data *rdata;
+
+ /* Reset pool. */
+ //note: already done by caller
+ //pj_pool_reset(pool);
+
+ rdata = PJ_POOL_ZALLOC_T(pool, pjsip_rx_data);
+
+ /* Init tp_info part. */
+ rdata->tp_info.pool = pool;
+ rdata->tp_info.transport = &tp->base;
+ rdata->tp_info.tp_data = (void*)(long)rdata_index;
+ rdata->tp_info.op_key.rdata = rdata;
+ pj_ioqueue_op_key_init(&rdata->tp_info.op_key.op_key,
+ sizeof(pj_ioqueue_op_key_t));
+
+ tp->rdata[rdata_index] = rdata;
+
+ if (p_rdata)
+ *p_rdata = rdata;
+}
+
+
+/*
+ * udp_on_read_complete()
+ *
+ * This is callback notification from ioqueue that a pending recvfrom()
+ * operation has completed.
+ */
+static void udp_on_read_complete( pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read)
+{
+ /* See https://trac.pjsip.org/repos/ticket/1197 */
+ enum { MAX_IMMEDIATE_PACKET = 50 };
+ pjsip_rx_data_op_key *rdata_op_key = (pjsip_rx_data_op_key*) op_key;
+ pjsip_rx_data *rdata = rdata_op_key->rdata;
+ struct udp_transport *tp = (struct udp_transport*)rdata->tp_info.transport;
+ int i;
+ pj_status_t status;
+
+ /* Don't do anything if transport is closing. */
+ if (tp->is_closing) {
+ tp->is_closing++;
+ return;
+ }
+
+ /* Don't do anything if transport is being paused. */
+ if (tp->is_paused)
+ return;
+
+ /*
+ * The idea of the loop is to process immediate data received by
+ * pj_ioqueue_recvfrom(), as long as i < MAX_IMMEDIATE_PACKET. When
+ * i is >= MAX_IMMEDIATE_PACKET, we force the recvfrom() operation to
+ * complete asynchronously, to allow other sockets to get their data.
+ */
+ for (i=0;; ++i) {
+ enum { MIN_SIZE = 32 };
+ pj_uint32_t flags;
+
+ /* Report the packet to transport manager. Only do so if packet size
+ * is relatively big enough for a SIP packet.
+ */
+ if (bytes_read > MIN_SIZE) {
+ pj_size_t size_eaten;
+ const pj_sockaddr *src_addr = &rdata->pkt_info.src_addr;
+
+ /* Init pkt_info part. */
+ rdata->pkt_info.len = bytes_read;
+ rdata->pkt_info.zero = 0;
+ pj_gettimeofday(&rdata->pkt_info.timestamp);
+ if (src_addr->addr.sa_family == pj_AF_INET()) {
+ pj_ansi_strcpy(rdata->pkt_info.src_name,
+ pj_inet_ntoa(src_addr->ipv4.sin_addr));
+ rdata->pkt_info.src_port = pj_ntohs(src_addr->ipv4.sin_port);
+ } else {
+ pj_inet_ntop(pj_AF_INET6(),
+ pj_sockaddr_get_addr(&rdata->pkt_info.src_addr),
+ rdata->pkt_info.src_name,
+ sizeof(rdata->pkt_info.src_name));
+ rdata->pkt_info.src_port = pj_ntohs(src_addr->ipv6.sin6_port);
+ }
+
+ size_eaten =
+ pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr,
+ rdata);
+
+ if (size_eaten < 0) {
+ pj_assert(!"It shouldn't happen!");
+ size_eaten = rdata->pkt_info.len;
+ }
+
+ /* Since this is UDP, the whole buffer is the message. */
+ rdata->pkt_info.len = 0;
+
+ } else if (bytes_read <= MIN_SIZE) {
+
+ /* TODO: */
+
+ } else if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) &&
+ -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) &&
+ -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET))
+ {
+
+ /* Report error to endpoint. */
+ PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt,
+ rdata->tp_info.transport->obj_name,
+ -bytes_read,
+ "Warning: pj_ioqueue_recvfrom()"
+ " callback error"));
+ }
+
+ if (i >= MAX_IMMEDIATE_PACKET) {
+ /* Force ioqueue_recvfrom() to return PJ_EPENDING */
+ flags = PJ_IOQUEUE_ALWAYS_ASYNC;
+ } else {
+ flags = 0;
+ }
+
+ /* Reset pool.
+ * Need to copy rdata fields to temp variable because they will
+ * be invalid after pj_pool_reset().
+ */
+ {
+ pj_pool_t *rdata_pool = rdata->tp_info.pool;
+ struct udp_transport *rdata_tp ;
+ unsigned rdata_index;
+
+ rdata_tp = (struct udp_transport*)rdata->tp_info.transport;
+ rdata_index = (unsigned)(unsigned long)rdata->tp_info.tp_data;
+
+ pj_pool_reset(rdata_pool);
+ init_rdata(rdata_tp, rdata_index, rdata_pool, &rdata);
+
+ /* Change some vars to point to new location after
+ * pool reset.
+ */
+ op_key = &rdata->tp_info.op_key.op_key;
+ }
+
+ /* Only read next packet if transport is not being paused. This
+ * check handles the case where transport is paused while endpoint
+ * is still processing a SIP message.
+ */
+ if (tp->is_paused)
+ return;
+
+ /* Read next packet. */
+ bytes_read = sizeof(rdata->pkt_info.packet);
+ rdata->pkt_info.src_addr_len = sizeof(rdata->pkt_info.src_addr);
+ status = pj_ioqueue_recvfrom(key, op_key,
+ rdata->pkt_info.packet,
+ &bytes_read, flags,
+ &rdata->pkt_info.src_addr,
+ &rdata->pkt_info.src_addr_len);
+
+ if (status == PJ_SUCCESS) {
+ /* Continue loop. */
+ pj_assert(i < MAX_IMMEDIATE_PACKET);
+
+ } else if (status == PJ_EPENDING) {
+ break;
+
+ } else {
+
+ if (i < MAX_IMMEDIATE_PACKET) {
+
+ /* Report error to endpoint if this is not EWOULDBLOCK error.*/
+ if (status != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) &&
+ status != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) &&
+ status != PJ_STATUS_FROM_OS(OSERR_ECONNRESET))
+ {
+
+ PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt,
+ rdata->tp_info.transport->obj_name,
+ status,
+ "Warning: pj_ioqueue_recvfrom"));
+ }
+
+ /* Continue loop. */
+ bytes_read = 0;
+ } else {
+ /* This is fatal error.
+ * Ioqueue operation will stop for this transport!
+ */
+ PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt,
+ rdata->tp_info.transport->obj_name,
+ status,
+ "FATAL: pj_ioqueue_recvfrom() error, "
+ "UDP transport stopping! Error"));
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * udp_on_write_complete()
+ *
+ * This is callback notification from ioqueue that a pending sendto()
+ * operation has completed.
+ */
+static void udp_on_write_complete( pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_sent)
+{
+ struct udp_transport *tp = (struct udp_transport*)
+ pj_ioqueue_get_user_data(key);
+ pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key;
+
+ tdata_op_key->tdata = NULL;
+
+ if (tdata_op_key->callback) {
+ tdata_op_key->callback(&tp->base, tdata_op_key->token, bytes_sent);
+ }
+}
+
+/*
+ * udp_send_msg()
+ *
+ * This function is called by transport manager (by transport->send_msg())
+ * to send outgoing message.
+ */
+static pj_status_t udp_send_msg( pjsip_transport *transport,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback callback)
+{
+ struct udp_transport *tp = (struct udp_transport*)transport;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX);
+
+ /* Return error if transport is paused */
+ if (tp->is_paused)
+ return PJSIP_ETPNOTAVAIL;
+
+ /* Init op key. */
+ tdata->op_key.tdata = tdata;
+ tdata->op_key.token = token;
+ tdata->op_key.callback = callback;
+
+ /* Send to ioqueue! */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_ioqueue_sendto(tp->key, (pj_ioqueue_op_key_t*)&tdata->op_key,
+ tdata->buf.start, &size, 0,
+ rem_addr, addr_len);
+
+ if (status != PJ_EPENDING)
+ tdata->op_key.tdata = NULL;
+
+ return status;
+}
+
+/*
+ * udp_destroy()
+ *
+ * This function is called by transport manager (by transport->destroy()).
+ */
+static pj_status_t udp_destroy( pjsip_transport *transport )
+{
+ struct udp_transport *tp = (struct udp_transport*)transport;
+ int i;
+
+ /* Mark this transport as closing. */
+ tp->is_closing = 1;
+
+ /* Cancel all pending operations. */
+ /* blp: NO NO NO...
+ * No need to post queued completion as we poll the ioqueue until
+ * we've got events anyway. Posting completion will only cause
+ * callback to be called twice with IOCP: one for the post completion
+ * and another one for closing the socket.
+ *
+ for (i=0; i<tp->rdata_cnt; ++i) {
+ pj_ioqueue_post_completion(tp->key,
+ &tp->rdata[i]->tp_info.op_key.op_key, -1);
+ }
+ */
+
+ /* Unregister from ioqueue. */
+ if (tp->key) {
+ pj_ioqueue_unregister(tp->key);
+ tp->key = NULL;
+ } else {
+ /* Close socket. */
+ if (tp->sock && tp->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(tp->sock);
+ tp->sock = PJ_INVALID_SOCKET;
+ }
+ }
+
+ /* Must poll ioqueue because IOCP calls the callback when socket
+ * is closed. We poll the ioqueue until all pending callbacks
+ * have been called.
+ */
+ for (i=0; i<50 && tp->is_closing < 1+tp->rdata_cnt; ++i) {
+ int cnt;
+ pj_time_val timeout = {0, 1};
+
+ cnt = pj_ioqueue_poll(pjsip_endpt_get_ioqueue(transport->endpt),
+ &timeout);
+ if (cnt == 0)
+ break;
+ }
+
+ /* Destroy rdata */
+ for (i=0; i<tp->rdata_cnt; ++i) {
+ pj_pool_release(tp->rdata[i]->tp_info.pool);
+ }
+
+ /* Destroy reference counter. */
+ if (tp->base.ref_cnt)
+ pj_atomic_destroy(tp->base.ref_cnt);
+
+ /* Destroy lock */
+ if (tp->base.lock)
+ pj_lock_destroy(tp->base.lock);
+
+ /* Destroy pool. */
+ pjsip_endpt_release_pool(tp->base.endpt, tp->base.pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * udp_shutdown()
+ *
+ * Start graceful UDP shutdown.
+ */
+static pj_status_t udp_shutdown(pjsip_transport *transport)
+{
+ return pjsip_transport_dec_ref(transport);
+}
+
+
+/* Create socket */
+static pj_status_t create_socket(int af, const pj_sockaddr_t *local_a,
+ int addr_len, pj_sock_t *p_sock)
+{
+ pj_sock_t sock;
+ pj_sockaddr_in tmp_addr;
+ pj_sockaddr_in6 tmp_addr6;
+ pj_status_t status;
+
+ status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (local_a == NULL) {
+ if (af == pj_AF_INET6()) {
+ pj_bzero(&tmp_addr6, sizeof(tmp_addr6));
+ tmp_addr6.sin6_family = (pj_uint16_t)af;
+ local_a = &tmp_addr6;
+ addr_len = sizeof(tmp_addr6);
+ } else {
+ pj_sockaddr_in_init(&tmp_addr, NULL, 0);
+ local_a = &tmp_addr;
+ addr_len = sizeof(tmp_addr);
+ }
+ }
+
+ status = pj_sock_bind(sock, local_a, addr_len);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ *p_sock = sock;
+ return PJ_SUCCESS;
+}
+
+
+/* Generate transport's published address */
+static pj_status_t get_published_name(pj_sock_t sock,
+ char hostbuf[],
+ int hostbufsz,
+ pjsip_host_port *bound_name)
+{
+ pj_sockaddr tmp_addr;
+ int addr_len;
+ pj_status_t status;
+
+ addr_len = sizeof(tmp_addr);
+ status = pj_sock_getsockname(sock, &tmp_addr, &addr_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ bound_name->host.ptr = hostbuf;
+ if (tmp_addr.addr.sa_family == pj_AF_INET()) {
+ bound_name->port = pj_ntohs(tmp_addr.ipv4.sin_port);
+
+ /* If bound address specifies "0.0.0.0", get the IP address
+ * of local hostname.
+ */
+ if (tmp_addr.ipv4.sin_addr.s_addr == PJ_INADDR_ANY) {
+ pj_sockaddr hostip;
+
+ status = pj_gethostip(pj_AF_INET(), &hostip);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_strcpy2(&bound_name->host, pj_inet_ntoa(hostip.ipv4.sin_addr));
+ } else {
+ /* Otherwise use bound address. */
+ pj_strcpy2(&bound_name->host,
+ pj_inet_ntoa(tmp_addr.ipv4.sin_addr));
+ status = PJ_SUCCESS;
+ }
+
+ } else {
+ /* If bound address specifies "INADDR_ANY" (IPv6), get the
+ * IP address of local hostname
+ */
+ pj_uint32_t loop6[4] = { 0, 0, 0, 0};
+
+ bound_name->port = pj_ntohs(tmp_addr.ipv6.sin6_port);
+
+ if (pj_memcmp(&tmp_addr.ipv6.sin6_addr, loop6, sizeof(loop6))==0) {
+ status = pj_gethostip(tmp_addr.addr.sa_family, &tmp_addr);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ status = pj_inet_ntop(tmp_addr.addr.sa_family,
+ pj_sockaddr_get_addr(&tmp_addr),
+ hostbuf, hostbufsz);
+ if (status == PJ_SUCCESS) {
+ bound_name->host.slen = pj_ansi_strlen(hostbuf);
+ }
+ }
+
+
+ return status;
+}
+
+/* Set the published address of the transport */
+static void udp_set_pub_name(struct udp_transport *tp,
+ const pjsip_host_port *a_name)
+{
+ enum { INFO_LEN = 80 };
+ char local_addr[PJ_INET6_ADDRSTRLEN+10];
+
+ pj_assert(a_name->host.slen != 0);
+ pj_strdup_with_null(tp->base.pool, &tp->base.local_name.host,
+ &a_name->host);
+ tp->base.local_name.port = a_name->port;
+
+ /* Update transport info. */
+ if (tp->base.info == NULL) {
+ tp->base.info = (char*) pj_pool_alloc(tp->base.pool, INFO_LEN);
+ }
+
+ pj_sockaddr_print(&tp->base.local_addr, local_addr, sizeof(local_addr), 3);
+
+ pj_ansi_snprintf(
+ tp->base.info, INFO_LEN, "udp %s [published as %s:%d]",
+ local_addr,
+ tp->base.local_name.host.ptr,
+ tp->base.local_name.port);
+}
+
+/* Set the socket handle of the transport */
+static void udp_set_socket(struct udp_transport *tp,
+ pj_sock_t sock,
+ const pjsip_host_port *a_name)
+{
+#if PJSIP_UDP_SO_RCVBUF_SIZE || PJSIP_UDP_SO_SNDBUF_SIZE
+ long sobuf_size;
+ pj_status_t status;
+#endif
+
+ /* Adjust socket rcvbuf size */
+#if PJSIP_UDP_SO_RCVBUF_SIZE
+ sobuf_size = PJSIP_UDP_SO_RCVBUF_SIZE;
+ status = pj_sock_setsockopt(sock, pj_SOL_SOCKET(), pj_SO_RCVBUF(),
+ &sobuf_size, sizeof(sobuf_size));
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(THIS_FILE, "Error setting SO_RCVBUF: %s [%d]", errmsg,
+ status));
+ }
+#endif
+
+ /* Adjust socket sndbuf size */
+#if PJSIP_UDP_SO_SNDBUF_SIZE
+ sobuf_size = PJSIP_UDP_SO_SNDBUF_SIZE;
+ status = pj_sock_setsockopt(sock, pj_SOL_SOCKET(), pj_SO_SNDBUF(),
+ &sobuf_size, sizeof(sobuf_size));
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(THIS_FILE, "Error setting SO_SNDBUF: %s [%d]", errmsg,
+ status));
+ }
+#endif
+
+ /* Set the socket. */
+ tp->sock = sock;
+
+ /* Init address name (published address) */
+ udp_set_pub_name(tp, a_name);
+}
+
+/* Register socket to ioqueue */
+static pj_status_t register_to_ioqueue(struct udp_transport *tp)
+{
+ pj_ioqueue_t *ioqueue;
+ pj_ioqueue_callback ioqueue_cb;
+
+ /* Ignore if already registered */
+ if (tp->key != NULL)
+ return PJ_SUCCESS;
+
+ /* Register to ioqueue. */
+ ioqueue = pjsip_endpt_get_ioqueue(tp->base.endpt);
+ pj_memset(&ioqueue_cb, 0, sizeof(ioqueue_cb));
+ ioqueue_cb.on_read_complete = &udp_on_read_complete;
+ ioqueue_cb.on_write_complete = &udp_on_write_complete;
+
+ return pj_ioqueue_register_sock(tp->base.pool, ioqueue, tp->sock, tp,
+ &ioqueue_cb, &tp->key);
+}
+
+/* Start ioqueue asynchronous reading to all rdata */
+static pj_status_t start_async_read(struct udp_transport *tp)
+{
+ pj_ioqueue_t *ioqueue;
+ int i;
+ pj_status_t status;
+
+ ioqueue = pjsip_endpt_get_ioqueue(tp->base.endpt);
+
+ /* Start reading the ioqueue. */
+ for (i=0; i<tp->rdata_cnt; ++i) {
+ pj_ssize_t size;
+
+ size = sizeof(tp->rdata[i]->pkt_info.packet);
+ tp->rdata[i]->pkt_info.src_addr_len = sizeof(tp->rdata[i]->pkt_info.src_addr);
+ status = pj_ioqueue_recvfrom(tp->key,
+ &tp->rdata[i]->tp_info.op_key.op_key,
+ tp->rdata[i]->pkt_info.packet,
+ &size, PJ_IOQUEUE_ALWAYS_ASYNC,
+ &tp->rdata[i]->pkt_info.src_addr,
+ &tp->rdata[i]->pkt_info.src_addr_len);
+ if (status == PJ_SUCCESS) {
+ pj_assert(!"Shouldn't happen because PJ_IOQUEUE_ALWAYS_ASYNC!");
+ udp_on_read_complete(tp->key, &tp->rdata[i]->tp_info.op_key.op_key,
+ size);
+ } else if (status != PJ_EPENDING) {
+ /* Error! */
+ return status;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * pjsip_udp_transport_attach()
+ *
+ * Attach UDP socket and start transport.
+ */
+static pj_status_t transport_attach( pjsip_endpoint *endpt,
+ pjsip_transport_type_e type,
+ pj_sock_t sock,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_transport **p_transport)
+{
+ pj_pool_t *pool;
+ struct udp_transport *tp;
+ const char *format, *ipv6_quoteb, *ipv6_quotee;
+ unsigned i;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt && sock!=PJ_INVALID_SOCKET && a_name && async_cnt>0,
+ PJ_EINVAL);
+
+ /* Object name. */
+ if (type & PJSIP_TRANSPORT_IPV6) {
+ format = "udpv6%p";
+ ipv6_quoteb = "[";
+ ipv6_quotee = "]";
+ } else {
+ format = "udp%p";
+ ipv6_quoteb = ipv6_quotee = "";
+ }
+
+ /* Create pool. */
+ pool = pjsip_endpt_create_pool(endpt, format, PJSIP_POOL_LEN_TRANSPORT,
+ PJSIP_POOL_INC_TRANSPORT);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ /* Create the UDP transport object. */
+ tp = PJ_POOL_ZALLOC_T(pool, struct udp_transport);
+
+ /* Save pool. */
+ tp->base.pool = pool;
+
+ pj_memcpy(tp->base.obj_name, pool->obj_name, PJ_MAX_OBJ_NAME);
+
+ /* Init reference counter. */
+ status = pj_atomic_create(pool, 0, &tp->base.ref_cnt);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init lock. */
+ status = pj_lock_create_recursive_mutex(pool, pool->obj_name,
+ &tp->base.lock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Set type. */
+ tp->base.key.type = type;
+
+ /* Remote address is left zero (except the family) */
+ tp->base.key.rem_addr.addr.sa_family = (pj_uint16_t)
+ ((type & PJSIP_TRANSPORT_IPV6) ? pj_AF_INET6() : pj_AF_INET());
+
+ /* Type name. */
+ tp->base.type_name = "UDP";
+
+ /* Transport flag */
+ tp->base.flag = pjsip_transport_get_flag_from_type(type);
+
+
+ /* Length of addressess. */
+ tp->base.addr_len = sizeof(tp->base.local_addr);
+
+ /* Init local address. */
+ status = pj_sock_getsockname(sock, &tp->base.local_addr,
+ &tp->base.addr_len);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init remote name. */
+ if (type == PJSIP_TRANSPORT_UDP)
+ tp->base.remote_name.host = pj_str("0.0.0.0");
+ else
+ tp->base.remote_name.host = pj_str("::0");
+ tp->base.remote_name.port = 0;
+
+ /* Init direction */
+ tp->base.dir = PJSIP_TP_DIR_NONE;
+
+ /* Set endpoint. */
+ tp->base.endpt = endpt;
+
+ /* Transport manager and timer will be initialized by tpmgr */
+
+ /* Attach socket and assign name. */
+ udp_set_socket(tp, sock, a_name);
+
+ /* Register to ioqueue */
+ status = register_to_ioqueue(tp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Set functions. */
+ tp->base.send_msg = &udp_send_msg;
+ tp->base.do_shutdown = &udp_shutdown;
+ tp->base.destroy = &udp_destroy;
+
+ /* This is a permanent transport, so we initialize the ref count
+ * to one so that transport manager don't destroy this transport
+ * when there's no user!
+ */
+ pj_atomic_inc(tp->base.ref_cnt);
+
+ /* Register to transport manager. */
+ tp->base.tpmgr = pjsip_endpt_get_tpmgr(endpt);
+ status = pjsip_transport_register( tp->base.tpmgr, (pjsip_transport*)tp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Create rdata and put it in the array. */
+ tp->rdata_cnt = 0;
+ tp->rdata = (pjsip_rx_data**)
+ pj_pool_calloc(tp->base.pool, async_cnt,
+ sizeof(pjsip_rx_data*));
+ for (i=0; i<async_cnt; ++i) {
+ pj_pool_t *rdata_pool = pjsip_endpt_create_pool(endpt, "rtd%p",
+ PJSIP_POOL_RDATA_LEN,
+ PJSIP_POOL_RDATA_INC);
+ if (!rdata_pool) {
+ pj_atomic_set(tp->base.ref_cnt, 0);
+ pjsip_transport_destroy(&tp->base);
+ return PJ_ENOMEM;
+ }
+
+ init_rdata(tp, i, rdata_pool, NULL);
+ tp->rdata_cnt++;
+ }
+
+ /* Start reading the ioqueue. */
+ status = start_async_read(tp);
+ if (status != PJ_SUCCESS) {
+ pjsip_transport_destroy(&tp->base);
+ return status;
+ }
+
+ /* Done. */
+ if (p_transport)
+ *p_transport = &tp->base;
+
+ PJ_LOG(4,(tp->base.obj_name,
+ "SIP %s started, published address is %s%.*s%s:%d",
+ pjsip_transport_get_type_desc((pjsip_transport_type_e)tp->base.key.type),
+ ipv6_quoteb,
+ (int)tp->base.local_name.host.slen,
+ tp->base.local_name.host.ptr,
+ ipv6_quotee,
+ tp->base.local_name.port));
+
+ return PJ_SUCCESS;
+
+on_error:
+ udp_destroy((pjsip_transport*)tp);
+ return status;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_udp_transport_attach( pjsip_endpoint *endpt,
+ pj_sock_t sock,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_transport **p_transport)
+{
+ return transport_attach(endpt, PJSIP_TRANSPORT_UDP, sock, a_name,
+ async_cnt, p_transport);
+}
+
+PJ_DEF(pj_status_t) pjsip_udp_transport_attach2( pjsip_endpoint *endpt,
+ pjsip_transport_type_e type,
+ pj_sock_t sock,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_transport **p_transport)
+{
+ return transport_attach(endpt, type, sock, a_name,
+ async_cnt, p_transport);
+}
+
+/*
+ * pjsip_udp_transport_start()
+ *
+ * Create a UDP socket in the specified address and start a transport.
+ */
+PJ_DEF(pj_status_t) pjsip_udp_transport_start( pjsip_endpoint *endpt,
+ const pj_sockaddr_in *local_a,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_transport **p_transport)
+{
+ pj_sock_t sock;
+ pj_status_t status;
+ char addr_buf[PJ_INET6_ADDRSTRLEN];
+ pjsip_host_port bound_name;
+
+ PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL);
+
+ status = create_socket(pj_AF_INET(), local_a, sizeof(pj_sockaddr_in),
+ &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (a_name == NULL) {
+ /* Address name is not specified.
+ * Build a name based on bound address.
+ */
+ status = get_published_name(sock, addr_buf, sizeof(addr_buf),
+ &bound_name);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ a_name = &bound_name;
+ }
+
+ return pjsip_udp_transport_attach( endpt, sock, a_name, async_cnt,
+ p_transport );
+}
+
+
+/*
+ * pjsip_udp_transport_start()
+ *
+ * Create a UDP socket in the specified address and start a transport.
+ */
+PJ_DEF(pj_status_t) pjsip_udp_transport_start6(pjsip_endpoint *endpt,
+ const pj_sockaddr_in6 *local_a,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_transport **p_transport)
+{
+ pj_sock_t sock;
+ pj_status_t status;
+ char addr_buf[PJ_INET6_ADDRSTRLEN];
+ pjsip_host_port bound_name;
+
+ PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL);
+
+ status = create_socket(pj_AF_INET6(), local_a, sizeof(pj_sockaddr_in6),
+ &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (a_name == NULL) {
+ /* Address name is not specified.
+ * Build a name based on bound address.
+ */
+ status = get_published_name(sock, addr_buf, sizeof(addr_buf),
+ &bound_name);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ a_name = &bound_name;
+ }
+
+ return pjsip_udp_transport_attach2(endpt, PJSIP_TRANSPORT_UDP6,
+ sock, a_name, async_cnt, p_transport);
+}
+
+/*
+ * Retrieve the internal socket handle used by the UDP transport.
+ */
+PJ_DEF(pj_sock_t) pjsip_udp_transport_get_socket(pjsip_transport *transport)
+{
+ struct udp_transport *tp;
+
+ PJ_ASSERT_RETURN(transport != NULL, PJ_INVALID_SOCKET);
+
+ tp = (struct udp_transport*) transport;
+
+ return tp->sock;
+}
+
+
+/*
+ * Temporarily pause or shutdown the transport.
+ */
+PJ_DEF(pj_status_t) pjsip_udp_transport_pause(pjsip_transport *transport,
+ unsigned option)
+{
+ struct udp_transport *tp;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(transport != NULL, PJ_EINVAL);
+
+ /* Flag must be specified */
+ PJ_ASSERT_RETURN((option & 0x03) != 0, PJ_EINVAL);
+
+ tp = (struct udp_transport*) transport;
+
+ /* Transport must not have been paused */
+ PJ_ASSERT_RETURN(tp->is_paused==0, PJ_EINVALIDOP);
+
+ /* Set transport to paused first, so that when the read callback is
+ * called by pj_ioqueue_post_completion() it will not try to
+ * re-register the rdata.
+ */
+ tp->is_paused = PJ_TRUE;
+
+ /* Cancel the ioqueue operation. */
+ for (i=0; i<(unsigned)tp->rdata_cnt; ++i) {
+ pj_ioqueue_post_completion(tp->key,
+ &tp->rdata[i]->tp_info.op_key.op_key, -1);
+ }
+
+ /* Destroy the socket? */
+ if (option & PJSIP_UDP_TRANSPORT_DESTROY_SOCKET) {
+ if (tp->key) {
+ /* This implicitly closes the socket */
+ pj_ioqueue_unregister(tp->key);
+ tp->key = NULL;
+ } else {
+ /* Close socket. */
+ if (tp->sock && tp->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(tp->sock);
+ tp->sock = PJ_INVALID_SOCKET;
+ }
+ }
+ tp->sock = PJ_INVALID_SOCKET;
+
+ }
+
+ PJ_LOG(4,(tp->base.obj_name, "SIP UDP transport paused"));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Restart transport.
+ *
+ * If option is KEEP_SOCKET, just re-activate ioqueue operation.
+ *
+ * If option is DESTROY_SOCKET:
+ * - if socket is specified, replace.
+ * - if socket is not specified, create and replace.
+ */
+PJ_DEF(pj_status_t) pjsip_udp_transport_restart(pjsip_transport *transport,
+ unsigned option,
+ pj_sock_t sock,
+ const pj_sockaddr_in *local,
+ const pjsip_host_port *a_name)
+{
+ struct udp_transport *tp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(transport != NULL, PJ_EINVAL);
+ /* Flag must be specified */
+ PJ_ASSERT_RETURN((option & 0x03) != 0, PJ_EINVAL);
+
+ tp = (struct udp_transport*) transport;
+
+ if (option & PJSIP_UDP_TRANSPORT_DESTROY_SOCKET) {
+ char addr_buf[PJ_INET6_ADDRSTRLEN];
+ pjsip_host_port bound_name;
+
+ /* Request to recreate transport */
+
+ /* Destroy existing socket, if any. */
+ if (tp->key) {
+ /* This implicitly closes the socket */
+ pj_ioqueue_unregister(tp->key);
+ tp->key = NULL;
+ } else {
+ /* Close socket. */
+ if (tp->sock && tp->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(tp->sock);
+ tp->sock = PJ_INVALID_SOCKET;
+ }
+ }
+ tp->sock = PJ_INVALID_SOCKET;
+
+ /* Create the socket if it's not specified */
+ if (sock == PJ_INVALID_SOCKET) {
+ status = create_socket(pj_AF_INET(), local,
+ sizeof(pj_sockaddr_in), &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* If transport published name is not specified, calculate it
+ * from the bound address.
+ */
+ if (a_name == NULL) {
+ status = get_published_name(sock, addr_buf, sizeof(addr_buf),
+ &bound_name);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ a_name = &bound_name;
+ }
+
+ /* Assign the socket and published address to transport. */
+ udp_set_socket(tp, sock, a_name);
+
+ } else {
+
+ /* For KEEP_SOCKET, transport must have been paused before */
+ PJ_ASSERT_RETURN(tp->is_paused, PJ_EINVALIDOP);
+
+ /* If address name is specified, update it */
+ if (a_name != NULL)
+ udp_set_pub_name(tp, a_name);
+ }
+
+ /* Re-register new or existing socket to ioqueue. */
+ status = register_to_ioqueue(tp);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Restart async read operation. */
+ status = start_async_read(tp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Everything has been set up */
+ tp->is_paused = PJ_FALSE;
+
+ PJ_LOG(4,(tp->base.obj_name,
+ "SIP UDP transport restarted, published address is %.*s:%d",
+ (int)tp->base.local_name.host.slen,
+ tp->base.local_name.host.ptr,
+ tp->base.local_name.port));
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsip/sip_transport_wrap.cpp b/pjsip/src/pjsip/sip_transport_wrap.cpp
new file mode 100644
index 0000000..80d1e88
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_transport_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_transport.c"
diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c
new file mode 100644
index 0000000..7128891
--- /dev/null
+++ b/pjsip/src/pjsip/sip_ua_layer.c
@@ -0,0 +1,978 @@
+/* $Id: sip_ua_layer.c 3664 2011-07-19 03:42:28Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_ua_layer.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_dialog.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_transaction.h>
+#include <pj/os.h>
+#include <pj/hash.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/log.h>
+
+
+#define THIS_FILE "sip_ua_layer.c"
+
+/*
+ * Static prototypes.
+ */
+static pj_status_t mod_ua_load(pjsip_endpoint *endpt);
+static pj_status_t mod_ua_unload(void);
+static pj_bool_t mod_ua_on_rx_request(pjsip_rx_data *rdata);
+static pj_bool_t mod_ua_on_rx_response(pjsip_rx_data *rdata);
+static void mod_ua_on_tsx_state(pjsip_transaction*, pjsip_event*);
+
+
+extern long pjsip_dlg_lock_tls_id; /* defined in sip_dialog.c */
+
+/* This struct is used to represent list of dialog inside a dialog set.
+ * We don't want to use pjsip_dialog for this purpose, to save some
+ * memory (about 100 bytes per dialog set).
+ */
+struct dlg_set_head
+{
+ PJ_DECL_LIST_MEMBER(pjsip_dialog);
+};
+
+/* This struct represents a dialog set.
+ * This is the value that will be put in the UA's hash table.
+ */
+struct dlg_set
+{
+ /* To put this node in free dlg_set nodes in UA. */
+ PJ_DECL_LIST_MEMBER(struct dlg_set);
+
+ /* This is the buffer to store this entry in the hash table. */
+ pj_hash_entry_buf ht_entry;
+
+ /* List of dialog in this dialog set. */
+ struct dlg_set_head dlg_list;
+};
+
+
+/*
+ * Module interface.
+ */
+static struct user_agent
+{
+ pjsip_module mod;
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pj_mutex_t *mutex;
+ pj_hash_table_t *dlg_table;
+ pjsip_ua_init_param param;
+ struct dlg_set free_dlgset_nodes;
+
+} mod_ua =
+{
+ {
+ NULL, NULL, /* prev, next. */
+ { "mod-ua", 6 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_UA_PROXY_LAYER, /* Priority */
+ &mod_ua_load, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ &mod_ua_unload, /* unload() */
+ &mod_ua_on_rx_request, /* on_rx_request() */
+ &mod_ua_on_rx_response, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ &mod_ua_on_tsx_state, /* on_tsx_state() */
+ }
+};
+
+/*
+ * mod_ua_load()
+ *
+ * Called when module is being loaded by endpoint.
+ */
+static pj_status_t mod_ua_load(pjsip_endpoint *endpt)
+{
+ pj_status_t status;
+
+ /* Initialize the user agent. */
+ mod_ua.endpt = endpt;
+ mod_ua.pool = pjsip_endpt_create_pool( endpt, "ua%p", PJSIP_POOL_LEN_UA,
+ PJSIP_POOL_INC_UA);
+ if (mod_ua.pool == NULL)
+ return PJ_ENOMEM;
+
+ status = pj_mutex_create_recursive(mod_ua.pool, " ua%p", &mod_ua.mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ mod_ua.dlg_table = pj_hash_create(mod_ua.pool, PJSIP_MAX_DIALOG_COUNT);
+ if (mod_ua.dlg_table == NULL)
+ return PJ_ENOMEM;
+
+ pj_list_init(&mod_ua.free_dlgset_nodes);
+
+ /* Initialize dialog lock. */
+ status = pj_thread_local_alloc(&pjsip_dlg_lock_tls_id);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_thread_local_set(pjsip_dlg_lock_tls_id, NULL);
+
+ return PJ_SUCCESS;
+
+}
+
+/*
+ * mod_ua_unload()
+ *
+ * Called when module is being unloaded.
+ */
+static pj_status_t mod_ua_unload(void)
+{
+ pj_thread_local_free(pjsip_dlg_lock_tls_id);
+ pj_mutex_destroy(mod_ua.mutex);
+
+ /* Release pool */
+ if (mod_ua.pool) {
+ pjsip_endpt_release_pool( mod_ua.endpt, mod_ua.pool );
+ }
+ return PJ_SUCCESS;
+}
+
+/*
+ * mod_ua_on_tsx_stats()
+ *
+ * Called on changed on transaction state.
+ */
+static void mod_ua_on_tsx_state( pjsip_transaction *tsx, pjsip_event *e)
+{
+ pjsip_dialog *dlg;
+
+ /* Get the dialog where this transaction belongs. */
+ dlg = (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id];
+
+ /* If dialog instance has gone, it could mean that the dialog
+ * may has been destroyed.
+ */
+ if (dlg == NULL)
+ return;
+
+ /* Hand over the event to the dialog. */
+ pjsip_dlg_on_tsx_state(dlg, tsx, e);
+}
+
+
+/*
+ * Init user agent module and register it to the endpoint.
+ */
+PJ_DEF(pj_status_t) pjsip_ua_init_module( pjsip_endpoint *endpt,
+ const pjsip_ua_init_param *prm)
+{
+ pj_status_t status;
+
+ /* Check if module already registered. */
+ PJ_ASSERT_RETURN(mod_ua.mod.id == -1, PJ_EINVALIDOP);
+
+ /* Copy param, if exists. */
+ if (prm)
+ pj_memcpy(&mod_ua.param, prm, sizeof(pjsip_ua_init_param));
+
+ /* Register the module. */
+ status = pjsip_endpt_register_module(endpt, &mod_ua.mod);
+
+ return status;
+}
+
+/*
+ * Get the instance of the user agent.
+ *
+ */
+PJ_DEF(pjsip_user_agent*) pjsip_ua_instance(void)
+{
+ return &mod_ua.mod;
+}
+
+
+/*
+ * Get the endpoint where this UA is currently registered.
+ */
+PJ_DEF(pjsip_endpoint*) pjsip_ua_get_endpt(pjsip_user_agent *ua)
+{
+ PJ_UNUSED_ARG(ua);
+ pj_assert(ua == &mod_ua.mod);
+ return mod_ua.endpt;
+}
+
+
+/*
+ * Destroy the user agent layer.
+ */
+PJ_DEF(pj_status_t) pjsip_ua_destroy(void)
+{
+ /* Check if module already destroyed. */
+ PJ_ASSERT_RETURN(mod_ua.mod.id != -1, PJ_EINVALIDOP);
+
+ return pjsip_endpt_unregister_module(mod_ua.endpt, &mod_ua.mod);
+}
+
+
+
+/*
+ * Create key to identify dialog set.
+ */
+/*
+PJ_DEF(void) pjsip_ua_create_dlg_set_key( pj_pool_t *pool,
+ pj_str_t *set_key,
+ const pj_str_t *call_id,
+ const pj_str_t *local_tag)
+{
+ PJ_ASSERT_ON_FAIL(pool && set_key && call_id && local_tag, return;);
+
+ set_key->slen = call_id->slen + local_tag->slen + 1;
+ set_key->ptr = (char*) pj_pool_alloc(pool, set_key->slen);
+ pj_assert(set_key->ptr != NULL);
+
+ pj_memcpy(set_key->ptr, call_id->ptr, call_id->slen);
+ set_key->ptr[call_id->slen] = '$';
+ pj_memcpy(set_key->ptr + call_id->slen + 1,
+ local_tag->ptr, local_tag->slen);
+}
+*/
+
+/*
+ * Acquire one dlg_set node to be put in the hash table.
+ * This will first look in the free nodes list, then allocate
+ * a new one from UA's pool when one is not available.
+ */
+static struct dlg_set *alloc_dlgset_node(void)
+{
+ struct dlg_set *set;
+
+ if (!pj_list_empty(&mod_ua.free_dlgset_nodes)) {
+ set = mod_ua.free_dlgset_nodes.next;
+ pj_list_erase(set);
+ return set;
+ } else {
+ set = PJ_POOL_ALLOC_T(mod_ua.pool, struct dlg_set);
+ return set;
+ }
+}
+
+/*
+ * Register new dialog. Called by pjsip_dlg_create_uac() and
+ * pjsip_dlg_create_uas();
+ */
+PJ_DEF(pj_status_t) pjsip_ua_register_dlg( pjsip_user_agent *ua,
+ pjsip_dialog *dlg )
+{
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(ua && dlg, PJ_EINVAL);
+
+ /* For all dialogs, local tag (inc hash) must has been initialized. */
+ PJ_ASSERT_RETURN(dlg->local.info && dlg->local.info->tag.slen &&
+ dlg->local.tag_hval != 0, PJ_EBUG);
+
+ /* For UAS dialog, remote tag (inc hash) must have been initialized. */
+ //PJ_ASSERT_RETURN(dlg->role==PJSIP_ROLE_UAC ||
+ // (dlg->role==PJSIP_ROLE_UAS && dlg->remote.info->tag.slen
+ // && dlg->remote.tag_hval != 0), PJ_EBUG);
+
+ /* Lock the user agent. */
+ pj_mutex_lock(mod_ua.mutex);
+
+ /* For UAC, check if there is existing dialog in the same set. */
+ if (dlg->role == PJSIP_ROLE_UAC) {
+ struct dlg_set *dlg_set;
+
+ dlg_set = (struct dlg_set*)
+ pj_hash_get( mod_ua.dlg_table, dlg->local.info->tag.ptr,
+ dlg->local.info->tag.slen,
+ &dlg->local.tag_hval);
+
+ if (dlg_set) {
+ /* This is NOT the first dialog in the dialog set.
+ * Just add this dialog in the list.
+ */
+ pj_assert(dlg_set->dlg_list.next != (void*)&dlg_set->dlg_list);
+ pj_list_push_back(&dlg_set->dlg_list, dlg);
+
+ dlg->dlg_set = dlg_set;
+
+ } else {
+ /* This is the first dialog in the dialog set.
+ * Create the dialog set and add this dialog to it.
+ */
+ dlg_set = alloc_dlgset_node();
+ pj_list_init(&dlg_set->dlg_list);
+ pj_list_push_back(&dlg_set->dlg_list, dlg);
+
+ dlg->dlg_set = dlg_set;
+
+ /* Register the dialog set in the hash table. */
+ pj_hash_set_np(mod_ua.dlg_table,
+ dlg->local.info->tag.ptr, dlg->local.info->tag.slen,
+ dlg->local.tag_hval, dlg_set->ht_entry, dlg_set);
+ }
+
+ } else {
+ /* For UAS, create the dialog set with a single dialog as member. */
+ struct dlg_set *dlg_set;
+
+ dlg_set = alloc_dlgset_node();
+ pj_list_init(&dlg_set->dlg_list);
+ pj_list_push_back(&dlg_set->dlg_list, dlg);
+
+ dlg->dlg_set = dlg_set;
+
+ pj_hash_set_np(mod_ua.dlg_table,
+ dlg->local.info->tag.ptr, dlg->local.info->tag.slen,
+ dlg->local.tag_hval, dlg_set->ht_entry, dlg_set);
+ }
+
+ /* Unlock user agent. */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_ua_unregister_dlg( pjsip_user_agent *ua,
+ pjsip_dialog *dlg )
+{
+ struct dlg_set *dlg_set;
+ pjsip_dialog *d;
+
+ /* Sanity-check arguments. */
+ PJ_ASSERT_RETURN(ua && dlg, PJ_EINVAL);
+
+ /* Check that dialog has been registered. */
+ PJ_ASSERT_RETURN(dlg->dlg_set, PJ_EINVALIDOP);
+
+ /* Lock user agent. */
+ pj_mutex_lock(mod_ua.mutex);
+
+ /* Find this dialog from the dialog set. */
+ dlg_set = (struct dlg_set*) dlg->dlg_set;
+ d = dlg_set->dlg_list.next;
+ while (d != (pjsip_dialog*)&dlg_set->dlg_list && d != dlg) {
+ d = d->next;
+ }
+
+ if (d != dlg) {
+ pj_assert(!"Dialog is not registered!");
+ pj_mutex_unlock(mod_ua.mutex);
+ return PJ_EINVALIDOP;
+ }
+
+ /* Remove this dialog from the list. */
+ pj_list_erase(dlg);
+
+ /* If dialog list is empty, remove the dialog set from the hash table. */
+ if (pj_list_empty(&dlg_set->dlg_list)) {
+ pj_hash_set(NULL, mod_ua.dlg_table, dlg->local.info->tag.ptr,
+ dlg->local.info->tag.slen, dlg->local.tag_hval, NULL);
+
+ /* Return dlg_set to free nodes. */
+ pj_list_push_back(&mod_ua.free_dlgset_nodes, dlg_set);
+ }
+
+ /* Unlock user agent. */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pjsip_dialog*) pjsip_rdata_get_dlg( pjsip_rx_data *rdata )
+{
+ return (pjsip_dialog*) rdata->endpt_info.mod_data[mod_ua.mod.id];
+}
+
+PJ_DEF(pjsip_dialog*) pjsip_tsx_get_dlg( pjsip_transaction *tsx )
+{
+ return (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id];
+}
+
+
+/*
+ * Retrieve the current number of dialog-set currently registered
+ * in the hash table.
+ */
+PJ_DEF(unsigned) pjsip_ua_get_dlg_set_count(void)
+{
+ unsigned count;
+
+ PJ_ASSERT_RETURN(mod_ua.endpt, 0);
+
+ pj_mutex_lock(mod_ua.mutex);
+ count = pj_hash_count(mod_ua.dlg_table);
+ pj_mutex_unlock(mod_ua.mutex);
+
+ return count;
+}
+
+
+/*
+ * Find a dialog.
+ */
+PJ_DEF(pjsip_dialog*) pjsip_ua_find_dialog(const pj_str_t *call_id,
+ const pj_str_t *local_tag,
+ const pj_str_t *remote_tag,
+ pj_bool_t lock_dialog)
+{
+ struct dlg_set *dlg_set;
+ pjsip_dialog *dlg;
+
+ PJ_ASSERT_RETURN(call_id && local_tag && remote_tag, NULL);
+
+ /* Lock user agent. */
+ pj_mutex_lock(mod_ua.mutex);
+
+ /* Lookup the dialog set. */
+ dlg_set = (struct dlg_set*)
+ pj_hash_get(mod_ua.dlg_table, local_tag->ptr, local_tag->slen,
+ NULL);
+ if (dlg_set == NULL) {
+ /* Not found */
+ pj_mutex_unlock(mod_ua.mutex);
+ return NULL;
+ }
+
+ /* Dialog set is found, now find the matching dialog based on the
+ * remote tag.
+ */
+ dlg = dlg_set->dlg_list.next;
+ while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) {
+ if (pj_strcmp(&dlg->remote.info->tag, remote_tag) == 0)
+ break;
+ dlg = dlg->next;
+ }
+
+ if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
+ /* Not found */
+ pj_mutex_unlock(mod_ua.mutex);
+ return NULL;
+ }
+
+ /* Dialog has been found. It SHOULD have the right Call-ID!! */
+ PJ_ASSERT_ON_FAIL(pj_strcmp(&dlg->call_id->id, call_id)==0,
+ {pj_mutex_unlock(mod_ua.mutex); return NULL;});
+
+ if (lock_dialog) {
+ if (pjsip_dlg_try_inc_lock(dlg) != PJ_SUCCESS) {
+
+ /*
+ * Unable to acquire dialog's lock while holding the user
+ * agent's mutex. Release the UA mutex before retrying once
+ * more.
+ *
+ * THIS MAY CAUSE RACE CONDITION!
+ */
+
+ /* Unlock user agent. */
+ pj_mutex_unlock(mod_ua.mutex);
+ /* Lock dialog */
+ pjsip_dlg_inc_lock(dlg);
+
+ } else {
+ /* Unlock user agent. */
+ pj_mutex_unlock(mod_ua.mutex);
+ }
+
+ } else {
+ /* Unlock user agent. */
+ pj_mutex_unlock(mod_ua.mutex);
+ }
+
+ return dlg;
+}
+
+
+/*
+ * Find the first dialog in dialog set in hash table for an incoming message.
+ */
+static struct dlg_set *find_dlg_set_for_msg( pjsip_rx_data *rdata )
+{
+ /* CANCEL message doesn't have To tag, so we must lookup the dialog
+ * by finding the INVITE UAS transaction being cancelled.
+ */
+ if (rdata->msg_info.cseq->method.id == PJSIP_CANCEL_METHOD) {
+
+ pjsip_dialog *dlg;
+
+ /* Create key for the rdata, but this time, use INVITE as the
+ * method.
+ */
+ pj_str_t key;
+ pjsip_role_e role;
+ pjsip_transaction *tsx;
+
+ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG)
+ role = PJSIP_ROLE_UAS;
+ else
+ role = PJSIP_ROLE_UAC;
+
+ pjsip_tsx_create_key(rdata->tp_info.pool, &key, role,
+ pjsip_get_invite_method(), rdata);
+
+ /* Lookup the INVITE transaction */
+ tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE);
+
+ /* We should find the dialog attached to the INVITE transaction */
+ if (tsx) {
+ dlg = (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id];
+ pj_mutex_unlock(tsx->mutex);
+
+ /* Dlg may be NULL on some extreme condition
+ * (e.g. during debugging where initially there is a dialog)
+ */
+ return dlg ? (struct dlg_set*) dlg->dlg_set : NULL;
+
+ } else {
+ return NULL;
+ }
+
+
+ } else {
+ pj_str_t *tag;
+ struct dlg_set *dlg_set;
+
+ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG)
+ tag = &rdata->msg_info.to->tag;
+ else
+ tag = &rdata->msg_info.from->tag;
+
+ /* Lookup the dialog set. */
+ dlg_set = (struct dlg_set*)
+ pj_hash_get(mod_ua.dlg_table, tag->ptr, tag->slen, NULL);
+ return dlg_set;
+ }
+}
+
+/* On received requests. */
+static pj_bool_t mod_ua_on_rx_request(pjsip_rx_data *rdata)
+{
+ struct dlg_set *dlg_set;
+ pj_str_t *from_tag;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ /* Optimized path: bail out early if request is not CANCEL and it doesn't
+ * have To tag
+ */
+ if (rdata->msg_info.to->tag.slen == 0 &&
+ rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD)
+ {
+ return PJ_FALSE;
+ }
+
+ /* Incoming REGISTER may have tags in it */
+ if (rdata->msg_info.msg->line.req.method.id == PJSIP_REGISTER_METHOD)
+ return PJ_FALSE;
+
+retry_on_deadlock:
+
+ /* Lock user agent before looking up the dialog hash table. */
+ pj_mutex_lock(mod_ua.mutex);
+
+ /* Lookup the dialog set, based on the To tag header. */
+ dlg_set = find_dlg_set_for_msg(rdata);
+
+ /* If dialog is not found, respond with 481 (Call/Transaction
+ * Does Not Exist).
+ */
+ if (dlg_set == NULL) {
+ /* Unable to find dialog. */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ PJ_LOG(5,(THIS_FILE,
+ "Unable to find dialogset for %s, answering with 481",
+ pjsip_rx_data_get_info(rdata)));
+
+ /* Respond with 481 . */
+ pjsip_endpt_respond_stateless( mod_ua.endpt, rdata, 481, NULL,
+ NULL, NULL );
+ }
+ return PJ_TRUE;
+ }
+
+ /* Dialog set has been found.
+ * Find the dialog in the dialog set based on the content of the remote
+ * tag.
+ */
+ from_tag = &rdata->msg_info.from->tag;
+ dlg = dlg_set->dlg_list.next;
+ while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) {
+
+ if (pj_strcmp(&dlg->remote.info->tag, from_tag) == 0)
+ break;
+
+ dlg = dlg->next;
+ }
+
+ /* Dialog may not be found, e.g. in this case:
+ * - UAC sends SUBSCRIBE, then UAS sends NOTIFY before answering
+ * SUBSCRIBE request with 2xx.
+ *
+ * In this case, we can accept the request ONLY when the original
+ * dialog still has empty To tag.
+ */
+ if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
+
+ pjsip_dialog *first_dlg = dlg_set->dlg_list.next;
+
+ if (first_dlg->remote.info->tag.slen != 0) {
+ /* Not found. Mulfunction UAC? */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ PJ_LOG(5,(THIS_FILE,
+ "Unable to find dialog for %s, answering with 481",
+ pjsip_rx_data_get_info(rdata)));
+
+ pjsip_endpt_respond_stateless(mod_ua.endpt, rdata,
+ PJSIP_SC_CALL_TSX_DOES_NOT_EXIST,
+ NULL, NULL, NULL);
+ } else {
+ PJ_LOG(5,(THIS_FILE,
+ "Unable to find dialog for %s",
+ pjsip_rx_data_get_info(rdata)));
+ }
+ return PJ_TRUE;
+ }
+
+ dlg = first_dlg;
+ }
+
+ /* Mark the dialog id of the request. */
+ rdata->endpt_info.mod_data[mod_ua.mod.id] = dlg;
+
+ /* Try to lock the dialog */
+ PJ_LOG(6,(dlg->obj_name, "UA layer acquiring dialog lock for request"));
+ status = pjsip_dlg_try_inc_lock(dlg);
+ if (status != PJ_SUCCESS) {
+ /* Failed to acquire dialog mutex immediately, this could be
+ * because of deadlock. Release UA mutex, yield, and retry
+ * the whole thing once again.
+ */
+ pj_mutex_unlock(mod_ua.mutex);
+ pj_thread_sleep(0);
+ goto retry_on_deadlock;
+ }
+
+ /* Done with processing in UA layer, release lock */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ /* Pass to dialog. */
+ pjsip_dlg_on_rx_request(dlg, rdata);
+
+ /* Unlock the dialog. This may destroy the dialog */
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Report as handled. */
+ return PJ_TRUE;
+}
+
+
+/* On rx response notification.
+ */
+static pj_bool_t mod_ua_on_rx_response(pjsip_rx_data *rdata)
+{
+ pjsip_transaction *tsx;
+ struct dlg_set *dlg_set;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ /*
+ * Find the dialog instance for the response.
+ * All outgoing dialog requests are sent statefully, which means
+ * there will be an UAC transaction associated with this response,
+ * and the dialog instance will be recorded in that transaction.
+ *
+ * But even when transaction is found, there is possibility that
+ * the response is a forked response.
+ */
+
+retry_on_deadlock:
+
+ dlg = NULL;
+
+ /* Lock user agent dlg table before we're doing anything. */
+ pj_mutex_lock(mod_ua.mutex);
+
+ /* Check if transaction is present. */
+ tsx = pjsip_rdata_get_tsx(rdata);
+ if (tsx) {
+ /* Check if dialog is present in the transaction. */
+ dlg = pjsip_tsx_get_dlg(tsx);
+ if (!dlg) {
+ /* Unlock dialog hash table. */
+ pj_mutex_unlock(mod_ua.mutex);
+ return PJ_FALSE;
+ }
+
+ /* Get the dialog set. */
+ dlg_set = (struct dlg_set*) dlg->dlg_set;
+
+ /* Even if transaction is found and (candidate) dialog has been
+ * identified, it's possible that the request has forked.
+ */
+
+ } else {
+ /* Transaction is not present.
+ * Check if this is a 2xx/OK response to INVITE, which in this
+ * case the response will be handled directly by the
+ * dialog.
+ */
+ pjsip_cseq_hdr *cseq_hdr = rdata->msg_info.cseq;
+
+ if (cseq_hdr->method.id != PJSIP_INVITE_METHOD ||
+ rdata->msg_info.msg->line.status.code / 100 != 2)
+ {
+ /* Not a 2xx response to INVITE.
+ * This must be some stateless response sent by other modules,
+ * or a very late response.
+ */
+ /* Unlock dialog hash table. */
+ pj_mutex_unlock(mod_ua.mutex);
+ return PJ_FALSE;
+ }
+
+
+ /* Get the dialog set. */
+ dlg_set = (struct dlg_set*)
+ pj_hash_get(mod_ua.dlg_table,
+ rdata->msg_info.from->tag.ptr,
+ rdata->msg_info.from->tag.slen,
+ NULL);
+
+ if (!dlg_set) {
+ /* Unlock dialog hash table. */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ /* Strayed 2xx response!! */
+ PJ_LOG(4,(THIS_FILE,
+ "Received strayed 2xx response (no dialog is found)"
+ " from %s:%d: %s",
+ rdata->pkt_info.src_name, rdata->pkt_info.src_port,
+ pjsip_rx_data_get_info(rdata)));
+
+ return PJ_TRUE;
+ }
+ }
+
+ /* At this point, we must have the dialog set, and the dialog set
+ * must have a dialog in the list.
+ */
+ pj_assert(dlg_set && !pj_list_empty(&dlg_set->dlg_list));
+
+ /* Check for forked response.
+ * Request will fork only for the initial INVITE request.
+ */
+
+ //This doesn't work when there is authentication challenge, since
+ //first_cseq evaluation will yield false.
+ //if (rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD &&
+ // rdata->msg_info.cseq->cseq == dlg_set->dlg_list.next->local.first_cseq)
+
+ if (rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD) {
+
+ int st_code = rdata->msg_info.msg->line.status.code;
+ pj_str_t *to_tag = &rdata->msg_info.to->tag;
+
+ dlg = dlg_set->dlg_list.next;
+
+ while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) {
+
+ /* If there is dialog with no remote tag (i.e. dialog has not
+ * been established yet), then send this response to that
+ * dialog.
+ */
+ if (dlg->remote.info->tag.slen == 0)
+ break;
+
+ /* Otherwise find the one with matching To tag. */
+ if (pj_strcmp(to_tag, &dlg->remote.info->tag) == 0)
+ break;
+
+ dlg = dlg->next;
+ }
+
+ /* If no dialog with matching remote tag is found, this must be
+ * a forked response. Respond to this ONLY when response is non-100
+ * provisional response OR a 2xx response.
+ */
+ if (dlg == (pjsip_dialog*)&dlg_set->dlg_list &&
+ ((st_code/100==1 && st_code!=100) || st_code/100==2))
+ {
+
+ PJ_LOG(5,(THIS_FILE,
+ "Received forked %s for existing dialog %s",
+ pjsip_rx_data_get_info(rdata),
+ dlg_set->dlg_list.next->obj_name));
+
+ /* Report to application about forked condition.
+ * Application can either create a dialog or ignore the response.
+ */
+ if (mod_ua.param.on_dlg_forked) {
+ dlg = (*mod_ua.param.on_dlg_forked)(dlg_set->dlg_list.next,
+ rdata);
+ if (dlg == NULL) {
+ pj_mutex_unlock(mod_ua.mutex);
+ return PJ_TRUE;
+ }
+ } else {
+ dlg = dlg_set->dlg_list.next;
+
+ PJ_LOG(4,(THIS_FILE,
+ "Unhandled forked %s from %s:%d, response will be "
+ "handed over to the first dialog",
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name, rdata->pkt_info.src_port));
+ }
+
+ } else if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
+
+ /* For 100 or non-2xx response which has different To tag,
+ * pass the response to the first dialog.
+ */
+
+ dlg = dlg_set->dlg_list.next;
+
+ }
+
+ } else {
+ /* Either this is a non-INVITE response, or subsequent INVITE
+ * within dialog. The dialog should have been identified when
+ * the transaction was found.
+ */
+ pj_assert(tsx != NULL);
+ pj_assert(dlg != NULL);
+ }
+
+ /* The dialog must have been found. */
+ pj_assert(dlg != NULL);
+
+ /* Put the dialog instance in the rdata. */
+ rdata->endpt_info.mod_data[mod_ua.mod.id] = dlg;
+
+ /* Attempt to acquire lock to the dialog. */
+ PJ_LOG(6,(dlg->obj_name, "UA layer acquiring dialog lock for response"));
+ status = pjsip_dlg_try_inc_lock(dlg);
+ if (status != PJ_SUCCESS) {
+ /* Failed to acquire dialog mutex. This could indicate a deadlock
+ * situation, and for safety, try to avoid deadlock by releasing
+ * UA mutex, yield, and retry the whole processing once again.
+ */
+ pj_mutex_unlock(mod_ua.mutex);
+ pj_thread_sleep(0);
+ goto retry_on_deadlock;
+ }
+
+ /* We're done with processing in the UA layer, we can release the mutex */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ /* Pass the response to the dialog. */
+ pjsip_dlg_on_rx_response(dlg, rdata);
+
+ /* Unlock the dialog. This may destroy the dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Done. */
+ return PJ_TRUE;
+}
+
+
+#if PJ_LOG_MAX_LEVEL >= 3
+static void print_dialog( const char *title,
+ pjsip_dialog *dlg, char *buf, pj_size_t size)
+{
+ int len;
+ char userinfo[128];
+
+ len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
+ if (len < 0)
+ pj_ansi_strcpy(userinfo, "<--uri too long-->");
+ else
+ userinfo[len] = '\0';
+
+ len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
+ title,
+ (dlg->state==PJSIP_DIALOG_STATE_NULL ? " - " :
+ "est"),
+ userinfo);
+ if (len < 1 || len >= (int)size) {
+ pj_ansi_strcpy(buf, "<--uri too long-->");
+ } else
+ buf[len] = '\0';
+}
+#endif
+
+/*
+ * Dump user agent contents (e.g. all dialogs).
+ */
+PJ_DEF(void) pjsip_ua_dump(pj_bool_t detail)
+{
+#if PJ_LOG_MAX_LEVEL >= 3
+ pj_hash_iterator_t itbuf, *it;
+ char dlginfo[128];
+
+ pj_mutex_lock(mod_ua.mutex);
+
+ PJ_LOG(3, (THIS_FILE, "Number of dialog sets: %u",
+ pj_hash_count(mod_ua.dlg_table)));
+
+ if (detail && pj_hash_count(mod_ua.dlg_table)) {
+ PJ_LOG(3, (THIS_FILE, "Dumping dialog sets:"));
+ it = pj_hash_first(mod_ua.dlg_table, &itbuf);
+ for (; it != NULL; it = pj_hash_next(mod_ua.dlg_table, it)) {
+ struct dlg_set *dlg_set;
+ pjsip_dialog *dlg;
+ const char *title;
+
+ dlg_set = (struct dlg_set*) pj_hash_this(mod_ua.dlg_table, it);
+ if (!dlg_set || pj_list_empty(&dlg_set->dlg_list)) continue;
+
+ /* First dialog in dialog set. */
+ dlg = dlg_set->dlg_list.next;
+ if (dlg->role == PJSIP_ROLE_UAC)
+ title = " [out] ";
+ else
+ title = " [in] ";
+
+ print_dialog(title, dlg, dlginfo, sizeof(dlginfo));
+ PJ_LOG(3,(THIS_FILE, "%s", dlginfo));
+
+ /* Next dialog in dialog set (forked) */
+ dlg = dlg->next;
+ while (dlg != (pjsip_dialog*) &dlg_set->dlg_list) {
+ print_dialog(" [forked] ", dlg, dlginfo, sizeof(dlginfo));
+ dlg = dlg->next;
+ }
+ }
+ }
+
+ pj_mutex_unlock(mod_ua.mutex);
+#endif
+}
+
diff --git a/pjsip/src/pjsip/sip_uri.c b/pjsip/src/pjsip/sip_uri.c
new file mode 100644
index 0000000..fba163c
--- /dev/null
+++ b/pjsip/src/pjsip/sip_uri.c
@@ -0,0 +1,729 @@
+/* $Id: sip_uri.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_uri.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/print_util.h>
+#include <pjsip/sip_errno.h>
+#include <pjlib-util/string.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+
+/*
+ * Generic parameter manipulation.
+ */
+PJ_DEF(pjsip_param*) pjsip_param_find( const pjsip_param *param_list,
+ const pj_str_t *name )
+{
+ pjsip_param *p = (pjsip_param*)param_list->next;
+ while (p != param_list) {
+ if (pj_stricmp(&p->name, name)==0)
+ return p;
+ p = p->next;
+ }
+ return NULL;
+}
+
+PJ_DEF(int) pjsip_param_cmp( const pjsip_param *param_list1,
+ const pjsip_param *param_list2,
+ pj_bool_t ig_nf)
+{
+ const pjsip_param *p1;
+
+ if ((ig_nf & 1)==0 && pj_list_size(param_list1)!=pj_list_size(param_list2))
+ return 1;
+
+ p1 = param_list1->next;
+ while (p1 != param_list1) {
+ const pjsip_param *p2;
+ p2 = pjsip_param_find(param_list2, &p1->name);
+ if (p2 ) {
+ int rc = pj_stricmp(&p1->value, &p2->value);
+ if (rc != 0)
+ return rc;
+ } else if ((ig_nf & 1)==0)
+ return 1;
+
+ p1 = p1->next;
+ }
+
+ return 0;
+}
+
+PJ_DEF(void) pjsip_param_clone( pj_pool_t *pool, pjsip_param *dst_list,
+ const pjsip_param *src_list)
+{
+ const pjsip_param *p = src_list->next;
+
+ pj_list_init(dst_list);
+ while (p && p != src_list) {
+ pjsip_param *new_param = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ pj_strdup(pool, &new_param->name, &p->name);
+ pj_strdup(pool, &new_param->value, &p->value);
+ pj_list_insert_before(dst_list, new_param);
+ p = p->next;
+ }
+}
+
+
+PJ_DEF(void) pjsip_param_shallow_clone( pj_pool_t *pool,
+ pjsip_param *dst_list,
+ const pjsip_param *src_list)
+{
+ const pjsip_param *p = src_list->next;
+
+ pj_list_init(dst_list);
+ while (p != src_list) {
+ pjsip_param *new_param = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ new_param->name = p->name;
+ new_param->value = p->value;
+ pj_list_insert_before(dst_list, new_param);
+ p = p->next;
+ }
+}
+
+PJ_DEF(pj_ssize_t) pjsip_param_print_on( const pjsip_param *param_list,
+ char *buf, pj_size_t size,
+ const pj_cis_t *pname_spec,
+ const pj_cis_t *pvalue_spec,
+ int sep)
+{
+ const pjsip_param *p;
+ char *startbuf;
+ char *endbuf;
+ int printed;
+
+ p = param_list->next;
+ if (p == NULL || p == param_list)
+ return 0;
+
+ startbuf = buf;
+ endbuf = buf + size;
+
+ PJ_UNUSED_ARG(pname_spec);
+
+ do {
+ *buf++ = (char)sep;
+ copy_advance_escape(buf, p->name, (*pname_spec));
+ if (p->value.slen) {
+ *buf++ = '=';
+ if (*p->value.ptr == '"')
+ copy_advance(buf, p->value);
+ else
+ copy_advance_escape(buf, p->value, (*pvalue_spec));
+ }
+ p = p->next;
+ if (sep == '?') sep = '&';
+ } while (p != param_list);
+
+ return buf-startbuf;
+}
+
+
+/*
+ * URI stuffs
+ */
+#define IS_SIPS(url) ((url)->vptr==&sips_url_vptr)
+
+static const pj_str_t *pjsip_url_get_scheme( const pjsip_sip_uri* );
+static const pj_str_t *pjsips_url_get_scheme( const pjsip_sip_uri* );
+static const pj_str_t *pjsip_name_addr_get_scheme( const pjsip_name_addr * );
+static void *pjsip_get_uri( pjsip_uri *uri );
+static void *pjsip_name_addr_get_uri( pjsip_name_addr *name );
+
+static pj_str_t sip_str = { "sip", 3 };
+static pj_str_t sips_str = { "sips", 4 };
+
+static pjsip_name_addr* pjsip_name_addr_clone( pj_pool_t *pool,
+ const pjsip_name_addr *rhs);
+static pj_ssize_t pjsip_name_addr_print(pjsip_uri_context_e context,
+ const pjsip_name_addr *name,
+ char *buf, pj_size_t size);
+static int pjsip_name_addr_compare( pjsip_uri_context_e context,
+ const pjsip_name_addr *naddr1,
+ const pjsip_name_addr *naddr2);
+static pj_ssize_t pjsip_url_print( pjsip_uri_context_e context,
+ const pjsip_sip_uri *url,
+ char *buf, pj_size_t size);
+static int pjsip_url_compare( pjsip_uri_context_e context,
+ const pjsip_sip_uri *url1,
+ const pjsip_sip_uri *url2);
+static pjsip_sip_uri* pjsip_url_clone(pj_pool_t *pool,
+ const pjsip_sip_uri *rhs);
+
+typedef const pj_str_t* (*P_GET_SCHEME)(const void*);
+typedef void* (*P_GET_URI)(void*);
+typedef pj_ssize_t (*P_PRINT_URI)(pjsip_uri_context_e,const void *,
+ char*,pj_size_t);
+typedef int (*P_CMP_URI)(pjsip_uri_context_e, const void*,
+ const void*);
+typedef void* (*P_CLONE)(pj_pool_t*, const void*);
+
+
+static pjsip_uri_vptr sip_url_vptr =
+{
+ (P_GET_SCHEME) &pjsip_url_get_scheme,
+ (P_GET_URI) &pjsip_get_uri,
+ (P_PRINT_URI) &pjsip_url_print,
+ (P_CMP_URI) &pjsip_url_compare,
+ (P_CLONE) &pjsip_url_clone
+};
+
+static pjsip_uri_vptr sips_url_vptr =
+{
+ (P_GET_SCHEME) &pjsips_url_get_scheme,
+ (P_GET_URI) &pjsip_get_uri,
+ (P_PRINT_URI) &pjsip_url_print,
+ (P_CMP_URI) &pjsip_url_compare,
+ (P_CLONE) &pjsip_url_clone
+};
+
+static pjsip_uri_vptr name_addr_vptr =
+{
+ (P_GET_SCHEME) &pjsip_name_addr_get_scheme,
+ (P_GET_URI) &pjsip_name_addr_get_uri,
+ (P_PRINT_URI) &pjsip_name_addr_print,
+ (P_CMP_URI) &pjsip_name_addr_compare,
+ (P_CLONE) &pjsip_name_addr_clone
+};
+
+static const pj_str_t *pjsip_url_get_scheme(const pjsip_sip_uri *url)
+{
+ PJ_UNUSED_ARG(url);
+ return &sip_str;
+}
+
+static const pj_str_t *pjsips_url_get_scheme(const pjsip_sip_uri *url)
+{
+ PJ_UNUSED_ARG(url);
+ return &sips_str;
+}
+
+static void *pjsip_get_uri( pjsip_uri *uri )
+{
+ return uri;
+}
+
+static void *pjsip_name_addr_get_uri( pjsip_name_addr *name )
+{
+ return pjsip_uri_get_uri(name->uri);
+}
+
+PJ_DEF(void) pjsip_sip_uri_set_secure( pjsip_sip_uri *url,
+ pj_bool_t secure )
+{
+ url->vptr = secure ? &sips_url_vptr : &sip_url_vptr;
+}
+
+PJ_DEF(void) pjsip_sip_uri_init(pjsip_sip_uri *url, pj_bool_t secure)
+{
+ pj_bzero(url, sizeof(*url));
+ url->ttl_param = -1;
+ pjsip_sip_uri_set_secure(url, secure);
+ pj_list_init(&url->other_param);
+ pj_list_init(&url->header_param);
+}
+
+PJ_DEF(pjsip_sip_uri*) pjsip_sip_uri_create( pj_pool_t *pool,
+ pj_bool_t secure )
+{
+ pjsip_sip_uri *url = PJ_POOL_ALLOC_T(pool, pjsip_sip_uri);
+ pjsip_sip_uri_init(url, secure);
+ return url;
+}
+
+static pj_ssize_t pjsip_url_print( pjsip_uri_context_e context,
+ const pjsip_sip_uri *url,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf+size;
+ const pj_str_t *scheme;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ *buf = '\0';
+
+ /* Print scheme ("sip:" or "sips:") */
+ scheme = pjsip_uri_get_scheme(url);
+ copy_advance_check(buf, *scheme);
+ *buf++ = ':';
+
+ /* Print "user:password@", if any. */
+ if (url->user.slen) {
+ copy_advance_escape(buf, url->user, pc->pjsip_USER_SPEC);
+ if (url->passwd.slen) {
+ *buf++ = ':';
+ copy_advance_escape(buf, url->passwd, pc->pjsip_PASSWD_SPEC);
+ }
+
+ *buf++ = '@';
+ }
+
+ /* Print host. */
+ pj_assert(url->host.slen != 0);
+ /* Detect IPv6 IP address */
+ if (pj_memchr(url->host.ptr, ':', url->host.slen)) {
+ copy_advance_pair_quote_cond(buf, "", 0, url->host, '[', ']');
+ } else {
+ copy_advance_check(buf, url->host);
+ }
+
+ /* Only print port if it is explicitly specified.
+ * Port is not allowed in To and From header, see Table 1 in
+ * RFC 3261 Section 19.1.1
+ */
+ /* Note: ticket #1141 adds run-time setting to allow port number to
+ * appear in From/To header. Default is still false.
+ */
+ if (url->port &&
+ (context != PJSIP_URI_IN_FROMTO_HDR ||
+ pjsip_cfg()->endpt.allow_port_in_fromto_hdr))
+ {
+ if (endbuf - buf < 10)
+ return -1;
+
+ *buf++ = ':';
+ printed = pj_utoa(url->port, buf);
+ buf += printed;
+ }
+
+ /* User param is allowed in all contexes */
+ copy_advance_pair_check(buf, ";user=", 6, url->user_param);
+
+ /* Method param is only allowed in external/other context. */
+ if (context == PJSIP_URI_IN_OTHER) {
+ copy_advance_pair_escape(buf, ";method=", 8, url->method_param,
+ pc->pjsip_PARAM_CHAR_SPEC);
+ }
+
+ /* Transport is not allowed in From/To header. */
+ if (context != PJSIP_URI_IN_FROMTO_HDR) {
+ copy_advance_pair_escape(buf, ";transport=", 11, url->transport_param,
+ pc->pjsip_PARAM_CHAR_SPEC);
+ }
+
+ /* TTL param is not allowed in From, To, Route, and Record-Route header. */
+ if (url->ttl_param >= 0 && context != PJSIP_URI_IN_FROMTO_HDR &&
+ context != PJSIP_URI_IN_ROUTING_HDR)
+ {
+ if (endbuf - buf < 15)
+ return -1;
+ pj_memcpy(buf, ";ttl=", 5);
+ printed = pj_utoa(url->ttl_param, buf+5);
+ buf += printed + 5;
+ }
+
+ /* maddr param is not allowed in From and To header. */
+ if (context != PJSIP_URI_IN_FROMTO_HDR && url->maddr_param.slen) {
+ /* Detect IPv6 IP address */
+ if (pj_memchr(url->maddr_param.ptr, ':', url->maddr_param.slen)) {
+ copy_advance_pair_quote_cond(buf, ";maddr=", 7, url->maddr_param,
+ '[', ']');
+ } else {
+ copy_advance_pair_escape(buf, ";maddr=", 7, url->maddr_param,
+ pc->pjsip_PARAM_CHAR_SPEC);
+ }
+ }
+
+ /* lr param is not allowed in From, To, and Contact header. */
+ if (url->lr_param && context != PJSIP_URI_IN_FROMTO_HDR &&
+ context != PJSIP_URI_IN_CONTACT_HDR)
+ {
+ pj_str_t lr = { ";lr", 3 };
+ if (endbuf - buf < 3)
+ return -1;
+ copy_advance_check(buf, lr);
+ }
+
+ /* Other param. */
+ printed = pjsip_param_print_on(&url->other_param, buf, endbuf-buf,
+ &pc->pjsip_PARAM_CHAR_SPEC,
+ &pc->pjsip_PARAM_CHAR_SPEC, ';');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ /* Header param.
+ * Header param is only allowed in these contexts:
+ * - PJSIP_URI_IN_CONTACT_HDR
+ * - PJSIP_URI_IN_OTHER
+ */
+ if (context == PJSIP_URI_IN_CONTACT_HDR || context == PJSIP_URI_IN_OTHER) {
+ printed = pjsip_param_print_on(&url->header_param, buf, endbuf-buf,
+ &pc->pjsip_HDR_CHAR_SPEC,
+ &pc->pjsip_HDR_CHAR_SPEC, '?');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+ }
+
+ *buf = '\0';
+ return buf-startbuf;
+}
+
+static pj_status_t pjsip_url_compare( pjsip_uri_context_e context,
+ const pjsip_sip_uri *url1,
+ const pjsip_sip_uri *url2)
+{
+ const pjsip_param *p1;
+
+ /*
+ * Compare two SIP URL's according to Section 19.1.4 of RFC 3261.
+ */
+
+ /* SIP and SIPS URI are never equivalent.
+ * Note: just compare the vptr to avoid string comparison.
+ * Pretty neat huh!!
+ */
+ if (url1->vptr != url2->vptr)
+ return PJSIP_ECMPSCHEME;
+
+ /* Comparison of the userinfo of SIP and SIPS URIs is case-sensitive.
+ * This includes userinfo containing passwords or formatted as
+ * telephone-subscribers.
+ */
+ if (pj_strcmp(&url1->user, &url2->user) != 0)
+ return PJSIP_ECMPUSER;
+ if (pj_strcmp(&url1->passwd, &url2->passwd) != 0)
+ return PJSIP_ECMPPASSWD;
+
+ /* Comparison of all other components of the URI is
+ * case-insensitive unless explicitly defined otherwise.
+ */
+
+ /* The ordering of parameters and header fields is not significant
+ * in comparing SIP and SIPS URIs.
+ */
+
+ /* Characters other than those in the reserved set (see RFC 2396 [5])
+ * are equivalent to their encoding.
+ */
+
+ /* An IP address that is the result of a DNS lookup of a host name
+ * does not match that host name.
+ */
+ if (pj_stricmp(&url1->host, &url2->host) != 0)
+ return PJSIP_ECMPHOST;
+
+ /* A URI omitting any component with a default value will not match a URI
+ * explicitly containing that component with its default value.
+ * For instance, a URI omitting the optional port component will not match
+ * a URI explicitly declaring port 5060.
+ * The same is true for the transport-parameter, ttl-parameter,
+ * user-parameter, and method components.
+ */
+
+ /* Port is not allowed in To and From header.
+ */
+ if (context != PJSIP_URI_IN_FROMTO_HDR) {
+ if (url1->port != url2->port)
+ return PJSIP_ECMPPORT;
+ }
+ /* Transport is not allowed in From/To header. */
+ if (context != PJSIP_URI_IN_FROMTO_HDR) {
+ if (pj_stricmp(&url1->transport_param, &url2->transport_param) != 0)
+ return PJSIP_ECMPTRANSPORTPRM;
+ }
+ /* TTL param is not allowed in From, To, Route, and Record-Route header. */
+ if (context != PJSIP_URI_IN_FROMTO_HDR &&
+ context != PJSIP_URI_IN_ROUTING_HDR)
+ {
+ if (url1->ttl_param != url2->ttl_param)
+ return PJSIP_ECMPTTLPARAM;
+ }
+ /* User param is allowed in all contexes */
+ if (pj_stricmp(&url1->user_param, &url2->user_param) != 0)
+ return PJSIP_ECMPUSERPARAM;
+ /* Method param is only allowed in external/other context. */
+ if (context == PJSIP_URI_IN_OTHER) {
+ if (pj_stricmp(&url1->method_param, &url2->method_param) != 0)
+ return PJSIP_ECMPMETHODPARAM;
+ }
+ /* maddr param is not allowed in From and To header. */
+ if (context != PJSIP_URI_IN_FROMTO_HDR) {
+ if (pj_stricmp(&url1->maddr_param, &url2->maddr_param) != 0)
+ return PJSIP_ECMPMADDRPARAM;
+ }
+
+ /* lr parameter is ignored (?) */
+ /* lr param is not allowed in From, To, and Contact header. */
+
+
+ /* All other uri-parameters appearing in only one URI are ignored when
+ * comparing the URIs.
+ */
+ if (pjsip_param_cmp(&url1->other_param, &url2->other_param, 1)!=0)
+ return PJSIP_ECMPOTHERPARAM;
+
+ /* URI header components are never ignored. Any present header component
+ * MUST be present in both URIs and match for the URIs to match.
+ * The matching rules are defined for each header field in Section 20.
+ */
+ p1 = url1->header_param.next;
+ while (p1 != &url1->header_param) {
+ const pjsip_param *p2;
+ p2 = pjsip_param_find(&url2->header_param, &p1->name);
+ if (p2) {
+ /* It seems too much to compare two header params according to
+ * the rule of each header. We'll just compare them string to
+ * string..
+ */
+ if (pj_stricmp(&p1->value, &p2->value) != 0)
+ return PJSIP_ECMPHEADERPARAM;
+ } else {
+ return PJSIP_ECMPHEADERPARAM;
+ }
+ p1 = p1->next;
+ }
+
+ /* Equal!! Pheuww.. */
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(void) pjsip_sip_uri_assign(pj_pool_t *pool, pjsip_sip_uri *url,
+ const pjsip_sip_uri *rhs)
+{
+ pj_strdup( pool, &url->user, &rhs->user);
+ pj_strdup( pool, &url->passwd, &rhs->passwd);
+ pj_strdup( pool, &url->host, &rhs->host);
+ url->port = rhs->port;
+ pj_strdup( pool, &url->user_param, &rhs->user_param);
+ pj_strdup( pool, &url->method_param, &rhs->method_param);
+ pj_strdup( pool, &url->transport_param, &rhs->transport_param);
+ url->ttl_param = rhs->ttl_param;
+ pj_strdup( pool, &url->maddr_param, &rhs->maddr_param);
+ pjsip_param_clone(pool, &url->other_param, &rhs->other_param);
+ pjsip_param_clone(pool, &url->header_param, &rhs->header_param);
+ url->lr_param = rhs->lr_param;
+}
+
+static pjsip_sip_uri* pjsip_url_clone(pj_pool_t *pool, const pjsip_sip_uri *rhs)
+{
+ pjsip_sip_uri *url = PJ_POOL_ALLOC_T(pool, pjsip_sip_uri);
+ if (!url)
+ return NULL;
+
+ pjsip_sip_uri_init(url, IS_SIPS(rhs));
+ pjsip_sip_uri_assign(pool, url, rhs);
+ return url;
+}
+
+static const pj_str_t *pjsip_name_addr_get_scheme(const pjsip_name_addr *name)
+{
+ pj_assert(name->uri != NULL);
+ return pjsip_uri_get_scheme(name->uri);
+}
+
+PJ_DEF(void) pjsip_name_addr_init(pjsip_name_addr *name)
+{
+ name->vptr = &name_addr_vptr;
+ name->uri = NULL;
+ name->display.slen = 0;
+ name->display.ptr = NULL;
+}
+
+PJ_DEF(pjsip_name_addr*) pjsip_name_addr_create(pj_pool_t *pool)
+{
+ pjsip_name_addr *name_addr = PJ_POOL_ALLOC_T(pool, pjsip_name_addr);
+ pjsip_name_addr_init(name_addr);
+ return name_addr;
+}
+
+static pj_ssize_t pjsip_name_addr_print(pjsip_uri_context_e context,
+ const pjsip_name_addr *name,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ pjsip_uri *uri;
+
+ uri = (pjsip_uri*) pjsip_uri_get_uri(name->uri);
+ pj_assert(uri != NULL);
+
+ if (context != PJSIP_URI_IN_REQ_URI) {
+ if (name->display.slen) {
+ if (endbuf-buf < 8) return -1;
+ *buf++ = '"';
+ copy_advance(buf, name->display);
+ *buf++ = '"';
+ *buf++ = ' ';
+ }
+ *buf++ = '<';
+ }
+
+ printed = pjsip_uri_print(context,uri, buf, size-(buf-startbuf));
+ if (printed < 1)
+ return -1;
+ buf += printed;
+
+ if (context != PJSIP_URI_IN_REQ_URI) {
+ *buf++ = '>';
+ }
+
+ *buf = '\0';
+ return buf-startbuf;
+}
+
+PJ_DEF(void) pjsip_name_addr_assign(pj_pool_t *pool, pjsip_name_addr *dst,
+ const pjsip_name_addr *src)
+{
+ pj_strdup( pool, &dst->display, &src->display);
+ dst->uri = (pjsip_uri*) pjsip_uri_clone(pool, src->uri);
+}
+
+static pjsip_name_addr* pjsip_name_addr_clone( pj_pool_t *pool,
+ const pjsip_name_addr *rhs)
+{
+ pjsip_name_addr *addr = PJ_POOL_ALLOC_T(pool, pjsip_name_addr);
+ if (!addr)
+ return NULL;
+
+ pjsip_name_addr_init(addr);
+ pjsip_name_addr_assign(pool, addr, rhs);
+ return addr;
+}
+
+static int pjsip_name_addr_compare( pjsip_uri_context_e context,
+ const pjsip_name_addr *naddr1,
+ const pjsip_name_addr *naddr2)
+{
+ int d;
+
+ /* Check that naddr2 is also a name_addr */
+ if (naddr1->vptr != naddr2->vptr)
+ return -1;
+
+ /* I'm not sure whether display name is included in the comparison. */
+ if (pj_strcmp(&naddr1->display, &naddr2->display) != 0) {
+ return -1;
+ }
+
+ pj_assert( naddr1->uri != NULL );
+ pj_assert( naddr2->uri != NULL );
+
+ /* Compare name-addr as URL */
+ d = pjsip_uri_cmp( context, naddr1->uri, naddr2->uri);
+ if (d)
+ return d;
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const pj_str_t *other_uri_get_scheme( const pjsip_other_uri*);
+static void *other_uri_get_uri( pjsip_other_uri*);
+static pj_ssize_t other_uri_print( pjsip_uri_context_e context,
+ const pjsip_other_uri *url,
+ char *buf, pj_size_t size);
+static int other_uri_cmp( pjsip_uri_context_e context,
+ const pjsip_other_uri *url1,
+ const pjsip_other_uri *url2);
+static pjsip_other_uri* other_uri_clone( pj_pool_t *pool,
+ const pjsip_other_uri *rhs);
+
+static pjsip_uri_vptr other_uri_vptr =
+{
+ (P_GET_SCHEME) &other_uri_get_scheme,
+ (P_GET_URI) &other_uri_get_uri,
+ (P_PRINT_URI) &other_uri_print,
+ (P_CMP_URI) &other_uri_cmp,
+ (P_CLONE) &other_uri_clone
+};
+
+
+PJ_DEF(pjsip_other_uri*) pjsip_other_uri_create(pj_pool_t *pool)
+{
+ pjsip_other_uri *uri = PJ_POOL_ZALLOC_T(pool, pjsip_other_uri);
+ uri->vptr = &other_uri_vptr;
+ return uri;
+}
+
+static const pj_str_t *other_uri_get_scheme( const pjsip_other_uri *uri )
+{
+ return &uri->scheme;
+}
+
+static void *other_uri_get_uri( pjsip_other_uri *uri )
+{
+ return uri;
+}
+
+static pj_ssize_t other_uri_print(pjsip_uri_context_e context,
+ const pjsip_other_uri *uri,
+ char *buf, pj_size_t size)
+{
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+
+ PJ_UNUSED_ARG(context);
+
+ if (uri->scheme.slen + uri->content.slen + 1 > (int)size)
+ return -1;
+
+ /* Print scheme. */
+ copy_advance(buf, uri->scheme);
+ *buf++ = ':';
+
+ /* Print content. */
+ copy_advance(buf, uri->content);
+
+ return (buf - startbuf);
+}
+
+static int other_uri_cmp(pjsip_uri_context_e context,
+ const pjsip_other_uri *uri1,
+ const pjsip_other_uri *uri2)
+{
+ PJ_UNUSED_ARG(context);
+
+ /* Check that uri2 is also an other_uri */
+ if (uri1->vptr != uri2->vptr)
+ return -1;
+
+ /* Scheme must match. */
+ if (pj_stricmp(&uri1->scheme, &uri2->scheme) != 0) {
+ return PJSIP_ECMPSCHEME;
+ }
+
+ /* Content must match. */
+ if(pj_stricmp(&uri1->content, &uri2->content) != 0) {
+ return -1;
+ }
+
+ /* Equal. */
+ return 0;
+}
+
+/* Clone *: URI */
+static pjsip_other_uri* other_uri_clone(pj_pool_t *pool,
+ const pjsip_other_uri *rhs)
+{
+ pjsip_other_uri *uri = pjsip_other_uri_create(pool);
+ pj_strdup(pool, &uri->scheme, &rhs->scheme);
+ pj_strdup(pool, &uri->content, &rhs->content);
+
+ return uri;
+}
+
diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c
new file mode 100644
index 0000000..60f2568
--- /dev/null
+++ b/pjsip/src/pjsip/sip_util.c
@@ -0,0 +1,1850 @@
+/* $Id: sip_util.c 4173 2012-06-20 10:39:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_errno.h>
+#include <pj/log.h>
+#include <pj/string.h>
+#include <pj/guid.h>
+#include <pj/pool.h>
+#include <pj/except.h>
+#include <pj/rand.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+
+#define THIS_FILE "endpoint"
+
+static const char *event_str[] =
+{
+ "UNIDENTIFIED",
+ "TIMER",
+ "TX_MSG",
+ "RX_MSG",
+ "TRANSPORT_ERROR",
+ "TSX_STATE",
+ "USER",
+};
+
+static pj_str_t str_TEXT = { "text", 4},
+ str_PLAIN = { "plain", 5 };
+
+/* Add URI to target-set */
+PJ_DEF(pj_status_t) pjsip_target_set_add_uri( pjsip_target_set *tset,
+ pj_pool_t *pool,
+ const pjsip_uri *uri,
+ int q1000)
+{
+ pjsip_target *t, *pos = NULL;
+
+ PJ_ASSERT_RETURN(tset && pool && uri, PJ_EINVAL);
+
+ /* Set q-value to 1 if it is not set */
+ if (q1000 <= 0)
+ q1000 = 1000;
+
+ /* Scan all the elements to see for duplicates, and at the same time
+ * get the position where the new element should be inserted to
+ * based on the q-value.
+ */
+ t = tset->head.next;
+ while (t != &tset->head) {
+ if (pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI, t->uri, uri)==PJ_SUCCESS)
+ return PJ_EEXISTS;
+ if (pos==NULL && t->q1000 < q1000)
+ pos = t;
+ t = t->next;
+ }
+
+ /* Create new element */
+ t = PJ_POOL_ZALLOC_T(pool, pjsip_target);
+ t->uri = (pjsip_uri*)pjsip_uri_clone(pool, uri);
+ t->q1000 = q1000;
+
+ /* Insert */
+ if (pos == NULL)
+ pj_list_push_back(&tset->head, t);
+ else
+ pj_list_insert_before(pos, t);
+
+ /* Set current target if this is the first URI */
+ if (tset->current == NULL)
+ tset->current = t;
+
+ return PJ_SUCCESS;
+}
+
+/* Add URI's in the Contact header in the message to target-set */
+PJ_DEF(pj_status_t) pjsip_target_set_add_from_msg( pjsip_target_set *tset,
+ pj_pool_t *pool,
+ const pjsip_msg *msg)
+{
+ const pjsip_hdr *hdr;
+ unsigned added = 0;
+
+ PJ_ASSERT_RETURN(tset && pool && msg, PJ_EINVAL);
+
+ /* Scan for Contact headers and add the URI */
+ hdr = msg->hdr.next;
+ while (hdr != &msg->hdr) {
+ if (hdr->type == PJSIP_H_CONTACT) {
+ const pjsip_contact_hdr *cn_hdr = (const pjsip_contact_hdr*)hdr;
+
+ if (!cn_hdr->star) {
+ pj_status_t rc;
+ rc = pjsip_target_set_add_uri(tset, pool, cn_hdr->uri,
+ cn_hdr->q1000);
+ if (rc == PJ_SUCCESS)
+ ++added;
+ }
+ }
+ hdr = hdr->next;
+ }
+
+ return added ? PJ_SUCCESS : PJ_EEXISTS;
+}
+
+
+/* Get next target, if any */
+PJ_DEF(pjsip_target*) pjsip_target_set_get_next(const pjsip_target_set *tset)
+{
+ const pjsip_target *t, *next = NULL;
+
+ t = tset->head.next;
+ while (t != &tset->head) {
+ if (PJSIP_IS_STATUS_IN_CLASS(t->code, 200)) {
+ /* No more target since one target has been successful */
+ return NULL;
+ }
+ if (PJSIP_IS_STATUS_IN_CLASS(t->code, 600)) {
+ /* No more target since one target returned global error */
+ return NULL;
+ }
+ if (t->code==0 && next==NULL) {
+ /* This would be the next target as long as we don't find
+ * targets with 2xx or 6xx status after this.
+ */
+ next = t;
+ }
+ t = t->next;
+ }
+
+ return (pjsip_target*)next;
+}
+
+
+/* Set current target */
+PJ_DEF(pj_status_t) pjsip_target_set_set_current( pjsip_target_set *tset,
+ pjsip_target *target)
+{
+ PJ_ASSERT_RETURN(tset && target, PJ_EINVAL);
+ PJ_ASSERT_RETURN(pj_list_find_node(tset, target) != NULL, PJ_ENOTFOUND);
+
+ tset->current = target;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Assign status to a target */
+PJ_DEF(pj_status_t) pjsip_target_assign_status( pjsip_target *target,
+ pj_pool_t *pool,
+ int status_code,
+ const pj_str_t *reason)
+{
+ PJ_ASSERT_RETURN(target && pool && status_code && reason, PJ_EINVAL);
+
+ target->code = (pjsip_status_code)status_code;
+ pj_strdup(pool, &target->reason, reason);
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Initialize transmit data (msg) with the headers and optional body.
+ * This will just put the headers in the message as it is. Be carefull
+ * when calling this function because once a header is put in a message,
+ * it CAN NOT be put in other message until the first message is deleted,
+ * because the way the header is put in the list.
+ * That's why the session will shallow_clone it's headers before calling
+ * this function.
+ */
+static void init_request_throw( pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata,
+ pjsip_method *method,
+ pjsip_uri *param_target,
+ pjsip_from_hdr *param_from,
+ pjsip_to_hdr *param_to,
+ pjsip_contact_hdr *param_contact,
+ pjsip_cid_hdr *param_call_id,
+ pjsip_cseq_hdr *param_cseq,
+ const pj_str_t *param_text)
+{
+ pjsip_msg *msg;
+ pjsip_msg_body *body;
+ pjsip_via_hdr *via;
+ const pjsip_hdr *endpt_hdr;
+
+ /* Create the message. */
+ msg = tdata->msg = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG);
+
+ /* Init request URI. */
+ pj_memcpy(&msg->line.req.method, method, sizeof(*method));
+ msg->line.req.uri = param_target;
+
+ /* Add additional request headers from endpoint. */
+ endpt_hdr = pjsip_endpt_get_request_headers(endpt)->next;
+ while (endpt_hdr != pjsip_endpt_get_request_headers(endpt)) {
+ pjsip_hdr *hdr = (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, endpt_hdr);
+ pjsip_msg_add_hdr( tdata->msg, hdr );
+ endpt_hdr = endpt_hdr->next;
+ }
+
+ /* Add From header. */
+ if (param_from->tag.slen == 0)
+ pj_create_unique_string(tdata->pool, &param_from->tag);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_from);
+
+ /* Add To header. */
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_to);
+
+ /* Add Contact header. */
+ if (param_contact) {
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_contact);
+ }
+
+ /* Add Call-ID header. */
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_call_id);
+
+ /* Add CSeq header. */
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_cseq);
+
+ /* Add a blank Via header in the front of the message. */
+ via = pjsip_via_hdr_create(tdata->pool);
+ via->rport_param = pjsip_cfg()->endpt.disable_rport ? -1 : 0;
+ pjsip_msg_insert_first_hdr(msg, (pjsip_hdr*)via);
+
+ /* Add header params as request headers */
+ if (PJSIP_URI_SCHEME_IS_SIP(param_target) ||
+ PJSIP_URI_SCHEME_IS_SIPS(param_target))
+ {
+ pjsip_sip_uri *uri = (pjsip_sip_uri*) pjsip_uri_get_uri(param_target);
+ pjsip_param *hparam;
+
+ hparam = uri->header_param.next;
+ while (hparam != &uri->header_param) {
+ pjsip_generic_string_hdr *hdr;
+
+ hdr = pjsip_generic_string_hdr_create(tdata->pool,
+ &hparam->name,
+ &hparam->value);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)hdr);
+ hparam = hparam->next;
+ }
+ }
+
+ /* Create message body. */
+ if (param_text) {
+ body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body);
+ body->content_type.type = str_TEXT;
+ body->content_type.subtype = str_PLAIN;
+ body->data = pj_pool_alloc(tdata->pool, param_text->slen );
+ pj_memcpy(body->data, param_text->ptr, param_text->slen);
+ body->len = param_text->slen;
+ body->print_body = &pjsip_print_text_body;
+ msg->body = body;
+ }
+
+ PJ_LOG(5,(THIS_FILE, "%s created.",
+ pjsip_tx_data_get_info(tdata)));
+
+}
+
+/*
+ * Create arbitrary request.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_request( pjsip_endpoint *endpt,
+ const pjsip_method *method,
+ const pj_str_t *param_target,
+ const pj_str_t *param_from,
+ const pj_str_t *param_to,
+ const pj_str_t *param_contact,
+ const pj_str_t *param_call_id,
+ int param_cseq,
+ const pj_str_t *param_text,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_uri *target;
+ pjsip_tx_data *tdata;
+ pjsip_from_hdr *from;
+ pjsip_to_hdr *to;
+ pjsip_contact_hdr *contact;
+ pjsip_cseq_hdr *cseq = NULL; /* = NULL, warning in VC6 */
+ pjsip_cid_hdr *call_id;
+ pj_str_t tmp;
+ pj_status_t status;
+ const pj_str_t STR_CONTACT = { "Contact", 7 };
+ PJ_USE_EXCEPTION;
+
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Init reference counter to 1. */
+ pjsip_tx_data_add_ref(tdata);
+
+ PJ_TRY {
+ /* Request target. */
+ pj_strdup_with_null(tdata->pool, &tmp, param_target);
+ target = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, 0);
+ if (target == NULL) {
+ status = PJSIP_EINVALIDREQURI;
+ goto on_error;
+ }
+
+ /* From */
+ from = pjsip_from_hdr_create(tdata->pool);
+ pj_strdup_with_null(tdata->pool, &tmp, param_from);
+ from->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (from->uri == NULL) {
+ status = PJSIP_EINVALIDHDR;
+ goto on_error;
+ }
+ pj_create_unique_string(tdata->pool, &from->tag);
+
+ /* To */
+ to = pjsip_to_hdr_create(tdata->pool);
+ pj_strdup_with_null(tdata->pool, &tmp, param_to);
+ to->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (to->uri == NULL) {
+ status = PJSIP_EINVALIDHDR;
+ goto on_error;
+ }
+
+ /* Contact. */
+ if (param_contact) {
+ pj_strdup_with_null(tdata->pool, &tmp, param_contact);
+ contact = (pjsip_contact_hdr*)
+ pjsip_parse_hdr(tdata->pool, &STR_CONTACT, tmp.ptr,
+ tmp.slen, NULL);
+ if (contact == NULL) {
+ status = PJSIP_EINVALIDHDR;
+ goto on_error;
+ }
+ } else {
+ contact = NULL;
+ }
+
+ /* Call-ID */
+ call_id = pjsip_cid_hdr_create(tdata->pool);
+ if (param_call_id != NULL && param_call_id->slen)
+ pj_strdup(tdata->pool, &call_id->id, param_call_id);
+ else
+ pj_create_unique_string(tdata->pool, &call_id->id);
+
+ /* CSeq */
+ cseq = pjsip_cseq_hdr_create(tdata->pool);
+ if (param_cseq >= 0)
+ cseq->cseq = param_cseq;
+ else
+ cseq->cseq = pj_rand() & 0xFFFF;
+
+ /* Method */
+ pjsip_method_copy(tdata->pool, &cseq->method, method);
+
+ /* Create the request. */
+ init_request_throw( endpt, tdata, &cseq->method, target, from, to,
+ contact, call_id, cseq, param_text);
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ PJ_END
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+
+on_error:
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+}
+
+PJ_DEF(pj_status_t) pjsip_endpt_create_request_from_hdr( pjsip_endpoint *endpt,
+ const pjsip_method *method,
+ const pjsip_uri *param_target,
+ const pjsip_from_hdr *param_from,
+ const pjsip_to_hdr *param_to,
+ const pjsip_contact_hdr *param_contact,
+ const pjsip_cid_hdr *param_call_id,
+ int param_cseq,
+ const pj_str_t *param_text,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_uri *target;
+ pjsip_tx_data *tdata;
+ pjsip_from_hdr *from;
+ pjsip_to_hdr *to;
+ pjsip_contact_hdr *contact;
+ pjsip_cid_hdr *call_id;
+ pjsip_cseq_hdr *cseq = NULL; /* The NULL because warning in VC6 */
+ pj_status_t status;
+ PJ_USE_EXCEPTION;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt && method && param_target && param_from &&
+ param_to && p_tdata, PJ_EINVAL);
+
+ /* Create new transmit data. */
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set initial reference counter to 1. */
+ pjsip_tx_data_add_ref(tdata);
+
+ PJ_TRY {
+ /* Duplicate target URI and headers. */
+ target = (pjsip_uri*) pjsip_uri_clone(tdata->pool, param_target);
+ from = (pjsip_from_hdr*) pjsip_hdr_clone(tdata->pool, param_from);
+ pjsip_fromto_hdr_set_from(from);
+ to = (pjsip_to_hdr*) pjsip_hdr_clone(tdata->pool, param_to);
+ pjsip_fromto_hdr_set_to(to);
+ if (param_contact) {
+ contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(tdata->pool, param_contact);
+ } else {
+ contact = NULL;
+ }
+ call_id = pjsip_cid_hdr_create(tdata->pool);
+ if (param_call_id != NULL && param_call_id->id.slen)
+ pj_strdup(tdata->pool, &call_id->id, &param_call_id->id);
+ else
+ pj_create_unique_string(tdata->pool, &call_id->id);
+
+ cseq = pjsip_cseq_hdr_create(tdata->pool);
+ if (param_cseq >= 0)
+ cseq->cseq = param_cseq;
+ else
+ cseq->cseq = pj_rand() % 0xFFFF;
+ pjsip_method_copy(tdata->pool, &cseq->method, method);
+
+ /* Copy headers to the request. */
+ init_request_throw(endpt, tdata, &cseq->method, target, from, to,
+ contact, call_id, cseq, param_text);
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ PJ_END;
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+
+on_error:
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+}
+
+/*
+ * Construct a minimal response message for the received request.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_response( pjsip_endpoint *endpt,
+ const pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_msg *msg, *req_msg;
+ pjsip_hdr *hdr;
+ pjsip_to_hdr *to_hdr;
+ pjsip_via_hdr *top_via = NULL, *via;
+ pjsip_rr_hdr *rr;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL);
+
+ /* Check status code. */
+ PJ_ASSERT_RETURN(st_code >= 100 && st_code <= 699, PJ_EINVAL);
+
+ /* rdata must be a request message. */
+ req_msg = rdata->msg_info.msg;
+ pj_assert(req_msg->type == PJSIP_REQUEST_MSG);
+
+ /* Request MUST NOT be ACK request! */
+ PJ_ASSERT_RETURN(req_msg->line.req.method.id != PJSIP_ACK_METHOD,
+ PJ_EINVALIDOP);
+
+ /* Create a new transmit buffer. */
+ status = pjsip_endpt_create_tdata( endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set initial reference count to 1. */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Create new response message. */
+ tdata->msg = msg = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG);
+
+ /* Set status code and reason text. */
+ msg->line.status.code = st_code;
+ if (st_text)
+ pj_strdup(tdata->pool, &msg->line.status.reason, st_text);
+ else
+ msg->line.status.reason = *pjsip_get_status_text(st_code);
+
+ /* Set TX data attributes. */
+ tdata->rx_timestamp = rdata->pkt_info.timestamp;
+
+ /* Copy all the via headers, in order. */
+ via = rdata->msg_info.via;
+ while (via) {
+ pjsip_via_hdr *new_via;
+
+ new_via = (pjsip_via_hdr*)pjsip_hdr_clone(tdata->pool, via);
+ if (top_via == NULL)
+ top_via = new_via;
+
+ pjsip_msg_add_hdr( msg, (pjsip_hdr*)new_via);
+ via = via->next;
+ if (via != (void*)&req_msg->hdr)
+ via = (pjsip_via_hdr*)
+ pjsip_msg_find_hdr(req_msg, PJSIP_H_VIA, via);
+ else
+ break;
+ }
+
+ /* Copy all Record-Route headers, in order. */
+ rr = (pjsip_rr_hdr*)
+ pjsip_msg_find_hdr(req_msg, PJSIP_H_RECORD_ROUTE, NULL);
+ while (rr) {
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rr));
+ rr = rr->next;
+ if (rr != (void*)&req_msg->hdr)
+ rr = (pjsip_rr_hdr*) pjsip_msg_find_hdr(req_msg,
+ PJSIP_H_RECORD_ROUTE, rr);
+ else
+ break;
+ }
+
+ /* Copy Call-ID header. */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr( req_msg, PJSIP_H_CALL_ID, NULL);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr));
+
+ /* Copy From header. */
+ hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.from);
+ pjsip_msg_add_hdr( msg, hdr);
+
+ /* Copy To header. */
+ to_hdr = (pjsip_to_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.to);
+ pjsip_msg_add_hdr( msg, (pjsip_hdr*)to_hdr);
+
+ /* Must add To tag in the response (Section 8.2.6.2), except if this is
+ * 100 (Trying) response. Same tag must be created for the same request
+ * (e.g. same tag in provisional and final response). The easiest way
+ * to do this is to derive the tag from Via branch parameter (or to
+ * use it directly).
+ */
+ if (to_hdr->tag.slen==0 && st_code > 100 && top_via) {
+ to_hdr->tag = top_via->branch_param;
+ }
+
+ /* Copy CSeq header. */
+ hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.cseq);
+ pjsip_msg_add_hdr( msg, hdr);
+
+ /* All done. */
+ *p_tdata = tdata;
+
+ PJ_LOG(5,(THIS_FILE, "%s created", pjsip_tx_data_get_info(tdata)));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Construct ACK for 3xx-6xx final response (according to chapter 17.1.1 of
+ * RFC3261). Note that the generation of ACK for 2xx response is different,
+ * and one must not use this function to generate such ACK.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_ack( pjsip_endpoint *endpt,
+ const pjsip_tx_data *tdata,
+ const pjsip_rx_data *rdata,
+ pjsip_tx_data **ack_tdata)
+{
+ pjsip_tx_data *ack = NULL;
+ const pjsip_msg *invite_msg;
+ const pjsip_from_hdr *from_hdr;
+ const pjsip_to_hdr *to_hdr;
+ const pjsip_cid_hdr *cid_hdr;
+ const pjsip_cseq_hdr *cseq_hdr;
+ const pjsip_hdr *hdr;
+ pjsip_hdr *via;
+ pjsip_to_hdr *to;
+ pj_status_t status;
+
+ /* rdata must be a non-2xx final response. */
+ pj_assert(rdata->msg_info.msg->type==PJSIP_RESPONSE_MSG &&
+ rdata->msg_info.msg->line.status.code >= 300);
+
+ /* Initialize return value to NULL. */
+ *ack_tdata = NULL;
+
+ /* The original INVITE message. */
+ invite_msg = tdata->msg;
+
+ /* Get the headers from original INVITE request. */
+# define FIND_HDR(m,HNAME) pjsip_msg_find_hdr(m, PJSIP_H_##HNAME, NULL)
+
+ from_hdr = (const pjsip_from_hdr*) FIND_HDR(invite_msg, FROM);
+ PJ_ASSERT_ON_FAIL(from_hdr != NULL, goto on_missing_hdr);
+
+ to_hdr = (const pjsip_to_hdr*) FIND_HDR(invite_msg, TO);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+ cid_hdr = (const pjsip_cid_hdr*) FIND_HDR(invite_msg, CALL_ID);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+ cseq_hdr = (const pjsip_cseq_hdr*) FIND_HDR(invite_msg, CSEQ);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+# undef FIND_HDR
+
+ /* Create new request message from the headers. */
+ status = pjsip_endpt_create_request_from_hdr(endpt,
+ pjsip_get_ack_method(),
+ tdata->msg->line.req.uri,
+ from_hdr, to_hdr,
+ NULL, cid_hdr,
+ cseq_hdr->cseq, NULL,
+ &ack);
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Update tag in To header with the one from the response (if any). */
+ to = (pjsip_to_hdr*) pjsip_msg_find_hdr(ack->msg, PJSIP_H_TO, NULL);
+ pj_strdup(ack->pool, &to->tag, &rdata->msg_info.to->tag);
+
+
+ /* Clear Via headers in the new request. */
+ while ((via=(pjsip_hdr*)pjsip_msg_find_hdr(ack->msg, PJSIP_H_VIA, NULL)) != NULL)
+ pj_list_erase(via);
+
+ /* Must contain single Via, just as the original INVITE. */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_VIA, NULL);
+ pjsip_msg_insert_first_hdr( ack->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(ack->pool,hdr) );
+
+ /* If the original INVITE has Route headers, those header fields MUST
+ * appear in the ACK.
+ */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_ROUTE, NULL);
+ while (hdr != NULL) {
+ pjsip_msg_add_hdr( ack->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(ack->pool, hdr) );
+ hdr = hdr->next;
+ if (hdr == &invite_msg->hdr)
+ break;
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_ROUTE, hdr);
+ }
+
+ /* We're done.
+ * "tdata" parameter now contains the ACK message.
+ */
+ *ack_tdata = ack;
+ return PJ_SUCCESS;
+
+on_missing_hdr:
+ if (ack)
+ pjsip_tx_data_dec_ref(ack);
+ return PJSIP_EMISSINGHDR;
+}
+
+
+/*
+ * Construct CANCEL request for the previously sent request, according to
+ * chapter 9.1 of RFC3261.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_cancel( pjsip_endpoint *endpt,
+ const pjsip_tx_data *req_tdata,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *cancel_tdata = NULL;
+ const pjsip_from_hdr *from_hdr;
+ const pjsip_to_hdr *to_hdr;
+ const pjsip_cid_hdr *cid_hdr;
+ const pjsip_cseq_hdr *cseq_hdr;
+ const pjsip_hdr *hdr;
+ pjsip_hdr *via;
+ pj_status_t status;
+
+ /* The transmit buffer must INVITE request. */
+ PJ_ASSERT_RETURN(req_tdata->msg->type == PJSIP_REQUEST_MSG &&
+ req_tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD,
+ PJ_EINVAL);
+
+ /* Get the headers from original INVITE request. */
+# define FIND_HDR(m,HNAME) pjsip_msg_find_hdr(m, PJSIP_H_##HNAME, NULL)
+
+ from_hdr = (const pjsip_from_hdr*) FIND_HDR(req_tdata->msg, FROM);
+ PJ_ASSERT_ON_FAIL(from_hdr != NULL, goto on_missing_hdr);
+
+ to_hdr = (const pjsip_to_hdr*) FIND_HDR(req_tdata->msg, TO);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+ cid_hdr = (const pjsip_cid_hdr*) FIND_HDR(req_tdata->msg, CALL_ID);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+ cseq_hdr = (const pjsip_cseq_hdr*) FIND_HDR(req_tdata->msg, CSEQ);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+# undef FIND_HDR
+
+ /* Create new request message from the headers. */
+ status = pjsip_endpt_create_request_from_hdr(endpt,
+ pjsip_get_cancel_method(),
+ req_tdata->msg->line.req.uri,
+ from_hdr, to_hdr,
+ NULL, cid_hdr,
+ cseq_hdr->cseq, NULL,
+ &cancel_tdata);
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Clear Via headers in the new request. */
+ while ((via=(pjsip_hdr*)pjsip_msg_find_hdr(cancel_tdata->msg, PJSIP_H_VIA, NULL)) != NULL)
+ pj_list_erase(via);
+
+
+ /* Must only have single Via which matches the top-most Via in the
+ * request being cancelled.
+ */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg, PJSIP_H_VIA, NULL);
+ if (hdr) {
+ pjsip_msg_insert_first_hdr(cancel_tdata->msg,
+ (pjsip_hdr*)pjsip_hdr_clone(cancel_tdata->pool, hdr));
+ }
+
+ /* If the original request has Route header, the CANCEL request must also
+ * has exactly the same.
+ * Copy "Route" header from the request.
+ */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg, PJSIP_H_ROUTE, NULL);
+ while (hdr != NULL) {
+ pjsip_msg_add_hdr(cancel_tdata->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(cancel_tdata->pool, hdr));
+ hdr = hdr->next;
+ if (hdr != &req_tdata->msg->hdr)
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg,
+ PJSIP_H_ROUTE, hdr);
+ else
+ break;
+ }
+
+ /* Must also copy the saved strict route header, otherwise CANCEL will be
+ * sent with swapped Route and request URI!
+ */
+ if (req_tdata->saved_strict_route) {
+ cancel_tdata->saved_strict_route = (pjsip_route_hdr*)
+ pjsip_hdr_clone(cancel_tdata->pool, req_tdata->saved_strict_route);
+ }
+
+ /* Copy the destination host name from the original request */
+ pj_strdup(cancel_tdata->pool, &cancel_tdata->dest_info.name,
+ &req_tdata->dest_info.name);
+
+ /* Finally copy the destination info from the original request */
+ pj_memcpy(&cancel_tdata->dest_info, &req_tdata->dest_info,
+ sizeof(req_tdata->dest_info));
+
+ /* Done.
+ * Return the transmit buffer containing the CANCEL request.
+ */
+ *p_tdata = cancel_tdata;
+ return PJ_SUCCESS;
+
+on_missing_hdr:
+ if (cancel_tdata)
+ pjsip_tx_data_dec_ref(cancel_tdata);
+ return PJSIP_EMISSINGHDR;
+}
+
+
+/* Fill-up destination information from a target URI */
+static pj_status_t get_dest_info(const pjsip_uri *target_uri,
+ pj_pool_t *pool,
+ pjsip_host_info *dest_info)
+{
+ /* The target URI must be a SIP/SIPS URL so we can resolve it's address.
+ * Otherwise we're in trouble (i.e. there's no host part in tel: URL).
+ */
+ pj_bzero(dest_info, sizeof(*dest_info));
+
+ if (PJSIP_URI_SCHEME_IS_SIPS(target_uri)) {
+ pjsip_uri *uri = (pjsip_uri*) target_uri;
+ const pjsip_sip_uri *url=(const pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+ unsigned flag;
+
+ dest_info->flag |= (PJSIP_TRANSPORT_SECURE | PJSIP_TRANSPORT_RELIABLE);
+ if (url->maddr_param.slen)
+ pj_strdup(pool, &dest_info->addr.host, &url->maddr_param);
+ else
+ pj_strdup(pool, &dest_info->addr.host, &url->host);
+ dest_info->addr.port = url->port;
+ dest_info->type =
+ pjsip_transport_get_type_from_name(&url->transport_param);
+ /* Double-check that the transport parameter match.
+ * Sample case: sips:host;transport=tcp
+ * See https://trac.pjsip.org/repos/ticket/1319
+ */
+ flag = pjsip_transport_get_flag_from_type(dest_info->type);
+ if ((flag & dest_info->flag) != dest_info->flag) {
+ pjsip_transport_type_e t;
+
+ t = pjsip_transport_get_type_from_flag(dest_info->flag);
+ if (t != PJSIP_TRANSPORT_UNSPECIFIED)
+ dest_info->type = t;
+ }
+
+ } else if (PJSIP_URI_SCHEME_IS_SIP(target_uri)) {
+ pjsip_uri *uri = (pjsip_uri*) target_uri;
+ const pjsip_sip_uri *url=(const pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+ if (url->maddr_param.slen)
+ pj_strdup(pool, &dest_info->addr.host, &url->maddr_param);
+ else
+ pj_strdup(pool, &dest_info->addr.host, &url->host);
+ dest_info->addr.port = url->port;
+ dest_info->type =
+ pjsip_transport_get_type_from_name(&url->transport_param);
+ dest_info->flag =
+ pjsip_transport_get_flag_from_type(dest_info->type);
+ } else {
+ /* Should have never reached here; app should have configured route
+ * set when sending to tel: URI
+ pj_assert(!"Unsupported URI scheme!");
+ */
+ PJ_TODO(SUPPORT_REQUEST_ADDR_RESOLUTION_FOR_TEL_URI);
+ return PJSIP_ENOROUTESET;
+ }
+
+ /* Handle IPv6 (http://trac.pjsip.org/repos/ticket/861) */
+ if (dest_info->type != PJSIP_TRANSPORT_UNSPECIFIED &&
+ pj_strchr(&dest_info->addr.host, ':'))
+ {
+ dest_info->type = (pjsip_transport_type_e)
+ ((int)dest_info->type | PJSIP_TRANSPORT_IPV6);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Find which destination to be used to send the request message, based
+ * on the request URI and Route headers in the message. The procedure
+ * used here follows the guidelines on sending the request in RFC 3261
+ * chapter 8.1.2.
+ */
+PJ_DEF(pj_status_t) pjsip_get_request_dest(const pjsip_tx_data *tdata,
+ pjsip_host_info *dest_info )
+{
+ const pjsip_uri *target_uri;
+ const pjsip_route_hdr *first_route_hdr;
+
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+ PJ_ASSERT_RETURN(dest_info != NULL, PJ_EINVAL);
+
+ /* Get the first "Route" header from the message.
+ */
+ first_route_hdr = (const pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
+ if (first_route_hdr) {
+ target_uri = first_route_hdr->name_addr.uri;
+ } else {
+ target_uri = tdata->msg->line.req.uri;
+ }
+
+ return get_dest_info(target_uri, (pj_pool_t*)tdata->pool, dest_info);
+}
+
+
+/*
+ * Process route-set found in the request and calculate
+ * the destination to be used to send the request message, based
+ * on the request URI and Route headers in the message. The procedure
+ * used here follows the guidelines on sending the request in RFC 3261
+ * chapter 8.1.2.
+ */
+PJ_DEF(pj_status_t) pjsip_process_route_set(pjsip_tx_data *tdata,
+ pjsip_host_info *dest_info )
+{
+ const pjsip_uri *new_request_uri, *target_uri;
+ const pjsip_name_addr *topmost_route_uri;
+ pjsip_route_hdr *first_route_hdr, *last_route_hdr;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+ PJ_ASSERT_RETURN(dest_info != NULL, PJ_EINVAL);
+
+ /* If the request contains strict route, check that the strict route
+ * has been restored to its original values before processing the
+ * route set. The strict route is restored to the original values
+ * with pjsip_restore_strict_route_set(). If caller did not restore
+ * the strict route before calling this function, we need to call it
+ * here, or otherwise the strict-route and Request-URI will be swapped
+ * twice!
+ */
+ if (tdata->saved_strict_route != NULL) {
+ pjsip_restore_strict_route_set(tdata);
+ }
+ PJ_ASSERT_RETURN(tdata->saved_strict_route==NULL, PJ_EBUG);
+
+ /* Find the first and last "Route" headers from the message. */
+ last_route_hdr = first_route_hdr = (pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
+ if (first_route_hdr) {
+ topmost_route_uri = &first_route_hdr->name_addr;
+ while (last_route_hdr->next != (void*)&tdata->msg->hdr) {
+ pjsip_route_hdr *hdr;
+ hdr = (pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE,
+ last_route_hdr->next);
+ if (!hdr)
+ break;
+ last_route_hdr = hdr;
+ }
+ } else {
+ topmost_route_uri = NULL;
+ }
+
+ /* If Route headers exist, and the first element indicates loose-route,
+ * the URI is taken from the Request-URI, and we keep all existing Route
+ * headers intact.
+ * If Route headers exist, and the first element DOESN'T indicate loose
+ * route, the URI is taken from the first Route header, and remove the
+ * first Route header from the message.
+ * Otherwise if there's no Route headers, the URI is taken from the
+ * Request-URI.
+ */
+ if (topmost_route_uri) {
+ pj_bool_t has_lr_param;
+
+ if (PJSIP_URI_SCHEME_IS_SIP(topmost_route_uri) ||
+ PJSIP_URI_SCHEME_IS_SIPS(topmost_route_uri))
+ {
+ const pjsip_sip_uri *url = (const pjsip_sip_uri*)
+ pjsip_uri_get_uri((const void*)topmost_route_uri);
+ has_lr_param = url->lr_param;
+ } else {
+ has_lr_param = 0;
+ }
+
+ if (has_lr_param) {
+ new_request_uri = tdata->msg->line.req.uri;
+ /* We shouldn't need to delete topmost Route if it has lr param.
+ * But seems like it breaks some proxy implementation, so we
+ * delete it anyway.
+ */
+ /*
+ pj_list_erase(first_route_hdr);
+ if (first_route_hdr == last_route_hdr)
+ last_route_hdr = NULL;
+ */
+ } else {
+ new_request_uri = (const pjsip_uri*)
+ pjsip_uri_get_uri((pjsip_uri*)topmost_route_uri);
+ pj_list_erase(first_route_hdr);
+ tdata->saved_strict_route = first_route_hdr;
+ if (first_route_hdr == last_route_hdr)
+ first_route_hdr = last_route_hdr = NULL;
+ }
+
+ target_uri = (pjsip_uri*)topmost_route_uri;
+
+ } else {
+ target_uri = new_request_uri = tdata->msg->line.req.uri;
+ }
+
+ /* Fill up the destination host/port from the URI. */
+ status = get_dest_info(target_uri, tdata->pool, dest_info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* If target URI is different than request URI, replace
+ * request URI add put the original URI in the last Route header.
+ */
+ if (new_request_uri && new_request_uri!=tdata->msg->line.req.uri) {
+ pjsip_route_hdr *route = pjsip_route_hdr_create(tdata->pool);
+ route->name_addr.uri = (pjsip_uri*)
+ pjsip_uri_get_uri(tdata->msg->line.req.uri);
+ if (last_route_hdr)
+ pj_list_insert_after(last_route_hdr, route);
+ else
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)route);
+ tdata->msg->line.req.uri = (pjsip_uri*)new_request_uri;
+ }
+
+ /* Success. */
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Swap the request URI and strict route back to the original position
+ * before #pjsip_process_route_set() function is called. This function
+ * should only used internally by PJSIP client authentication module.
+ */
+PJ_DEF(void) pjsip_restore_strict_route_set(pjsip_tx_data *tdata)
+{
+ pjsip_route_hdr *first_route_hdr, *last_route_hdr;
+
+ /* Check if we have found strict route before */
+ if (tdata->saved_strict_route == NULL) {
+ /* This request doesn't contain strict route */
+ return;
+ }
+
+ /* Find the first "Route" headers from the message. */
+ first_route_hdr = (pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
+
+ if (first_route_hdr == NULL) {
+ /* User has modified message route? We don't expect this! */
+ pj_assert(!"Message route was modified?");
+ tdata->saved_strict_route = NULL;
+ return;
+ }
+
+ /* Find last Route header */
+ last_route_hdr = first_route_hdr;
+ while (last_route_hdr->next != (void*)&tdata->msg->hdr) {
+ pjsip_route_hdr *hdr;
+ hdr = (pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE,
+ last_route_hdr->next);
+ if (!hdr)
+ break;
+ last_route_hdr = hdr;
+ }
+
+ /* Put the last Route header as request URI, delete last Route
+ * header, and insert the saved strict route as the first Route.
+ */
+ tdata->msg->line.req.uri = last_route_hdr->name_addr.uri;
+ pj_list_insert_before(first_route_hdr, tdata->saved_strict_route);
+ pj_list_erase(last_route_hdr);
+
+ /* Reset */
+ tdata->saved_strict_route = NULL;
+}
+
+
+/* Transport callback for sending stateless request.
+ * This is one of the most bizzare function in pjsip, so
+ * good luck if you happen to debug this function!!
+ */
+static void stateless_send_transport_cb( void *token,
+ pjsip_tx_data *tdata,
+ pj_ssize_t sent )
+{
+ pjsip_send_state *stateless_data = (pjsip_send_state*) token;
+
+ PJ_UNUSED_ARG(tdata);
+ pj_assert(tdata == stateless_data->tdata);
+
+ for (;;) {
+ pj_status_t status;
+ pj_bool_t cont;
+
+ pj_sockaddr_t *cur_addr;
+ pjsip_transport_type_e cur_addr_type;
+ int cur_addr_len;
+
+ pjsip_via_hdr *via;
+
+ if (sent == -PJ_EPENDING) {
+ /* This is the initial process.
+ * When the process started, this function will be called by
+ * stateless_send_resolver_callback() with sent argument set to
+ * -PJ_EPENDING.
+ */
+ cont = PJ_TRUE;
+ } else {
+ /* There are two conditions here:
+ * (1) Message is sent (i.e. sent > 0),
+ * (2) Failure (i.e. sent <= 0)
+ */
+ cont = (sent > 0) ? PJ_FALSE :
+ (tdata->dest_info.cur_addr<tdata->dest_info.addr.count-1);
+ if (stateless_data->app_cb) {
+ (*stateless_data->app_cb)(stateless_data, sent, &cont);
+ } else {
+ /* Doesn't have application callback.
+ * Terminate the process.
+ */
+ cont = PJ_FALSE;
+ }
+ }
+
+ /* Finished with this transport. */
+ if (stateless_data->cur_transport) {
+ pjsip_transport_dec_ref(stateless_data->cur_transport);
+ stateless_data->cur_transport = NULL;
+ }
+
+ /* Done if application doesn't want to continue. */
+ if (sent > 0 || !cont) {
+ pjsip_tx_data_dec_ref(tdata);
+ return;
+ }
+
+ /* Try next address, if any, and only when this is not the
+ * first invocation.
+ */
+ if (sent != -PJ_EPENDING) {
+ tdata->dest_info.cur_addr++;
+ }
+
+ /* Have next address? */
+ if (tdata->dest_info.cur_addr >= tdata->dest_info.addr.count) {
+ /* This only happens when a rather buggy application has
+ * sent 'cont' to PJ_TRUE when the initial value was PJ_FALSE.
+ * In this case just stop the processing; we don't need to
+ * call the callback again as application has been informed
+ * before.
+ */
+ pjsip_tx_data_dec_ref(tdata);
+ return;
+ }
+
+ /* Keep current server address information handy. */
+ cur_addr = &tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr;
+ cur_addr_type = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].type;
+ cur_addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len;
+
+ /* Acquire transport. */
+ status = pjsip_endpt_acquire_transport2(stateless_data->endpt,
+ cur_addr_type,
+ cur_addr,
+ cur_addr_len,
+ &tdata->tp_sel,
+ tdata,
+ &stateless_data->cur_transport);
+ if (status != PJ_SUCCESS) {
+ sent = -status;
+ continue;
+ }
+
+ /* Modify Via header. */
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr( tdata->msg,
+ PJSIP_H_VIA, NULL);
+ if (!via) {
+ /* Shouldn't happen if request was created with PJSIP API!
+ * But we handle the case anyway for robustness.
+ */
+ pj_assert(!"Via header not found!");
+ via = pjsip_via_hdr_create(tdata->pool);
+ pjsip_msg_insert_first_hdr(tdata->msg, (pjsip_hdr*)via);
+ }
+
+ if (via->branch_param.slen == 0) {
+ pj_str_t tmp;
+ via->branch_param.ptr = (char*)pj_pool_alloc(tdata->pool,
+ PJSIP_MAX_BRANCH_LEN);
+ via->branch_param.slen = PJSIP_MAX_BRANCH_LEN;
+ pj_memcpy(via->branch_param.ptr, PJSIP_RFC3261_BRANCH_ID,
+ PJSIP_RFC3261_BRANCH_LEN);
+ tmp.ptr = via->branch_param.ptr + PJSIP_RFC3261_BRANCH_LEN + 2;
+ *(tmp.ptr-2) = 80; *(tmp.ptr-1) = 106;
+ pj_generate_unique_string(&tmp);
+ }
+
+ via->transport = pj_str(stateless_data->cur_transport->type_name);
+ if (tdata->via_addr.host.slen > 0 &&
+ tdata->via_tp == (void *)stateless_data->cur_transport)
+ {
+ via->sent_by = tdata->via_addr;
+ } else {
+ via->sent_by = stateless_data->cur_transport->local_name;
+ }
+ via->rport_param = pjsip_cfg()->endpt.disable_rport ? -1 : 0;
+
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Send message using this transport. */
+ status = pjsip_transport_send( stateless_data->cur_transport,
+ tdata,
+ cur_addr,
+ cur_addr_len,
+ stateless_data,
+ &stateless_send_transport_cb);
+ if (status == PJ_SUCCESS) {
+ /* Recursively call this function. */
+ sent = tdata->buf.cur - tdata->buf.start;
+ stateless_send_transport_cb( stateless_data, tdata, sent );
+ return;
+ } else if (status == PJ_EPENDING) {
+ /* This callback will be called later. */
+ return;
+ } else {
+ /* Recursively call this function. */
+ sent = -status;
+ stateless_send_transport_cb( stateless_data, tdata, sent );
+ return;
+ }
+ }
+
+}
+
+/* Resolver callback for sending stateless request. */
+static void
+stateless_send_resolver_callback( pj_status_t status,
+ void *token,
+ const struct pjsip_server_addresses *addr)
+{
+ pjsip_send_state *stateless_data = (pjsip_send_state*) token;
+ pjsip_tx_data *tdata = stateless_data->tdata;
+
+ /* Fail on server resolution. */
+ if (status != PJ_SUCCESS) {
+ if (stateless_data->app_cb) {
+ pj_bool_t cont = PJ_FALSE;
+ (*stateless_data->app_cb)(stateless_data, -status, &cont);
+ }
+ pjsip_tx_data_dec_ref(tdata);
+ return;
+ }
+
+ /* Copy server addresses */
+ if (addr && addr != &tdata->dest_info.addr) {
+ pj_memcpy( &tdata->dest_info.addr, addr,
+ sizeof(pjsip_server_addresses));
+ }
+ pj_assert(tdata->dest_info.addr.count != 0);
+
+ /* RFC 3261 section 18.1.1:
+ * If a request is within 200 bytes of the path MTU, or if it is larger
+ * than 1300 bytes and the path MTU is unknown, the request MUST be sent
+ * using an RFC 2914 [43] congestion controlled transport protocol, such
+ * as TCP.
+ */
+ if (pjsip_cfg()->endpt.disable_tcp_switch==0 &&
+ tdata->msg->type == PJSIP_REQUEST_MSG &&
+ tdata->dest_info.addr.count > 0 &&
+ tdata->dest_info.addr.entry[0].type == PJSIP_TRANSPORT_UDP)
+ {
+ int len;
+
+ /* Encode the request */
+ status = pjsip_tx_data_encode(tdata);
+ if (status != PJ_SUCCESS) {
+ if (stateless_data->app_cb) {
+ pj_bool_t cont = PJ_FALSE;
+ (*stateless_data->app_cb)(stateless_data, -status, &cont);
+ }
+ pjsip_tx_data_dec_ref(tdata);
+ return;
+ }
+
+ /* Check if request message is larger than 1300 bytes. */
+ len = tdata->buf.cur - tdata->buf.start;
+ if (len >= PJSIP_UDP_SIZE_THRESHOLD) {
+ int i;
+ int count = tdata->dest_info.addr.count;
+
+ PJ_LOG(5,(THIS_FILE, "%s exceeds UDP size threshold (%u), "
+ "sending with TCP",
+ pjsip_tx_data_get_info(tdata),
+ PJSIP_UDP_SIZE_THRESHOLD));
+
+ /* Insert "TCP version" of resolved UDP addresses at the
+ * beginning.
+ */
+ if (count * 2 > PJSIP_MAX_RESOLVED_ADDRESSES)
+ count = PJSIP_MAX_RESOLVED_ADDRESSES / 2;
+ for (i = 0; i < count; ++i) {
+ pj_memcpy(&tdata->dest_info.addr.entry[i+count],
+ &tdata->dest_info.addr.entry[i],
+ sizeof(tdata->dest_info.addr.entry[0]));
+ tdata->dest_info.addr.entry[i].type = PJSIP_TRANSPORT_TCP;
+ }
+ tdata->dest_info.addr.count = count * 2;
+ }
+ }
+
+ /* Process the addresses. */
+ stateless_send_transport_cb( stateless_data, tdata, -PJ_EPENDING);
+}
+
+/*
+ * Send stateless request.
+ * The sending process consists of several stages:
+ * - determine which host to contact (#pjsip_get_request_addr).
+ * - resolve the host (#pjsip_endpt_resolve)
+ * - establish transport (#pjsip_endpt_acquire_transport)
+ * - send the message (#pjsip_transport_send)
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_send_request_stateless(pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata,
+ void *token,
+ pjsip_send_callback cb)
+{
+ pjsip_host_info dest_info;
+ pjsip_send_state *stateless_data;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt && tdata, PJ_EINVAL);
+
+ /* Get destination name to contact. */
+ status = pjsip_process_route_set(tdata, &dest_info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Keep stateless data. */
+ stateless_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_send_state);
+ stateless_data->token = token;
+ stateless_data->endpt = endpt;
+ stateless_data->tdata = tdata;
+ stateless_data->app_cb = cb;
+
+ /* If destination info has not been initialized (this applies for most
+ * all requests except CANCEL), resolve destination host. The processing
+ * then resumed when the resolving callback is called. For CANCEL, the
+ * destination info must have been copied from the original INVITE so
+ * proceed to sending the request directly.
+ */
+ if (tdata->dest_info.addr.count == 0) {
+ /* Copy the destination host name to TX data */
+ pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host);
+
+ pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, stateless_data,
+ &stateless_send_resolver_callback);
+ } else {
+ PJ_LOG(5,(THIS_FILE, "%s: skipping target resolution because "
+ "address is already set",
+ pjsip_tx_data_get_info(tdata)));
+ stateless_send_resolver_callback(PJ_SUCCESS, stateless_data,
+ &tdata->dest_info.addr);
+ }
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Send raw data to a destination.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_send_raw( pjsip_endpoint *endpt,
+ pjsip_transport_type_e tp_type,
+ const pjsip_tpselector *sel,
+ const void *raw_data,
+ pj_size_t data_len,
+ const pj_sockaddr_t *addr,
+ int addr_len,
+ void *token,
+ pjsip_tp_send_callback cb)
+{
+ return pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(endpt), tp_type, sel,
+ NULL, raw_data, data_len, addr, addr_len,
+ token, cb);
+}
+
+
+/* Callback data for sending raw data */
+struct send_raw_data
+{
+ pjsip_endpoint *endpt;
+ pjsip_tx_data *tdata;
+ pjsip_tpselector *sel;
+ void *app_token;
+ pjsip_tp_send_callback app_cb;
+};
+
+
+/* Resolver callback for sending raw data. */
+static void send_raw_resolver_callback( pj_status_t status,
+ void *token,
+ const pjsip_server_addresses *addr)
+{
+ struct send_raw_data *sraw_data = (struct send_raw_data*) token;
+
+ if (status != PJ_SUCCESS) {
+ if (sraw_data->app_cb) {
+ (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata,
+ -status);
+ }
+ } else {
+ pj_size_t data_len;
+
+ pj_assert(addr->count != 0);
+
+ /* Avoid tdata destroyed by pjsip_tpmgr_send_raw(). */
+ pjsip_tx_data_add_ref(sraw_data->tdata);
+
+ data_len = sraw_data->tdata->buf.cur - sraw_data->tdata->buf.start;
+ status = pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(sraw_data->endpt),
+ addr->entry[0].type,
+ sraw_data->sel, sraw_data->tdata,
+ sraw_data->tdata->buf.start, data_len,
+ &addr->entry[0].addr,
+ addr->entry[0].addr_len,
+ sraw_data->app_token,
+ sraw_data->app_cb);
+ if (status == PJ_SUCCESS) {
+ (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata,
+ data_len);
+ } else if (status != PJ_EPENDING) {
+ (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata,
+ -status);
+ }
+ }
+
+ if (sraw_data->sel) {
+ pjsip_tpselector_dec_ref(sraw_data->sel);
+ }
+ pjsip_tx_data_dec_ref(sraw_data->tdata);
+}
+
+
+/*
+ * Send raw data to the specified destination URI.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_send_raw_to_uri(pjsip_endpoint *endpt,
+ const pj_str_t *p_dst_uri,
+ const pjsip_tpselector *sel,
+ const void *raw_data,
+ pj_size_t data_len,
+ void *token,
+ pjsip_tp_send_callback cb)
+{
+ pjsip_tx_data *tdata;
+ struct send_raw_data *sraw_data;
+ pj_str_t dst_uri;
+ pjsip_uri *uri;
+ pjsip_host_info dest_info;
+ pj_status_t status;
+
+ /* Allocate buffer */
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Duplicate URI since parser requires URI to be NULL terminated */
+ pj_strdup_with_null(tdata->pool, &dst_uri, p_dst_uri);
+
+ /* Parse URI */
+ uri = pjsip_parse_uri(tdata->pool, dst_uri.ptr, dst_uri.slen, 0);
+ if (uri == NULL) {
+ pjsip_tx_data_dec_ref(tdata);
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Build destination info. */
+ status = get_dest_info(uri, tdata->pool, &dest_info);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+
+ /* Copy data (note: data_len may be zero!) */
+ tdata->buf.start = (char*) pj_pool_alloc(tdata->pool, data_len+1);
+ tdata->buf.end = tdata->buf.start + data_len + 1;
+ if (data_len)
+ pj_memcpy(tdata->buf.start, raw_data, data_len);
+ tdata->buf.cur = tdata->buf.start + data_len;
+
+ /* Init send_raw_data */
+ sraw_data = PJ_POOL_ZALLOC_T(tdata->pool, struct send_raw_data);
+ sraw_data->endpt = endpt;
+ sraw_data->tdata = tdata;
+ sraw_data->app_token = token;
+ sraw_data->app_cb = cb;
+
+ if (sel) {
+ sraw_data->sel = PJ_POOL_ALLOC_T(tdata->pool, pjsip_tpselector);
+ pj_memcpy(sraw_data->sel, sel, sizeof(pjsip_tpselector));
+ pjsip_tpselector_add_ref(sraw_data->sel);
+ }
+
+ /* Copy the destination host name to TX data */
+ pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host);
+
+ /* Resolve destination host.
+ * The processing then resumed when the resolving callback is called.
+ */
+ pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, sraw_data,
+ &send_raw_resolver_callback);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Determine which address (and transport) to use to send response message
+ * based on the received request. This function follows the specification
+ * in section 18.2.2 of RFC 3261 and RFC 3581 for calculating the destination
+ * address and transport.
+ */
+PJ_DEF(pj_status_t) pjsip_get_response_addr( pj_pool_t *pool,
+ pjsip_rx_data *rdata,
+ pjsip_response_addr *res_addr )
+{
+ pjsip_transport *src_transport = rdata->tp_info.transport;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(pool && rdata && res_addr, PJ_EINVAL);
+
+ /* rdata must be a request message! */
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJ_EINVAL);
+
+ /* All requests must have "received" parameter.
+ * This must always be done in transport layer.
+ */
+ pj_assert(rdata->msg_info.via->recvd_param.slen != 0);
+
+ /* Do the calculation based on RFC 3261 Section 18.2.2 and RFC 3581 */
+
+ if (PJSIP_TRANSPORT_IS_RELIABLE(src_transport)) {
+ /* For reliable protocol such as TCP or SCTP, or TLS over those, the
+ * response MUST be sent using the existing connection to the source
+ * of the original request that created the transaction, if that
+ * connection is still open.
+ * If that connection is no longer open, the server SHOULD open a
+ * connection to the IP address in the received parameter, if present,
+ * using the port in the sent-by value, or the default port for that
+ * transport, if no port is specified.
+ * If that connection attempt fails, the server SHOULD use the
+ * procedures in [4] for servers in order to determine the IP address
+ * and port to open the connection and send the response to.
+ */
+ res_addr->transport = rdata->tp_info.transport;
+ pj_memcpy(&res_addr->addr, &rdata->pkt_info.src_addr,
+ rdata->pkt_info.src_addr_len);
+ res_addr->addr_len = rdata->pkt_info.src_addr_len;
+ res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
+ res_addr->dst_host.flag = src_transport->flag;
+ pj_strdup( pool, &res_addr->dst_host.addr.host,
+ &rdata->msg_info.via->recvd_param);
+ res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
+ if (res_addr->dst_host.addr.port == 0) {
+ res_addr->dst_host.addr.port =
+ pjsip_transport_get_default_port_for_type(res_addr->dst_host.type);
+ }
+
+ } else if (rdata->msg_info.via->maddr_param.slen) {
+ /* Otherwise, if the Via header field value contains a maddr parameter,
+ * the response MUST be forwarded to the address listed there, using
+ * the port indicated in sent-by, or port 5060 if none is present.
+ * If the address is a multicast address, the response SHOULD be sent
+ * using the TTL indicated in the ttl parameter, or with a TTL of 1 if
+ * that parameter is not present.
+ */
+ res_addr->transport = NULL;
+ res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
+ res_addr->dst_host.flag = src_transport->flag;
+ pj_strdup( pool, &res_addr->dst_host.addr.host,
+ &rdata->msg_info.via->maddr_param);
+ res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
+ if (res_addr->dst_host.addr.port == 0)
+ res_addr->dst_host.addr.port = 5060;
+
+ } else if (rdata->msg_info.via->rport_param >= 0) {
+ /* There is both a "received" parameter and an "rport" parameter,
+ * the response MUST be sent to the IP address listed in the "received"
+ * parameter, and the port in the "rport" parameter.
+ * The response MUST be sent from the same address and port that the
+ * corresponding request was received on.
+ */
+ res_addr->transport = rdata->tp_info.transport;
+ pj_memcpy(&res_addr->addr, &rdata->pkt_info.src_addr,
+ rdata->pkt_info.src_addr_len);
+ res_addr->addr_len = rdata->pkt_info.src_addr_len;
+ res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
+ res_addr->dst_host.flag = src_transport->flag;
+ pj_strdup( pool, &res_addr->dst_host.addr.host,
+ &rdata->msg_info.via->recvd_param);
+ res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
+ if (res_addr->dst_host.addr.port == 0) {
+ res_addr->dst_host.addr.port =
+ pjsip_transport_get_default_port_for_type(res_addr->dst_host.type);
+ }
+
+ } else {
+ res_addr->transport = NULL;
+ res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
+ res_addr->dst_host.flag = src_transport->flag;
+ pj_strdup( pool, &res_addr->dst_host.addr.host,
+ &rdata->msg_info.via->recvd_param);
+ res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
+ if (res_addr->dst_host.addr.port == 0) {
+ res_addr->dst_host.addr.port =
+ pjsip_transport_get_default_port_for_type(res_addr->dst_host.type);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Callback called by transport during send_response.
+ */
+static void send_response_transport_cb(void *token, pjsip_tx_data *tdata,
+ pj_ssize_t sent)
+{
+ pjsip_send_state *send_state = (pjsip_send_state*) token;
+ pj_bool_t cont = PJ_FALSE;
+
+ /* Call callback, if any. */
+ if (send_state->app_cb)
+ (*send_state->app_cb)(send_state, sent, &cont);
+
+ /* Decrement transport reference counter. */
+ pjsip_transport_dec_ref(send_state->cur_transport);
+
+ /* Decrement transmit data ref counter. */
+ pjsip_tx_data_dec_ref(tdata);
+}
+
+/*
+ * Resolver calback during send_response.
+ */
+static void send_response_resolver_cb( pj_status_t status, void *token,
+ const pjsip_server_addresses *addr )
+{
+ pjsip_send_state *send_state = (pjsip_send_state*) token;
+
+ if (status != PJ_SUCCESS) {
+ if (send_state->app_cb) {
+ pj_bool_t cont = PJ_FALSE;
+ (*send_state->app_cb)(send_state, -status, &cont);
+ }
+ pjsip_tx_data_dec_ref(send_state->tdata);
+ return;
+ }
+
+ /* Only handle the first address resolved. */
+
+ /* Acquire transport. */
+ status = pjsip_endpt_acquire_transport2(send_state->endpt,
+ addr->entry[0].type,
+ &addr->entry[0].addr,
+ addr->entry[0].addr_len,
+ &send_state->tdata->tp_sel,
+ send_state->tdata,
+ &send_state->cur_transport);
+ if (status != PJ_SUCCESS) {
+ if (send_state->app_cb) {
+ pj_bool_t cont = PJ_FALSE;
+ (*send_state->app_cb)(send_state, -status, &cont);
+ }
+ pjsip_tx_data_dec_ref(send_state->tdata);
+ return;
+ }
+
+ /* Update address in send_state. */
+ pj_memcpy(&send_state->tdata->dest_info.addr, addr, sizeof(*addr));
+
+ /* Send response using the transoprt. */
+ status = pjsip_transport_send( send_state->cur_transport,
+ send_state->tdata,
+ &addr->entry[0].addr,
+ addr->entry[0].addr_len,
+ send_state,
+ &send_response_transport_cb);
+ if (status == PJ_SUCCESS) {
+ pj_ssize_t sent = send_state->tdata->buf.cur -
+ send_state->tdata->buf.start;
+ send_response_transport_cb(send_state, send_state->tdata, sent);
+
+ } else if (status == PJ_EPENDING) {
+ /* Transport callback will be called later. */
+ } else {
+ send_response_transport_cb(send_state, send_state->tdata, -status);
+ }
+}
+
+/*
+ * Send response.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt,
+ pjsip_response_addr *res_addr,
+ pjsip_tx_data *tdata,
+ void *token,
+ pjsip_send_callback cb)
+{
+ /* Determine which transports and addresses to send the response,
+ * based on Section 18.2.2 of RFC 3261.
+ */
+ pjsip_send_state *send_state;
+ pj_status_t status;
+
+ /* Create structure to keep the sending state. */
+ send_state = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_send_state);
+ send_state->endpt = endpt;
+ send_state->tdata = tdata;
+ send_state->token = token;
+ send_state->app_cb = cb;
+
+ if (res_addr->transport != NULL) {
+ send_state->cur_transport = res_addr->transport;
+ pjsip_transport_add_ref(send_state->cur_transport);
+
+ status = pjsip_transport_send( send_state->cur_transport, tdata,
+ &res_addr->addr,
+ res_addr->addr_len,
+ send_state,
+ &send_response_transport_cb );
+ if (status == PJ_SUCCESS) {
+ pj_ssize_t sent = tdata->buf.cur - tdata->buf.start;
+ send_response_transport_cb(send_state, tdata, sent);
+ return PJ_SUCCESS;
+ } else if (status == PJ_EPENDING) {
+ /* Callback will be called later. */
+ return PJ_SUCCESS;
+ } else {
+ pjsip_transport_dec_ref(send_state->cur_transport);
+ return status;
+ }
+ } else {
+ /* Copy the destination host name to TX data */
+ pj_strdup(tdata->pool, &tdata->dest_info.name,
+ &res_addr->dst_host.addr.host);
+
+ pjsip_endpt_resolve(endpt, tdata->pool, &res_addr->dst_host,
+ send_state, &send_response_resolver_cb);
+ return PJ_SUCCESS;
+ }
+}
+
+/*
+ * Send response combo
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_send_response2( pjsip_endpoint *endpt,
+ pjsip_rx_data *rdata,
+ pjsip_tx_data *tdata,
+ void *token,
+ pjsip_send_callback cb)
+{
+ pjsip_response_addr res_addr;
+ pj_status_t status;
+
+ status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_SUCCESS;
+ }
+
+ status = pjsip_endpt_send_response(endpt, &res_addr, tdata, token, cb);
+ return status;
+}
+
+
+/*
+ * Send response
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_respond_stateless( pjsip_endpoint *endpt,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ const pjsip_hdr *hdr_list,
+ const pjsip_msg_body *body)
+{
+ pj_status_t status;
+ pjsip_response_addr res_addr;
+ pjsip_tx_data *tdata;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(endpt && rdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Check that no UAS transaction has been created for this request.
+ * If UAS transaction has been created for this request, application
+ * MUST send the response statefully using that transaction.
+ */
+ PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata)==NULL, PJ_EINVALIDOP);
+
+ /* Create response message */
+ status = pjsip_endpt_create_response( endpt, rdata, st_code, st_text,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add the message headers, if any */
+ if (hdr_list) {
+ const pjsip_hdr *hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pjsip_msg_add_hdr(tdata->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr) );
+ hdr = hdr->next;
+ }
+ }
+
+ /* Add the message body, if any. */
+ if (body) {
+ tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body );
+ if (tdata->msg->body == NULL) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+ }
+
+ /* Get where to send request. */
+ status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr );
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+
+ /* Send! */
+ status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL );
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the event string from the event ID.
+ */
+PJ_DEF(const char *) pjsip_event_str(pjsip_event_id_e e)
+{
+ return event_str[e];
+}
+
diff --git a/pjsip/src/pjsip/sip_util_proxy.c b/pjsip/src/pjsip/sip_util_proxy.c
new file mode 100644
index 0000000..7a34534
--- /dev/null
+++ b/pjsip/src/pjsip/sip_util_proxy.c
@@ -0,0 +1,389 @@
+/* $Id: sip_util_proxy.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_msg.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/except.h>
+#include <pj/guid.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pjlib-util/md5.h>
+
+
+/**
+ * Clone the incoming SIP request or response message. A forwarding proxy
+ * typically would need to clone the incoming SIP message before processing
+ * the message.
+ *
+ * Once a transmit data is created, the reference counter is initialized to 1.
+ *
+ * @param endpt The endpoint instance.
+ * @param rdata The incoming SIP message.
+ * @param p_tdata Pointer to receive the transmit data containing
+ * the duplicated message.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+/*
+PJ_DEF(pj_status_t) pjsip_endpt_clone_msg( pjsip_endpoint *endpt,
+ const pjsip_rx_data *rdata,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ tdata->msg = pjsip_msg_clone(tdata->pool, rdata->msg_info.msg);
+
+ pjsip_tx_data_add_ref(tdata);
+
+ *p_tdata = tdata;
+
+ return PJ_SUCCESS;
+}
+*/
+
+
+/*
+ * Create new request message to be forwarded upstream to new destination URI
+ * in uri.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_request_fwd(pjsip_endpoint *endpt,
+ pjsip_rx_data *rdata,
+ const pjsip_uri *uri,
+ const pj_str_t *branch,
+ unsigned options,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+ PJ_USE_EXCEPTION;
+
+
+ PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ PJ_UNUSED_ARG(options);
+
+
+ /* Request forwarding rule in RFC 3261 section 16.6:
+ *
+ * For each target, the proxy forwards the request following these
+ * steps:
+ *
+ * 1. Make a copy of the received request
+ * 2. Update the Request-URI
+ * 3. Update the Max-Forwards header field
+ * 4. Optionally add a Record-route header field value
+ * 5. Optionally add additional header fields
+ * 6. Postprocess routing information
+ * 7. Determine the next-hop address, port, and transport
+ * 8. Add a Via header field value
+ * 9. Add a Content-Length header field if necessary
+ * 10. Forward the new request
+ *
+ * Of these steps, we only do step 1-3, since the later will be
+ * done by application.
+ */
+
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Always increment ref counter to 1 */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Duplicate the request */
+ PJ_TRY {
+ pjsip_msg *dst;
+ const pjsip_msg *src = rdata->msg_info.msg;
+ const pjsip_hdr *hsrc;
+
+ /* Create the request */
+ tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG);
+
+ /* Duplicate request method */
+ pjsip_method_copy(tdata->pool, &tdata->msg->line.req.method,
+ &src->line.req.method);
+
+ /* Set request URI */
+ if (uri) {
+ dst->line.req.uri = (pjsip_uri*)
+ pjsip_uri_clone(tdata->pool, uri);
+ } else {
+ dst->line.req.uri= (pjsip_uri*)
+ pjsip_uri_clone(tdata->pool, src->line.req.uri);
+ }
+
+ /* Clone ALL headers */
+ hsrc = src->hdr.next;
+ while (hsrc != &src->hdr) {
+
+ pjsip_hdr *hdst;
+
+ /* If this is the top-most Via header, insert our own before
+ * cloning the header.
+ */
+ if (hsrc == (pjsip_hdr*)rdata->msg_info.via) {
+ pjsip_via_hdr *hvia;
+ hvia = pjsip_via_hdr_create(tdata->pool);
+ if (branch)
+ pj_strdup(tdata->pool, &hvia->branch_param, branch);
+ else {
+ pj_str_t new_branch = pjsip_calculate_branch_id(rdata);
+ pj_strdup(tdata->pool, &hvia->branch_param, &new_branch);
+ }
+ pjsip_msg_add_hdr(dst, (pjsip_hdr*)hvia);
+
+ }
+ /* Skip Content-Type and Content-Length as these would be
+ * generated when the the message is printed.
+ */
+ else if (hsrc->type == PJSIP_H_CONTENT_LENGTH ||
+ hsrc->type == PJSIP_H_CONTENT_TYPE) {
+
+ hsrc = hsrc->next;
+ continue;
+
+ }
+#if 0
+ /* If this is the top-most Route header and it indicates loose
+ * route, remove the header.
+ */
+ else if (hsrc == (pjsip_hdr*)rdata->msg_info.route) {
+
+ const pjsip_route_hdr *hroute = (const pjsip_route_hdr*) hsrc;
+ const pjsip_sip_uri *sip_uri;
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(hroute->name_addr.uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(hroute->name_addr.uri))
+ {
+ /* This is a bad request! */
+ status = PJSIP_EINVALIDHDR;
+ goto on_error;
+ }
+
+ sip_uri = (pjsip_sip_uri*) hroute->name_addr.uri;
+
+ if (sip_uri->lr_param) {
+ /* Yes lr param is present, skip this Route header */
+ hsrc = hsrc->next;
+ continue;
+ }
+ }
+#endif
+
+ /* Clone the header */
+ hdst = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hsrc);
+
+ /* If this is Max-Forward header, decrement the value */
+ if (hdst->type == PJSIP_H_MAX_FORWARDS) {
+ pjsip_max_fwd_hdr *hmaxfwd = (pjsip_max_fwd_hdr*)hdst;
+ --hmaxfwd->ivalue;
+ }
+
+ /* Append header to new request */
+ pjsip_msg_add_hdr(dst, hdst);
+
+
+ hsrc = hsrc->next;
+ }
+
+ /* 16.6.3:
+ * If the copy does not contain a Max-Forwards header field, the
+ * proxy MUST add one with a field value, which SHOULD be 70.
+ */
+ if (rdata->msg_info.max_fwd == NULL) {
+ pjsip_max_fwd_hdr *hmaxfwd =
+ pjsip_max_fwd_hdr_create(tdata->pool, 70);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hmaxfwd);
+ }
+
+ /* Clone request body */
+ if (src->body) {
+ dst->body = pjsip_msg_body_clone(tdata->pool, src->body);
+ }
+
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ PJ_END
+
+
+ /* Done */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+
+on_error:
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_endpt_create_response_fwd( pjsip_endpoint *endpt,
+ pjsip_rx_data *rdata,
+ unsigned options,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+ PJ_USE_EXCEPTION;
+
+ PJ_UNUSED_ARG(options);
+
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pjsip_tx_data_add_ref(tdata);
+
+ PJ_TRY {
+ pjsip_msg *dst;
+ const pjsip_msg *src = rdata->msg_info.msg;
+ const pjsip_hdr *hsrc;
+
+ /* Create the request */
+ tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG);
+
+ /* Clone the status line */
+ dst->line.status.code = src->line.status.code;
+ pj_strdup(tdata->pool, &dst->line.status.reason,
+ &src->line.status.reason);
+
+ /* Duplicate all headers */
+ hsrc = src->hdr.next;
+ while (hsrc != &src->hdr) {
+
+ /* Skip Content-Type and Content-Length as these would be
+ * generated when the the message is printed.
+ */
+ if (hsrc->type == PJSIP_H_CONTENT_LENGTH ||
+ hsrc->type == PJSIP_H_CONTENT_TYPE) {
+
+ hsrc = hsrc->next;
+ continue;
+
+ }
+ /* Remove the first Via header */
+ else if (hsrc == (pjsip_hdr*) rdata->msg_info.via) {
+
+ hsrc = hsrc->next;
+ continue;
+ }
+
+ pjsip_msg_add_hdr(dst,
+ (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hsrc));
+
+ hsrc = hsrc->next;
+ }
+
+ /* Clone message body */
+ if (src->body)
+ dst->body = pjsip_msg_body_clone(tdata->pool, src->body);
+
+
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ PJ_END;
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+
+on_error:
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+}
+
+
+static void digest2str(const unsigned char digest[], char *output)
+{
+ int i;
+ for (i = 0; i<16; ++i) {
+ pj_val_to_hex_digit(digest[i], output);
+ output += 2;
+ }
+}
+
+
+PJ_DEF(pj_str_t) pjsip_calculate_branch_id( pjsip_rx_data *rdata )
+{
+ pj_md5_context ctx;
+ pj_uint8_t digest[16];
+ pj_str_t branch;
+ pj_str_t rfc3261_branch = {PJSIP_RFC3261_BRANCH_ID,
+ PJSIP_RFC3261_BRANCH_LEN};
+
+ /* If incoming request does not have RFC 3261 branch value, create
+ * a branch value from GUID .
+ */
+ if (pj_strncmp(&rdata->msg_info.via->branch_param,
+ &rfc3261_branch, PJSIP_RFC3261_BRANCH_LEN) != 0 )
+ {
+ pj_str_t tmp;
+
+ branch.ptr = (char*)
+ pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_BRANCH_LEN);
+ branch.slen = PJSIP_RFC3261_BRANCH_LEN;
+ pj_memcpy(branch.ptr, PJSIP_RFC3261_BRANCH_ID,
+ PJSIP_RFC3261_BRANCH_LEN);
+
+ tmp.ptr = branch.ptr + PJSIP_RFC3261_BRANCH_LEN + 2;
+ *(tmp.ptr-2) = (pj_int8_t)(branch.slen+73);
+ *(tmp.ptr-1) = (pj_int8_t)(branch.slen+99);
+ pj_generate_unique_string( &tmp );
+
+ branch.slen = PJSIP_MAX_BRANCH_LEN;
+ return branch;
+ }
+
+ /* Create branch ID for new request by calculating MD5 hash
+ * of the branch parameter in top-most Via header.
+ */
+ pj_md5_init(&ctx);
+ pj_md5_update(&ctx, (pj_uint8_t*)rdata->msg_info.via->branch_param.ptr,
+ rdata->msg_info.via->branch_param.slen);
+ pj_md5_final(&ctx, digest);
+
+ branch.ptr = (char*)
+ pj_pool_alloc(rdata->tp_info.pool,
+ 34 + PJSIP_RFC3261_BRANCH_LEN);
+ pj_memcpy(branch.ptr, PJSIP_RFC3261_BRANCH_ID, PJSIP_RFC3261_BRANCH_LEN);
+ branch.slen = PJSIP_RFC3261_BRANCH_LEN;
+ *(branch.ptr+PJSIP_RFC3261_BRANCH_LEN) = (pj_int8_t)(branch.slen+73);
+ *(branch.ptr+PJSIP_RFC3261_BRANCH_LEN+1) = (pj_int8_t)(branch.slen+99);
+ digest2str(digest, branch.ptr+PJSIP_RFC3261_BRANCH_LEN+2);
+ branch.slen = 34 + PJSIP_RFC3261_BRANCH_LEN;
+
+ return branch;
+}
+
+
diff --git a/pjsip/src/pjsip/sip_util_proxy_wrap.cpp b/pjsip/src/pjsip/sip_util_proxy_wrap.cpp
new file mode 100644
index 0000000..e77786f
--- /dev/null
+++ b/pjsip/src/pjsip/sip_util_proxy_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_util_proxy_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_util_proxy.c"
diff --git a/pjsip/src/pjsip/sip_util_statefull.c b/pjsip/src/pjsip/sip_util_statefull.c
new file mode 100644
index 0000000..3212cab
--- /dev/null
+++ b/pjsip/src/pjsip/sip_util_statefull.c
@@ -0,0 +1,192 @@
+/* $Id: sip_util_statefull.c 4169 2012-06-18 09:19:58Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+struct tsx_data
+{
+ void *token;
+ void (*cb)(void*, pjsip_event*);
+};
+
+static void mod_util_on_tsx_state(pjsip_transaction*, pjsip_event*);
+
+/* This module will be registered in pjsip_endpt.c */
+
+pjsip_module mod_stateful_util =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-stateful-util", 17 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ NULL, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ &mod_util_on_tsx_state, /* on_tsx_state() */
+};
+
+static void mod_util_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
+{
+ struct tsx_data *tsx_data;
+
+ /* Check if the module has been unregistered (see ticket #1535) and also
+ * verify the event type.
+ */
+ if (mod_stateful_util.id < 0 || event->type != PJSIP_EVENT_TSX_STATE)
+ return;
+
+ tsx_data = (struct tsx_data*) tsx->mod_data[mod_stateful_util.id];
+ if (tsx_data == NULL)
+ return;
+
+ if (tsx->status_code < 200)
+ return;
+
+ /* Call the callback, if any, and prevent the callback to be called again
+ * by clearing the transaction's module_data.
+ */
+ tsx->mod_data[mod_stateful_util.id] = NULL;
+
+ if (tsx_data->cb) {
+ (*tsx_data->cb)(tsx_data->token, event);
+ }
+}
+
+
+PJ_DEF(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata,
+ pj_int32_t timeout,
+ void *token,
+ pjsip_endpt_send_callback cb)
+{
+ pjsip_transaction *tsx;
+ struct tsx_data *tsx_data;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt && tdata && (timeout==-1 || timeout>0), PJ_EINVAL);
+
+ /* Check that transaction layer module is registered to endpoint */
+ PJ_ASSERT_RETURN(mod_stateful_util.id != -1, PJ_EINVALIDOP);
+
+ PJ_UNUSED_ARG(timeout);
+
+ status = pjsip_tsx_create_uac(&mod_stateful_util, tdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+
+ pjsip_tsx_set_transport(tsx, &tdata->tp_sel);
+
+ tsx_data = PJ_POOL_ALLOC_T(tsx->pool, struct tsx_data);
+ tsx_data->token = token;
+ tsx_data->cb = cb;
+
+ tsx->mod_data[mod_stateful_util.id] = tsx_data;
+
+ status = pjsip_tsx_send_msg(tsx, NULL);
+ if (status != PJ_SUCCESS)
+ pjsip_tx_data_dec_ref(tdata);
+
+ return status;
+}
+
+
+/*
+ * Send response statefully.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_respond( pjsip_endpoint *endpt,
+ pjsip_module *tsx_user,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ const pjsip_hdr *hdr_list,
+ const pjsip_msg_body *body,
+ pjsip_transaction **p_tsx )
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+ pjsip_transaction *tsx;
+
+ /* Validate arguments. */
+ PJ_ASSERT_RETURN(endpt && rdata, PJ_EINVAL);
+
+ if (p_tsx) *p_tsx = NULL;
+
+ /* Create response message */
+ status = pjsip_endpt_create_response( endpt, rdata, st_code, st_text,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add the message headers, if any */
+ if (hdr_list) {
+ const pjsip_hdr *hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_clone(tdata->pool, hdr) );
+ hdr = hdr->next;
+ }
+ }
+
+ /* Add the message body, if any. */
+ if (body) {
+ tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body );
+ if (tdata->msg->body == NULL) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+ }
+
+ /* Create UAS transaction. */
+ status = pjsip_tsx_create_uas(tsx_user, rdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+
+ /* Feed the request to the transaction. */
+ pjsip_tsx_recv_msg(tsx, rdata);
+
+ /* Send the message. */
+ status = pjsip_tsx_send_msg(tsx, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ } else if (p_tsx) {
+ *p_tsx = tsx;
+ }
+
+ return status;
+}
+
+
diff --git a/pjsip/src/pjsip/sip_util_wrap.cpp b/pjsip/src/pjsip/sip_util_wrap.cpp
new file mode 100644
index 0000000..140e907
--- /dev/null
+++ b/pjsip/src/pjsip/sip_util_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_util_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_util.c"
diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c
new file mode 100644
index 0000000..c5278c4
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_acc.c
@@ -0,0 +1,3078 @@
+/* $Id: pjsua_acc.c 4185 2012-06-28 14:16:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+
+#define THIS_FILE "pjsua_acc.c"
+
+enum
+{
+ OUTBOUND_UNKNOWN, // status unknown
+ OUTBOUND_WANTED, // initiated in registration
+ OUTBOUND_ACTIVE, // got positive response from server
+ OUTBOUND_NA // not wanted or got negative response from server
+};
+
+
+static void schedule_reregistration(pjsua_acc *acc);
+static void keep_alive_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te);
+
+/*
+ * Get number of current accounts.
+ */
+PJ_DEF(unsigned) pjsua_acc_get_count(void)
+{
+ return pjsua_var.acc_cnt;
+}
+
+
+/*
+ * Check if the specified account ID is valid.
+ */
+PJ_DEF(pj_bool_t) pjsua_acc_is_valid(pjsua_acc_id acc_id)
+{
+ return acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc) &&
+ pjsua_var.acc[acc_id].valid;
+}
+
+
+/*
+ * Set default account
+ */
+PJ_DEF(pj_status_t) pjsua_acc_set_default(pjsua_acc_id acc_id)
+{
+ pjsua_var.default_acc = acc_id;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get default account.
+ */
+PJ_DEF(pjsua_acc_id) pjsua_acc_get_default(void)
+{
+ return pjsua_var.default_acc;
+}
+
+
+/*
+ * Copy account configuration.
+ */
+PJ_DEF(void) pjsua_acc_config_dup( pj_pool_t *pool,
+ pjsua_acc_config *dst,
+ const pjsua_acc_config *src)
+{
+ unsigned i;
+
+ pj_memcpy(dst, src, sizeof(pjsua_acc_config));
+
+ pj_strdup_with_null(pool, &dst->id, &src->id);
+ pj_strdup_with_null(pool, &dst->reg_uri, &src->reg_uri);
+ pj_strdup_with_null(pool, &dst->force_contact, &src->force_contact);
+ pj_strdup_with_null(pool, &dst->contact_params, &src->contact_params);
+ pj_strdup_with_null(pool, &dst->contact_uri_params,
+ &src->contact_uri_params);
+ pj_strdup_with_null(pool, &dst->pidf_tuple_id, &src->pidf_tuple_id);
+ pj_strdup_with_null(pool, &dst->rfc5626_instance_id,
+ &src->rfc5626_instance_id);
+ pj_strdup_with_null(pool, &dst->rfc5626_reg_id, &src->rfc5626_reg_id);
+
+ dst->proxy_cnt = src->proxy_cnt;
+ for (i=0; i<src->proxy_cnt; ++i)
+ pj_strdup_with_null(pool, &dst->proxy[i], &src->proxy[i]);
+
+ dst->reg_timeout = src->reg_timeout;
+ dst->reg_delay_before_refresh = src->reg_delay_before_refresh;
+ dst->cred_count = src->cred_count;
+
+ for (i=0; i<src->cred_count; ++i) {
+ pjsip_cred_dup(pool, &dst->cred_info[i], &src->cred_info[i]);
+ }
+
+ pj_list_init(&dst->reg_hdr_list);
+ if (!pj_list_empty(&src->reg_hdr_list)) {
+ const pjsip_hdr *hdr;
+
+ hdr = src->reg_hdr_list.next;
+ while (hdr != &src->reg_hdr_list) {
+ pj_list_push_back(&dst->reg_hdr_list, pjsip_hdr_clone(pool, hdr));
+ hdr = hdr->next;
+ }
+ }
+
+ pj_list_init(&dst->sub_hdr_list);
+ if (!pj_list_empty(&src->sub_hdr_list)) {
+ const pjsip_hdr *hdr;
+
+ hdr = src->sub_hdr_list.next;
+ while (hdr != &src->sub_hdr_list) {
+ pj_list_push_back(&dst->sub_hdr_list, pjsip_hdr_clone(pool, hdr));
+ hdr = hdr->next;
+ }
+ }
+
+ pjsip_auth_clt_pref_dup(pool, &dst->auth_pref, &src->auth_pref);
+
+ pjsua_transport_config_dup(pool, &dst->rtp_cfg, &src->rtp_cfg);
+
+ pj_strdup(pool, &dst->ka_data, &src->ka_data);
+}
+
+/*
+ * Calculate CRC of proxy list.
+ */
+static pj_uint32_t calc_proxy_crc(const pj_str_t proxy[], pj_size_t cnt)
+{
+ pj_crc32_context ctx;
+ unsigned i;
+
+ pj_crc32_init(&ctx);
+ for (i=0; i<cnt; ++i) {
+ pj_crc32_update(&ctx, (pj_uint8_t*)proxy[i].ptr, proxy[i].slen);
+ }
+
+ return pj_crc32_final(&ctx);
+}
+
+/*
+ * Initialize a new account (after configuration is set).
+ */
+static pj_status_t initialize_acc(unsigned acc_id)
+{
+ pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+ pjsip_name_addr *name_addr;
+ pjsip_sip_uri *sip_reg_uri;
+ pj_status_t status;
+ unsigned i;
+
+ /* Need to parse local_uri to get the elements: */
+
+ name_addr = (pjsip_name_addr*)
+ pjsip_parse_uri(acc->pool, acc_cfg->id.ptr,
+ acc_cfg->id.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (name_addr == NULL) {
+ pjsua_perror(THIS_FILE, "Invalid local URI",
+ PJSIP_EINVALIDURI);
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Local URI MUST be a SIP or SIPS: */
+ if (!PJSIP_URI_SCHEME_IS_SIP(name_addr) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(name_addr))
+ {
+ acc->display = name_addr->display;
+ acc->user_part = name_addr->display;
+ acc->srv_domain = pj_str("");
+ acc->srv_port = 0;
+ } else {
+ pjsip_sip_uri *sip_uri;
+
+ /* Get the SIP URI object: */
+ sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(name_addr);
+
+ /* Save the user and domain part. These will be used when finding an
+ * account for incoming requests.
+ */
+ acc->display = name_addr->display;
+ acc->user_part = sip_uri->user;
+ acc->srv_domain = sip_uri->host;
+ acc->srv_port = 0;
+ }
+
+
+ /* Parse registrar URI, if any */
+ if (acc_cfg->reg_uri.slen) {
+ pjsip_uri *reg_uri;
+
+ reg_uri = pjsip_parse_uri(acc->pool, acc_cfg->reg_uri.ptr,
+ acc_cfg->reg_uri.slen, 0);
+ if (reg_uri == NULL) {
+ pjsua_perror(THIS_FILE, "Invalid registrar URI",
+ PJSIP_EINVALIDURI);
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Registrar URI MUST be a SIP or SIPS: */
+ if (!PJSIP_URI_SCHEME_IS_SIP(reg_uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(reg_uri))
+ {
+ pjsua_perror(THIS_FILE, "Invalid registar URI",
+ PJSIP_EINVALIDSCHEME);
+ return PJSIP_EINVALIDSCHEME;
+ }
+
+ sip_reg_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(reg_uri);
+
+ } else {
+ sip_reg_uri = NULL;
+ }
+
+ if (sip_reg_uri) {
+ acc->srv_port = sip_reg_uri->port;
+ }
+
+ /* Create Contact header if not present. */
+ //if (acc_cfg->contact.slen == 0) {
+ // acc_cfg->contact = acc_cfg->id;
+ //}
+
+ /* Build account route-set from outbound proxies and route set from
+ * account configuration.
+ */
+ pj_list_init(&acc->route_set);
+
+ if (!pj_list_empty(&pjsua_var.outbound_proxy)) {
+ pjsip_route_hdr *r;
+
+ r = pjsua_var.outbound_proxy.next;
+ while (r != &pjsua_var.outbound_proxy) {
+ pj_list_push_back(&acc->route_set,
+ pjsip_hdr_shallow_clone(acc->pool, r));
+ r = r->next;
+ }
+ }
+
+ for (i=0; i<acc_cfg->proxy_cnt; ++i) {
+ pj_str_t hname = { "Route", 5};
+ pjsip_route_hdr *r;
+ pj_str_t tmp;
+
+ pj_strdup_with_null(acc->pool, &tmp, &acc_cfg->proxy[i]);
+ r = (pjsip_route_hdr*)
+ pjsip_parse_hdr(acc->pool, &hname, tmp.ptr, tmp.slen, NULL);
+ if (r == NULL) {
+ pjsua_perror(THIS_FILE, "Invalid URI in account route set",
+ PJ_EINVAL);
+ return PJ_EINVAL;
+ }
+ pj_list_push_back(&acc->route_set, r);
+ }
+
+ /* Concatenate credentials from account config and global config */
+ acc->cred_cnt = 0;
+ for (i=0; i<acc_cfg->cred_count; ++i) {
+ acc->cred[acc->cred_cnt++] = acc_cfg->cred_info[i];
+ }
+ for (i=0; i<pjsua_var.ua_cfg.cred_count &&
+ acc->cred_cnt < PJ_ARRAY_SIZE(acc->cred); ++i)
+ {
+ acc->cred[acc->cred_cnt++] = pjsua_var.ua_cfg.cred_info[i];
+ }
+
+ /* If ICE is enabled, add "+sip.ice" media feature tag in account's
+ * contact params.
+ */
+#if PJSUA_ADD_ICE_TAGS
+ if (pjsua_var.media_cfg.enable_ice) {
+ unsigned new_len;
+ pj_str_t new_prm;
+
+ new_len = acc_cfg->contact_params.slen + 10;
+ new_prm.ptr = (char*)pj_pool_alloc(acc->pool, new_len);
+ pj_strcpy(&new_prm, &acc_cfg->contact_params);
+ pj_strcat2(&new_prm, ";+sip.ice");
+ acc_cfg->contact_params = new_prm;
+ }
+#endif
+
+ status = pjsua_pres_init_acc(acc_id);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* If SIP outbound is enabled, generate instance and reg ID if they are
+ * not specified
+ */
+ if (acc_cfg->use_rfc5626) {
+ if (acc_cfg->rfc5626_instance_id.slen==0) {
+ const pj_str_t *hostname;
+ pj_uint32_t hval, pos;
+ char instprm[] = ";+sip.instance=\"<urn:uuid:00000000-0000-0000-0000-0000CCDDEEFF>\"";
+
+ hostname = pj_gethostname();
+ pos = pj_ansi_strlen(instprm) - 10;
+ hval = pj_hash_calc(0, hostname->ptr, hostname->slen);
+ pj_val_to_hex_digit( ((char*)&hval)[0], instprm+pos+0);
+ pj_val_to_hex_digit( ((char*)&hval)[1], instprm+pos+2);
+ pj_val_to_hex_digit( ((char*)&hval)[2], instprm+pos+4);
+ pj_val_to_hex_digit( ((char*)&hval)[3], instprm+pos+6);
+
+ pj_strdup2(acc->pool, &acc->rfc5626_instprm, instprm);
+ } else {
+ const char *prmname = ";+sip.instance=\"";
+ unsigned len;
+
+ len = pj_ansi_strlen(prmname) + acc_cfg->rfc5626_instance_id.slen + 1;
+ acc->rfc5626_instprm.ptr = (char*)pj_pool_alloc(acc->pool, len+1);
+ pj_ansi_snprintf(acc->rfc5626_instprm.ptr, len+1,
+ "%s%.*s\"",
+ prmname,
+ (int)acc_cfg->rfc5626_instance_id.slen,
+ acc_cfg->rfc5626_instance_id.ptr);
+ acc->rfc5626_instprm.slen = len;
+ }
+
+ if (acc_cfg->rfc5626_reg_id.slen==0) {
+ acc->rfc5626_regprm = pj_str(";reg-id=1");
+ } else {
+ const char *prmname = ";reg-id=";
+ unsigned len;
+
+ len = pj_ansi_strlen(prmname) + acc_cfg->rfc5626_reg_id.slen;
+ acc->rfc5626_regprm.ptr = (char*)pj_pool_alloc(acc->pool, len+1);
+ pj_ansi_snprintf(acc->rfc5626_regprm.ptr, len+1,
+ "%s%.*s\"",
+ prmname,
+ (int)acc_cfg->rfc5626_reg_id.slen,
+ acc_cfg->rfc5626_reg_id.ptr);
+ acc->rfc5626_regprm.slen = len;
+ }
+ }
+
+ /* Mark account as valid */
+ pjsua_var.acc[acc_id].valid = PJ_TRUE;
+
+ /* Insert account ID into account ID array, sorted by priority */
+ for (i=0; i<pjsua_var.acc_cnt; ++i) {
+ if ( pjsua_var.acc[pjsua_var.acc_ids[i]].cfg.priority <
+ pjsua_var.acc[acc_id].cfg.priority)
+ {
+ break;
+ }
+ }
+ pj_array_insert(pjsua_var.acc_ids, sizeof(pjsua_var.acc_ids[0]),
+ pjsua_var.acc_cnt, i, &acc_id);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add a new account to pjsua.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg,
+ pj_bool_t is_default,
+ pjsua_acc_id *p_acc_id)
+{
+ pjsua_acc *acc;
+ unsigned i, id;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(cfg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.acc_cnt < PJ_ARRAY_SIZE(pjsua_var.acc),
+ PJ_ETOOMANY);
+
+ /* Must have a transport */
+ PJ_ASSERT_RETURN(pjsua_var.tpdata[0].data.ptr != NULL, PJ_EINVALIDOP);
+
+ PJ_LOG(4,(THIS_FILE, "Adding account: id=%.*s",
+ (int)cfg->id.slen, cfg->id.ptr));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ /* Find empty account id. */
+ for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.acc); ++id) {
+ if (pjsua_var.acc[id].valid == PJ_FALSE)
+ break;
+ }
+
+ /* Expect to find a slot */
+ PJ_ASSERT_ON_FAIL( id < PJ_ARRAY_SIZE(pjsua_var.acc),
+ {PJSUA_UNLOCK(); return PJ_EBUG;});
+
+ acc = &pjsua_var.acc[id];
+
+ /* Create pool for this account. */
+ if (acc->pool)
+ pj_pool_reset(acc->pool);
+ else
+ acc->pool = pjsua_pool_create("acc%p", 512, 256);
+
+ /* Copy config */
+ pjsua_acc_config_dup(acc->pool, &pjsua_var.acc[id].cfg, cfg);
+
+ /* Normalize registration timeout and refresh delay */
+ if (pjsua_var.acc[id].cfg.reg_uri.slen) {
+ if (pjsua_var.acc[id].cfg.reg_timeout == 0) {
+ pjsua_var.acc[id].cfg.reg_timeout = PJSUA_REG_INTERVAL;
+ }
+ if (pjsua_var.acc[id].cfg.reg_delay_before_refresh == 0) {
+ pjsua_var.acc[id].cfg.reg_delay_before_refresh =
+ PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH;
+ }
+ }
+
+ /* Check the route URI's and force loose route if required */
+ for (i=0; i<acc->cfg.proxy_cnt; ++i) {
+ status = normalize_route_uri(acc->pool, &acc->cfg.proxy[i]);
+ if (status != PJ_SUCCESS) {
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+ }
+ }
+
+ /* Get CRC of account proxy setting */
+ acc->local_route_crc = calc_proxy_crc(acc->cfg.proxy, acc->cfg.proxy_cnt);
+
+ /* Get CRC of global outbound proxy setting */
+ acc->global_route_crc=calc_proxy_crc(pjsua_var.ua_cfg.outbound_proxy,
+ pjsua_var.ua_cfg.outbound_proxy_cnt);
+
+ status = initialize_acc(id);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error adding account", status);
+ pj_pool_release(acc->pool);
+ acc->pool = NULL;
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+ }
+
+ if (is_default)
+ pjsua_var.default_acc = id;
+
+ if (p_acc_id)
+ *p_acc_id = id;
+
+ pjsua_var.acc_cnt++;
+
+ PJSUA_UNLOCK();
+
+ PJ_LOG(4,(THIS_FILE, "Account %.*s added with id %d",
+ (int)cfg->id.slen, cfg->id.ptr, id));
+
+ /* If accounts has registration enabled, start registration */
+ if (pjsua_var.acc[id].cfg.reg_uri.slen) {
+ if (pjsua_var.acc[id].cfg.register_on_acc_add)
+ pjsua_acc_set_registration(id, PJ_TRUE);
+ } else {
+ /* Otherwise subscribe to MWI, if it's enabled */
+ if (pjsua_var.acc[id].cfg.mwi_enabled)
+ pjsua_start_mwi(id, PJ_TRUE);
+ }
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add local account
+ */
+PJ_DEF(pj_status_t) pjsua_acc_add_local( pjsua_transport_id tid,
+ pj_bool_t is_default,
+ pjsua_acc_id *p_acc_id)
+{
+ pjsua_acc_config cfg;
+ pjsua_transport_data *t = &pjsua_var.tpdata[tid];
+ const char *beginquote, *endquote;
+ char transport_param[32];
+ char uri[PJSIP_MAX_URL_SIZE];
+
+ /* ID must be valid */
+ PJ_ASSERT_RETURN(tid>=0 && tid<(int)PJ_ARRAY_SIZE(pjsua_var.tpdata),
+ PJ_EINVAL);
+
+ /* Transport must be valid */
+ PJ_ASSERT_RETURN(t->data.ptr != NULL, PJ_EINVAL);
+
+ pjsua_acc_config_default(&cfg);
+
+ /* Lower the priority of local account */
+ --cfg.priority;
+
+ /* Enclose IPv6 address in square brackets */
+ if (t->type & PJSIP_TRANSPORT_IPV6) {
+ beginquote = "[";
+ endquote = "]";
+ } else {
+ beginquote = endquote = "";
+ }
+
+ /* Don't add transport parameter if it's UDP */
+ if (t->type!=PJSIP_TRANSPORT_UDP && t->type!=PJSIP_TRANSPORT_UDP6) {
+ pj_ansi_snprintf(transport_param, sizeof(transport_param),
+ ";transport=%s",
+ pjsip_transport_get_type_name(t->type));
+ } else {
+ transport_param[0] = '\0';
+ }
+
+ /* Build URI for the account */
+ pj_ansi_snprintf(uri, PJSIP_MAX_URL_SIZE,
+ "<sip:%s%.*s%s:%d%s>",
+ beginquote,
+ (int)t->local_name.host.slen,
+ t->local_name.host.ptr,
+ endquote,
+ t->local_name.port,
+ transport_param);
+
+ cfg.id = pj_str(uri);
+
+ return pjsua_acc_add(&cfg, is_default, p_acc_id);
+}
+
+
+/*
+ * Set arbitrary data to be associated with the account.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_set_user_data(pjsua_acc_id acc_id,
+ void *user_data)
+{
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+ PJSUA_LOCK();
+
+ pjsua_var.acc[acc_id].cfg.user_data = user_data;
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Retrieve arbitrary data associated with the account.
+ */
+PJ_DEF(void*) pjsua_acc_get_user_data(pjsua_acc_id acc_id)
+{
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+ NULL);
+ PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, NULL);
+
+ return pjsua_var.acc[acc_id].cfg.user_data;
+}
+
+
+/*
+ * Delete account.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_id)
+{
+ pjsua_acc *acc;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+ PJ_LOG(4,(THIS_FILE, "Deleting account %d..", acc_id));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ acc = &pjsua_var.acc[acc_id];
+
+ /* Cancel keep-alive timer, if any */
+ if (acc->ka_timer.id) {
+ pjsip_endpt_cancel_timer(pjsua_var.endpt, &acc->ka_timer);
+ acc->ka_timer.id = PJ_FALSE;
+ }
+ if (acc->ka_transport) {
+ pjsip_transport_dec_ref(acc->ka_transport);
+ acc->ka_transport = NULL;
+ }
+
+ /* Cancel any re-registration timer */
+ if (acc->auto_rereg.timer.id) {
+ acc->auto_rereg.timer.id = PJ_FALSE;
+ pjsua_cancel_timer(&acc->auto_rereg.timer);
+ }
+
+ /* Delete registration */
+ if (acc->regc != NULL) {
+ pjsua_acc_set_registration(acc_id, PJ_FALSE);
+ if (acc->regc) {
+ pjsip_regc_destroy(acc->regc);
+ }
+ acc->regc = NULL;
+ }
+
+ /* Terminate mwi subscription */
+ if (acc->cfg.mwi_enabled) {
+ acc->cfg.mwi_enabled = PJ_FALSE;
+ pjsua_start_mwi(acc_id, PJ_FALSE);
+ }
+
+ /* Delete server presence subscription */
+ pjsua_pres_delete_acc(acc_id, 0);
+
+ /* Release account pool */
+ if (acc->pool) {
+ pj_pool_release(acc->pool);
+ acc->pool = NULL;
+ }
+
+ /* Invalidate */
+ acc->valid = PJ_FALSE;
+ acc->contact.slen = 0;
+ pj_bzero(&acc->via_addr, sizeof(acc->via_addr));
+ acc->via_tp = NULL;
+
+ /* Remove from array */
+ for (i=0; i<pjsua_var.acc_cnt; ++i) {
+ if (pjsua_var.acc_ids[i] == acc_id)
+ break;
+ }
+ if (i != pjsua_var.acc_cnt) {
+ pj_array_erase(pjsua_var.acc_ids, sizeof(pjsua_var.acc_ids[0]),
+ pjsua_var.acc_cnt, i);
+ --pjsua_var.acc_cnt;
+ }
+
+ /* Leave the calls intact, as I don't think calls need to
+ * access account once it's created
+ */
+
+ /* Update default account */
+ if (pjsua_var.default_acc == acc_id)
+ pjsua_var.default_acc = 0;
+
+ PJSUA_UNLOCK();
+
+ PJ_LOG(4,(THIS_FILE, "Account id %d deleted", acc_id));
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+/* Get config */
+PJ_DEF(pj_status_t) pjsua_acc_get_config(pjsua_acc_id acc_id,
+ pjsua_acc_config *acc_cfg)
+{
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc)
+ && pjsua_var.acc[acc_id].valid, PJ_EINVAL);
+ pj_memcpy(acc_cfg, &pjsua_var.acc[acc_id].cfg, sizeof(*acc_cfg));
+ return PJ_SUCCESS;
+}
+
+/*
+ * Modify account information.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_modify( pjsua_acc_id acc_id,
+ const pjsua_acc_config *cfg)
+{
+ pjsua_acc *acc;
+ pjsip_name_addr *id_name_addr = NULL;
+ pjsip_sip_uri *id_sip_uri = NULL;
+ pjsip_sip_uri *reg_sip_uri = NULL;
+ pj_uint32_t local_route_crc, global_route_crc;
+ pjsip_route_hdr global_route;
+ pjsip_route_hdr local_route;
+ pj_str_t acc_proxy[PJSUA_ACC_MAX_PROXIES];
+ pj_bool_t update_reg = PJ_FALSE;
+ pj_bool_t unreg_first = PJ_FALSE;
+ pj_bool_t update_mwi = PJ_FALSE;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Modifying accunt %d", acc_id));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ acc = &pjsua_var.acc[acc_id];
+ if (!acc->valid) {
+ status = PJ_EINVAL;
+ goto on_return;
+ }
+
+ /* == Validate first == */
+
+ /* Account id */
+ if (pj_strcmp(&acc->cfg.id, &cfg->id)) {
+ /* Need to parse id to get the elements: */
+ id_name_addr = (pjsip_name_addr*)
+ pjsip_parse_uri(acc->pool, cfg->id.ptr, cfg->id.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (id_name_addr == NULL) {
+ status = PJSIP_EINVALIDURI;
+ pjsua_perror(THIS_FILE, "Invalid local URI", status);
+ goto on_return;
+ }
+
+ /* URI MUST be a SIP or SIPS: */
+ if (!PJSIP_URI_SCHEME_IS_SIP(id_name_addr) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(id_name_addr))
+ {
+ status = PJSIP_EINVALIDSCHEME;
+ pjsua_perror(THIS_FILE, "Invalid local URI", status);
+ goto on_return;
+ }
+
+ /* Get the SIP URI object: */
+ id_sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(id_name_addr);
+ }
+
+ /* Registrar URI */
+ if (pj_strcmp(&acc->cfg.reg_uri, &cfg->reg_uri) && cfg->reg_uri.slen) {
+ pjsip_uri *reg_uri;
+
+ /* Need to parse reg_uri to get the elements: */
+ reg_uri = pjsip_parse_uri(acc->pool, cfg->reg_uri.ptr,
+ cfg->reg_uri.slen, 0);
+ if (reg_uri == NULL) {
+ status = PJSIP_EINVALIDURI;
+ pjsua_perror(THIS_FILE, "Invalid registrar URI", status);
+ goto on_return;
+ }
+
+ /* Registrar URI MUST be a SIP or SIPS: */
+ if (!PJSIP_URI_SCHEME_IS_SIP(reg_uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(reg_uri))
+ {
+ status = PJSIP_EINVALIDSCHEME;
+ pjsua_perror(THIS_FILE, "Invalid registar URI", status);
+ goto on_return;
+ }
+
+ reg_sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(reg_uri);
+ }
+
+ /* Global outbound proxy */
+ global_route_crc = calc_proxy_crc(pjsua_var.ua_cfg.outbound_proxy,
+ pjsua_var.ua_cfg.outbound_proxy_cnt);
+ if (global_route_crc != acc->global_route_crc) {
+ pjsip_route_hdr *r;
+
+ /* Copy from global outbound proxies */
+ pj_list_init(&global_route);
+ r = pjsua_var.outbound_proxy.next;
+ while (r != &pjsua_var.outbound_proxy) {
+ pj_list_push_back(&global_route,
+ pjsip_hdr_shallow_clone(acc->pool, r));
+ r = r->next;
+ }
+ }
+
+ /* Account proxy */
+ local_route_crc = calc_proxy_crc(cfg->proxy, cfg->proxy_cnt);
+ if (local_route_crc != acc->local_route_crc) {
+ pjsip_route_hdr *r;
+ unsigned i;
+
+ /* Validate the local route and save it to temporary var */
+ pj_list_init(&local_route);
+ for (i=0; i<cfg->proxy_cnt; ++i) {
+ pj_str_t hname = { "Route", 5};
+
+ pj_strdup_with_null(acc->pool, &acc_proxy[i], &cfg->proxy[i]);
+ status = normalize_route_uri(acc->pool, &acc_proxy[i]);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ r = (pjsip_route_hdr*)
+ pjsip_parse_hdr(acc->pool, &hname, acc_proxy[i].ptr,
+ acc_proxy[i].slen, NULL);
+ if (r == NULL) {
+ status = PJSIP_EINVALIDURI;
+ pjsua_perror(THIS_FILE, "Invalid URI in account route set",
+ status);
+ goto on_return;
+ }
+
+ pj_list_push_back(&local_route, r);
+ }
+
+ /* Recalculate the CRC again after route URI normalization */
+ local_route_crc = calc_proxy_crc(acc_proxy, cfg->proxy_cnt);
+ }
+
+
+ /* == Apply the new config == */
+
+ /* Account ID. */
+ if (id_name_addr && id_sip_uri) {
+ pj_strdup_with_null(acc->pool, &acc->cfg.id, &cfg->id);
+ pj_strdup_with_null(acc->pool, &acc->display, &id_name_addr->display);
+ pj_strdup_with_null(acc->pool, &acc->user_part, &id_sip_uri->user);
+ pj_strdup_with_null(acc->pool, &acc->srv_domain, &id_sip_uri->host);
+ acc->srv_port = 0;
+ update_reg = PJ_TRUE;
+ unreg_first = PJ_TRUE;
+ }
+
+ /* User data */
+ acc->cfg.user_data = cfg->user_data;
+
+ /* Priority */
+ if (acc->cfg.priority != cfg->priority) {
+ unsigned i;
+
+ acc->cfg.priority = cfg->priority;
+
+ /* Resort accounts priority */
+ for (i=0; i<pjsua_var.acc_cnt; ++i) {
+ if (pjsua_var.acc_ids[i] == acc_id)
+ break;
+ }
+ pj_assert(i < pjsua_var.acc_cnt);
+ pj_array_erase(pjsua_var.acc_ids, sizeof(acc_id),
+ pjsua_var.acc_cnt, i);
+ for (i=0; i<pjsua_var.acc_cnt; ++i) {
+ if (pjsua_var.acc[pjsua_var.acc_ids[i]].cfg.priority <
+ acc->cfg.priority)
+ {
+ break;
+ }
+ }
+ pj_array_insert(pjsua_var.acc_ids, sizeof(acc_id),
+ pjsua_var.acc_cnt, i, &acc_id);
+ }
+
+ /* MWI */
+ if (acc->cfg.mwi_enabled != cfg->mwi_enabled) {
+ acc->cfg.mwi_enabled = cfg->mwi_enabled;
+ update_mwi = PJ_TRUE;
+ }
+ if (acc->cfg.mwi_expires != cfg->mwi_expires && cfg->mwi_expires > 0) {
+ acc->cfg.mwi_expires = cfg->mwi_expires;
+ update_mwi = PJ_TRUE;
+ }
+
+ /* PIDF tuple ID */
+ if (pj_strcmp(&acc->cfg.pidf_tuple_id, &cfg->pidf_tuple_id))
+ pj_strdup_with_null(acc->pool, &acc->cfg.pidf_tuple_id,
+ &cfg->pidf_tuple_id);
+
+ /* Publish */
+ acc->cfg.publish_opt = cfg->publish_opt;
+ acc->cfg.unpublish_max_wait_time_msec = cfg->unpublish_max_wait_time_msec;
+ if (acc->cfg.publish_enabled != cfg->publish_enabled) {
+ acc->cfg.publish_enabled = cfg->publish_enabled;
+ if (!acc->cfg.publish_enabled)
+ pjsua_pres_unpublish(acc, 0);
+ else
+ update_reg = PJ_TRUE;
+ }
+
+ /* Force contact URI */
+ if (pj_strcmp(&acc->cfg.force_contact, &cfg->force_contact)) {
+ pj_strdup_with_null(acc->pool, &acc->cfg.force_contact,
+ &cfg->force_contact);
+ update_reg = PJ_TRUE;
+ unreg_first = PJ_TRUE;
+ }
+
+ /* Contact param */
+ if (pj_strcmp(&acc->cfg.contact_params, &cfg->contact_params)) {
+ pj_strdup_with_null(acc->pool, &acc->cfg.contact_params,
+ &cfg->contact_params);
+ update_reg = PJ_TRUE;
+ }
+
+ /* Contact URI params */
+ if (pj_strcmp(&acc->cfg.contact_uri_params, &cfg->contact_uri_params)) {
+ pj_strdup_with_null(acc->pool, &acc->cfg.contact_uri_params,
+ &cfg->contact_uri_params);
+ update_reg = PJ_TRUE;
+ }
+
+ /* Reliable provisional response */
+ acc->cfg.require_100rel = cfg->require_100rel;
+
+ /* Session timer */
+ acc->cfg.use_timer = cfg->use_timer;
+ acc->cfg.timer_setting = cfg->timer_setting;
+
+ /* Transport */
+ if (acc->cfg.transport_id != cfg->transport_id) {
+ acc->cfg.transport_id = cfg->transport_id;
+ update_reg = PJ_TRUE;
+ }
+
+ /* Update keep-alive */
+ if (acc->cfg.ka_interval != cfg->ka_interval ||
+ pj_strcmp(&acc->cfg.ka_data, &cfg->ka_data))
+ {
+ pjsip_transport *ka_transport = acc->ka_transport;
+
+ if (acc->ka_timer.id) {
+ pjsip_endpt_cancel_timer(pjsua_var.endpt, &acc->ka_timer);
+ acc->ka_timer.id = PJ_FALSE;
+ }
+ if (acc->ka_transport) {
+ pjsip_transport_dec_ref(acc->ka_transport);
+ acc->ka_transport = NULL;
+ }
+
+ acc->cfg.ka_interval = cfg->ka_interval;
+
+ if (cfg->ka_interval) {
+ if (ka_transport) {
+ /* Keep-alive has been running so we can just restart it */
+ pj_time_val delay;
+
+ pjsip_transport_add_ref(ka_transport);
+ acc->ka_transport = ka_transport;
+
+ acc->ka_timer.cb = &keep_alive_timer_cb;
+ acc->ka_timer.user_data = (void*)acc;
+
+ delay.sec = acc->cfg.ka_interval;
+ delay.msec = 0;
+ status = pjsua_schedule_timer(&acc->ka_timer, &delay);
+ if (status == PJ_SUCCESS) {
+ acc->ka_timer.id = PJ_TRUE;
+ } else {
+ pjsip_transport_dec_ref(ka_transport);
+ acc->ka_transport = NULL;
+ pjsua_perror(THIS_FILE, "Error starting keep-alive timer",
+ status);
+ }
+
+ } else {
+ /* Keep-alive has not been running, we need to (re)register
+ * first.
+ */
+ update_reg = PJ_TRUE;
+ }
+ }
+ }
+
+ if (pj_strcmp(&acc->cfg.ka_data, &cfg->ka_data))
+ pj_strdup(acc->pool, &acc->cfg.ka_data, &cfg->ka_data);
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ acc->cfg.use_srtp = cfg->use_srtp;
+ acc->cfg.srtp_secure_signaling = cfg->srtp_secure_signaling;
+ acc->cfg.srtp_optional_dup_offer = cfg->srtp_optional_dup_offer;
+#endif
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0)
+ acc->cfg.use_stream_ka = cfg->use_stream_ka;
+#endif
+
+ /* Use of proxy */
+ if (acc->cfg.reg_use_proxy != cfg->reg_use_proxy) {
+ acc->cfg.reg_use_proxy = cfg->reg_use_proxy;
+ update_reg = PJ_TRUE;
+ }
+
+ /* Global outbound proxy */
+ if (global_route_crc != acc->global_route_crc) {
+ unsigned i, rcnt;
+
+ /* Remove the outbound proxies from the route set */
+ rcnt = pj_list_size(&acc->route_set);
+ for (i=0; i < rcnt - acc->cfg.proxy_cnt; ++i) {
+ pjsip_route_hdr *r = acc->route_set.next;
+ pj_list_erase(r);
+ }
+
+ /* Insert the outbound proxies to the beginning of route set */
+ pj_list_merge_first(&acc->route_set, &global_route);
+
+ /* Update global route CRC */
+ acc->global_route_crc = global_route_crc;
+
+ update_reg = PJ_TRUE;
+ }
+
+ /* Account proxy */
+ if (local_route_crc != acc->local_route_crc) {
+ unsigned i;
+
+ /* Remove the current account proxies from the route set */
+ for (i=0; i < acc->cfg.proxy_cnt; ++i) {
+ pjsip_route_hdr *r = acc->route_set.prev;
+ pj_list_erase(r);
+ }
+
+ /* Insert new proxy setting to the route set */
+ pj_list_merge_last(&acc->route_set, &local_route);
+
+ /* Update the proxy setting */
+ acc->cfg.proxy_cnt = cfg->proxy_cnt;
+ for (i = 0; i < cfg->proxy_cnt; ++i)
+ acc->cfg.proxy[i] = acc_proxy[i];
+
+ /* Update local route CRC */
+ acc->local_route_crc = local_route_crc;
+
+ update_reg = PJ_TRUE;
+ }
+
+ /* Credential info */
+ {
+ unsigned i;
+
+ /* Selective update credential info. */
+ for (i = 0; i < cfg->cred_count; ++i) {
+ unsigned j;
+ pjsip_cred_info ci;
+
+ /* Find if this credential is already listed */
+ for (j = i; j < acc->cfg.cred_count; ++j) {
+ if (pjsip_cred_info_cmp(&acc->cfg.cred_info[j],
+ &cfg->cred_info[i]) == 0)
+ {
+ /* Found, but different index/position, swap */
+ if (j != i) {
+ ci = acc->cfg.cred_info[i];
+ acc->cfg.cred_info[i] = acc->cfg.cred_info[j];
+ acc->cfg.cred_info[j] = ci;
+ }
+ break;
+ }
+ }
+
+ /* Not found, insert this */
+ if (j == acc->cfg.cred_count) {
+ /* If account credential is full, discard the last one. */
+ if (acc->cfg.cred_count == PJ_ARRAY_SIZE(acc->cfg.cred_info)) {
+ pj_array_erase(acc->cfg.cred_info, sizeof(pjsip_cred_info),
+ acc->cfg.cred_count, acc->cfg.cred_count-1);
+ acc->cfg.cred_count--;
+ }
+
+ /* Insert this */
+ pjsip_cred_info_dup(acc->pool, &ci, &cfg->cred_info[i]);
+ pj_array_insert(acc->cfg.cred_info, sizeof(pjsip_cred_info),
+ acc->cfg.cred_count, i, &ci);
+ }
+ }
+ acc->cfg.cred_count = cfg->cred_count;
+
+ /* Concatenate credentials from account config and global config */
+ acc->cred_cnt = 0;
+ for (i=0; i<acc->cfg.cred_count; ++i) {
+ acc->cred[acc->cred_cnt++] = acc->cfg.cred_info[i];
+ }
+ for (i=0; i<pjsua_var.ua_cfg.cred_count &&
+ acc->cred_cnt < PJ_ARRAY_SIZE(acc->cred); ++i)
+ {
+ acc->cred[acc->cred_cnt++] = pjsua_var.ua_cfg.cred_info[i];
+ }
+ }
+
+ /* Authentication preference */
+ acc->cfg.auth_pref.initial_auth = cfg->auth_pref.initial_auth;
+ if (pj_strcmp(&acc->cfg.auth_pref.algorithm, &cfg->auth_pref.algorithm))
+ pj_strdup_with_null(acc->pool, &acc->cfg.auth_pref.algorithm,
+ &cfg->auth_pref.algorithm);
+
+ /* Registration */
+ if (acc->cfg.reg_timeout != cfg->reg_timeout) {
+ acc->cfg.reg_timeout = cfg->reg_timeout;
+ if (acc->regc != NULL)
+ pjsip_regc_update_expires(acc->regc, acc->cfg.reg_timeout);
+
+ update_reg = PJ_TRUE;
+ }
+ acc->cfg.unreg_timeout = cfg->unreg_timeout;
+ acc->cfg.allow_contact_rewrite = cfg->allow_contact_rewrite;
+ acc->cfg.reg_retry_interval = cfg->reg_retry_interval;
+ acc->cfg.reg_first_retry_interval = cfg->reg_first_retry_interval;
+ acc->cfg.drop_calls_on_reg_fail = cfg->drop_calls_on_reg_fail;
+ acc->cfg.register_on_acc_add = cfg->register_on_acc_add;
+ if (acc->cfg.reg_delay_before_refresh != cfg->reg_delay_before_refresh) {
+ acc->cfg.reg_delay_before_refresh = cfg->reg_delay_before_refresh;
+ if (acc->regc != NULL)
+ pjsip_regc_set_delay_before_refresh(acc->regc,
+ cfg->reg_delay_before_refresh);
+ }
+
+ /* Allow via rewrite */
+ if (acc->cfg.allow_via_rewrite != cfg->allow_via_rewrite) {
+ if (acc->regc != NULL) {
+ if (cfg->allow_via_rewrite) {
+ pjsip_regc_set_via_sent_by(acc->regc, &acc->via_addr,
+ acc->via_tp);
+ } else
+ pjsip_regc_set_via_sent_by(acc->regc, NULL, NULL);
+ }
+ if (acc->publish_sess != NULL) {
+ if (cfg->allow_via_rewrite) {
+ pjsip_publishc_set_via_sent_by(acc->publish_sess,
+ &acc->via_addr, acc->via_tp);
+ } else
+ pjsip_publishc_set_via_sent_by(acc->publish_sess, NULL, NULL);
+ }
+ acc->cfg.allow_via_rewrite = cfg->allow_via_rewrite;
+ }
+
+ /* Normalize registration timeout and refresh delay */
+ if (acc->cfg.reg_uri.slen ) {
+ if (acc->cfg.reg_timeout == 0) {
+ acc->cfg.reg_timeout = PJSUA_REG_INTERVAL;
+ }
+ if (acc->cfg.reg_delay_before_refresh == 0) {
+ acc->cfg.reg_delay_before_refresh =
+ PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH;
+ }
+ }
+
+ /* Registrar URI */
+ if (pj_strcmp(&acc->cfg.reg_uri, &cfg->reg_uri)) {
+ if (cfg->reg_uri.slen) {
+ pj_strdup_with_null(acc->pool, &acc->cfg.reg_uri, &cfg->reg_uri);
+ if (reg_sip_uri)
+ acc->srv_port = reg_sip_uri->port;
+ } else {
+ /* Unregister if registration was set */
+ if (acc->cfg.reg_uri.slen)
+ pjsua_acc_set_registration(acc->index, PJ_FALSE);
+ pj_bzero(&acc->cfg.reg_uri, sizeof(acc->cfg.reg_uri));
+ }
+ update_reg = PJ_TRUE;
+ unreg_first = PJ_TRUE;
+ }
+
+ /* SIP outbound setting */
+ if (acc->cfg.use_rfc5626 != cfg->use_rfc5626 ||
+ pj_strcmp(&acc->cfg.rfc5626_instance_id, &cfg->rfc5626_instance_id) ||
+ pj_strcmp(&acc->cfg.rfc5626_reg_id, &cfg->rfc5626_reg_id))
+ {
+ update_reg = PJ_TRUE;
+ }
+
+ /* Unregister first */
+ if (unreg_first) {
+ pjsua_acc_set_registration(acc->index, PJ_FALSE);
+ if (acc->regc != NULL) {
+ pjsip_regc_destroy(acc->regc);
+ acc->regc = NULL;
+ acc->contact.slen = 0;
+ }
+ }
+
+ /* Update registration */
+ if (update_reg) {
+ /* If accounts has registration enabled, start registration */
+ if (acc->cfg.reg_uri.slen)
+ pjsua_acc_set_registration(acc->index, PJ_TRUE);
+ }
+
+ /* Update MWI subscription */
+ if (update_mwi) {
+ pjsua_start_mwi(acc_id, PJ_TRUE);
+ }
+
+ /* Video settings */
+ acc->cfg.vid_in_auto_show = cfg->vid_in_auto_show;
+ acc->cfg.vid_out_auto_transmit = cfg->vid_out_auto_transmit;
+ acc->cfg.vid_wnd_flags = cfg->vid_wnd_flags;
+ acc->cfg.vid_cap_dev = cfg->vid_cap_dev;
+ acc->cfg.vid_rend_dev = cfg->vid_rend_dev;
+
+ /* Call hold type */
+ acc->cfg.call_hold_type = cfg->call_hold_type;
+
+on_return:
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Modify account's presence status to be advertised to remote/presence
+ * subscribers.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_set_online_status( pjsua_acc_id acc_id,
+ pj_bool_t is_online)
+{
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+ PJ_LOG(4,(THIS_FILE, "Acc %d: setting online status to %d..",
+ acc_id, is_online));
+ pj_log_push_indent();
+
+ pjsua_var.acc[acc_id].online_status = is_online;
+ pj_bzero(&pjsua_var.acc[acc_id].rpid, sizeof(pjrpid_element));
+ pjsua_pres_update_acc(acc_id, PJ_FALSE);
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set online status with extended information
+ */
+PJ_DEF(pj_status_t) pjsua_acc_set_online_status2( pjsua_acc_id acc_id,
+ pj_bool_t is_online,
+ const pjrpid_element *pr)
+{
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+ PJ_LOG(4,(THIS_FILE, "Acc %d: setting online status to %d..",
+ acc_id, is_online));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+ pjsua_var.acc[acc_id].online_status = is_online;
+ pjrpid_element_dup(pjsua_var.acc[acc_id].pool, &pjsua_var.acc[acc_id].rpid, pr);
+ PJSUA_UNLOCK();
+
+ pjsua_pres_update_acc(acc_id, PJ_TRUE);
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+}
+
+/* Create reg_contact, mainly for SIP outbound */
+static void update_regc_contact(pjsua_acc *acc)
+{
+ pjsua_acc_config *acc_cfg = &acc->cfg;
+ pj_bool_t need_outbound = PJ_FALSE;
+ const pj_str_t tcp_param = pj_str(";transport=tcp");
+ const pj_str_t tls_param = pj_str(";transport=tls");
+
+ if (!acc_cfg->use_rfc5626)
+ goto done;
+
+ /* Check if outbound has been requested and rejected */
+ if (acc->rfc5626_status == OUTBOUND_NA)
+ goto done;
+
+ if (pj_stristr(&acc->contact, &tcp_param)==NULL &&
+ pj_stristr(&acc->contact, &tls_param)==NULL)
+ {
+ /* Currently we can only do SIP outbound for TCP
+ * and TLS.
+ */
+ goto done;
+ }
+
+ /* looks like we can use outbound */
+ need_outbound = PJ_TRUE;
+
+done:
+ if (!need_outbound) {
+ /* Outbound is not needed/wanted for the account. acc->reg_contact
+ * is set to the same as acc->contact.
+ */
+ acc->reg_contact = acc->contact;
+ acc->rfc5626_status = OUTBOUND_NA;
+ } else {
+ /* Need to use outbound, append the contact with +sip.instance and
+ * reg-id parameters.
+ */
+ unsigned len;
+ pj_str_t reg_contact;
+
+ acc->rfc5626_status = OUTBOUND_WANTED;
+ len = acc->contact.slen + acc->rfc5626_instprm.slen +
+ acc->rfc5626_regprm.slen;
+ reg_contact.ptr = (char*) pj_pool_alloc(acc->pool, len);
+
+ pj_strcpy(&reg_contact, &acc->contact);
+ pj_strcat(&reg_contact, &acc->rfc5626_regprm);
+ pj_strcat(&reg_contact, &acc->rfc5626_instprm);
+
+ acc->reg_contact = reg_contact;
+
+ PJ_LOG(4,(THIS_FILE,
+ "Contact for acc %d updated for SIP outbound: %.*s",
+ acc->index,
+ (int)acc->reg_contact.slen,
+ acc->reg_contact.ptr));
+ }
+}
+
+/* Check if IP is private IP address */
+static pj_bool_t is_private_ip(const pj_str_t *addr)
+{
+ const pj_str_t private_net[] =
+ {
+ { "10.", 3 },
+ { "127.", 4 },
+ { "172.16.", 7 },
+ { "192.168.", 8 }
+ };
+ unsigned i;
+
+ for (i=0; i<PJ_ARRAY_SIZE(private_net); ++i) {
+ if (pj_strncmp(addr, &private_net[i], private_net[i].slen)==0)
+ return PJ_TRUE;
+ }
+
+ return PJ_FALSE;
+}
+
+/* Update NAT address from the REGISTER response */
+static pj_bool_t acc_check_nat_addr(pjsua_acc *acc,
+ struct pjsip_regc_cbparam *param)
+{
+ pjsip_transport *tp;
+ const pj_str_t *via_addr;
+ pj_pool_t *pool;
+ int rport;
+ pjsip_sip_uri *uri;
+ pjsip_via_hdr *via;
+ pj_sockaddr contact_addr;
+ pj_sockaddr recv_addr;
+ pj_status_t status;
+ pj_bool_t matched;
+ pj_str_t srv_ip;
+ pjsip_contact_hdr *contact_hdr;
+ const pj_str_t STR_CONTACT = { "Contact", 7 };
+
+ tp = param->rdata->tp_info.transport;
+
+ /* Get the received and rport info */
+ via = param->rdata->msg_info.via;
+ if (via->rport_param < 1) {
+ /* Remote doesn't support rport */
+ rport = via->sent_by.port;
+ if (rport==0) {
+ pjsip_transport_type_e tp_type;
+ tp_type = (pjsip_transport_type_e) tp->key.type;
+ rport = pjsip_transport_get_default_port_for_type(tp_type);
+ }
+ } else
+ rport = via->rport_param;
+
+ if (via->recvd_param.slen != 0)
+ via_addr = &via->recvd_param;
+ else
+ via_addr = &via->sent_by.host;
+
+ /* If allow_via_rewrite is enabled, we save the Via "received" address
+ * from the response.
+ */
+ if (acc->cfg.allow_via_rewrite &&
+ (acc->via_addr.host.slen == 0 || acc->via_tp != tp))
+ {
+ if (pj_strcmp(&acc->via_addr.host, via_addr))
+ pj_strdup(acc->pool, &acc->via_addr.host, via_addr);
+ acc->via_addr.port = rport;
+ acc->via_tp = tp;
+ pjsip_regc_set_via_sent_by(acc->regc, &acc->via_addr, acc->via_tp);
+ if (acc->publish_sess != NULL) {
+ pjsip_publishc_set_via_sent_by(acc->publish_sess,
+ &acc->via_addr, acc->via_tp);
+ }
+ }
+
+ /* Only update if account is configured to auto-update */
+ if (acc->cfg.allow_contact_rewrite == PJ_FALSE)
+ return PJ_FALSE;
+
+ /* If SIP outbound is active, no need to update */
+ if (acc->rfc5626_status == OUTBOUND_ACTIVE) {
+ PJ_LOG(4,(THIS_FILE, "Acc %d has SIP outbound active, no need to "
+ "update registration Contact", acc->index));
+ return PJ_FALSE;
+ }
+
+#if 0
+ // Always update
+ // See http://lists.pjsip.org/pipermail/pjsip_lists.pjsip.org/2008-March/002178.html
+
+ /* For UDP, only update if STUN is enabled (for now).
+ * For TCP/TLS, always check.
+ */
+ if ((tp->key.type == PJSIP_TRANSPORT_UDP &&
+ (pjsua_var.ua_cfg.stun_domain.slen != 0 ||
+ (pjsua_var.ua_cfg.stun_host.slen != 0)) ||
+ (tp->key.type == PJSIP_TRANSPORT_TCP) ||
+ (tp->key.type == PJSIP_TRANSPORT_TLS))
+ {
+ /* Yes we will check */
+ } else {
+ return PJ_FALSE;
+ }
+#endif
+
+ /* Compare received and rport with the URI in our registration */
+ pool = pjsua_pool_create("tmp", 512, 512);
+ contact_hdr = (pjsip_contact_hdr*)
+ pjsip_parse_hdr(pool, &STR_CONTACT, acc->contact.ptr,
+ acc->contact.slen, NULL);
+ pj_assert(contact_hdr != NULL);
+ uri = (pjsip_sip_uri*) contact_hdr->uri;
+ pj_assert(uri != NULL);
+ uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri);
+
+ if (uri->port == 0) {
+ pjsip_transport_type_e tp_type;
+ tp_type = (pjsip_transport_type_e) tp->key.type;
+ uri->port = pjsip_transport_get_default_port_for_type(tp_type);
+ }
+
+ /* Convert IP address strings into sockaddr for comparison.
+ * (http://trac.pjsip.org/repos/ticket/863)
+ */
+ status = pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &uri->host,
+ &contact_addr);
+ if (status == PJ_SUCCESS)
+ status = pj_sockaddr_parse(pj_AF_UNSPEC(), 0, via_addr,
+ &recv_addr);
+ if (status == PJ_SUCCESS) {
+ /* Compare the addresses as sockaddr according to the ticket above */
+ matched = (uri->port == rport &&
+ pj_sockaddr_cmp(&contact_addr, &recv_addr)==0);
+ } else {
+ /* Compare the addresses as string, as before */
+ matched = (uri->port == rport &&
+ pj_stricmp(&uri->host, via_addr)==0);
+ }
+
+ if (matched) {
+ /* Address doesn't change */
+ pj_pool_release(pool);
+ return PJ_FALSE;
+ }
+
+ /* Get server IP */
+ srv_ip = pj_str(param->rdata->pkt_info.src_name);
+
+ /* At this point we've detected that the address as seen by registrar.
+ * has changed.
+ */
+
+ /* Do not switch if both Contact and server's IP address are
+ * public but response contains private IP. A NAT in the middle
+ * might have messed up with the SIP packets. See:
+ * http://trac.pjsip.org/repos/ticket/643
+ *
+ * This exception can be disabled by setting allow_contact_rewrite
+ * to 2. In this case, the switch will always be done whenever there
+ * is difference in the IP address in the response.
+ */
+ if (acc->cfg.allow_contact_rewrite != 2 && !is_private_ip(&uri->host) &&
+ !is_private_ip(&srv_ip) && is_private_ip(via_addr))
+ {
+ /* Don't switch */
+ pj_pool_release(pool);
+ return PJ_FALSE;
+ }
+
+ /* Also don't switch if only the port number part is different, and
+ * the Via received address is private.
+ * See http://trac.pjsip.org/repos/ticket/864
+ */
+ if (acc->cfg.allow_contact_rewrite != 2 &&
+ pj_sockaddr_cmp(&contact_addr, &recv_addr)==0 &&
+ is_private_ip(via_addr))
+ {
+ /* Don't switch */
+ pj_pool_release(pool);
+ return PJ_FALSE;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "IP address change detected for account %d "
+ "(%.*s:%d --> %.*s:%d). Updating registration "
+ "(using method %d)",
+ acc->index,
+ (int)uri->host.slen,
+ uri->host.ptr,
+ uri->port,
+ (int)via_addr->slen,
+ via_addr->ptr,
+ rport,
+ acc->cfg.contact_rewrite_method));
+
+ pj_assert(acc->cfg.contact_rewrite_method == 1 ||
+ acc->cfg.contact_rewrite_method == 2);
+
+ if (acc->cfg.contact_rewrite_method == 1) {
+ /* Unregister current contact */
+ pjsua_acc_set_registration(acc->index, PJ_FALSE);
+ if (acc->regc != NULL) {
+ pjsip_regc_destroy(acc->regc);
+ acc->regc = NULL;
+ acc->contact.slen = 0;
+ }
+ }
+
+ /*
+ * Build new Contact header
+ */
+ {
+ const char *ob = ";ob";
+ char *tmp;
+ const char *beginquote, *endquote;
+ int len;
+
+ /* Enclose IPv6 address in square brackets */
+ if (tp->key.type & PJSIP_TRANSPORT_IPV6) {
+ beginquote = "[";
+ endquote = "]";
+ } else {
+ beginquote = endquote = "";
+ }
+
+ tmp = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+ len = pj_ansi_snprintf(tmp, PJSIP_MAX_URL_SIZE,
+ "<sip:%.*s%s%s%.*s%s:%d;transport=%s%.*s%s>%.*s",
+ (int)acc->user_part.slen,
+ acc->user_part.ptr,
+ (acc->user_part.slen? "@" : ""),
+ beginquote,
+ (int)via_addr->slen,
+ via_addr->ptr,
+ endquote,
+ rport,
+ tp->type_name,
+ (int)acc->cfg.contact_uri_params.slen,
+ acc->cfg.contact_uri_params.ptr,
+ (acc->cfg.use_rfc5626? ob: ""),
+ (int)acc->cfg.contact_params.slen,
+ acc->cfg.contact_params.ptr);
+ if (len < 1) {
+ PJ_LOG(1,(THIS_FILE, "URI too long"));
+ pj_pool_release(pool);
+ return PJ_FALSE;
+ }
+ pj_strdup2_with_null(acc->pool, &acc->contact, tmp);
+
+ update_regc_contact(acc);
+
+ /* Always update, by http://trac.pjsip.org/repos/ticket/864. */
+ /* Since the Via address will now be overwritten to the correct
+ * address by https://trac.pjsip.org/repos/ticket/1537, we do
+ * not need to update the transport address.
+ */
+ /*
+ pj_strdup_with_null(tp->pool, &tp->local_name.host, via_addr);
+ tp->local_name.port = rport;
+ */
+
+ }
+
+ if (acc->cfg.contact_rewrite_method == 2 && acc->regc != NULL) {
+ pjsip_regc_update_contact(acc->regc, 1, &acc->reg_contact);
+ }
+
+ /* Perform new registration */
+ pjsua_acc_set_registration(acc->index, PJ_TRUE);
+
+ pj_pool_release(pool);
+
+ return PJ_TRUE;
+}
+
+/* Check and update Service-Route header */
+void update_service_route(pjsua_acc *acc, pjsip_rx_data *rdata)
+{
+ pjsip_generic_string_hdr *hsr = NULL;
+ pjsip_route_hdr *hr, *h;
+ const pj_str_t HNAME = { "Service-Route", 13 };
+ const pj_str_t HROUTE = { "Route", 5 };
+ pjsip_uri *uri[PJSUA_ACC_MAX_PROXIES];
+ unsigned i, uri_cnt = 0, rcnt;
+
+ /* Find and parse Service-Route headers */
+ for (;;) {
+ char saved;
+ int parsed_len;
+
+ /* Find Service-Route header */
+ hsr = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &HNAME, hsr);
+ if (!hsr)
+ break;
+
+ /* Parse as Route header since the syntax is similar. This may
+ * return more than one headers.
+ */
+ saved = hsr->hvalue.ptr[hsr->hvalue.slen];
+ hsr->hvalue.ptr[hsr->hvalue.slen] = '\0';
+ hr = (pjsip_route_hdr*)
+ pjsip_parse_hdr(rdata->tp_info.pool, &HROUTE, hsr->hvalue.ptr,
+ hsr->hvalue.slen, &parsed_len);
+ hsr->hvalue.ptr[hsr->hvalue.slen] = saved;
+
+ if (hr == NULL) {
+ /* Error */
+ PJ_LOG(1,(THIS_FILE, "Error parsing Service-Route header"));
+ return;
+ }
+
+ /* Save each URI in the result */
+ h = hr;
+ do {
+ if (!PJSIP_URI_SCHEME_IS_SIP(h->name_addr.uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(h->name_addr.uri))
+ {
+ PJ_LOG(1,(THIS_FILE,"Error: non SIP URI in Service-Route: %.*s",
+ (int)hsr->hvalue.slen, hsr->hvalue.ptr));
+ return;
+ }
+
+ uri[uri_cnt++] = h->name_addr.uri;
+ h = h->next;
+ } while (h != hr && uri_cnt != PJ_ARRAY_SIZE(uri));
+
+ if (h != hr) {
+ PJ_LOG(1,(THIS_FILE, "Error: too many Service-Route headers"));
+ return;
+ }
+
+ /* Prepare to find next Service-Route header */
+ hsr = hsr->next;
+ if ((void*)hsr == (void*)&rdata->msg_info.msg->hdr)
+ break;
+ }
+
+ if (uri_cnt == 0)
+ return;
+
+ /*
+ * Update account's route set
+ */
+
+ /* First remove all routes which are not the outbound proxies */
+ rcnt = pj_list_size(&acc->route_set);
+ if (rcnt != pjsua_var.ua_cfg.outbound_proxy_cnt + acc->cfg.proxy_cnt) {
+ for (i=pjsua_var.ua_cfg.outbound_proxy_cnt + acc->cfg.proxy_cnt,
+ hr=acc->route_set.prev;
+ i<rcnt;
+ ++i)
+ {
+ pjsip_route_hdr *prev = hr->prev;
+ pj_list_erase(hr);
+ hr = prev;
+ }
+ }
+
+ /* Then append the Service-Route URIs */
+ for (i=0; i<uri_cnt; ++i) {
+ hr = pjsip_route_hdr_create(acc->pool);
+ hr->name_addr.uri = (pjsip_uri*)pjsip_uri_clone(acc->pool, uri[i]);
+ pj_list_push_back(&acc->route_set, hr);
+ }
+
+ /* Done */
+
+ PJ_LOG(4,(THIS_FILE, "Service-Route updated for acc %d with %d URI(s)",
+ acc->index, uri_cnt));
+}
+
+
+/* Keep alive timer callback */
+static void keep_alive_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te)
+{
+ pjsua_acc *acc;
+ pjsip_tpselector tp_sel;
+ pj_time_val delay;
+ char addrtxt[PJ_INET6_ADDRSTRLEN];
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(th);
+
+ PJSUA_LOCK();
+
+ te->id = PJ_FALSE;
+
+ acc = (pjsua_acc*) te->user_data;
+
+ /* Select the transport to send the packet */
+ pj_bzero(&tp_sel, sizeof(tp_sel));
+ tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
+ tp_sel.u.transport = acc->ka_transport;
+
+ PJ_LOG(5,(THIS_FILE,
+ "Sending %d bytes keep-alive packet for acc %d to %s",
+ acc->cfg.ka_data.slen, acc->index,
+ pj_sockaddr_print(&acc->ka_target, addrtxt, sizeof(addrtxt),3)));
+
+ /* Send raw packet */
+ status = pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(pjsua_var.endpt),
+ PJSIP_TRANSPORT_UDP, &tp_sel,
+ NULL, acc->cfg.ka_data.ptr,
+ acc->cfg.ka_data.slen,
+ &acc->ka_target, acc->ka_target_len,
+ NULL, NULL);
+
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ pjsua_perror(THIS_FILE, "Error sending keep-alive packet", status);
+ }
+
+ /* Check just in case keep-alive has been disabled. This shouldn't happen
+ * though as when ka_interval is changed this timer should have been
+ * cancelled.
+ */
+ if (acc->cfg.ka_interval == 0)
+ goto on_return;
+
+ /* Reschedule next timer */
+ delay.sec = acc->cfg.ka_interval;
+ delay.msec = 0;
+ status = pjsip_endpt_schedule_timer(pjsua_var.endpt, te, &delay);
+ if (status == PJ_SUCCESS) {
+ te->id = PJ_TRUE;
+ } else {
+ pjsua_perror(THIS_FILE, "Error starting keep-alive timer", status);
+ }
+
+on_return:
+ PJSUA_UNLOCK();
+}
+
+
+/* Update keep-alive for the account */
+static void update_keep_alive(pjsua_acc *acc, pj_bool_t start,
+ struct pjsip_regc_cbparam *param)
+{
+ /* In all cases, stop keep-alive timer if it's running. */
+ if (acc->ka_timer.id) {
+ pjsip_endpt_cancel_timer(pjsua_var.endpt, &acc->ka_timer);
+ acc->ka_timer.id = PJ_FALSE;
+
+ pjsip_transport_dec_ref(acc->ka_transport);
+ acc->ka_transport = NULL;
+ }
+
+ if (start) {
+ pj_time_val delay;
+ pj_status_t status;
+
+ /* Only do keep-alive if:
+ * - ka_interval is not zero in the account, and
+ * - transport is UDP.
+ *
+ * Previously we only enabled keep-alive when STUN is enabled, since
+ * we thought that keep-alive is only needed in Internet situation.
+ * But it has been discovered that Windows Firewall on WinXP also
+ * needs to be kept-alive, otherwise incoming packets will be dropped.
+ * So because of this, now keep-alive is always enabled for UDP,
+ * regardless of whether STUN is enabled or not.
+ *
+ * Note that this applies only for UDP. For TCP/TLS, the keep-alive
+ * is done by the transport layer.
+ */
+ if (/*pjsua_var.stun_srv.ipv4.sin_family == 0 ||*/
+ acc->cfg.ka_interval == 0 ||
+ param->rdata->tp_info.transport->key.type != PJSIP_TRANSPORT_UDP)
+ {
+ /* Keep alive is not necessary */
+ return;
+ }
+
+ /* Save transport and destination address. */
+ acc->ka_transport = param->rdata->tp_info.transport;
+ pjsip_transport_add_ref(acc->ka_transport);
+ pj_memcpy(&acc->ka_target, &param->rdata->pkt_info.src_addr,
+ param->rdata->pkt_info.src_addr_len);
+ acc->ka_target_len = param->rdata->pkt_info.src_addr_len;
+
+ /* Setup and start the timer */
+ acc->ka_timer.cb = &keep_alive_timer_cb;
+ acc->ka_timer.user_data = (void*)acc;
+
+ delay.sec = acc->cfg.ka_interval;
+ delay.msec = 0;
+ status = pjsip_endpt_schedule_timer(pjsua_var.endpt, &acc->ka_timer,
+ &delay);
+ if (status == PJ_SUCCESS) {
+ acc->ka_timer.id = PJ_TRUE;
+ PJ_LOG(4,(THIS_FILE, "Keep-alive timer started for acc %d, "
+ "destination:%s:%d, interval:%ds",
+ acc->index,
+ param->rdata->pkt_info.src_name,
+ param->rdata->pkt_info.src_port,
+ acc->cfg.ka_interval));
+ } else {
+ acc->ka_timer.id = PJ_FALSE;
+ pjsip_transport_dec_ref(acc->ka_transport);
+ acc->ka_transport = NULL;
+ pjsua_perror(THIS_FILE, "Error starting keep-alive timer", status);
+ }
+ }
+}
+
+
+/* Update the status of SIP outbound registration request */
+static void update_rfc5626_status(pjsua_acc *acc, pjsip_rx_data *rdata)
+{
+ pjsip_require_hdr *hreq;
+ const pj_str_t STR_OUTBOUND = {"outbound", 8};
+ unsigned i;
+
+ if (acc->rfc5626_status == OUTBOUND_UNKNOWN) {
+ goto on_return;
+ }
+
+ hreq = rdata->msg_info.require;
+ if (!hreq) {
+ acc->rfc5626_status = OUTBOUND_NA;
+ goto on_return;
+ }
+
+ for (i=0; i<hreq->count; ++i) {
+ if (pj_stricmp(&hreq->values[i], &STR_OUTBOUND)==0) {
+ acc->rfc5626_status = OUTBOUND_ACTIVE;
+ goto on_return;
+ }
+ }
+
+ /* Server does not support outbound */
+ acc->rfc5626_status = OUTBOUND_NA;
+
+on_return:
+ if (acc->rfc5626_status != OUTBOUND_ACTIVE) {
+ acc->reg_contact = acc->contact;
+ }
+ PJ_LOG(4,(THIS_FILE, "SIP outbound status for acc %d is %s",
+ acc->index, (acc->rfc5626_status==OUTBOUND_ACTIVE?
+ "active": "not active")));
+}
+
+/*
+ * This callback is called by pjsip_regc when outgoing register
+ * request has completed.
+ */
+static void regc_cb(struct pjsip_regc_cbparam *param)
+{
+
+ pjsua_acc *acc = (pjsua_acc*) param->token;
+
+ PJSUA_LOCK();
+
+ if (param->regc != acc->regc) {
+ PJSUA_UNLOCK();
+ return;
+ }
+
+ pj_log_push_indent();
+
+ /*
+ * Print registration status.
+ */
+ if (param->status!=PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "SIP registration error",
+ param->status);
+ pjsip_regc_destroy(acc->regc);
+ acc->regc = NULL;
+ acc->contact.slen = 0;
+
+ /* Stop keep-alive timer if any. */
+ update_keep_alive(acc, PJ_FALSE, NULL);
+
+ } else if (param->code < 0 || param->code >= 300) {
+ PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%.*s)",
+ param->code,
+ (int)param->reason.slen, param->reason.ptr));
+ pjsip_regc_destroy(acc->regc);
+ acc->regc = NULL;
+ acc->contact.slen = 0;
+
+ /* Stop keep-alive timer if any. */
+ update_keep_alive(acc, PJ_FALSE, NULL);
+
+ } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) {
+
+ /* Update auto registration flag */
+ acc->auto_rereg.active = PJ_FALSE;
+ acc->auto_rereg.attempt_cnt = 0;
+
+ if (param->expiration < 1) {
+ pjsip_regc_destroy(acc->regc);
+ acc->regc = NULL;
+ acc->contact.slen = 0;
+
+ /* Stop keep-alive timer if any. */
+ update_keep_alive(acc, PJ_FALSE, NULL);
+
+ PJ_LOG(3,(THIS_FILE, "%s: unregistration success",
+ pjsua_var.acc[acc->index].cfg.id.ptr));
+ } else {
+ /* Check and update SIP outbound status first, since the result
+ * will determine if we should update re-registration
+ */
+ update_rfc5626_status(acc, param->rdata);
+
+ /* Check NAT bound address */
+ if (acc_check_nat_addr(acc, param)) {
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return;
+ }
+
+ /* Check and update Service-Route header */
+ update_service_route(acc, param->rdata);
+
+ PJ_LOG(3, (THIS_FILE,
+ "%s: registration success, status=%d (%.*s), "
+ "will re-register in %d seconds",
+ pjsua_var.acc[acc->index].cfg.id.ptr,
+ param->code,
+ (int)param->reason.slen, param->reason.ptr,
+ param->expiration));
+
+ /* Start keep-alive timer if necessary. */
+ update_keep_alive(acc, PJ_TRUE, param);
+
+ /* Send initial PUBLISH if it is enabled */
+ if (acc->cfg.publish_enabled && acc->publish_sess==NULL)
+ pjsua_pres_init_publish_acc(acc->index);
+
+ /* Subscribe to MWI, if it's enabled */
+ if (acc->cfg.mwi_enabled)
+ pjsua_start_mwi(acc->index, PJ_FALSE);
+ }
+
+ } else {
+ PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code));
+ }
+
+ acc->reg_last_err = param->status;
+ acc->reg_last_code = param->code;
+
+ /* Check if we need to auto retry registration. Basically, registration
+ * failure codes triggering auto-retry are those of temporal failures
+ * considered to be recoverable in relatively short term.
+ */
+ if (acc->cfg.reg_retry_interval &&
+ (param->code == PJSIP_SC_REQUEST_TIMEOUT ||
+ param->code == PJSIP_SC_INTERNAL_SERVER_ERROR ||
+ param->code == PJSIP_SC_BAD_GATEWAY ||
+ param->code == PJSIP_SC_SERVICE_UNAVAILABLE ||
+ param->code == PJSIP_SC_SERVER_TIMEOUT ||
+ PJSIP_IS_STATUS_IN_CLASS(param->code, 600))) /* Global failure */
+ {
+ schedule_reregistration(acc);
+ }
+
+ /* Call the registration status callback */
+
+ if (pjsua_var.ua_cfg.cb.on_reg_state) {
+ (*pjsua_var.ua_cfg.cb.on_reg_state)(acc->index);
+ }
+
+ if (pjsua_var.ua_cfg.cb.on_reg_state2) {
+ pjsua_reg_info reg_info;
+
+ reg_info.cbparam = param;
+ (*pjsua_var.ua_cfg.cb.on_reg_state2)(acc->index, &reg_info);
+ }
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Initialize client registration.
+ */
+static pj_status_t pjsua_regc_init(int acc_id)
+{
+ pjsua_acc *acc;
+ pj_pool_t *pool;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL);
+ acc = &pjsua_var.acc[acc_id];
+
+ if (acc->cfg.reg_uri.slen == 0) {
+ PJ_LOG(3,(THIS_FILE, "Registrar URI is not specified"));
+ return PJ_SUCCESS;
+ }
+
+ /* Destroy existing session, if any */
+ if (acc->regc) {
+ pjsip_regc_destroy(acc->regc);
+ acc->regc = NULL;
+ acc->contact.slen = 0;
+ }
+
+ /* initialize SIP registration if registrar is configured */
+
+ status = pjsip_regc_create( pjsua_var.endpt,
+ acc, &regc_cb, &acc->regc);
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create client registration",
+ status);
+ return status;
+ }
+
+ pool = pjsua_pool_create("tmpregc", 512, 512);
+
+ if (acc->contact.slen == 0) {
+ pj_str_t tmp_contact;
+
+ status = pjsua_acc_create_uac_contact( pool, &tmp_contact,
+ acc_id, &acc->cfg.reg_uri);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to generate suitable Contact header"
+ " for registration",
+ status);
+ pjsip_regc_destroy(acc->regc);
+ pj_pool_release(pool);
+ acc->regc = NULL;
+ return status;
+ }
+
+ pj_strdup_with_null(acc->pool, &acc->contact, &tmp_contact);
+ update_regc_contact(acc);
+ }
+
+ status = pjsip_regc_init( acc->regc,
+ &acc->cfg.reg_uri,
+ &acc->cfg.id,
+ &acc->cfg.id,
+ 1, &acc->reg_contact,
+ acc->cfg.reg_timeout);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Client registration initialization error",
+ status);
+ pjsip_regc_destroy(acc->regc);
+ pj_pool_release(pool);
+ acc->regc = NULL;
+ acc->contact.slen = 0;
+ return status;
+ }
+
+ /* If account is locked to specific transport, then set transport to
+ * the client registration.
+ */
+ if (pjsua_var.acc[acc_id].cfg.transport_id != PJSUA_INVALID_ID) {
+ pjsip_tpselector tp_sel;
+
+ pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel);
+ pjsip_regc_set_transport(acc->regc, &tp_sel);
+ }
+
+
+ /* Set credentials
+ */
+ if (acc->cred_cnt) {
+ pjsip_regc_set_credentials( acc->regc, acc->cred_cnt, acc->cred);
+ }
+
+ /* Set delay before registration refresh */
+ pjsip_regc_set_delay_before_refresh(acc->regc,
+ acc->cfg.reg_delay_before_refresh);
+
+ /* Set authentication preference */
+ pjsip_regc_set_prefs(acc->regc, &acc->cfg.auth_pref);
+
+ /* Set route-set
+ */
+ if (acc->cfg.reg_use_proxy) {
+ pjsip_route_hdr route_set;
+ const pjsip_route_hdr *r;
+
+ pj_list_init(&route_set);
+
+ if (acc->cfg.reg_use_proxy & PJSUA_REG_USE_OUTBOUND_PROXY) {
+ r = pjsua_var.outbound_proxy.next;
+ while (r != &pjsua_var.outbound_proxy) {
+ pj_list_push_back(&route_set, pjsip_hdr_shallow_clone(pool, r));
+ r = r->next;
+ }
+ }
+
+ if (acc->cfg.reg_use_proxy & PJSUA_REG_USE_ACC_PROXY &&
+ acc->cfg.proxy_cnt)
+ {
+ int cnt = acc->cfg.proxy_cnt;
+ pjsip_route_hdr *pos = route_set.prev;
+ int i;
+
+ r = acc->route_set.prev;
+ for (i=0; i<cnt; ++i) {
+ pj_list_push_front(pos, pjsip_hdr_shallow_clone(pool, r));
+ r = r->prev;
+ }
+ }
+
+ if (!pj_list_empty(&route_set))
+ pjsip_regc_set_route_set( acc->regc, &route_set );
+ }
+
+ /* Add custom request headers specified in the account config */
+ pjsip_regc_add_headers(acc->regc, &acc->cfg.reg_hdr_list);
+
+ /* Add other request headers. */
+ if (pjsua_var.ua_cfg.user_agent.slen) {
+ pjsip_hdr hdr_list;
+ const pj_str_t STR_USER_AGENT = { "User-Agent", 10 };
+ pjsip_generic_string_hdr *h;
+
+ pj_list_init(&hdr_list);
+
+ h = pjsip_generic_string_hdr_create(pool, &STR_USER_AGENT,
+ &pjsua_var.ua_cfg.user_agent);
+ pj_list_push_back(&hdr_list, (pjsip_hdr*)h);
+
+ pjsip_regc_add_headers(acc->regc, &hdr_list);
+ }
+
+ /* If SIP outbound is used, add "Supported: outbound, path header" */
+ if (acc->rfc5626_status == OUTBOUND_WANTED) {
+ pjsip_hdr hdr_list;
+ pjsip_supported_hdr *hsup;
+
+ pj_list_init(&hdr_list);
+ hsup = pjsip_supported_hdr_create(pool);
+ pj_list_push_back(&hdr_list, hsup);
+
+ hsup->count = 2;
+ hsup->values[0] = pj_str("outbound");
+ hsup->values[1] = pj_str("path");
+
+ pjsip_regc_add_headers(acc->regc, &hdr_list);
+ }
+
+ pj_pool_release(pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Update registration or perform unregistration.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id,
+ pj_bool_t renew)
+{
+ pj_status_t status = 0;
+ pjsip_tx_data *tdata = 0;
+
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+ PJ_LOG(4,(THIS_FILE, "Acc %d: setting %sregistration..",
+ acc_id, (renew? "" : "un")));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ /* Cancel any re-registration timer */
+ if (pjsua_var.acc[acc_id].auto_rereg.timer.id) {
+ pjsua_var.acc[acc_id].auto_rereg.timer.id = PJ_FALSE;
+ pjsua_cancel_timer(&pjsua_var.acc[acc_id].auto_rereg.timer);
+ }
+
+ /* Reset pointer to registration transport */
+ pjsua_var.acc[acc_id].auto_rereg.reg_tp = NULL;
+
+ if (renew) {
+ if (pjsua_var.acc[acc_id].regc == NULL) {
+ status = pjsua_regc_init(acc_id);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create registration",
+ status);
+ goto on_return;
+ }
+ }
+ if (!pjsua_var.acc[acc_id].regc) {
+ status = PJ_EINVALIDOP;
+ goto on_return;
+ }
+
+ status = pjsip_regc_register(pjsua_var.acc[acc_id].regc, 1,
+ &tdata);
+
+ if (0 && status == PJ_SUCCESS && pjsua_var.acc[acc_id].cred_cnt) {
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+ pjsip_authorization_hdr *h;
+ char *uri;
+ int d;
+
+ uri = (char*) pj_pool_alloc(tdata->pool, acc->cfg.reg_uri.slen+10);
+ d = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, tdata->msg->line.req.uri,
+ uri, acc->cfg.reg_uri.slen+10);
+ pj_assert(d > 0);
+
+ h = pjsip_authorization_hdr_create(tdata->pool);
+ h->scheme = pj_str("Digest");
+ h->credential.digest.username = acc->cred[0].username;
+ h->credential.digest.realm = acc->srv_domain;
+ h->credential.digest.uri = pj_str(uri);
+ h->credential.digest.algorithm = pj_str("md5");
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)h);
+ }
+
+ } else {
+ if (pjsua_var.acc[acc_id].regc == NULL) {
+ PJ_LOG(3,(THIS_FILE, "Currently not registered"));
+ status = PJ_EINVALIDOP;
+ goto on_return;
+ }
+
+ pjsua_pres_unpublish(&pjsua_var.acc[acc_id], 0);
+
+ status = pjsip_regc_unregister(pjsua_var.acc[acc_id].regc, &tdata);
+ }
+
+ if (status == PJ_SUCCESS) {
+ if (pjsua_var.acc[acc_id].cfg.allow_via_rewrite &&
+ pjsua_var.acc[acc_id].via_addr.host.slen > 0)
+ {
+ pjsip_regc_set_via_sent_by(pjsua_var.acc[acc_id].regc,
+ &pjsua_var.acc[acc_id].via_addr,
+ pjsua_var.acc[acc_id].via_tp);
+ }
+
+ //pjsua_process_msg_data(tdata, NULL);
+ status = pjsip_regc_send( pjsua_var.acc[acc_id].regc, tdata );
+ }
+
+ /* Update pointer to registration transport */
+ if (status == PJ_SUCCESS) {
+ pjsip_regc_info reg_info;
+
+ pjsip_regc_get_info(pjsua_var.acc[acc_id].regc, &reg_info);
+ pjsua_var.acc[acc_id].auto_rereg.reg_tp = reg_info.transport;
+
+ if (pjsua_var.ua_cfg.cb.on_reg_started) {
+ (*pjsua_var.ua_cfg.cb.on_reg_started)(acc_id, renew);
+ }
+ }
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create/send REGISTER",
+ status);
+ } else {
+ PJ_LOG(4,(THIS_FILE, "Acc %d: %s sent", acc_id,
+ (renew? "Registration" : "Unregistration")));
+ }
+
+on_return:
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Get account information.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_get_info( pjsua_acc_id acc_id,
+ pjsua_acc_info *info)
+{
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+ pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
+
+ PJ_ASSERT_RETURN(info != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL);
+
+ pj_bzero(info, sizeof(pjsua_acc_info));
+
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+ PJSUA_LOCK();
+
+ if (pjsua_var.acc[acc_id].valid == PJ_FALSE) {
+ PJSUA_UNLOCK();
+ return PJ_EINVALIDOP;
+ }
+
+ info->id = acc_id;
+ info->is_default = (pjsua_var.default_acc == acc_id);
+ info->acc_uri = acc_cfg->id;
+ info->has_registration = (acc->cfg.reg_uri.slen > 0);
+ info->online_status = acc->online_status;
+ pj_memcpy(&info->rpid, &acc->rpid, sizeof(pjrpid_element));
+ if (info->rpid.note.slen)
+ info->online_status_text = info->rpid.note;
+ else if (info->online_status)
+ info->online_status_text = pj_str("Online");
+ else
+ info->online_status_text = pj_str("Offline");
+
+ if (acc->reg_last_code) {
+ if (info->has_registration) {
+ info->status = (pjsip_status_code) acc->reg_last_code;
+ info->status_text = *pjsip_get_status_text(acc->reg_last_code);
+ if (acc->reg_last_err)
+ info->reg_last_err = acc->reg_last_err;
+ } else {
+ info->status = (pjsip_status_code) 0;
+ info->status_text = pj_str("not registered");
+ }
+ } else if (acc->cfg.reg_uri.slen) {
+ info->status = PJSIP_SC_TRYING;
+ info->status_text = pj_str("In Progress");
+ } else {
+ info->status = (pjsip_status_code) 0;
+ info->status_text = pj_str("does not register");
+ }
+
+ if (acc->regc) {
+ pjsip_regc_info regc_info;
+ pjsip_regc_get_info(acc->regc, &regc_info);
+ info->expires = regc_info.next_reg;
+ } else {
+ info->expires = -1;
+ }
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+
+}
+
+
+/*
+ * Enum accounts all account ids.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_accs(pjsua_acc_id ids[],
+ unsigned *count )
+{
+ unsigned i, c;
+
+ PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL);
+
+ PJSUA_LOCK();
+
+ for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (!pjsua_var.acc[i].valid)
+ continue;
+ ids[c] = i;
+ ++c;
+ }
+
+ *count = c;
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Enum accounts info.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_enum_info( pjsua_acc_info info[],
+ unsigned *count )
+{
+ unsigned i, c;
+
+ PJ_ASSERT_RETURN(info && *count, PJ_EINVAL);
+
+ PJSUA_LOCK();
+
+ for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (!pjsua_var.acc[i].valid)
+ continue;
+
+ pjsua_acc_get_info(i, &info[c]);
+ ++c;
+ }
+
+ *count = c;
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This is an internal function to find the most appropriate account to
+ * used to reach to the specified URL.
+ */
+PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *url)
+{
+ pj_str_t tmp;
+ pjsip_uri *uri;
+ pjsip_sip_uri *sip_uri;
+ pj_pool_t *tmp_pool;
+ unsigned i;
+
+ PJSUA_LOCK();
+
+ tmp_pool = pjsua_pool_create("tmpacc10", 256, 256);
+
+ pj_strdup_with_null(tmp_pool, &tmp, url);
+
+ uri = pjsip_parse_uri(tmp_pool, tmp.ptr, tmp.slen, 0);
+ if (!uri) {
+ pj_pool_release(tmp_pool);
+ PJSUA_UNLOCK();
+ return pjsua_var.default_acc;
+ }
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(uri))
+ {
+ /* Return the first account with proxy */
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (!pjsua_var.acc[i].valid)
+ continue;
+ if (!pj_list_empty(&pjsua_var.acc[i].route_set))
+ break;
+ }
+
+ if (i != PJ_ARRAY_SIZE(pjsua_var.acc)) {
+ /* Found rather matching account */
+ pj_pool_release(tmp_pool);
+ PJSUA_UNLOCK();
+ return i;
+ }
+
+ /* Not found, use default account */
+ pj_pool_release(tmp_pool);
+ PJSUA_UNLOCK();
+ return pjsua_var.default_acc;
+ }
+
+ sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri);
+
+ /* Find matching domain AND port */
+ for (i=0; i<pjsua_var.acc_cnt; ++i) {
+ unsigned acc_id = pjsua_var.acc_ids[i];
+ if (pj_stricmp(&pjsua_var.acc[acc_id].srv_domain, &sip_uri->host)==0 &&
+ pjsua_var.acc[acc_id].srv_port == sip_uri->port)
+ {
+ pj_pool_release(tmp_pool);
+ PJSUA_UNLOCK();
+ return acc_id;
+ }
+ }
+
+ /* If no match, try to match the domain part only */
+ for (i=0; i<pjsua_var.acc_cnt; ++i) {
+ unsigned acc_id = pjsua_var.acc_ids[i];
+ if (pj_stricmp(&pjsua_var.acc[acc_id].srv_domain, &sip_uri->host)==0)
+ {
+ pj_pool_release(tmp_pool);
+ PJSUA_UNLOCK();
+ return acc_id;
+ }
+ }
+
+
+ /* Still no match, just use default account */
+ pj_pool_release(tmp_pool);
+ PJSUA_UNLOCK();
+ return pjsua_var.default_acc;
+}
+
+
+/*
+ * This is an internal function to find the most appropriate account to be
+ * used to handle incoming calls.
+ */
+PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata)
+{
+ pjsip_uri *uri;
+ pjsip_sip_uri *sip_uri;
+ unsigned i;
+
+ /* Check that there's at least one account configured */
+ PJ_ASSERT_RETURN(pjsua_var.acc_cnt!=0, pjsua_var.default_acc);
+
+ uri = rdata->msg_info.to->uri;
+
+ /* Just return default account if To URI is not SIP: */
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(uri))
+ {
+ return pjsua_var.default_acc;
+ }
+
+
+ PJSUA_LOCK();
+
+ sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+
+ /* Find account which has matching username and domain. */
+ for (i=0; i < pjsua_var.acc_cnt; ++i) {
+ unsigned acc_id = pjsua_var.acc_ids[i];
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+
+ if (acc->valid && pj_stricmp(&acc->user_part, &sip_uri->user)==0 &&
+ pj_stricmp(&acc->srv_domain, &sip_uri->host)==0)
+ {
+ /* Match ! */
+ PJSUA_UNLOCK();
+ return acc_id;
+ }
+ }
+
+ /* No matching account, try match domain part only. */
+ for (i=0; i < pjsua_var.acc_cnt; ++i) {
+ unsigned acc_id = pjsua_var.acc_ids[i];
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+
+ if (acc->valid && pj_stricmp(&acc->srv_domain, &sip_uri->host)==0) {
+ /* Match ! */
+ PJSUA_UNLOCK();
+ return acc_id;
+ }
+ }
+
+ /* No matching account, try match user part (and transport type) only. */
+ for (i=0; i < pjsua_var.acc_cnt; ++i) {
+ unsigned acc_id = pjsua_var.acc_ids[i];
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+
+ if (acc->valid && pj_stricmp(&acc->user_part, &sip_uri->user)==0) {
+
+ if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
+ pjsip_transport_type_e type;
+ type = pjsip_transport_get_type_from_name(&sip_uri->transport_param);
+ if (type == PJSIP_TRANSPORT_UNSPECIFIED)
+ type = PJSIP_TRANSPORT_UDP;
+
+ if (pjsua_var.tpdata[acc->cfg.transport_id].type != type)
+ continue;
+ }
+
+ /* Match ! */
+ PJSUA_UNLOCK();
+ return acc_id;
+ }
+ }
+
+ /* Still no match, use default account */
+ PJSUA_UNLOCK();
+ return pjsua_var.default_acc;
+}
+
+
+/*
+ * Create arbitrary requests for this account.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_create_request(pjsua_acc_id acc_id,
+ const pjsip_method *method,
+ const pj_str_t *target,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pjsua_acc *acc;
+ pjsip_route_hdr *r;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(method && target && p_tdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL);
+
+ acc = &pjsua_var.acc[acc_id];
+
+ status = pjsip_endpt_create_request(pjsua_var.endpt, method, target,
+ &acc->cfg.id, target,
+ NULL, NULL, -1, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create request", status);
+ return status;
+ }
+
+ /* Copy routeset */
+ r = acc->route_set.next;
+ while (r != &acc->route_set) {
+ pjsip_msg_add_hdr(tdata->msg,
+ (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, r));
+ r = r->next;
+ }
+
+ /* If account is locked to specific transport, then set that transport to
+ * the transmit data.
+ */
+ if (pjsua_var.acc[acc_id].cfg.transport_id != PJSUA_INVALID_ID) {
+ pjsip_tpselector tp_sel;
+
+ pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
+ pjsip_tx_data_set_transport(tdata, &tp_sel);
+ }
+
+ /* If via_addr is set, use this address for the Via header. */
+ if (pjsua_var.acc[acc_id].cfg.allow_via_rewrite &&
+ pjsua_var.acc[acc_id].via_addr.host.slen > 0)
+ {
+ tdata->via_addr = pjsua_var.acc[acc_id].via_addr;
+ tdata->via_tp = pjsua_var.acc[acc_id].via_tp;
+ }
+
+ /* Done */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool,
+ pj_str_t *contact,
+ pjsua_acc_id acc_id,
+ const pj_str_t *suri)
+{
+ pjsua_acc *acc;
+ pjsip_sip_uri *sip_uri;
+ pj_status_t status;
+ pjsip_transport_type_e tp_type = PJSIP_TRANSPORT_UNSPECIFIED;
+ pj_str_t local_addr;
+ pjsip_tpselector tp_sel;
+ unsigned flag;
+ int secure;
+ int local_port;
+ const char *beginquote, *endquote;
+ char transport_param[32];
+ const char *ob = ";ob";
+
+
+ PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL);
+ acc = &pjsua_var.acc[acc_id];
+
+ /* If force_contact is configured, then use use it */
+ if (acc->cfg.force_contact.slen) {
+ *contact = acc->cfg.force_contact;
+ return PJ_SUCCESS;
+ }
+
+ /* If route-set is configured for the account, then URI is the
+ * first entry of the route-set.
+ */
+ if (!pj_list_empty(&acc->route_set)) {
+ sip_uri = (pjsip_sip_uri*)
+ pjsip_uri_get_uri(acc->route_set.next->name_addr.uri);
+ } else {
+ pj_str_t tmp;
+ pjsip_uri *uri;
+
+ pj_strdup_with_null(pool, &tmp, suri);
+
+ uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, 0);
+ if (uri == NULL)
+ return PJSIP_EINVALIDURI;
+
+ /* For non-SIP scheme, route set should be configured */
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))
+ return PJSIP_ENOROUTESET;
+
+ sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+ }
+
+ /* Get transport type of the URI */
+ if (PJSIP_URI_SCHEME_IS_SIPS(sip_uri))
+ tp_type = PJSIP_TRANSPORT_TLS;
+ else if (sip_uri->transport_param.slen == 0) {
+ tp_type = PJSIP_TRANSPORT_UDP;
+ } else
+ tp_type = pjsip_transport_get_type_from_name(&sip_uri->transport_param);
+
+ if (tp_type == PJSIP_TRANSPORT_UNSPECIFIED)
+ return PJSIP_EUNSUPTRANSPORT;
+
+ /* If destination URI specifies IPv6, then set transport type
+ * to use IPv6 as well.
+ */
+ if (pj_strchr(&sip_uri->host, ':'))
+ tp_type = (pjsip_transport_type_e)(((int)tp_type) + PJSIP_TRANSPORT_IPV6);
+
+ flag = pjsip_transport_get_flag_from_type(tp_type);
+ secure = (flag & PJSIP_TRANSPORT_SECURE) != 0;
+
+ /* Init transport selector. */
+ pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel);
+
+ /* Get local address suitable to send request from */
+ status = pjsip_tpmgr_find_local_addr(pjsip_endpt_get_tpmgr(pjsua_var.endpt),
+ pool, tp_type, &tp_sel,
+ &local_addr, &local_port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Enclose IPv6 address in square brackets */
+ if (tp_type & PJSIP_TRANSPORT_IPV6) {
+ beginquote = "[";
+ endquote = "]";
+ } else {
+ beginquote = endquote = "";
+ }
+
+ /* Don't add transport parameter if it's UDP */
+ if (tp_type!=PJSIP_TRANSPORT_UDP && tp_type!=PJSIP_TRANSPORT_UDP6) {
+ pj_ansi_snprintf(transport_param, sizeof(transport_param),
+ ";transport=%s",
+ pjsip_transport_get_type_name(tp_type));
+ } else {
+ transport_param[0] = '\0';
+ }
+
+
+ /* Create the contact header */
+ contact->ptr = (char*)pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+ contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE,
+ "%s%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s%s>%.*s",
+ (acc->display.slen?"\"" : ""),
+ (int)acc->display.slen,
+ acc->display.ptr,
+ (acc->display.slen?"\" " : ""),
+ (secure ? PJSUA_SECURE_SCHEME : "sip"),
+ (int)acc->user_part.slen,
+ acc->user_part.ptr,
+ (acc->user_part.slen?"@":""),
+ beginquote,
+ (int)local_addr.slen,
+ local_addr.ptr,
+ endquote,
+ local_port,
+ transport_param,
+ (int)acc->cfg.contact_uri_params.slen,
+ acc->cfg.contact_uri_params.ptr,
+ (acc->cfg.use_rfc5626? ob: ""),
+ (int)acc->cfg.contact_params.slen,
+ acc->cfg.contact_params.ptr);
+
+ return PJ_SUCCESS;
+}
+
+
+
+PJ_DEF(pj_status_t) pjsua_acc_create_uas_contact( pj_pool_t *pool,
+ pj_str_t *contact,
+ pjsua_acc_id acc_id,
+ pjsip_rx_data *rdata )
+{
+ /*
+ * Section 12.1.1, paragraph about using SIPS URI in Contact.
+ * If the request that initiated the dialog contained a SIPS URI
+ * in the Request-URI or in the top Record-Route header field value,
+ * if there was any, or the Contact header field if there was no
+ * Record-Route header field, the Contact header field in the response
+ * MUST be a SIPS URI.
+ */
+ pjsua_acc *acc;
+ pjsip_sip_uri *sip_uri;
+ pj_status_t status;
+ pjsip_transport_type_e tp_type = PJSIP_TRANSPORT_UNSPECIFIED;
+ pj_str_t local_addr;
+ pjsip_tpselector tp_sel;
+ unsigned flag;
+ int secure;
+ int local_port;
+ const char *beginquote, *endquote;
+ char transport_param[32];
+
+ PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL);
+ acc = &pjsua_var.acc[acc_id];
+
+ /* If force_contact is configured, then use use it */
+ if (acc->cfg.force_contact.slen) {
+ *contact = acc->cfg.force_contact;
+ return PJ_SUCCESS;
+ }
+
+ /* If Record-Route is present, then URI is the top Record-Route. */
+ if (rdata->msg_info.record_route) {
+ sip_uri = (pjsip_sip_uri*)
+ pjsip_uri_get_uri(rdata->msg_info.record_route->name_addr.uri);
+ } else {
+ pjsip_hdr *pos = NULL;
+ pjsip_contact_hdr *h_contact;
+ pjsip_uri *uri = NULL;
+
+ /* Otherwise URI is Contact URI.
+ * Iterate the Contact URI until we find sip: or sips: scheme.
+ */
+ do {
+ h_contact = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
+ pos);
+ if (h_contact) {
+ if (h_contact->uri)
+ uri = (pjsip_uri*) pjsip_uri_get_uri(h_contact->uri);
+ else
+ uri = NULL;
+ if (!uri || (!PJSIP_URI_SCHEME_IS_SIP(uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(uri)))
+ {
+ pos = (pjsip_hdr*)h_contact->next;
+ if (pos == &rdata->msg_info.msg->hdr)
+ h_contact = NULL;
+ } else {
+ break;
+ }
+ }
+ } while (h_contact);
+
+
+ /* Or if Contact URI is not present, take the remote URI from
+ * the From URI.
+ */
+ if (uri == NULL)
+ uri = (pjsip_uri*) pjsip_uri_get_uri(rdata->msg_info.from->uri);
+
+
+ /* Can only do sip/sips scheme at present. */
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))
+ return PJSIP_EINVALIDREQURI;
+
+ sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+ }
+
+ /* Get transport type of the URI */
+ if (PJSIP_URI_SCHEME_IS_SIPS(sip_uri))
+ tp_type = PJSIP_TRANSPORT_TLS;
+ else if (sip_uri->transport_param.slen == 0) {
+ tp_type = PJSIP_TRANSPORT_UDP;
+ } else
+ tp_type = pjsip_transport_get_type_from_name(&sip_uri->transport_param);
+
+ if (tp_type == PJSIP_TRANSPORT_UNSPECIFIED)
+ return PJSIP_EUNSUPTRANSPORT;
+
+ /* If destination URI specifies IPv6, then set transport type
+ * to use IPv6 as well.
+ */
+ if (pj_strchr(&sip_uri->host, ':'))
+ tp_type = (pjsip_transport_type_e)(((int)tp_type) + PJSIP_TRANSPORT_IPV6);
+
+ flag = pjsip_transport_get_flag_from_type(tp_type);
+ secure = (flag & PJSIP_TRANSPORT_SECURE) != 0;
+
+ /* Init transport selector. */
+ pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel);
+
+ /* Get local address suitable to send request from */
+ status = pjsip_tpmgr_find_local_addr(pjsip_endpt_get_tpmgr(pjsua_var.endpt),
+ pool, tp_type, &tp_sel,
+ &local_addr, &local_port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Enclose IPv6 address in square brackets */
+ if (tp_type & PJSIP_TRANSPORT_IPV6) {
+ beginquote = "[";
+ endquote = "]";
+ } else {
+ beginquote = endquote = "";
+ }
+
+ /* Don't add transport parameter if it's UDP */
+ if (tp_type!=PJSIP_TRANSPORT_UDP && tp_type!=PJSIP_TRANSPORT_UDP6) {
+ pj_ansi_snprintf(transport_param, sizeof(transport_param),
+ ";transport=%s",
+ pjsip_transport_get_type_name(tp_type));
+ } else {
+ transport_param[0] = '\0';
+ }
+
+
+ /* Create the contact header */
+ contact->ptr = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+ contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE,
+ "%s%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s>%.*s",
+ (acc->display.slen?"\"" : ""),
+ (int)acc->display.slen,
+ acc->display.ptr,
+ (acc->display.slen?"\" " : ""),
+ (secure ? PJSUA_SECURE_SCHEME : "sip"),
+ (int)acc->user_part.slen,
+ acc->user_part.ptr,
+ (acc->user_part.slen?"@":""),
+ beginquote,
+ (int)local_addr.slen,
+ local_addr.ptr,
+ endquote,
+ local_port,
+ transport_param,
+ (int)acc->cfg.contact_uri_params.slen,
+ acc->cfg.contact_uri_params.ptr,
+ (int)acc->cfg.contact_params.slen,
+ acc->cfg.contact_params.ptr);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsua_acc_set_transport( pjsua_acc_id acc_id,
+ pjsua_transport_id tp_id)
+{
+ pjsua_acc *acc;
+
+ PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL);
+ acc = &pjsua_var.acc[acc_id];
+
+ PJ_ASSERT_RETURN(tp_id >= 0 && tp_id < (int)PJ_ARRAY_SIZE(pjsua_var.tpdata),
+ PJ_EINVAL);
+
+ acc->cfg.transport_id = tp_id;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Auto re-registration timeout callback */
+static void auto_rereg_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te)
+{
+ pjsua_acc *acc;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(th);
+ acc = (pjsua_acc*) te->user_data;
+ pj_assert(acc);
+
+ PJSUA_LOCK();
+
+ /* Check if the reregistration timer is still valid, e.g: while waiting
+ * timeout timer application might have deleted the account or disabled
+ * the auto-reregistration.
+ */
+ if (!acc->valid || !acc->auto_rereg.active ||
+ acc->cfg.reg_retry_interval == 0)
+ {
+ goto on_return;
+ }
+
+ /* Start re-registration */
+ acc->auto_rereg.attempt_cnt++;
+ status = pjsua_acc_set_registration(acc->index, PJ_TRUE);
+ if (status != PJ_SUCCESS)
+ schedule_reregistration(acc);
+
+on_return:
+ PJSUA_UNLOCK();
+}
+
+
+/* Schedule reregistration for specified account. Note that the first
+ * re-registration after a registration failure will be done immediately.
+ * Also note that this function should be called within PJSUA mutex.
+ */
+static void schedule_reregistration(pjsua_acc *acc)
+{
+ pj_time_val delay;
+
+ pj_assert(acc);
+
+ /* Validate the account and re-registration feature status */
+ if (!acc->valid || acc->cfg.reg_retry_interval == 0) {
+ return;
+ }
+
+ /* If configured, disconnect calls of this account after the first
+ * reregistration attempt failed.
+ */
+ if (acc->cfg.drop_calls_on_reg_fail && acc->auto_rereg.attempt_cnt >= 1)
+ {
+ unsigned i, cnt;
+
+ for (i = 0, cnt = 0; i < pjsua_var.ua_cfg.max_calls; ++i) {
+ if (pjsua_var.calls[i].acc_id == acc->index) {
+ pjsua_call_hangup(i, 0, NULL, NULL);
+ ++cnt;
+ }
+ }
+
+ if (cnt) {
+ PJ_LOG(3, (THIS_FILE, "Disconnecting %d call(s) of account #%d "
+ "after reregistration attempt failed",
+ cnt, acc->index));
+ }
+ }
+
+ /* Cancel any re-registration timer */
+ if (acc->auto_rereg.timer.id) {
+ acc->auto_rereg.timer.id = PJ_FALSE;
+ pjsua_cancel_timer(&acc->auto_rereg.timer);
+ }
+
+ /* Update re-registration flag */
+ acc->auto_rereg.active = PJ_TRUE;
+
+ /* Set up timer for reregistration */
+ acc->auto_rereg.timer.cb = &auto_rereg_timer_cb;
+ acc->auto_rereg.timer.user_data = acc;
+
+ /* Reregistration attempt. The first attempt will be done immediately. */
+ delay.sec = acc->auto_rereg.attempt_cnt? acc->cfg.reg_retry_interval :
+ acc->cfg.reg_first_retry_interval;
+ delay.msec = 0;
+
+ /* Randomize interval by +/- 10 secs */
+ if (delay.sec >= 10) {
+ delay.msec = -10000 + (pj_rand() % 20000);
+ } else {
+ delay.sec = 0;
+ delay.msec = (pj_rand() % 10000);
+ }
+ pj_time_val_normalize(&delay);
+
+ PJ_LOG(4,(THIS_FILE,
+ "Scheduling re-registration retry for acc %d in %u seconds..",
+ acc->index, delay.sec));
+
+ acc->auto_rereg.timer.id = PJ_TRUE;
+ if (pjsua_schedule_timer(&acc->auto_rereg.timer, &delay) != PJ_SUCCESS)
+ acc->auto_rereg.timer.id = PJ_FALSE;
+}
+
+
+/* Internal function to perform auto-reregistration on transport
+ * connection/disconnection events.
+ */
+void pjsua_acc_on_tp_state_changed(pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info)
+{
+ unsigned i;
+
+ PJ_UNUSED_ARG(info);
+
+ /* Only care for transport disconnection events */
+ if (state != PJSIP_TP_STATE_DISCONNECTED)
+ return;
+
+ PJ_LOG(4,(THIS_FILE, "Disconnected notification for transport %s",
+ tp->obj_name));
+ pj_log_push_indent();
+
+ /* Shutdown this transport, to make sure that the transport manager
+ * will create a new transport for reconnection.
+ */
+ pjsip_transport_shutdown(tp);
+
+ PJSUA_LOCK();
+
+ /* Enumerate accounts using this transport and perform actions
+ * based on the transport state.
+ */
+ for (i = 0; i < PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ pjsua_acc *acc = &pjsua_var.acc[i];
+
+ /* Skip if this account is not valid OR auto re-registration
+ * feature is disabled OR this transport is not used by this account.
+ */
+ if (!acc->valid || !acc->cfg.reg_retry_interval ||
+ tp != acc->auto_rereg.reg_tp)
+ {
+ continue;
+ }
+
+ /* Release regc transport immediately
+ * See https://trac.pjsip.org/repos/ticket/1481
+ */
+ if (pjsua_var.acc[i].regc) {
+ pjsip_regc_release_transport(pjsua_var.acc[i].regc);
+ }
+
+ /* Schedule reregistration for this account */
+ schedule_reregistration(acc);
+ }
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+}
diff --git a/pjsip/src/pjsua-lib/pjsua_aud.c b/pjsip/src/pjsua-lib/pjsua_aud.c
new file mode 100644
index 0000000..557a147
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_aud.c
@@ -0,0 +1,2148 @@
+/* $Id: pjsua_aud.c 4145 2012-05-22 23:13:22Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0
+
+#define THIS_FILE "pjsua_aud.c"
+#define NULL_SND_DEV_ID -99
+
+/*****************************************************************************
+ *
+ * Prototypes
+ */
+/* Open sound dev */
+static pj_status_t open_snd_dev(pjmedia_snd_port_param *param);
+/* Close existing sound device */
+static void close_snd_dev(void);
+/* Create audio device param */
+static pj_status_t create_aud_param(pjmedia_aud_param *param,
+ pjmedia_aud_dev_index capture_dev,
+ pjmedia_aud_dev_index playback_dev,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample);
+
+/*****************************************************************************
+ *
+ * Call API that are closely tied to PJMEDIA
+ */
+/*
+ * Check if call has an active media session.
+ */
+PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id)
+{
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+ return call->audio_idx >= 0 && call->media[call->audio_idx].strm.a.stream;
+}
+
+
+/*
+ * Get the conference port identification associated with the call.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id)
+{
+ pjsua_call *call;
+ pjsua_conf_port_id port_id = PJSUA_INVALID_ID;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ /* Use PJSUA_LOCK() instead of acquire_call():
+ * https://trac.pjsip.org/repos/ticket/1371
+ */
+ PJSUA_LOCK();
+
+ if (!pjsua_call_is_active(call_id))
+ goto on_return;
+
+ call = &pjsua_var.calls[call_id];
+ port_id = call->media[call->audio_idx].strm.a.conf_slot;
+
+on_return:
+ PJSUA_UNLOCK();
+
+ return port_id;
+}
+
+
+/*
+ * Get media stream info for the specified media index.
+ */
+PJ_DEF(pj_status_t) pjsua_call_get_stream_info( pjsua_call_id call_id,
+ unsigned med_idx,
+ pjsua_stream_info *psi)
+{
+ pjsua_call *call;
+ pjsua_call_media *call_med;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(psi, PJ_EINVAL);
+
+ PJSUA_LOCK();
+
+ call = &pjsua_var.calls[call_id];
+
+ if (med_idx >= call->med_cnt) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ call_med = &call->media[med_idx];
+ psi->type = call_med->type;
+ switch (call_med->type) {
+ case PJMEDIA_TYPE_AUDIO:
+ status = pjmedia_stream_get_info(call_med->strm.a.stream,
+ &psi->info.aud);
+ break;
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ case PJMEDIA_TYPE_VIDEO:
+ status = pjmedia_vid_stream_get_info(call_med->strm.v.stream,
+ &psi->info.vid);
+ break;
+#endif
+ default:
+ status = PJMEDIA_EINVALIMEDIATYPE;
+ break;
+ }
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+
+/*
+ * Get media stream statistic for the specified media index.
+ */
+PJ_DEF(pj_status_t) pjsua_call_get_stream_stat( pjsua_call_id call_id,
+ unsigned med_idx,
+ pjsua_stream_stat *stat)
+{
+ pjsua_call *call;
+ pjsua_call_media *call_med;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(stat, PJ_EINVAL);
+
+ PJSUA_LOCK();
+
+ call = &pjsua_var.calls[call_id];
+
+ if (med_idx >= call->med_cnt) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ call_med = &call->media[med_idx];
+ switch (call_med->type) {
+ case PJMEDIA_TYPE_AUDIO:
+ status = pjmedia_stream_get_stat(call_med->strm.a.stream,
+ &stat->rtcp);
+ if (status == PJ_SUCCESS)
+ status = pjmedia_stream_get_stat_jbuf(call_med->strm.a.stream,
+ &stat->jbuf);
+ break;
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ case PJMEDIA_TYPE_VIDEO:
+ status = pjmedia_vid_stream_get_stat(call_med->strm.v.stream,
+ &stat->rtcp);
+ if (status == PJ_SUCCESS)
+ status = pjmedia_vid_stream_get_stat_jbuf(call_med->strm.v.stream,
+ &stat->jbuf);
+ break;
+#endif
+ default:
+ status = PJMEDIA_EINVALIMEDIATYPE;
+ break;
+ }
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+/*
+ * Send DTMF digits to remote using RFC 2833 payload formats.
+ */
+PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id,
+ const pj_str_t *digits)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Call %d dialing DTMF %.*s",
+ call_id, (int)digits->slen, digits->ptr));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_dial_dtmf()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ if (!pjsua_call_has_media(call_id)) {
+ PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
+ status = PJ_EINVALIDOP;
+ goto on_return;
+ }
+
+ status = pjmedia_stream_dial_dtmf(
+ call->media[call->audio_idx].strm.a.stream, digits);
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*****************************************************************************
+ *
+ * Audio media with PJMEDIA backend
+ */
+
+/* Init pjmedia audio subsystem */
+pj_status_t pjsua_aud_subsys_init()
+{
+ pj_str_t codec_id = {NULL, 0};
+ unsigned opt;
+ pjmedia_audio_codec_config codec_cfg;
+ pj_status_t status;
+
+ /* To suppress warning about unused var when all codecs are disabled */
+ PJ_UNUSED_ARG(codec_id);
+
+ /*
+ * Register all codecs
+ */
+ pjmedia_audio_codec_config_default(&codec_cfg);
+ codec_cfg.speex.quality = pjsua_var.media_cfg.quality;
+ codec_cfg.speex.complexity = -1;
+ codec_cfg.ilbc.mode = pjsua_var.media_cfg.ilbc_mode;
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODECS
+ /* Register passthrough codecs */
+ {
+ unsigned aud_idx;
+ unsigned ext_fmt_cnt = 0;
+ pjmedia_format ext_fmts[32];
+
+ /* List extended formats supported by audio devices */
+ for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) {
+ pjmedia_aud_dev_info aud_info;
+ unsigned i;
+
+ status = pjmedia_aud_dev_get_info(aud_idx, &aud_info);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error querying audio device info",
+ status);
+ goto on_error;
+ }
+
+ /* Collect extended formats supported by this audio device */
+ for (i = 0; i < aud_info.ext_fmt_cnt; ++i) {
+ unsigned j;
+ pj_bool_t is_listed = PJ_FALSE;
+
+ /* See if this extended format is already in the list */
+ for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) {
+ if (ext_fmts[j].id == aud_info.ext_fmt[i].id &&
+ ext_fmts[j].det.aud.avg_bps ==
+ aud_info.ext_fmt[i].det.aud.avg_bps)
+ {
+ is_listed = PJ_TRUE;
+ }
+ }
+
+ /* Put this format into the list, if it is not in the list */
+ if (!is_listed)
+ ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i];
+
+ pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts));
+ }
+ }
+
+ /* Init the passthrough codec with supported formats only */
+ codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt;
+ codec_cfg.passthrough.setting.fmts = ext_fmts;
+ codec_cfg.passthrough.setting.ilbc_mode = cfg->ilbc_mode;
+ }
+#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */
+
+ /* Register all codecs */
+ status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt,
+ &codec_cfg);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs"));
+ goto on_error;
+ }
+
+ /* Set speex/16000 to higher priority*/
+ codec_id = pj_str("speex/16000");
+ pjmedia_codec_mgr_set_codec_priority(
+ pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
+ &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2);
+
+ /* Set speex/8000 to next higher priority*/
+ codec_id = pj_str("speex/8000");
+ pjmedia_codec_mgr_set_codec_priority(
+ pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
+ &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1);
+
+ /* Disable ALL L16 codecs */
+ codec_id = pj_str("L16");
+ pjmedia_codec_mgr_set_codec_priority(
+ pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt),
+ &codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
+
+
+ /* Save additional conference bridge parameters for future
+ * reference.
+ */
+ pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count;
+ pjsua_var.mconf_cfg.bits_per_sample = 16;
+ pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate *
+ pjsua_var.mconf_cfg.channel_count *
+ pjsua_var.media_cfg.audio_frame_ptime /
+ 1000;
+
+ /* Init options for conference bridge. */
+ opt = PJMEDIA_CONF_NO_DEVICE;
+ if (pjsua_var.media_cfg.quality >= 3 &&
+ pjsua_var.media_cfg.quality <= 4)
+ {
+ opt |= PJMEDIA_CONF_SMALL_FILTER;
+ }
+ else if (pjsua_var.media_cfg.quality < 3) {
+ opt |= PJMEDIA_CONF_USE_LINEAR;
+ }
+
+ /* Init conference bridge. */
+ status = pjmedia_conf_create(pjsua_var.pool,
+ pjsua_var.media_cfg.max_media_ports,
+ pjsua_var.media_cfg.clock_rate,
+ pjsua_var.mconf_cfg.channel_count,
+ pjsua_var.mconf_cfg.samples_per_frame,
+ pjsua_var.mconf_cfg.bits_per_sample,
+ opt, &pjsua_var.mconf);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating conference bridge",
+ status);
+ goto on_error;
+ }
+
+ /* Are we using the audio switchboard (a.k.a APS-Direct)? */
+ pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf)
+ ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE;
+
+ /* Create null port just in case user wants to use null sound. */
+ status = pjmedia_null_port_create(pjsua_var.pool,
+ pjsua_var.media_cfg.clock_rate,
+ pjsua_var.mconf_cfg.channel_count,
+ pjsua_var.mconf_cfg.samples_per_frame,
+ pjsua_var.mconf_cfg.bits_per_sample,
+ &pjsua_var.null_port);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ return status;
+
+on_error:
+ return status;
+}
+
+/* Check if sound device is idle. */
+void pjsua_check_snd_dev_idle()
+{
+ unsigned call_cnt;
+
+ /* Check if the sound device auto-close feature is disabled. */
+ if (pjsua_var.media_cfg.snd_auto_close_time < 0)
+ return;
+
+ /* Check if the sound device is currently closed. */
+ if (!pjsua_var.snd_is_on)
+ return;
+
+ /* Get the call count, we shouldn't close the sound device when there is
+ * any calls active.
+ */
+ call_cnt = pjsua_call_get_count();
+
+ /* When this function is called from pjsua_media_channel_deinit() upon
+ * disconnecting call, actually the call count hasn't been updated/
+ * decreased. So we put additional check here, if there is only one
+ * call and it's in DISCONNECTED state, there is actually no active
+ * call.
+ */
+ if (call_cnt == 1) {
+ pjsua_call_id call_id;
+ pj_status_t status;
+
+ status = pjsua_enum_calls(&call_id, &call_cnt);
+ if (status == PJ_SUCCESS && call_cnt > 0 &&
+ !pjsua_call_is_active(call_id))
+ {
+ call_cnt = 0;
+ }
+ }
+
+ /* Activate sound device auto-close timer if sound device is idle.
+ * It is idle when there is no port connection in the bridge and
+ * there is no active call.
+ */
+ if (pjsua_var.snd_idle_timer.id == PJ_FALSE &&
+ call_cnt == 0 &&
+ pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0)
+ {
+ pj_time_val delay;
+
+ delay.msec = 0;
+ delay.sec = pjsua_var.media_cfg.snd_auto_close_time;
+
+ pjsua_var.snd_idle_timer.id = PJ_TRUE;
+ pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer,
+ &delay);
+ }
+}
+
+/* Timer callback to close sound device */
+static void close_snd_timer_cb( pj_timer_heap_t *th,
+ pj_timer_entry *entry)
+{
+ PJ_UNUSED_ARG(th);
+
+ PJSUA_LOCK();
+ if (entry->id) {
+ PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d second(s)",
+ pjsua_var.media_cfg.snd_auto_close_time));
+
+ entry->id = PJ_FALSE;
+
+ close_snd_dev();
+ }
+ PJSUA_UNLOCK();
+}
+
+pj_status_t pjsua_aud_subsys_start(void)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL,
+ &close_snd_timer_cb);
+
+ return status;
+}
+
+pj_status_t pjsua_aud_subsys_destroy()
+{
+ unsigned i;
+
+ close_snd_dev();
+
+ if (pjsua_var.mconf) {
+ pjmedia_conf_destroy(pjsua_var.mconf);
+ pjsua_var.mconf = NULL;
+ }
+
+ if (pjsua_var.null_port) {
+ pjmedia_port_destroy(pjsua_var.null_port);
+ pjsua_var.null_port = NULL;
+ }
+
+ /* Destroy file players */
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
+ if (pjsua_var.player[i].port) {
+ pjmedia_port_destroy(pjsua_var.player[i].port);
+ pjsua_var.player[i].port = NULL;
+ }
+ }
+
+ /* Destroy file recorders */
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
+ if (pjsua_var.recorder[i].port) {
+ pjmedia_port_destroy(pjsua_var.recorder[i].port);
+ pjsua_var.recorder[i].port = NULL;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+void pjsua_aud_stop_stream(pjsua_call_media *call_med)
+{
+ pjmedia_stream *strm = call_med->strm.a.stream;
+ pjmedia_rtcp_stat stat;
+
+ if (strm) {
+ pjmedia_stream_send_rtcp_bye(strm);
+
+ if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) {
+ if (pjsua_var.mconf) {
+ pjsua_conf_remove_port(call_med->strm.a.conf_slot);
+ }
+ call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
+ }
+
+ if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
+ (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS))
+ {
+ /* Save RTP timestamp & sequence, so when media session is
+ * restarted, those values will be restored as the initial
+ * RTP timestamp & sequence of the new media session. So in
+ * the same call session, RTP timestamp and sequence are
+ * guaranteed to be contigue.
+ */
+ call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
+ call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
+ call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
+ }
+
+ if (pjsua_var.ua_cfg.cb.on_stream_destroyed) {
+ pjsua_var.ua_cfg.cb.on_stream_destroyed(call_med->call->index,
+ strm, call_med->idx);
+ }
+
+ pjmedia_stream_destroy(strm);
+ call_med->strm.a.stream = NULL;
+ }
+
+ pjsua_check_snd_dev_idle();
+}
+
+/*
+ * DTMF callback from the stream.
+ */
+static void dtmf_callback(pjmedia_stream *strm, void *user_data,
+ int digit)
+{
+ PJ_UNUSED_ARG(strm);
+
+ pj_log_push_indent();
+
+ /* For discussions about call mutex protection related to this
+ * callback, please see ticket #460:
+ * http://trac.pjsip.org/repos/ticket/460#comment:4
+ */
+ if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
+ pjsua_call_id call_id;
+
+ call_id = (pjsua_call_id)(long)user_data;
+ pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit);
+ }
+
+ pj_log_pop_indent();
+}
+
+
+pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med,
+ pj_pool_t *tmp_pool,
+ pjmedia_stream_info *si,
+ const pjmedia_sdp_session *local_sdp,
+ const pjmedia_sdp_session *remote_sdp)
+{
+ pjsua_call *call = call_med->call;
+ pjmedia_port *media_port;
+ unsigned strm_idx = call_med->idx;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_UNUSED_ARG(tmp_pool);
+ PJ_UNUSED_ARG(local_sdp);
+ PJ_UNUSED_ARG(remote_sdp);
+
+ PJ_LOG(4,(THIS_FILE,"Audio channel update.."));
+ pj_log_push_indent();
+
+ si->rtcp_sdes_bye_disabled = PJ_TRUE;
+
+ /* Check if no media is active */
+ if (si->dir != PJMEDIA_DIR_NONE) {
+
+ /* Override ptime, if this option is specified. */
+ if (pjsua_var.media_cfg.ptime != 0) {
+ si->param->setting.frm_per_pkt = (pj_uint8_t)
+ (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime);
+ if (si->param->setting.frm_per_pkt == 0)
+ si->param->setting.frm_per_pkt = 1;
+ }
+
+ /* Disable VAD, if this option is specified. */
+ if (pjsua_var.media_cfg.no_vad) {
+ si->param->setting.vad = 0;
+ }
+
+
+ /* Optionally, application may modify other stream settings here
+ * (such as jitter buffer parameters, codec ptime, etc.)
+ */
+ si->jb_init = pjsua_var.media_cfg.jb_init;
+ si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
+ si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
+ si->jb_max = pjsua_var.media_cfg.jb_max;
+
+ /* Set SSRC */
+ si->ssrc = call_med->ssrc;
+
+ /* Set RTP timestamp & sequence, normally these value are intialized
+ * automatically when stream session created, but for some cases (e.g:
+ * call reinvite, call update) timestamp and sequence need to be kept
+ * contigue.
+ */
+ si->rtp_ts = call_med->rtp_tx_ts;
+ si->rtp_seq = call_med->rtp_tx_seq;
+ si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ /* Enable/disable stream keep-alive and NAT hole punch. */
+ si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
+#endif
+
+ /* Create session based on session info. */
+ status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si,
+ call_med->tp, NULL,
+ &call_med->strm.a.stream);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ /* Start stream */
+ status = pjmedia_stream_start(call_med->strm.a.stream);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE)
+ pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream);
+
+ /* If DTMF callback is installed by application, install our
+ * callback to the session.
+ */
+ if (pjsua_var.ua_cfg.cb.on_dtmf_digit) {
+ pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream,
+ &dtmf_callback,
+ (void*)(long)(call->index));
+ }
+
+ /* Get the port interface of the first stream in the session.
+ * We need the port interface to add to the conference bridge.
+ */
+ pjmedia_stream_get_port(call_med->strm.a.stream, &media_port);
+
+ /* Notify application about stream creation.
+ * Note: application may modify media_port to point to different
+ * media port
+ */
+ if (pjsua_var.ua_cfg.cb.on_stream_created) {
+ pjsua_var.ua_cfg.cb.on_stream_created(call->index,
+ call_med->strm.a.stream,
+ strm_idx, &media_port);
+ }
+
+ /*
+ * Add the call to conference bridge.
+ */
+ {
+ char tmp[PJSIP_MAX_URL_SIZE];
+ pj_str_t port_name;
+
+ port_name.ptr = tmp;
+ port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
+ call->inv->dlg->remote.info->uri,
+ tmp, sizeof(tmp));
+ if (port_name.slen < 1) {
+ port_name = pj_str("call");
+ }
+ status = pjmedia_conf_add_port( pjsua_var.mconf,
+ call->inv->pool_prov,
+ media_port,
+ &port_name,
+ (unsigned*)
+ &call_med->strm.a.conf_slot);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+ }
+ }
+
+on_return:
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Get maxinum number of conference ports.
+ */
+PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
+{
+ return pjsua_var.media_cfg.max_media_ports;
+}
+
+
+/*
+ * Get current number of active ports in the bridge.
+ */
+PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
+{
+ unsigned ports[PJSUA_MAX_CONF_PORTS];
+ unsigned count = PJ_ARRAY_SIZE(ports);
+ pj_status_t status;
+
+ status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
+ if (status != PJ_SUCCESS)
+ count = 0;
+
+ return count;
+}
+
+
+/*
+ * Enumerate all conference ports.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
+ unsigned *count)
+{
+ return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
+}
+
+
+/*
+ * Get information about the specified conference port
+ */
+PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
+ pjsua_conf_port_info *info)
+{
+ pjmedia_conf_port_info cinfo;
+ unsigned i;
+ pj_status_t status;
+
+ status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(info, sizeof(*info));
+ info->slot_id = id;
+ info->name = cinfo.name;
+ info->clock_rate = cinfo.clock_rate;
+ info->channel_count = cinfo.channel_count;
+ info->samples_per_frame = cinfo.samples_per_frame;
+ info->bits_per_sample = cinfo.bits_per_sample;
+
+ /* Build array of listeners */
+ info->listener_cnt = cinfo.listener_cnt;
+ for (i=0; i<cinfo.listener_cnt; ++i) {
+ info->listeners[i] = cinfo.listener_slots[i];
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add arbitrary media port to PJSUA's conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool,
+ pjmedia_port *port,
+ pjsua_conf_port_id *p_id)
+{
+ pj_status_t status;
+
+ status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
+ port, NULL, (unsigned*)p_id);
+ if (status != PJ_SUCCESS) {
+ if (p_id)
+ *p_id = PJSUA_INVALID_ID;
+ }
+
+ return status;
+}
+
+
+/*
+ * Remove arbitrary slot from the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id)
+{
+ pj_status_t status;
+
+ status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id);
+ pjsua_check_snd_dev_idle();
+
+ return status;
+}
+
+
+/*
+ * Establish unidirectional media flow from souce to sink.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
+ pjsua_conf_port_id sink)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d",
+ (pjsua_var.is_mswitch ? "Switch" : "Conf"),
+ source, sink));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ /* If sound device idle timer is active, cancel it first. */
+ if (pjsua_var.snd_idle_timer.id) {
+ pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer);
+ pjsua_var.snd_idle_timer.id = PJ_FALSE;
+ }
+
+
+ /* For audio switchboard (i.e. APS-Direct):
+ * Check if sound device need to be reopened, i.e: its attributes
+ * (format, clock rate, channel count) must match to peer's.
+ * Note that sound device can be reopened only if it doesn't have
+ * any connection.
+ */
+ if (pjsua_var.is_mswitch) {
+ pjmedia_conf_port_info port0_info;
+ pjmedia_conf_port_info peer_info;
+ unsigned peer_id;
+ pj_bool_t need_reopen = PJ_FALSE;
+
+ peer_id = (source!=0)? source : sink;
+ status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id,
+ &peer_info);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Check if sound device is instantiated. */
+ need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
+ !pjsua_var.no_snd);
+
+ /* Check if sound device need to reopen because it needs to modify
+ * settings to match its peer. Sound device must be idle in this case
+ * though.
+ */
+ if (!need_reopen &&
+ port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0)
+ {
+ need_reopen = (peer_info.format.id != port0_info.format.id ||
+ peer_info.format.det.aud.avg_bps !=
+ port0_info.format.det.aud.avg_bps ||
+ peer_info.clock_rate != port0_info.clock_rate ||
+ peer_info.channel_count!=port0_info.channel_count);
+ }
+
+ if (need_reopen) {
+ if (pjsua_var.cap_dev != NULL_SND_DEV_ID) {
+ pjmedia_snd_port_param param;
+
+ pjmedia_snd_port_param_default(&param);
+ param.ec_options = pjsua_var.media_cfg.ec_options;
+
+ /* Create parameter based on peer info */
+ status = create_aud_param(&param.base, pjsua_var.cap_dev,
+ pjsua_var.play_dev,
+ peer_info.clock_rate,
+ peer_info.channel_count,
+ peer_info.samples_per_frame,
+ peer_info.bits_per_sample);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error opening sound device",
+ status);
+ goto on_return;
+ }
+
+ /* And peer format */
+ if (peer_info.format.id != PJMEDIA_FORMAT_PCM) {
+ param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT;
+ param.base.ext_fmt = peer_info.format;
+ }
+
+ param.options = 0;
+ status = open_snd_dev(&param);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error opening sound device",
+ status);
+ goto on_return;
+ }
+ } else {
+ /* Null-audio */
+ status = pjsua_set_snd_dev(pjsua_var.cap_dev,
+ pjsua_var.play_dev);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error opening sound device",
+ status);
+ goto on_return;
+ }
+ }
+ } else if (pjsua_var.no_snd) {
+ if (!pjsua_var.snd_is_on) {
+ pjsua_var.snd_is_on = PJ_TRUE;
+ /* Notify app */
+ if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
+ (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
+ }
+ }
+ }
+
+ } else {
+ /* The bridge version */
+
+ /* Create sound port if none is instantiated */
+ if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL &&
+ !pjsua_var.no_snd)
+ {
+ status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error opening sound device", status);
+ goto on_return;
+ }
+ } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) {
+ pjsua_var.snd_is_on = PJ_TRUE;
+ /* Notify app */
+ if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
+ (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
+ }
+ }
+ }
+
+on_return:
+ PJSUA_UNLOCK();
+
+ if (status == PJ_SUCCESS) {
+ status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
+ }
+
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Disconnect media flow from the source to destination port.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
+ pjsua_conf_port_id sink)
+{
+ pj_status_t status;
+
+ PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d",
+ (pjsua_var.is_mswitch ? "Switch" : "Conf"),
+ source, sink));
+ pj_log_push_indent();
+
+ status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
+ pjsua_check_snd_dev_idle();
+
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Adjust the signal level to be transmitted from the bridge to the
+ * specified port by making it louder or quieter.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot,
+ float level)
+{
+ return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot,
+ (int)((level-1) * 128));
+}
+
+/*
+ * Adjust the signal level to be received from the specified port (to
+ * the bridge) by making it louder or quieter.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot,
+ float level)
+{
+ return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot,
+ (int)((level-1) * 128));
+}
+
+
+/*
+ * Get last signal level transmitted to or received from the specified port.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot,
+ unsigned *tx_level,
+ unsigned *rx_level)
+{
+ return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot,
+ tx_level, rx_level);
+}
+
+/*****************************************************************************
+ * File player.
+ */
+
+static char* get_basename(const char *path, unsigned len)
+{
+ char *p = ((char*)path) + len;
+
+ if (len==0)
+ return p;
+
+ for (--p; p!=path && *p!='/' && *p!='\\'; ) --p;
+
+ return (p==path) ? p : p+1;
+}
+
+
+/*
+ * Create a file player, and automatically connect this player to
+ * the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
+ unsigned options,
+ pjsua_player_id *p_id)
+{
+ unsigned slot, file_id;
+ char path[PJ_MAXPATH];
+ pj_pool_t *pool = NULL;
+ pjmedia_port *port;
+ pj_status_t status = PJ_SUCCESS;
+
+ if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
+ return PJ_ETOOMANY;
+
+ PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..",
+ (int)filename->slen, filename->ptr));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
+ if (pjsua_var.player[file_id].port == NULL)
+ break;
+ }
+
+ if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
+ /* This is unexpected */
+ pj_assert(0);
+ status = PJ_EBUG;
+ goto on_error;
+ }
+
+ pj_memcpy(path, filename->ptr, filename->slen);
+ path[filename->slen] = '\0';
+
+ pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
+ if (!pool) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+
+ status = pjmedia_wav_player_port_create(
+ pool, path,
+ pjsua_var.mconf_cfg.samples_per_frame *
+ 1000 / pjsua_var.media_cfg.channel_count /
+ pjsua_var.media_cfg.clock_rate,
+ options, 0, &port);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
+ goto on_error;
+ }
+
+ status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
+ port, filename, &slot);
+ if (status != PJ_SUCCESS) {
+ pjmedia_port_destroy(port);
+ pjsua_perror(THIS_FILE, "Unable to add file to conference bridge",
+ status);
+ goto on_error;
+ }
+
+ pjsua_var.player[file_id].type = 0;
+ pjsua_var.player[file_id].pool = pool;
+ pjsua_var.player[file_id].port = port;
+ pjsua_var.player[file_id].slot = slot;
+
+ if (p_id) *p_id = file_id;
+
+ ++pjsua_var.player_cnt;
+
+ PJSUA_UNLOCK();
+
+ PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot));
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ PJSUA_UNLOCK();
+ if (pool) pj_pool_release(pool);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Create a file playlist media port, and automatically add the port
+ * to the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[],
+ unsigned file_count,
+ const pj_str_t *label,
+ unsigned options,
+ pjsua_player_id *p_id)
+{
+ unsigned slot, file_id, ptime;
+ pj_pool_t *pool = NULL;
+ pjmedia_port *port;
+ pj_status_t status = PJ_SUCCESS;
+
+ if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
+ return PJ_ETOOMANY;
+
+ PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
+ if (pjsua_var.player[file_id].port == NULL)
+ break;
+ }
+
+ if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
+ /* This is unexpected */
+ pj_assert(0);
+ status = PJ_EBUG;
+ goto on_error;
+ }
+
+
+ ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 /
+ pjsua_var.media_cfg.clock_rate;
+
+ pool = pjsua_pool_create("playlist", 1000, 1000);
+ if (!pool) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+
+ status = pjmedia_wav_playlist_create(pool, label,
+ file_names, file_count,
+ ptime, options, 0, &port);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create playlist", status);
+ goto on_error;
+ }
+
+ status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
+ port, &port->info.name, &slot);
+ if (status != PJ_SUCCESS) {
+ pjmedia_port_destroy(port);
+ pjsua_perror(THIS_FILE, "Unable to add port", status);
+ goto on_error;
+ }
+
+ pjsua_var.player[file_id].type = 1;
+ pjsua_var.player[file_id].pool = pool;
+ pjsua_var.player[file_id].port = port;
+ pjsua_var.player[file_id].slot = slot;
+
+ if (p_id) *p_id = file_id;
+
+ ++pjsua_var.player_cnt;
+
+ PJSUA_UNLOCK();
+
+ PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot));
+
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+
+on_error:
+ PJSUA_UNLOCK();
+ if (pool) pj_pool_release(pool);
+ pj_log_pop_indent();
+
+ return status;
+}
+
+
+/*
+ * Get conference port ID associated with player.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
+{
+ PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+
+ return pjsua_var.player[id].slot;
+}
+
+/*
+ * Get the media port for the player.
+ */
+PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id,
+ pjmedia_port **p_port)
+{
+ PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
+
+ *p_port = pjsua_var.player[id].port;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Set playback position.
+ */
+PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
+ pj_uint32_t samples)
+{
+ PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL);
+
+ return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
+}
+
+
+/*
+ * Close the file, remove the player from the bridge, and free
+ * resources associated with the file player.
+ */
+PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
+{
+ PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ if (pjsua_var.player[id].port) {
+ pjsua_conf_remove_port(pjsua_var.player[id].slot);
+ pjmedia_port_destroy(pjsua_var.player[id].port);
+ pjsua_var.player[id].port = NULL;
+ pjsua_var.player[id].slot = 0xFFFF;
+ pj_pool_release(pjsua_var.player[id].pool);
+ pjsua_var.player[id].pool = NULL;
+ pjsua_var.player_cnt--;
+ }
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * File recorder.
+ */
+
+/*
+ * Create a file recorder, and automatically connect this recorder to
+ * the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
+ unsigned enc_type,
+ void *enc_param,
+ pj_ssize_t max_size,
+ unsigned options,
+ pjsua_recorder_id *p_id)
+{
+ enum Format
+ {
+ FMT_UNKNOWN,
+ FMT_WAV,
+ FMT_MP3,
+ };
+ unsigned slot, file_id;
+ char path[PJ_MAXPATH];
+ pj_str_t ext;
+ int file_format;
+ pj_pool_t *pool = NULL;
+ pjmedia_port *port;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Filename must present */
+ PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL);
+
+ /* Don't support max_size at present */
+ PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL);
+
+ /* Don't support encoding type at present */
+ PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..",
+ (int)filename->slen, filename->ptr));
+ pj_log_push_indent();
+
+ if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) {
+ pj_log_pop_indent();
+ return PJ_ETOOMANY;
+ }
+
+ /* Determine the file format */
+ ext.ptr = filename->ptr + filename->slen - 4;
+ ext.slen = 4;
+
+ if (pj_stricmp2(&ext, ".wav") == 0)
+ file_format = FMT_WAV;
+ else if (pj_stricmp2(&ext, ".mp3") == 0)
+ file_format = FMT_MP3;
+ else {
+ PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to "
+ "determine file format for %.*s",
+ (int)filename->slen, filename->ptr));
+ pj_log_pop_indent();
+ return PJ_ENOTSUP;
+ }
+
+ PJSUA_LOCK();
+
+ for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
+ if (pjsua_var.recorder[file_id].port == NULL)
+ break;
+ }
+
+ if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
+ /* This is unexpected */
+ pj_assert(0);
+ status = PJ_EBUG;
+ goto on_return;
+ }
+
+ pj_memcpy(path, filename->ptr, filename->slen);
+ path[filename->slen] = '\0';
+
+ pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000);
+ if (!pool) {
+ status = PJ_ENOMEM;
+ goto on_return;
+ }
+
+ if (file_format == FMT_WAV) {
+ status = pjmedia_wav_writer_port_create(pool, path,
+ pjsua_var.media_cfg.clock_rate,
+ pjsua_var.mconf_cfg.channel_count,
+ pjsua_var.mconf_cfg.samples_per_frame,
+ pjsua_var.mconf_cfg.bits_per_sample,
+ options, 0, &port);
+ } else {
+ PJ_UNUSED_ARG(enc_param);
+ port = NULL;
+ status = PJ_ENOTSUP;
+ }
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
+ goto on_return;
+ }
+
+ status = pjmedia_conf_add_port(pjsua_var.mconf, pool,
+ port, filename, &slot);
+ if (status != PJ_SUCCESS) {
+ pjmedia_port_destroy(port);
+ goto on_return;
+ }
+
+ pjsua_var.recorder[file_id].port = port;
+ pjsua_var.recorder[file_id].slot = slot;
+ pjsua_var.recorder[file_id].pool = pool;
+
+ if (p_id) *p_id = file_id;
+
+ ++pjsua_var.rec_cnt;
+
+ PJSUA_UNLOCK();
+
+ PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot));
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_return:
+ PJSUA_UNLOCK();
+ if (pool) pj_pool_release(pool);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Get conference port associated with recorder.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
+{
+ PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
+
+ return pjsua_var.recorder[id].slot;
+}
+
+/*
+ * Get the media port for the recorder.
+ */
+PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id,
+ pjmedia_port **p_port)
+{
+ PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL);
+
+ *p_port = pjsua_var.recorder[id].port;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Destroy recorder (this will complete recording).
+ */
+PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
+{
+ PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder),
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ if (pjsua_var.recorder[id].port) {
+ pjsua_conf_remove_port(pjsua_var.recorder[id].slot);
+ pjmedia_port_destroy(pjsua_var.recorder[id].port);
+ pjsua_var.recorder[id].port = NULL;
+ pjsua_var.recorder[id].slot = 0xFFFF;
+ pj_pool_release(pjsua_var.recorder[id].pool);
+ pjsua_var.recorder[id].pool = NULL;
+ pjsua_var.rec_cnt--;
+ }
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Sound devices.
+ */
+
+/*
+ * Enum sound devices.
+ */
+
+PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[],
+ unsigned *count)
+{
+ unsigned i, dev_count;
+
+ dev_count = pjmedia_aud_dev_count();
+
+ if (dev_count > *count) dev_count = *count;
+
+ for (i=0; i<dev_count; ++i) {
+ pj_status_t status;
+
+ status = pjmedia_aud_dev_get_info(i, &info[i]);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ *count = dev_count;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
+ unsigned *count)
+{
+ unsigned i, dev_count;
+
+ dev_count = pjmedia_aud_dev_count();
+
+ if (dev_count > *count) dev_count = *count;
+ pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info));
+
+ for (i=0; i<dev_count; ++i) {
+ pjmedia_aud_dev_info ai;
+ pj_status_t status;
+
+ status = pjmedia_aud_dev_get_info(i, &ai);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ strncpy(info[i].name, ai.name, sizeof(info[i].name));
+ info[i].name[sizeof(info[i].name)-1] = '\0';
+ info[i].input_count = ai.input_count;
+ info[i].output_count = ai.output_count;
+ info[i].default_samples_per_sec = ai.default_samples_per_sec;
+ }
+
+ *count = dev_count;
+
+ return PJ_SUCCESS;
+}
+
+/* Create audio device parameter to open the device */
+static pj_status_t create_aud_param(pjmedia_aud_param *param,
+ pjmedia_aud_dev_index capture_dev,
+ pjmedia_aud_dev_index playback_dev,
+ unsigned clock_rate,
+ unsigned channel_count,
+ unsigned samples_per_frame,
+ unsigned bits_per_sample)
+{
+ pj_status_t status;
+
+ /* Normalize device ID with new convention about default device ID */
+ if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV)
+ playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+
+ /* Create default parameters for the device */
+ status = pjmedia_aud_dev_default_param(capture_dev, param);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error retrieving default audio "
+ "device parameters", status);
+ return status;
+ }
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = capture_dev;
+ param->play_id = playback_dev;
+ param->clock_rate = clock_rate;
+ param->channel_count = channel_count;
+ param->samples_per_frame = samples_per_frame;
+ param->bits_per_sample = bits_per_sample;
+
+ /* Update the setting with user preference */
+#define update_param(cap, field) \
+ if (pjsua_var.aud_param.flags & cap) { \
+ param->flags |= cap; \
+ param->field = pjsua_var.aud_param.field; \
+ }
+ update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
+ update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
+ update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
+ update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
+#undef update_param
+
+ /* Latency settings */
+ param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY);
+ param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency;
+ param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency;
+
+ /* EC settings */
+ if (pjsua_var.media_cfg.ec_tail_len) {
+ param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+ param->ec_enabled = PJ_TRUE;
+ param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len;
+ } else {
+ param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Internal: the first time the audio device is opened (during app
+ * startup), retrieve the audio settings such as volume level
+ * so that aud_get_settings() will work.
+ */
+static pj_status_t update_initial_aud_param()
+{
+ pjmedia_aud_stream *strm;
+ pjmedia_aud_param param;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG);
+
+ strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+
+ status = pjmedia_aud_stream_get_param(strm, &param);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error audio stream "
+ "device parameters", status);
+ return status;
+ }
+
+#define update_saved_param(cap, field) \
+ if (param.flags & cap) { \
+ pjsua_var.aud_param.flags |= cap; \
+ pjsua_var.aud_param.field = param.field; \
+ }
+
+ update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol);
+ update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol);
+ update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route);
+ update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route);
+#undef update_saved_param
+
+ return PJ_SUCCESS;
+}
+
+/* Get format name */
+static const char *get_fmt_name(pj_uint32_t id)
+{
+ static char name[8];
+
+ if (id == PJMEDIA_FORMAT_L16)
+ return "PCM";
+ pj_memcpy(name, &id, 4);
+ name[4] = '\0';
+ return name;
+}
+
+/* Open sound device with the setting. */
+static pj_status_t open_snd_dev(pjmedia_snd_port_param *param)
+{
+ pjmedia_port *conf_port;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(param, PJ_EINVAL);
+
+ /* Check if NULL sound device is used */
+ if (NULL_SND_DEV_ID==param->base.rec_id ||
+ NULL_SND_DEV_ID==param->base.play_id)
+ {
+ return pjsua_set_null_snd_dev();
+ }
+
+ /* Close existing sound port */
+ close_snd_dev();
+
+ /* Notify app */
+ if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
+ (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
+ }
+
+ /* Create memory pool for sound device. */
+ pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
+ PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
+
+
+ PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms",
+ get_fmt_name(param->base.ext_fmt.id),
+ param->base.clock_rate, param->base.channel_count,
+ param->base.samples_per_frame / param->base.channel_count *
+ 1000 / param->base.clock_rate));
+ pj_log_push_indent();
+
+ status = pjmedia_snd_port_create2( pjsua_var.snd_pool,
+ param, &pjsua_var.snd_port);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Get the port0 of the conference bridge. */
+ conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
+ pj_assert(conf_port != NULL);
+
+ /* For conference bridge, resample if necessary if the bridge's
+ * clock rate is different than the sound device's clock rate.
+ */
+ if (!pjsua_var.is_mswitch &&
+ param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM &&
+ PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate)
+ {
+ pjmedia_port *resample_port;
+ unsigned resample_opt = 0;
+
+ if (pjsua_var.media_cfg.quality >= 3 &&
+ pjsua_var.media_cfg.quality <= 4)
+ {
+ resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER;
+ }
+ else if (pjsua_var.media_cfg.quality < 3) {
+ resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR;
+ }
+
+ status = pjmedia_resample_port_create(pjsua_var.snd_pool,
+ conf_port,
+ param->base.clock_rate,
+ resample_opt,
+ &resample_port);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4, (THIS_FILE,
+ "Error creating resample port: %s",
+ errmsg));
+ close_snd_dev();
+ goto on_error;
+ }
+
+ conf_port = resample_port;
+ }
+
+ /* Otherwise for audio switchboard, the switch's port0 setting is
+ * derived from the sound device setting, so update the setting.
+ */
+ if (pjsua_var.is_mswitch) {
+ if (param->base.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) {
+ conf_port->info.fmt = param->base.ext_fmt;
+ } else {
+ unsigned bps, ptime_usec;
+ bps = param->base.clock_rate * param->base.bits_per_sample;
+ ptime_usec = param->base.samples_per_frame /
+ param->base.channel_count * 1000000 /
+ param->base.clock_rate;
+ pjmedia_format_init_audio(&conf_port->info.fmt,
+ PJMEDIA_FORMAT_PCM,
+ param->base.clock_rate,
+ param->base.channel_count,
+ param->base.bits_per_sample,
+ ptime_usec,
+ bps, bps);
+ }
+ }
+
+
+ /* Connect sound port to the bridge */
+ status = pjmedia_snd_port_connect(pjsua_var.snd_port,
+ conf_port );
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to connect conference port to "
+ "sound device", status);
+ pjmedia_snd_port_destroy(pjsua_var.snd_port);
+ pjsua_var.snd_port = NULL;
+ goto on_error;
+ }
+
+ /* Save the device IDs */
+ pjsua_var.cap_dev = param->base.rec_id;
+ pjsua_var.play_dev = param->base.play_id;
+
+ /* Update sound device name. */
+ {
+ pjmedia_aud_dev_info rec_info;
+ pjmedia_aud_stream *strm;
+ pjmedia_aud_param si;
+ pj_str_t tmp;
+
+ strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+ status = pjmedia_aud_stream_get_param(strm, &si);
+ if (status == PJ_SUCCESS)
+ status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info);
+
+ if (status==PJ_SUCCESS) {
+ if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) {
+ char tmp_buf[128];
+ int tmp_buf_len = sizeof(tmp_buf);
+
+ tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1,
+ "%s (%dKHz)",
+ rec_info.name,
+ param->base.clock_rate/1000);
+ pj_strset(&tmp, tmp_buf, tmp_buf_len);
+ pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp);
+ } else {
+ pjmedia_conf_set_port0_name(pjsua_var.mconf,
+ pj_cstr(&tmp, rec_info.name));
+ }
+ }
+
+ /* Any error is not major, let it through */
+ status = PJ_SUCCESS;
+ }
+
+ /* If this is the first time the audio device is open, retrieve some
+ * settings from the device (such as volume settings) so that the
+ * pjsua_snd_get_setting() work.
+ */
+ if (pjsua_var.aud_open_cnt == 0) {
+ update_initial_aud_param();
+ ++pjsua_var.aud_open_cnt;
+ }
+
+ pjsua_var.snd_is_on = PJ_TRUE;
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/* Close existing sound device */
+static void close_snd_dev(void)
+{
+ pj_log_push_indent();
+
+ /* Notify app */
+ if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
+ (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0);
+ }
+
+ /* Close sound device */
+ if (pjsua_var.snd_port) {
+ pjmedia_aud_dev_info cap_info, play_info;
+ pjmedia_aud_stream *strm;
+ pjmedia_aud_param param;
+
+ strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+ pjmedia_aud_stream_get_param(strm, &param);
+
+ if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS)
+ cap_info.name[0] = '\0';
+ if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS)
+ play_info.name[0] = '\0';
+
+ PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
+ "%s sound capture device",
+ play_info.name, cap_info.name));
+
+ pjmedia_snd_port_disconnect(pjsua_var.snd_port);
+ pjmedia_snd_port_destroy(pjsua_var.snd_port);
+ pjsua_var.snd_port = NULL;
+ }
+
+ /* Close null sound device */
+ if (pjsua_var.null_snd) {
+ PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
+ pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
+ pjsua_var.null_snd = NULL;
+ }
+
+ if (pjsua_var.snd_pool)
+ pj_pool_release(pjsua_var.snd_pool);
+
+ pjsua_var.snd_pool = NULL;
+ pjsua_var.snd_is_on = PJ_FALSE;
+
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Select or change sound device. Application may call this function at
+ * any time to replace current sound device.
+ */
+PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
+ int playback_dev)
+{
+ unsigned alt_cr_cnt = 1;
+ unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000};
+ unsigned i;
+ pj_status_t status = -1;
+
+ PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d",
+ capture_dev, playback_dev));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ /* Null-sound */
+ if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) {
+ PJSUA_UNLOCK();
+ status = pjsua_set_null_snd_dev();
+ pj_log_pop_indent();
+ return status;
+ }
+
+ /* Set default clock rate */
+ alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate;
+ if (alt_cr[0] == 0)
+ alt_cr[0] = pjsua_var.media_cfg.clock_rate;
+
+ /* Allow retrying of different clock rate if we're using conference
+ * bridge (meaning audio format is always PCM), otherwise lock on
+ * to one clock rate.
+ */
+ if (pjsua_var.is_mswitch) {
+ alt_cr_cnt = 1;
+ } else {
+ alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr);
+ }
+
+ /* Attempts to open the sound device with different clock rates */
+ for (i=0; i<alt_cr_cnt; ++i) {
+ pjmedia_snd_port_param param;
+ unsigned samples_per_frame;
+
+ /* Create the default audio param */
+ samples_per_frame = alt_cr[i] *
+ pjsua_var.media_cfg.audio_frame_ptime *
+ pjsua_var.media_cfg.channel_count / 1000;
+ pjmedia_snd_port_param_default(&param);
+ param.ec_options = pjsua_var.media_cfg.ec_options;
+ status = create_aud_param(&param.base, capture_dev, playback_dev,
+ alt_cr[i], pjsua_var.media_cfg.channel_count,
+ samples_per_frame, 16);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Open! */
+ param.options = 0;
+ status = open_snd_dev(&param);
+ if (status == PJ_SUCCESS)
+ break;
+ }
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to open sound device", status);
+ goto on_error;
+ }
+
+ pjsua_var.no_snd = PJ_FALSE;
+ pjsua_var.snd_is_on = PJ_TRUE;
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Get currently active sound devices. If sound devices has not been created
+ * (for example when pjsua_start() is not called), it is possible that
+ * the function returns PJ_SUCCESS with -1 as device IDs.
+ */
+PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev,
+ int *playback_dev)
+{
+ PJSUA_LOCK();
+
+ if (capture_dev) {
+ *capture_dev = pjsua_var.cap_dev;
+ }
+ if (playback_dev) {
+ *playback_dev = pjsua_var.play_dev;
+ }
+
+ PJSUA_UNLOCK();
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Use null sound device.
+ */
+PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
+{
+ pjmedia_port *conf_port;
+ pj_status_t status;
+
+ PJ_LOG(4,(THIS_FILE, "Setting null sound device.."));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ /* Close existing sound device */
+ close_snd_dev();
+
+ /* Notify app */
+ if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) {
+ (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1);
+ }
+
+ /* Create memory pool for sound device. */
+ pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000);
+ PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM);
+
+ PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
+
+ /* Get the port0 of the conference bridge. */
+ conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
+ pj_assert(conf_port != NULL);
+
+ /* Create master port, connecting port0 of the conference bridge to
+ * a null port.
+ */
+ status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port,
+ conf_port, 0, &pjsua_var.null_snd);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create null sound device",
+ status);
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+ }
+
+ /* Start the master port */
+ status = pjmedia_master_port_start(pjsua_var.null_snd);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ pjsua_var.cap_dev = NULL_SND_DEV_ID;
+ pjsua_var.play_dev = NULL_SND_DEV_ID;
+
+ pjsua_var.no_snd = PJ_FALSE;
+ pjsua_var.snd_is_on = PJ_TRUE;
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Use no device!
+ */
+PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void)
+{
+ PJSUA_LOCK();
+
+ /* Close existing sound device */
+ close_snd_dev();
+ pjsua_var.no_snd = PJ_TRUE;
+
+ PJSUA_UNLOCK();
+
+ return pjmedia_conf_get_master_port(pjsua_var.mconf);
+}
+
+
+/*
+ * Configure the AEC settings of the sound port.
+ */
+PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ PJSUA_LOCK();
+
+ pjsua_var.media_cfg.ec_tail_len = tail_ms;
+ pjsua_var.media_cfg.ec_options = options;
+
+ if (pjsua_var.snd_port)
+ status = pjmedia_snd_port_set_ec(pjsua_var.snd_port, pjsua_var.pool,
+ tail_ms, options);
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+
+/*
+ * Get current AEC tail length.
+ */
+PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms)
+{
+ *p_tail_ms = pjsua_var.media_cfg.ec_tail_len;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Check whether the sound device is currently active.
+ */
+PJ_DEF(pj_bool_t) pjsua_snd_is_active(void)
+{
+ return pjsua_var.snd_port != NULL;
+}
+
+
+/*
+ * Configure sound device setting to the sound device being used.
+ */
+PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap,
+ const void *pval,
+ pj_bool_t keep)
+{
+ pj_status_t status;
+
+ /* Check if we are allowed to set the cap */
+ if ((cap & pjsua_var.aud_svmask) == 0) {
+ return PJMEDIA_EAUD_INVCAP;
+ }
+
+ PJSUA_LOCK();
+
+ /* If sound is active, set it immediately */
+ if (pjsua_snd_is_active()) {
+ pjmedia_aud_stream *strm;
+
+ strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+ status = pjmedia_aud_stream_set_cap(strm, cap, pval);
+ } else {
+ status = PJ_SUCCESS;
+ }
+
+ if (status != PJ_SUCCESS) {
+ PJSUA_UNLOCK();
+ return status;
+ }
+
+ /* Save in internal param for later device open */
+ if (keep) {
+ status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param,
+ cap, pval);
+ }
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+/*
+ * Retrieve a sound device setting.
+ */
+PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap,
+ void *pval)
+{
+ pj_status_t status;
+
+ PJSUA_LOCK();
+
+ /* If sound device has never been opened before, open it to
+ * retrieve the initial setting from the device (e.g. audio
+ * volume)
+ */
+ if (pjsua_var.aud_open_cnt==0) {
+ PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings"));
+ pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
+ close_snd_dev();
+ }
+
+ if (pjsua_snd_is_active()) {
+ /* Sound is active, retrieve from device directly */
+ pjmedia_aud_stream *strm;
+
+ strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port);
+ status = pjmedia_aud_stream_get_cap(strm, cap, pval);
+ } else {
+ /* Otherwise retrieve from internal param */
+ status = pjmedia_aud_param_get_cap(&pjsua_var.aud_param,
+ cap, pval);
+ }
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+#endif /* PJSUA_MEDIA_HAS_PJMEDIA */
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
new file mode 100644
index 0000000..6f14709
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -0,0 +1,4397 @@
+/* $Id: pjsua_call.c 4176 2012-06-23 03:06:52Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+
+#define THIS_FILE "pjsua_call.c"
+
+
+/* Retry interval of sending re-INVITE for locking a codec when remote
+ * SDP answer contains multiple codec, in milliseconds.
+ */
+#define LOCK_CODEC_RETRY_INTERVAL 200
+
+/*
+ * Max UPDATE/re-INVITE retry to lock codec
+ */
+#define LOCK_CODEC_MAX_RETRY 5
+
+
+/*
+ * The INFO method.
+ */
+const pjsip_method pjsip_info_method =
+{
+ PJSIP_OTHER_METHOD,
+ { "INFO", 4 }
+};
+
+
+/* This callback receives notification from invite session when the
+ * session state has changed.
+ */
+static void pjsua_call_on_state_changed(pjsip_inv_session *inv,
+ pjsip_event *e);
+
+/* This callback is called by invite session framework when UAC session
+ * has forked.
+ */
+static void pjsua_call_on_forked( pjsip_inv_session *inv,
+ pjsip_event *e);
+
+/*
+ * Callback to be called when SDP offer/answer negotiation has just completed
+ * in the session. This function will start/update media if negotiation
+ * has succeeded.
+ */
+static void pjsua_call_on_media_update(pjsip_inv_session *inv,
+ pj_status_t status);
+
+/*
+ * Called when session received new offer.
+ */
+static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
+ const pjmedia_sdp_session *offer);
+
+/*
+ * Called to generate new offer.
+ */
+static void pjsua_call_on_create_offer(pjsip_inv_session *inv,
+ pjmedia_sdp_session **offer);
+
+/*
+ * This callback is called when transaction state has changed in INVITE
+ * session. We use this to trap:
+ * - incoming REFER request.
+ * - incoming MESSAGE request.
+ */
+static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
+ pjsip_transaction *tsx,
+ pjsip_event *e);
+
+/*
+ * Redirection handler.
+ */
+static pjsip_redirect_op pjsua_call_on_redirected(pjsip_inv_session *inv,
+ const pjsip_uri *target,
+ const pjsip_event *e);
+
+
+/* Create SDP for call hold. */
+static pj_status_t create_sdp_of_call_hold(pjsua_call *call,
+ pjmedia_sdp_session **p_sdp);
+
+/*
+ * Callback called by event framework when the xfer subscription state
+ * has changed.
+ */
+static void xfer_client_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
+static void xfer_server_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
+
+/*
+ * Reset call descriptor.
+ */
+static void reset_call(pjsua_call_id id)
+{
+ pjsua_call *call = &pjsua_var.calls[id];
+ unsigned i;
+
+ pj_bzero(call, sizeof(*call));
+ call->index = id;
+ call->last_text.ptr = call->last_text_buf_;
+ for (i=0; i<PJ_ARRAY_SIZE(call->media); ++i) {
+ pjsua_call_media *call_med = &call->media[i];
+ call_med->ssrc = pj_rand();
+ call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
+ call_med->strm.v.cap_win_id = PJSUA_INVALID_ID;
+ call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID;
+ call_med->call = call;
+ call_med->idx = i;
+ call_med->tp_auto_del = PJ_TRUE;
+ }
+ pjsua_call_setting_default(&call->opt);
+}
+
+
+/*
+ * Init call subsystem.
+ */
+pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg)
+{
+ pjsip_inv_callback inv_cb;
+ unsigned i;
+ const pj_str_t str_norefersub = { "norefersub", 10 };
+ pj_status_t status;
+
+ /* Init calls array. */
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.calls); ++i)
+ reset_call(i);
+
+ /* Copy config */
+ pjsua_config_dup(pjsua_var.pool, &pjsua_var.ua_cfg, cfg);
+
+ /* Verify settings */
+ if (pjsua_var.ua_cfg.max_calls >= PJSUA_MAX_CALLS) {
+ pjsua_var.ua_cfg.max_calls = PJSUA_MAX_CALLS;
+ }
+
+ /* Check the route URI's and force loose route if required */
+ for (i=0; i<pjsua_var.ua_cfg.outbound_proxy_cnt; ++i) {
+ status = normalize_route_uri(pjsua_var.pool,
+ &pjsua_var.ua_cfg.outbound_proxy[i]);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Initialize invite session callback. */
+ pj_bzero(&inv_cb, sizeof(inv_cb));
+ inv_cb.on_state_changed = &pjsua_call_on_state_changed;
+ inv_cb.on_new_session = &pjsua_call_on_forked;
+ inv_cb.on_media_update = &pjsua_call_on_media_update;
+ inv_cb.on_rx_offer = &pjsua_call_on_rx_offer;
+ inv_cb.on_create_offer = &pjsua_call_on_create_offer;
+ inv_cb.on_tsx_state_changed = &pjsua_call_on_tsx_state_changed;
+ inv_cb.on_redirected = &pjsua_call_on_redirected;
+
+ /* Initialize invite session module: */
+ status = pjsip_inv_usage_init(pjsua_var.endpt, &inv_cb);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Add "norefersub" in Supported header */
+ pjsip_endpt_add_capability(pjsua_var.endpt, NULL, PJSIP_H_SUPPORTED,
+ NULL, 1, &str_norefersub);
+
+ return status;
+}
+
+
+/*
+ * Start call subsystem.
+ */
+pj_status_t pjsua_call_subsys_start(void)
+{
+ /* Nothing to do */
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get maximum number of calls configured in pjsua.
+ */
+PJ_DEF(unsigned) pjsua_call_get_max_count(void)
+{
+ return pjsua_var.ua_cfg.max_calls;
+}
+
+
+/*
+ * Get number of currently active calls.
+ */
+PJ_DEF(unsigned) pjsua_call_get_count(void)
+{
+ return pjsua_var.call_cnt;
+}
+
+
+/*
+ * Enum calls.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_calls( pjsua_call_id ids[],
+ unsigned *count)
+{
+ unsigned i, c;
+
+ PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL);
+
+ PJSUA_LOCK();
+
+ for (i=0, c=0; c<*count && i<pjsua_var.ua_cfg.max_calls; ++i) {
+ if (!pjsua_var.calls[i].inv)
+ continue;
+ ids[c] = i;
+ ++c;
+ }
+
+ *count = c;
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+}
+
+
+/* Allocate one call id */
+static pjsua_call_id alloc_call_id(void)
+{
+ pjsua_call_id cid;
+
+#if 1
+ /* New algorithm: round-robin */
+ if (pjsua_var.next_call_id >= (int)pjsua_var.ua_cfg.max_calls ||
+ pjsua_var.next_call_id < 0)
+ {
+ pjsua_var.next_call_id = 0;
+ }
+
+ for (cid=pjsua_var.next_call_id;
+ cid<(int)pjsua_var.ua_cfg.max_calls;
+ ++cid)
+ {
+ if (pjsua_var.calls[cid].inv == NULL &&
+ pjsua_var.calls[cid].async_call.dlg == NULL)
+ {
+ ++pjsua_var.next_call_id;
+ return cid;
+ }
+ }
+
+ for (cid=0; cid < pjsua_var.next_call_id; ++cid) {
+ if (pjsua_var.calls[cid].inv == NULL &&
+ pjsua_var.calls[cid].async_call.dlg == NULL)
+ {
+ ++pjsua_var.next_call_id;
+ return cid;
+ }
+ }
+
+#else
+ /* Old algorithm */
+ for (cid=0; cid<(int)pjsua_var.ua_cfg.max_calls; ++cid) {
+ if (pjsua_var.calls[cid].inv == NULL)
+ return cid;
+ }
+#endif
+
+ return PJSUA_INVALID_ID;
+}
+
+/* Get signaling secure level.
+ * Return:
+ * 0: if signaling is not secure
+ * 1: if TLS transport is used for immediate hop
+ * 2: if end-to-end signaling is secure.
+ */
+static int get_secure_level(pjsua_acc_id acc_id, const pj_str_t *dst_uri)
+{
+ const pj_str_t tls = pj_str(";transport=tls");
+ const pj_str_t sips = pj_str("sips:");
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+
+ if (pj_stristr(dst_uri, &sips))
+ return 2;
+
+ if (!pj_list_empty(&acc->route_set)) {
+ pjsip_route_hdr *r = acc->route_set.next;
+ pjsip_uri *uri = r->name_addr.uri;
+ pjsip_sip_uri *sip_uri;
+
+ sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+ if (pj_stricmp2(&sip_uri->transport_param, "tls")==0)
+ return 1;
+
+ } else {
+ if (pj_stristr(dst_uri, &tls))
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+static int call_get_secure_level(pjsua_call *call)
+{
+ if (call->inv->dlg->secure)
+ return 2;
+
+ if (!pj_list_empty(&call->inv->dlg->route_set)) {
+ pjsip_route_hdr *r = call->inv->dlg->route_set.next;
+ pjsip_uri *uri = r->name_addr.uri;
+ pjsip_sip_uri *sip_uri;
+
+ sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+ if (pj_stricmp2(&sip_uri->transport_param, "tls")==0)
+ return 1;
+
+ } else {
+ pjsip_sip_uri *sip_uri;
+
+ if (PJSIP_URI_SCHEME_IS_SIPS(call->inv->dlg->target))
+ return 2;
+ if (!PJSIP_URI_SCHEME_IS_SIP(call->inv->dlg->target))
+ return 0;
+
+ sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(call->inv->dlg->target);
+ if (pj_stricmp2(&sip_uri->transport_param, "tls")==0)
+ return 1;
+ }
+
+ return 0;
+}
+*/
+
+/* Outgoing call callback when media transport creation is completed. */
+static pj_status_t
+on_make_call_med_tp_complete(pjsua_call_id call_id,
+ const pjsua_med_tp_state_info *info)
+{
+ pjmedia_sdp_session *offer;
+ pjsip_inv_session *inv = NULL;
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
+ pjsip_dialog *dlg = call->async_call.dlg;
+ unsigned options = 0;
+ pjsip_tx_data *tdata;
+ pj_status_t status = (info? info->status: PJ_SUCCESS);
+
+ PJSUA_LOCK();
+
+ /* Increment the dialog's lock otherwise when invite session creation
+ * fails the dialog will be destroyed prematurely.
+ */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Decrement dialog session. */
+ pjsip_dlg_dec_session(dlg, &pjsua_var.mod);
+
+ if (status != PJ_SUCCESS) {
+ pj_str_t err_str;
+ int title_len;
+
+ call->last_code = PJSIP_SC_TEMPORARILY_UNAVAILABLE;
+ pj_strcpy2(&call->last_text, "Media init error: ");
+
+ title_len = call->last_text.slen;
+ err_str = pj_strerror(status, call->last_text_buf_ + title_len,
+ sizeof(call->last_text_buf_) - title_len);
+ call->last_text.slen += err_str.slen;
+
+ pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+ goto on_error;
+ }
+
+ /* pjsua_media_channel_deinit() has been called. */
+ if (call->async_call.med_ch_deinit)
+ goto on_error;
+
+ /* Create offer */
+ status = pjsua_media_channel_create_sdp(call->index, dlg->pool, NULL,
+ &offer, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+ goto on_error;
+ }
+
+ /* Create the INVITE session: */
+ options |= PJSIP_INV_SUPPORT_100REL;
+ if (acc->cfg.require_100rel)
+ options |= PJSIP_INV_REQUIRE_100REL;
+ if (acc->cfg.use_timer != PJSUA_SIP_TIMER_INACTIVE) {
+ options |= PJSIP_INV_SUPPORT_TIMER;
+ if (acc->cfg.use_timer == PJSUA_SIP_TIMER_REQUIRED)
+ options |= PJSIP_INV_REQUIRE_TIMER;
+ else if (acc->cfg.use_timer == PJSUA_SIP_TIMER_ALWAYS)
+ options |= PJSIP_INV_ALWAYS_USE_TIMER;
+ }
+
+ status = pjsip_inv_create_uac( dlg, offer, options, &inv);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Invite session creation failed", status);
+ goto on_error;
+ }
+
+ /* Init Session Timers */
+ status = pjsip_timer_init_session(inv, &acc->cfg.timer_setting);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Session Timer init failed", status);
+ goto on_error;
+ }
+
+ /* Create and associate our data in the session. */
+ call->inv = inv;
+
+ dlg->mod_data[pjsua_var.mod.id] = call;
+ inv->mod_data[pjsua_var.mod.id] = call;
+
+ /* If account is locked to specific transport, then lock dialog
+ * to this transport too.
+ */
+ if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
+ pjsip_tpselector tp_sel;
+
+ pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
+ pjsip_dlg_set_transport(dlg, &tp_sel);
+ }
+
+ /* Set dialog Route-Set: */
+ if (!pj_list_empty(&acc->route_set))
+ pjsip_dlg_set_route_set(dlg, &acc->route_set);
+
+
+ /* Set credentials: */
+ if (acc->cred_cnt) {
+ pjsip_auth_clt_set_credentials( &dlg->auth_sess,
+ acc->cred_cnt, acc->cred);
+ }
+
+ /* Set authentication preference */
+ pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref);
+
+ /* Create initial INVITE: */
+
+ status = pjsip_inv_invite(inv, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create initial INVITE request",
+ status);
+ goto on_error;
+ }
+
+
+ /* Add additional headers etc */
+
+ pjsua_process_msg_data( tdata,
+ call->async_call.call_var.out_call.msg_data);
+
+ /* Must increment call counter now */
+ ++pjsua_var.call_cnt;
+
+ /* Send initial INVITE: */
+
+ status = pjsip_inv_send_msg(inv, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send initial INVITE request",
+ status);
+
+ /* Upon failure to send first request, the invite
+ * session would have been cleared.
+ */
+ inv = NULL;
+ goto on_error;
+ }
+
+ /* Done. */
+
+ pjsip_dlg_dec_lock(dlg);
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (inv == NULL && call_id != -1 && pjsua_var.ua_cfg.cb.on_call_state)
+ (*pjsua_var.ua_cfg.cb.on_call_state)(call_id, NULL);
+
+ if (dlg) {
+ /* This may destroy the dialog */
+ pjsip_dlg_dec_lock(dlg);
+ }
+
+ if (inv != NULL) {
+ pjsip_inv_terminate(inv, PJSIP_SC_OK, PJ_FALSE);
+ }
+
+ if (call_id != -1) {
+ reset_call(call_id);
+ pjsua_media_channel_deinit(call_id);
+ }
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+
+/*
+ * Initialize call settings based on account ID.
+ */
+PJ_DEF(void) pjsua_call_setting_default(pjsua_call_setting *opt)
+{
+ pj_assert(opt);
+
+ pj_bzero(opt, sizeof(*opt));
+ opt->flag = PJSUA_CALL_INCLUDE_DISABLED_MEDIA;
+ opt->aud_cnt = 1;
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ opt->vid_cnt = 1;
+ opt->req_keyframe_method = PJSUA_VID_REQ_KEYFRAME_SIP_INFO |
+ PJSUA_VID_REQ_KEYFRAME_RTCP_PLI;
+#endif
+}
+
+static pj_status_t apply_call_setting(pjsua_call *call,
+ const pjsua_call_setting *opt,
+ const pjmedia_sdp_session *rem_sdp)
+{
+ pj_assert(call);
+
+ if (!opt)
+ return PJ_SUCCESS;
+
+#if !PJMEDIA_HAS_VIDEO
+ pj_assert(opt->vid_cnt == 0);
+#endif
+
+ /* If call is established, reinit media channel */
+ if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
+ pjsua_call_setting old_opt;
+ pj_status_t status;
+
+ old_opt = call->opt;
+ call->opt = *opt;
+
+ /* Reinit media channel when media count is changed or we are the
+ * answerer (as remote offer may 'extremely' modify the existing
+ * media session, e.g: media type order).
+ */
+ if (rem_sdp ||
+ opt->aud_cnt!=old_opt.aud_cnt || opt->vid_cnt!=old_opt.vid_cnt)
+ {
+ pjsip_role_e role = rem_sdp? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC;
+ status = pjsua_media_channel_init(call->index, role,
+ call->secure_level,
+ call->inv->pool_prov,
+ rem_sdp, NULL,
+ PJ_FALSE, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error re-initializing media channel",
+ status);
+ return status;
+ }
+ }
+ } else {
+ call->opt = *opt;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Make outgoing call to the specified URI using the specified account.
+ */
+PJ_DEF(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id,
+ const pj_str_t *dest_uri,
+ const pjsua_call_setting *opt,
+ void *user_data,
+ const pjsua_msg_data *msg_data,
+ pjsua_call_id *p_call_id)
+{
+ pj_pool_t *tmp_pool = NULL;
+ pjsip_dialog *dlg = NULL;
+ pjsua_acc *acc;
+ pjsua_call *call;
+ int call_id = -1;
+ pj_str_t contact;
+ pj_status_t status;
+
+
+ /* Check that account is valid */
+ PJ_ASSERT_RETURN(acc_id>=0 || acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+ PJ_EINVAL);
+
+ /* Check arguments */
+ PJ_ASSERT_RETURN(dest_uri, PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Making call with acc #%d to %.*s", acc_id,
+ (int)dest_uri->slen, dest_uri->ptr));
+
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ /* Create sound port if none is instantiated, to check if sound device
+ * can be used. But only do this with the conference bridge, as with
+ * audio switchboard (i.e. APS-Direct), we can only open the sound
+ * device once the correct format has been known
+ */
+ if (!pjsua_var.is_mswitch && pjsua_var.snd_port==NULL &&
+ pjsua_var.null_snd==NULL && !pjsua_var.no_snd)
+ {
+ status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ acc = &pjsua_var.acc[acc_id];
+ if (!acc->valid) {
+ pjsua_perror(THIS_FILE, "Unable to make call because account "
+ "is not valid", PJ_EINVALIDOP);
+ status = PJ_EINVALIDOP;
+ goto on_error;
+ }
+
+ /* Find free call slot. */
+ call_id = alloc_call_id();
+
+ if (call_id == PJSUA_INVALID_ID) {
+ pjsua_perror(THIS_FILE, "Error making call", PJ_ETOOMANY);
+ status = PJ_ETOOMANY;
+ goto on_error;
+ }
+
+ call = &pjsua_var.calls[call_id];
+
+ /* Associate session with account */
+ call->acc_id = acc_id;
+ call->call_hold_type = acc->cfg.call_hold_type;
+
+ /* Apply call setting */
+ status = apply_call_setting(call, opt, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Failed to apply call setting", status);
+ goto on_error;
+ }
+
+ /* Create temporary pool */
+ tmp_pool = pjsua_pool_create("tmpcall10", 512, 256);
+
+ /* Verify that destination URI is valid before calling
+ * pjsua_acc_create_uac_contact, or otherwise there
+ * a misleading "Invalid Contact URI" error will be printed
+ * when pjsua_acc_create_uac_contact() fails.
+ */
+ if (1) {
+ pjsip_uri *uri;
+ pj_str_t dup;
+
+ pj_strdup_with_null(tmp_pool, &dup, dest_uri);
+ uri = pjsip_parse_uri(tmp_pool, dup.ptr, dup.slen, 0);
+
+ if (uri == NULL) {
+ pjsua_perror(THIS_FILE, "Unable to make call",
+ PJSIP_EINVALIDREQURI);
+ status = PJSIP_EINVALIDREQURI;
+ goto on_error;
+ }
+ }
+
+ /* Mark call start time. */
+ pj_gettimeofday(&call->start_time);
+
+ /* Reset first response time */
+ call->res_time.sec = 0;
+
+ /* Create suitable Contact header unless a Contact header has been
+ * set in the account.
+ */
+ if (acc->contact.slen) {
+ contact = acc->contact;
+ } else {
+ status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
+ acc_id, dest_uri);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to generate Contact header",
+ status);
+ goto on_error;
+ }
+ }
+
+ /* Create outgoing dialog: */
+ status = pjsip_dlg_create_uac( pjsip_ua_instance(),
+ &acc->cfg.id, &contact,
+ dest_uri, dest_uri, &dlg);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Dialog creation failed", status);
+ goto on_error;
+ }
+
+ /* Increment the dialog's lock otherwise when invite session creation
+ * fails the dialog will be destroyed prematurely.
+ */
+ pjsip_dlg_inc_lock(dlg);
+
+ if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0)
+ pjsip_dlg_set_via_sent_by(dlg, &acc->via_addr, acc->via_tp);
+
+ /* Calculate call's secure level */
+ call->secure_level = get_secure_level(acc_id, dest_uri);
+
+ /* Attach user data */
+ call->user_data = user_data;
+
+ /* Store variables required for the callback after the async
+ * media transport creation is completed.
+ */
+ if (msg_data) {
+ call->async_call.call_var.out_call.msg_data = pjsua_msg_data_clone(
+ dlg->pool, msg_data);
+ }
+ call->async_call.dlg = dlg;
+
+ /* Temporarily increment dialog session. Without this, dialog will be
+ * prematurely destroyed if dec_lock() is called on the dialog before
+ * the invite session is created.
+ */
+ pjsip_dlg_inc_session(dlg, &pjsua_var.mod);
+
+ /* Init media channel */
+ status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC,
+ call->secure_level, dlg->pool,
+ NULL, NULL, PJ_TRUE,
+ &on_make_call_med_tp_complete);
+ if (status == PJ_SUCCESS) {
+ status = on_make_call_med_tp_complete(call->index, NULL);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ } else if (status != PJ_EPENDING) {
+ pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+ pjsip_dlg_dec_session(dlg, &pjsua_var.mod);
+ goto on_error;
+ }
+
+ /* Done. */
+
+ if (p_call_id)
+ *p_call_id = call_id;
+
+ pjsip_dlg_dec_lock(dlg);
+ pj_pool_release(tmp_pool);
+ PJSUA_UNLOCK();
+
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+
+
+on_error:
+ if (dlg) {
+ /* This may destroy the dialog */
+ pjsip_dlg_dec_lock(dlg);
+ }
+
+ if (call_id != -1) {
+ reset_call(call_id);
+ pjsua_media_channel_deinit(call_id);
+ }
+
+ if (tmp_pool)
+ pj_pool_release(tmp_pool);
+ PJSUA_UNLOCK();
+
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/* Get the NAT type information in remote's SDP */
+static void update_remote_nat_type(pjsua_call *call,
+ const pjmedia_sdp_session *sdp)
+{
+ const pjmedia_sdp_attr *xnat;
+
+ xnat = pjmedia_sdp_attr_find2(sdp->attr_count, sdp->attr, "X-nat", NULL);
+ if (xnat) {
+ call->rem_nat_type = (pj_stun_nat_type) (xnat->value.ptr[0] - '0');
+ } else {
+ call->rem_nat_type = PJ_STUN_NAT_TYPE_UNKNOWN;
+ }
+
+ PJ_LOG(5,(THIS_FILE, "Call %d: remote NAT type is %d (%s)", call->index,
+ call->rem_nat_type, pj_stun_get_nat_name(call->rem_nat_type)));
+}
+
+
+static pj_status_t process_incoming_call_replace(pjsua_call *call,
+ pjsip_dialog *replaced_dlg)
+{
+ pjsip_inv_session *replaced_inv;
+ struct pjsua_call *replaced_call;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Get the invite session in the dialog */
+ replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg);
+
+ /* Get the replaced call instance */
+ replaced_call = (pjsua_call*) replaced_dlg->mod_data[pjsua_var.mod.id];
+
+ /* Notify application */
+ if (pjsua_var.ua_cfg.cb.on_call_replaced)
+ pjsua_var.ua_cfg.cb.on_call_replaced(replaced_call->index,
+ call->index);
+
+ PJ_LOG(4,(THIS_FILE, "Answering replacement call %d with 200/OK",
+ call->index));
+
+ /* Answer the new call with 200 response */
+ status = pjsip_inv_answer(call->inv, 200, NULL, NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_inv_send_msg(call->inv, tdata);
+
+ if (status != PJ_SUCCESS)
+ pjsua_perror(THIS_FILE, "Error answering session", status);
+
+ /* Note that inv may be invalid if 200/OK has caused error in
+ * starting the media.
+ */
+
+ PJ_LOG(4,(THIS_FILE, "Disconnecting replaced call %d",
+ replaced_call->index));
+
+ /* Disconnect replaced invite session */
+ status = pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, NULL,
+ &tdata);
+ if (status == PJ_SUCCESS && tdata)
+ status = pjsip_inv_send_msg(replaced_inv, tdata);
+
+ if (status != PJ_SUCCESS)
+ pjsua_perror(THIS_FILE, "Error terminating session", status);
+
+ return status;
+}
+
+
+static void process_pending_call_answer(pjsua_call *call)
+{
+ struct call_answer *answer, *next;
+
+ answer = call->async_call.call_var.inc_call.answers.next;
+ while (answer != &call->async_call.call_var.inc_call.answers) {
+ next = answer->next;
+ pjsua_call_answer2(call->index, answer->opt, answer->code,
+ answer->reason, answer->msg_data);
+
+ /* Call might have been disconnected if application is answering
+ * with 200/OK and the media failed to start.
+ * See pjsua_call_answer() below.
+ */
+ if (!call->inv || !call->inv->pool_prov)
+ break;
+
+ pj_list_erase(answer);
+ answer = next;
+ }
+}
+
+
+/* Incoming call callback when media transport creation is completed. */
+static pj_status_t
+on_incoming_call_med_tp_complete(pjsua_call_id call_id,
+ const pjsua_med_tp_state_info *info)
+{
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ const pjmedia_sdp_session *offer=NULL;
+ pjmedia_sdp_session *answer;
+ pjsip_tx_data *response = NULL;
+ unsigned options = 0;
+ int sip_err_code = (info? info->sip_err_code: 0);
+ pj_status_t status = (info? info->status: PJ_SUCCESS);
+
+ PJSUA_LOCK();
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+ goto on_return;
+ }
+
+ /* pjsua_media_channel_deinit() has been called. */
+ if (call->async_call.med_ch_deinit) {
+ pjsua_media_channel_deinit(call->index);
+ call->med_ch_cb = NULL;
+ PJSUA_UNLOCK();
+ return PJ_SUCCESS;
+ }
+
+ /* Get remote SDP offer (if any). */
+ if (call->inv->neg)
+ pjmedia_sdp_neg_get_neg_remote(call->inv->neg, &offer);
+
+ status = pjsua_media_channel_create_sdp(call_id,
+ call->async_call.dlg->pool,
+ offer, &answer, &sip_err_code);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating SDP answer", status);
+ goto on_return;
+ }
+
+ status = pjsip_inv_set_local_sdp(call->inv, answer);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error setting local SDP", status);
+ sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+ goto on_return;
+ }
+
+ /* Verify that we can handle the request. */
+ status = pjsip_inv_verify_request3(NULL,
+ call->inv->pool_prov, &options, offer,
+ answer, NULL, pjsua_var.endpt, &response);
+ if (status != PJ_SUCCESS) {
+ /*
+ * No we can't handle the incoming INVITE request.
+ */
+ sip_err_code = PJSIP_ERRNO_TO_SIP_STATUS(status);
+ goto on_return;
+ }
+
+on_return:
+ if (status != PJ_SUCCESS) {
+ /* If the callback is called from pjsua_call_on_incoming(), the
+ * invite's state is PJSIP_INV_STATE_NULL, so the invite session
+ * will be terminated later, otherwise we end the session here.
+ */
+ if (call->inv->state > PJSIP_INV_STATE_NULL) {
+ pjsip_tx_data *tdata;
+ pj_status_t status_;
+
+ status_ = pjsip_inv_end_session(call->inv, sip_err_code, NULL,
+ &tdata);
+ if (status_ == PJ_SUCCESS && tdata)
+ status_ = pjsip_inv_send_msg(call->inv, tdata);
+ }
+
+ pjsua_media_channel_deinit(call->index);
+ }
+
+ /* Set the callback to NULL to indicate that the async operation
+ * has completed.
+ */
+ call->med_ch_cb = NULL;
+
+ /* Finish any pending process */
+ if (status == PJ_SUCCESS) {
+ if (call->async_call.call_var.inc_call.replaced_dlg) {
+ /* Process pending call replace */
+ pjsip_dialog *replaced_dlg =
+ call->async_call.call_var.inc_call.replaced_dlg;
+ process_incoming_call_replace(call, replaced_dlg);
+ } else {
+ /* Process pending call answers */
+ process_pending_call_answer(call);
+ }
+ }
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+
+/**
+ * Handle incoming INVITE request.
+ * Called by pjsua_core.c
+ */
+pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
+{
+ pj_str_t contact;
+ pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
+ pjsip_dialog *replaced_dlg = NULL;
+ pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
+ pjsip_msg *msg = rdata->msg_info.msg;
+ pjsip_tx_data *response = NULL;
+ unsigned options = 0;
+ pjsip_inv_session *inv = NULL;
+ int acc_id;
+ pjsua_call *call;
+ int call_id = -1;
+ int sip_err_code;
+ pjmedia_sdp_session *offer=NULL;
+ pj_status_t status;
+
+ /* Don't want to handle anything but INVITE */
+ if (msg->line.req.method.id != PJSIP_INVITE_METHOD)
+ return PJ_FALSE;
+
+ /* Don't want to handle anything that's already associated with
+ * existing dialog or transaction.
+ */
+ if (dlg || tsx)
+ return PJ_FALSE;
+
+ /* Don't want to accept the call if shutdown is in progress */
+ if (pjsua_var.thread_quit_flag) {
+ pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
+ PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL,
+ NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ PJ_LOG(4,(THIS_FILE, "Incoming %s", rdata->msg_info.info));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ /* Find free call slot. */
+ call_id = alloc_call_id();
+
+ if (call_id == PJSUA_INVALID_ID) {
+ pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
+ PJSIP_SC_BUSY_HERE, NULL,
+ NULL, NULL);
+ PJ_LOG(2,(THIS_FILE,
+ "Unable to accept incoming call (too many calls)"));
+ goto on_return;
+ }
+
+ /* Clear call descriptor */
+ reset_call(call_id);
+
+ call = &pjsua_var.calls[call_id];
+
+ /* Mark call start time. */
+ pj_gettimeofday(&call->start_time);
+
+ /* Check INVITE request for Replaces header. If Replaces header is
+ * present, the function will make sure that we can handle the request.
+ */
+ status = pjsip_replaces_verify_request(rdata, &replaced_dlg, PJ_FALSE,
+ &response);
+ if (status != PJ_SUCCESS) {
+ /*
+ * Something wrong with the Replaces header.
+ */
+ if (response) {
+ pjsip_response_addr res_addr;
+
+ pjsip_get_response_addr(response->pool, rdata, &res_addr);
+ pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response,
+ NULL, NULL);
+
+ } else {
+
+ /* Respond with 500 (Internal Server Error) */
+ pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
+ NULL, NULL);
+ }
+
+ goto on_return;
+ }
+
+ /* If this INVITE request contains Replaces header, notify application
+ * about the request so that application can do subsequent checking
+ * if it wants to.
+ */
+ if (replaced_dlg != NULL &&
+ (pjsua_var.ua_cfg.cb.on_call_replace_request ||
+ pjsua_var.ua_cfg.cb.on_call_replace_request2))
+ {
+ pjsua_call *replaced_call;
+ int st_code = 200;
+ pj_str_t st_text = { "OK", 2 };
+
+ /* Get the replaced call instance */
+ replaced_call = (pjsua_call*) replaced_dlg->mod_data[pjsua_var.mod.id];
+
+ /* Copy call setting from the replaced call */
+ call->opt = replaced_call->opt;
+
+ /* Notify application */
+ if (pjsua_var.ua_cfg.cb.on_call_replace_request) {
+ pjsua_var.ua_cfg.cb.on_call_replace_request(replaced_call->index,
+ rdata,
+ &st_code, &st_text);
+ }
+
+ if (pjsua_var.ua_cfg.cb.on_call_replace_request2) {
+ pjsua_var.ua_cfg.cb.on_call_replace_request2(replaced_call->index,
+ rdata,
+ &st_code, &st_text,
+ &call->opt);
+ }
+
+ /* Must specify final response */
+ PJ_ASSERT_ON_FAIL(st_code >= 200, st_code = 200);
+
+ /* Check if application rejects this request. */
+ if (st_code >= 300) {
+
+ if (st_text.slen == 2)
+ st_text = *pjsip_get_status_text(st_code);
+
+ pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata,
+ st_code, &st_text, NULL, NULL, NULL);
+ goto on_return;
+ }
+ }
+
+ /*
+ * Get which account is most likely to be associated with this incoming
+ * call. We need the account to find which contact URI to put for
+ * the call.
+ */
+ acc_id = call->acc_id = pjsua_acc_find_for_incoming(rdata);
+ call->call_hold_type = pjsua_var.acc[acc_id].cfg.call_hold_type;
+
+ /* Get call's secure level */
+ if (PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.msg->line.req.uri))
+ call->secure_level = 2;
+ else if (PJSIP_TRANSPORT_IS_SECURE(rdata->tp_info.transport))
+ call->secure_level = 1;
+ else
+ call->secure_level = 0;
+
+ /* Parse SDP from incoming request */
+ if (rdata->msg_info.msg->body) {
+ pjsip_rdata_sdp_info *sdp_info;
+
+ sdp_info = pjsip_rdata_get_sdp_info(rdata);
+ offer = sdp_info->sdp;
+
+ status = sdp_info->sdp_err;
+ if (status==PJ_SUCCESS && sdp_info->sdp==NULL)
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
+
+ if (status != PJ_SUCCESS) {
+ const pj_str_t reason = pj_str("Bad SDP");
+ pjsip_hdr hdr_list;
+ pjsip_warning_hdr *w;
+
+ pjsua_perror(THIS_FILE, "Bad SDP in incoming INVITE",
+ status);
+
+ w = pjsip_warning_hdr_create_from_status(rdata->tp_info.pool,
+ pjsip_endpt_name(pjsua_var.endpt),
+ status);
+ pj_list_init(&hdr_list);
+ pj_list_push_back(&hdr_list, w);
+
+ pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 400,
+ &reason, &hdr_list, NULL, NULL);
+ goto on_return;
+ }
+
+ /* Do quick checks on SDP before passing it to transports. More elabore
+ * checks will be done in pjsip_inv_verify_request2() below.
+ */
+ if (offer->media_count==0) {
+ const pj_str_t reason = pj_str("Missing media in SDP");
+ pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 400, &reason,
+ NULL, NULL, NULL);
+ goto on_return;
+ }
+
+ } else {
+ offer = NULL;
+ }
+
+ /* Verify that we can handle the request. */
+ options |= PJSIP_INV_SUPPORT_100REL;
+ options |= PJSIP_INV_SUPPORT_TIMER;
+ if (pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_MANDATORY)
+ options |= PJSIP_INV_REQUIRE_100REL;
+ if (pjsua_var.media_cfg.enable_ice)
+ options |= PJSIP_INV_SUPPORT_ICE;
+ if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_REQUIRED)
+ options |= PJSIP_INV_REQUIRE_TIMER;
+ else if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_ALWAYS)
+ options |= PJSIP_INV_ALWAYS_USE_TIMER;
+
+ status = pjsip_inv_verify_request2(rdata, &options, offer, NULL, NULL,
+ pjsua_var.endpt, &response);
+ if (status != PJ_SUCCESS) {
+
+ /*
+ * No we can't handle the incoming INVITE request.
+ */
+ if (response) {
+ pjsip_response_addr res_addr;
+
+ pjsip_get_response_addr(response->pool, rdata, &res_addr);
+ pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response,
+ NULL, NULL);
+
+ } else {
+ /* Respond with 500 (Internal Server Error) */
+ pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 500, NULL,
+ NULL, NULL, NULL);
+ }
+
+ goto on_return;
+ }
+
+ /* Get suitable Contact header */
+ if (pjsua_var.acc[acc_id].contact.slen) {
+ contact = pjsua_var.acc[acc_id].contact;
+ } else {
+ status = pjsua_acc_create_uas_contact(rdata->tp_info.pool, &contact,
+ acc_id, rdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to generate Contact header",
+ status);
+ pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
+ NULL, NULL);
+ goto on_return;
+ }
+ }
+
+ /* Create dialog: */
+ status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
+ &contact, &dlg);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
+ NULL, NULL);
+ goto on_return;
+ }
+
+ if (pjsua_var.acc[acc_id].cfg.allow_via_rewrite &&
+ pjsua_var.acc[acc_id].via_addr.host.slen > 0)
+ {
+ pjsip_dlg_set_via_sent_by(dlg, &pjsua_var.acc[acc_id].via_addr,
+ pjsua_var.acc[acc_id].via_tp);
+ }
+
+ /* Set credentials */
+ if (pjsua_var.acc[acc_id].cred_cnt) {
+ pjsip_auth_clt_set_credentials(&dlg->auth_sess,
+ pjsua_var.acc[acc_id].cred_cnt,
+ pjsua_var.acc[acc_id].cred);
+ }
+
+ /* Set preference */
+ pjsip_auth_clt_set_prefs(&dlg->auth_sess,
+ &pjsua_var.acc[acc_id].cfg.auth_pref);
+
+ /* Disable Session Timers if not prefered and the incoming INVITE request
+ * did not require it.
+ */
+ if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_INACTIVE &&
+ (options & PJSIP_INV_REQUIRE_TIMER) == 0)
+ {
+ options &= ~(PJSIP_INV_SUPPORT_TIMER);
+ }
+
+ /* If 100rel is optional and UAC supports it, use it. */
+ if ((options & PJSIP_INV_REQUIRE_100REL)==0 &&
+ pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_OPTIONAL)
+ {
+ const pj_str_t token = { "100rel", 6};
+ pjsip_dialog_cap_status cap_status;
+
+ cap_status = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_SUPPORTED, NULL,
+ &token);
+ if (cap_status == PJSIP_DIALOG_CAP_SUPPORTED)
+ options |= PJSIP_INV_REQUIRE_100REL;
+ }
+
+ /* Create invite session: */
+ status = pjsip_inv_create_uas( dlg, rdata, NULL, options, &inv);
+ if (status != PJ_SUCCESS) {
+ pjsip_hdr hdr_list;
+ pjsip_warning_hdr *w;
+
+ w = pjsip_warning_hdr_create_from_status(dlg->pool,
+ pjsip_endpt_name(pjsua_var.endpt),
+ status);
+ pj_list_init(&hdr_list);
+ pj_list_push_back(&hdr_list, w);
+
+ pjsip_dlg_respond(dlg, rdata, 500, NULL, &hdr_list, NULL);
+
+ /* Can't terminate dialog because transaction is in progress.
+ pjsip_dlg_terminate(dlg);
+ */
+ goto on_return;
+ }
+
+ /* If account is locked to specific transport, then lock dialog
+ * to this transport too.
+ */
+ if (pjsua_var.acc[acc_id].cfg.transport_id != PJSUA_INVALID_ID) {
+ pjsip_tpselector tp_sel;
+
+ pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel);
+ pjsip_dlg_set_transport(dlg, &tp_sel);
+ }
+
+ /* Create and attach pjsua_var data to the dialog */
+ call->inv = inv;
+
+ /* Store variables required for the callback after the async
+ * media transport creation is completed.
+ */
+ call->async_call.dlg = dlg;
+ pj_list_init(&call->async_call.call_var.inc_call.answers);
+
+ /* Init media channel, only when there is offer or call replace request.
+ * For incoming call without SDP offer, media channel init will be done
+ * in pjsua_call_answer(), see ticket #1526.
+ */
+ if (offer || replaced_dlg) {
+ status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS,
+ call->secure_level,
+ rdata->tp_info.pool,
+ offer,
+ &sip_err_code, PJ_TRUE,
+ &on_incoming_call_med_tp_complete);
+ if (status == PJ_SUCCESS) {
+ status = on_incoming_call_med_tp_complete(call_id, NULL);
+ if (status != PJ_SUCCESS) {
+ sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
+ /* Since the call invite's state is still PJSIP_INV_STATE_NULL,
+ * the invite session was not ended in
+ * on_incoming_call_med_tp_complete(), so we need to send
+ * a response message and terminate the invite here.
+ */
+ pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL);
+ pjsip_inv_terminate(call->inv, sip_err_code, PJ_FALSE);
+ call->inv = NULL;
+ goto on_return;
+ }
+ } else if (status != PJ_EPENDING) {
+ pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+ pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL);
+ pjsip_inv_terminate(call->inv, sip_err_code, PJ_FALSE);
+ call->inv = NULL;
+ goto on_return;
+ }
+ }
+
+ /* Create answer */
+/*
+ status = pjsua_media_channel_create_sdp(call->index, rdata->tp_info.pool,
+ offer, &answer, &sip_err_code);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating SDP answer", status);
+ pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata,
+ sip_err_code, NULL, NULL, NULL, NULL);
+ goto on_return;
+ }
+*/
+
+ /* Init Session Timers */
+ status = pjsip_timer_init_session(inv,
+ &pjsua_var.acc[acc_id].cfg.timer_setting);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Session Timer init failed", status);
+ pjsip_dlg_respond(dlg, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL);
+ pjsip_inv_terminate(inv, PJSIP_SC_INTERNAL_SERVER_ERROR, PJ_FALSE);
+
+ pjsua_media_channel_deinit(call->index);
+ call->inv = NULL;
+
+ goto on_return;
+ }
+
+ /* Update NAT type of remote endpoint, only when there is SDP in
+ * incoming INVITE!
+ */
+ if (pjsua_var.ua_cfg.nat_type_in_sdp && inv->neg &&
+ pjmedia_sdp_neg_get_state(inv->neg) > PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
+ {
+ const pjmedia_sdp_session *remote_sdp;
+
+ if (pjmedia_sdp_neg_get_neg_remote(inv->neg, &remote_sdp)==PJ_SUCCESS)
+ update_remote_nat_type(call, remote_sdp);
+ }
+
+ /* Must answer with some response to initial INVITE. We'll do this before
+ * attaching the call to the invite session/dialog, so that the application
+ * will not get notification about this event (on another scenario, it is
+ * also possible that inv_send_msg() fails and causes the invite session to
+ * be disconnected. If we have the call attached at this time, this will
+ * cause the disconnection callback to be called before on_incoming_call()
+ * callback is called, which is not right).
+ */
+ status = pjsip_inv_initial_answer(inv, rdata,
+ 100, NULL, NULL, &response);
+ if (status != PJ_SUCCESS) {
+ if (response == NULL) {
+ pjsua_perror(THIS_FILE, "Unable to send answer to incoming INVITE",
+ status);
+ pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL);
+ pjsip_inv_terminate(inv, 500, PJ_FALSE);
+ } else {
+ pjsip_inv_send_msg(inv, response);
+ pjsip_inv_terminate(inv, response->msg->line.status.code,
+ PJ_FALSE);
+ }
+ pjsua_media_channel_deinit(call->index);
+ call->inv = NULL;
+ goto on_return;
+
+ } else {
+ status = pjsip_inv_send_msg(inv, response);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send 100 response", status);
+ pjsua_media_channel_deinit(call->index);
+ call->inv = NULL;
+ goto on_return;
+ }
+ }
+
+ /* Only do this after sending 100/Trying (really! see the long comment
+ * above)
+ */
+ dlg->mod_data[pjsua_var.mod.id] = call;
+ inv->mod_data[pjsua_var.mod.id] = call;
+
+ ++pjsua_var.call_cnt;
+
+ /* Check if this request should replace existing call */
+ if (replaced_dlg) {
+ /* Process call replace. If the media channel init has been completed,
+ * just process now, otherwise, just queue the replaced dialog so
+ * it will be processed once the media channel async init is finished
+ * successfully.
+ */
+ if (call->med_ch_cb == NULL) {
+ process_incoming_call_replace(call, replaced_dlg);
+ } else {
+ call->async_call.call_var.inc_call.replaced_dlg = replaced_dlg;
+ }
+ } else {
+ /* Notify application if on_incoming_call() is overriden,
+ * otherwise hangup the call with 480
+ */
+ if (pjsua_var.ua_cfg.cb.on_incoming_call) {
+ pjsua_var.ua_cfg.cb.on_incoming_call(acc_id, call_id, rdata);
+ } else {
+ pjsua_call_hangup(call_id, PJSIP_SC_TEMPORARILY_UNAVAILABLE,
+ NULL, NULL);
+ }
+ }
+
+
+ /* This INVITE request has been handled. */
+on_return:
+ pj_log_pop_indent();
+ PJSUA_UNLOCK();
+ return PJ_TRUE;
+}
+
+
+
+/*
+ * Check if the specified call has active INVITE session and the INVITE
+ * session has not been disconnected.
+ */
+PJ_DEF(pj_bool_t) pjsua_call_is_active(pjsua_call_id call_id)
+{
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+ return pjsua_var.calls[call_id].inv != NULL &&
+ pjsua_var.calls[call_id].inv->state != PJSIP_INV_STATE_DISCONNECTED;
+}
+
+
+/* Acquire lock to the specified call_id */
+pj_status_t acquire_call(const char *title,
+ pjsua_call_id call_id,
+ pjsua_call **p_call,
+ pjsip_dialog **p_dlg)
+{
+ unsigned retry;
+ pjsua_call *call = NULL;
+ pj_bool_t has_pjsua_lock = PJ_FALSE;
+ pj_status_t status = PJ_SUCCESS;
+ pj_time_val time_start, timeout;
+
+ pj_gettimeofday(&time_start);
+ timeout.sec = 0;
+ timeout.msec = PJSUA_ACQUIRE_CALL_TIMEOUT;
+ pj_time_val_normalize(&timeout);
+
+ for (retry=0; ; ++retry) {
+
+ if (retry % 10 == 9) {
+ pj_time_val dtime;
+
+ pj_gettimeofday(&dtime);
+ PJ_TIME_VAL_SUB(dtime, time_start);
+ if (!PJ_TIME_VAL_LT(dtime, timeout))
+ break;
+ }
+
+ has_pjsua_lock = PJ_FALSE;
+
+ status = PJSUA_TRY_LOCK();
+ if (status != PJ_SUCCESS) {
+ pj_thread_sleep(retry/10);
+ continue;
+ }
+
+ has_pjsua_lock = PJ_TRUE;
+ call = &pjsua_var.calls[call_id];
+
+ if (call->inv == NULL) {
+ PJSUA_UNLOCK();
+ PJ_LOG(3,(THIS_FILE, "Invalid call_id %d in %s", call_id, title));
+ return PJSIP_ESESSIONTERMINATED;
+ }
+
+ status = pjsip_dlg_try_inc_lock(call->inv->dlg);
+ if (status != PJ_SUCCESS) {
+ PJSUA_UNLOCK();
+ pj_thread_sleep(retry/10);
+ continue;
+ }
+
+ PJSUA_UNLOCK();
+
+ break;
+ }
+
+ if (status != PJ_SUCCESS) {
+ if (has_pjsua_lock == PJ_FALSE)
+ PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire PJSUA mutex "
+ "(possibly system has deadlocked) in %s",
+ title));
+ else
+ PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire dialog mutex "
+ "(possibly system has deadlocked) in %s",
+ title));
+ return PJ_ETIMEDOUT;
+ }
+
+ *p_call = call;
+ *p_dlg = call->inv->dlg;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Obtain detail information about the specified call.
+ */
+PJ_DEF(pj_status_t) pjsua_call_get_info( pjsua_call_id call_id,
+ pjsua_call_info *info)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg;
+ unsigned mi;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ pj_bzero(info, sizeof(*info));
+
+ /* Use PJSUA_LOCK() instead of acquire_call():
+ * https://trac.pjsip.org/repos/ticket/1371
+ */
+ PJSUA_LOCK();
+
+ call = &pjsua_var.calls[call_id];
+ dlg = (call->inv ? call->inv->dlg : call->async_call.dlg);
+ if (!dlg) {
+ PJSUA_UNLOCK();
+ return PJSIP_ESESSIONTERMINATED;
+ }
+
+ /* id and role */
+ info->id = call_id;
+ info->role = dlg->role;
+ info->acc_id = call->acc_id;
+
+ /* local info */
+ info->local_info.ptr = info->buf_.local_info;
+ pj_strncpy(&info->local_info, &dlg->local.info_str,
+ sizeof(info->buf_.local_info));
+
+ /* local contact */
+ info->local_contact.ptr = info->buf_.local_contact;
+ info->local_contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR,
+ dlg->local.contact->uri,
+ info->local_contact.ptr,
+ sizeof(info->buf_.local_contact));
+
+ /* remote info */
+ info->remote_info.ptr = info->buf_.remote_info;
+ pj_strncpy(&info->remote_info, &dlg->remote.info_str,
+ sizeof(info->buf_.remote_info));
+
+ /* remote contact */
+ if (dlg->remote.contact) {
+ int len;
+ info->remote_contact.ptr = info->buf_.remote_contact;
+ len = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR,
+ dlg->remote.contact->uri,
+ info->remote_contact.ptr,
+ sizeof(info->buf_.remote_contact));
+ if (len < 0) len = 0;
+ info->remote_contact.slen = len;
+ } else {
+ info->remote_contact.slen = 0;
+ }
+
+ /* call id */
+ info->call_id.ptr = info->buf_.call_id;
+ pj_strncpy(&info->call_id, &dlg->call_id->id,
+ sizeof(info->buf_.call_id));
+
+ /* call setting */
+ pj_memcpy(&info->setting, &call->opt, sizeof(call->opt));
+
+ /* state, state_text */
+ if (call->inv) {
+ info->state = call->inv->state;
+ } else if (call->async_call.dlg && call->last_code==0) {
+ info->state = PJSIP_INV_STATE_NULL;
+ } else {
+ info->state = PJSIP_INV_STATE_DISCONNECTED;
+ }
+ info->state_text = pj_str((char*)pjsip_inv_state_name(info->state));
+
+ /* If call is disconnected, set the last_status from the cause code */
+ if (call->inv && call->inv->state >= PJSIP_INV_STATE_DISCONNECTED) {
+ /* last_status, last_status_text */
+ info->last_status = call->inv->cause;
+
+ info->last_status_text.ptr = info->buf_.last_status_text;
+ pj_strncpy(&info->last_status_text, &call->inv->cause_text,
+ sizeof(info->buf_.last_status_text));
+ } else {
+ /* last_status, last_status_text */
+ info->last_status = call->last_code;
+
+ info->last_status_text.ptr = info->buf_.last_status_text;
+ pj_strncpy(&info->last_status_text, &call->last_text,
+ sizeof(info->buf_.last_status_text));
+ }
+
+ /* Audio & video count offered by remote */
+ info->rem_offerer = call->rem_offerer;
+ if (call->rem_offerer) {
+ info->rem_aud_cnt = call->rem_aud_cnt;
+ info->rem_vid_cnt = call->rem_vid_cnt;
+ }
+
+ /* Build array of active media info */
+ info->media_cnt = 0;
+ for (mi=0; mi < call->med_cnt &&
+ info->media_cnt < PJ_ARRAY_SIZE(info->media); ++mi)
+ {
+ pjsua_call_media *call_med = &call->media[mi];
+
+ info->media[info->media_cnt].index = mi;
+ info->media[info->media_cnt].status = call_med->state;
+ info->media[info->media_cnt].dir = call_med->dir;
+ info->media[info->media_cnt].type = call_med->type;
+
+ if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+ info->media[info->media_cnt].stream.aud.conf_slot =
+ call_med->strm.a.conf_slot;
+ } else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
+ pjmedia_vid_dev_index cap_dev = PJMEDIA_VID_INVALID_DEV;
+
+ info->media[info->media_cnt].stream.vid.win_in =
+ call_med->strm.v.rdr_win_id;
+
+ if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) {
+ cap_dev = call_med->strm.v.cap_dev;
+ }
+ info->media[info->media_cnt].stream.vid.cap_dev = cap_dev;
+ } else {
+ continue;
+ }
+ ++info->media_cnt;
+ }
+
+ if (call->audio_idx != -1) {
+ info->media_status = call->media[call->audio_idx].state;
+ info->media_dir = call->media[call->audio_idx].dir;
+ info->conf_slot = call->media[call->audio_idx].strm.a.conf_slot;
+ }
+
+ /* Build array of provisional media info */
+ info->prov_media_cnt = 0;
+ for (mi=0; mi < call->med_prov_cnt &&
+ info->prov_media_cnt < PJ_ARRAY_SIZE(info->prov_media); ++mi)
+ {
+ pjsua_call_media *call_med = &call->media_prov[mi];
+
+ info->prov_media[info->prov_media_cnt].index = mi;
+ info->prov_media[info->prov_media_cnt].status = call_med->state;
+ info->prov_media[info->prov_media_cnt].dir = call_med->dir;
+ info->prov_media[info->prov_media_cnt].type = call_med->type;
+ if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+ info->prov_media[info->prov_media_cnt].stream.aud.conf_slot =
+ call_med->strm.a.conf_slot;
+ } else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
+ pjmedia_vid_dev_index cap_dev = PJMEDIA_VID_INVALID_DEV;
+
+ info->prov_media[info->prov_media_cnt].stream.vid.win_in =
+ call_med->strm.v.rdr_win_id;
+
+ if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) {
+ cap_dev = call_med->strm.v.cap_dev;
+ }
+ info->prov_media[info->prov_media_cnt].stream.vid.cap_dev=cap_dev;
+ } else {
+ continue;
+ }
+ ++info->prov_media_cnt;
+ }
+
+ /* calculate duration */
+ if (info->state >= PJSIP_INV_STATE_DISCONNECTED) {
+
+ info->total_duration = call->dis_time;
+ PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
+
+ if (call->conn_time.sec) {
+ info->connect_duration = call->dis_time;
+ PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time);
+ }
+
+ } else if (info->state == PJSIP_INV_STATE_CONFIRMED) {
+
+ pj_gettimeofday(&info->total_duration);
+ PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
+
+ pj_gettimeofday(&info->connect_duration);
+ PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time);
+
+ } else {
+ pj_gettimeofday(&info->total_duration);
+ PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
+ }
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Check if call remote peer support the specified capability.
+ */
+PJ_DEF(pjsip_dialog_cap_status) pjsua_call_remote_has_cap(
+ pjsua_call_id call_id,
+ int htype,
+ const pj_str_t *hname,
+ const pj_str_t *token)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+ pjsip_dialog_cap_status cap_status;
+
+ status = acquire_call("pjsua_call_peer_has_cap()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ return PJSIP_DIALOG_CAP_UNKNOWN;
+
+ cap_status = pjsip_dlg_remote_has_cap(dlg, htype, hname, token);
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return cap_status;
+}
+
+
+/*
+ * Attach application specific data to the call.
+ */
+PJ_DEF(pj_status_t) pjsua_call_set_user_data( pjsua_call_id call_id,
+ void *user_data)
+{
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+ pjsua_var.calls[call_id].user_data = user_data;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get user data attached to the call.
+ */
+PJ_DEF(void*) pjsua_call_get_user_data(pjsua_call_id call_id)
+{
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ NULL);
+ return pjsua_var.calls[call_id].user_data;
+}
+
+
+/*
+ * Get remote's NAT type.
+ */
+PJ_DEF(pj_status_t) pjsua_call_get_rem_nat_type(pjsua_call_id call_id,
+ pj_stun_nat_type *p_type)
+{
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(p_type != NULL, PJ_EINVAL);
+
+ *p_type = pjsua_var.calls[call_id].rem_nat_type;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get media transport info for the specified media index.
+ */
+PJ_DEF(pj_status_t)
+pjsua_call_get_med_transport_info(pjsua_call_id call_id,
+ unsigned med_idx,
+ pjmedia_transport_info *t)
+{
+ pjsua_call *call;
+ pjsua_call_media *call_med;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(t, PJ_EINVAL);
+
+ PJSUA_LOCK();
+
+ call = &pjsua_var.calls[call_id];
+
+ if (med_idx >= call->med_cnt) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ call_med = &call->media[med_idx];
+
+ pjmedia_transport_info_init(t);
+ status = pjmedia_transport_get_info(call_med->tp, t);
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+
+/* Media channel init callback for pjsua_call_answer(). */
+static pj_status_t
+on_answer_call_med_tp_complete(pjsua_call_id call_id,
+ const pjsua_med_tp_state_info *info)
+{
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ pjmedia_sdp_session *sdp;
+ int sip_err_code = (info? info->sip_err_code: 0);
+ pj_status_t status = (info? info->status: PJ_SUCCESS);
+
+ PJSUA_LOCK();
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+ goto on_return;
+ }
+
+ /* pjsua_media_channel_deinit() has been called. */
+ if (call->async_call.med_ch_deinit) {
+ pjsua_media_channel_deinit(call->index);
+ call->med_ch_cb = NULL;
+ PJSUA_UNLOCK();
+ return PJ_SUCCESS;
+ }
+
+ status = pjsua_media_channel_create_sdp(call_id,
+ call->async_call.dlg->pool,
+ NULL, &sdp, &sip_err_code);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating SDP answer", status);
+ goto on_return;
+ }
+
+ status = pjsip_inv_set_local_sdp(call->inv, sdp);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error setting local SDP", status);
+ sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+ goto on_return;
+ }
+
+on_return:
+ if (status != PJ_SUCCESS) {
+ /* If the callback is called from pjsua_call_on_incoming(), the
+ * invite's state is PJSIP_INV_STATE_NULL, so the invite session
+ * will be terminated later, otherwise we end the session here.
+ */
+ if (call->inv->state > PJSIP_INV_STATE_NULL) {
+ pjsip_tx_data *tdata;
+ pj_status_t status_;
+
+ status_ = pjsip_inv_end_session(call->inv, sip_err_code, NULL,
+ &tdata);
+ if (status_ == PJ_SUCCESS && tdata)
+ status_ = pjsip_inv_send_msg(call->inv, tdata);
+ }
+
+ pjsua_media_channel_deinit(call->index);
+ }
+
+ /* Set the callback to NULL to indicate that the async operation
+ * has completed.
+ */
+ call->med_ch_cb = NULL;
+
+ /* Finish any pending process */
+ if (status == PJ_SUCCESS) {
+ /* Process pending call answers */
+ process_pending_call_answer(call);
+ }
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+
+/*
+ * Send response to incoming INVITE request.
+ */
+PJ_DEF(pj_status_t) pjsua_call_answer( pjsua_call_id call_id,
+ unsigned code,
+ const pj_str_t *reason,
+ const pjsua_msg_data *msg_data)
+{
+ return pjsua_call_answer2(call_id, NULL, code, reason, msg_data);
+}
+
+
+/*
+ * Send response to incoming INVITE request.
+ */
+PJ_DEF(pj_status_t) pjsua_call_answer2(pjsua_call_id call_id,
+ const pjsua_call_setting *opt,
+ unsigned code,
+ const pj_str_t *reason,
+ const pjsua_msg_data *msg_data)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Answering call %d: code=%d", call_id, code));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_answer()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Apply call setting, only if status code is 1xx or 2xx. */
+ if (opt && code < 300) {
+ /* Check if it has not been set previously or it is different to
+ * the previous one.
+ */
+ if (!call->opt_inited) {
+ call->opt_inited = PJ_TRUE;
+ apply_call_setting(call, opt, NULL);
+ } else if (pj_memcmp(opt, &call->opt, sizeof(*opt)) != 0) {
+ /* Warn application about call setting inconsistency */
+ PJ_LOG(2,(THIS_FILE, "The call setting changes is ignored."));
+ }
+ }
+
+ PJSUA_LOCK();
+
+ /* Ticket #1526: When the incoming call contains no SDP offer, the media
+ * channel may have not been initialized at this stage. The media channel
+ * will be initialized here (along with SDP local offer generation) when
+ * the following conditions are met:
+ * - no pending media channel init
+ * - local SDP has not been generated
+ * - call setting has just been set, or SDP offer needs to be sent, i.e:
+ * answer code 183 or 2xx is issued
+ */
+ if (!call->med_ch_cb &&
+ (call->opt_inited || (code==183 && code/100==2)) &&
+ (!call->inv->neg ||
+ pjmedia_sdp_neg_get_state(call->inv->neg) ==
+ PJMEDIA_SDP_NEG_STATE_NULL))
+ {
+ /* Mark call setting as initialized as it is just about to be used
+ * for initializing the media channel.
+ */
+ call->opt_inited = PJ_TRUE;
+
+ status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC,
+ call->secure_level,
+ dlg->pool,
+ NULL, NULL, PJ_TRUE,
+ &on_answer_call_med_tp_complete);
+ if (status == PJ_SUCCESS) {
+ status = on_answer_call_med_tp_complete(call->index, NULL);
+ if (status != PJ_SUCCESS) {
+ PJSUA_UNLOCK();
+ goto on_return;
+ }
+ } else if (status != PJ_EPENDING) {
+ PJSUA_UNLOCK();
+ pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+ goto on_return;
+ }
+ }
+
+ /* If media transport creation is not yet completed, we will answer
+ * the call in the media transport creation callback instead.
+ */
+ if (call->med_ch_cb) {
+ struct call_answer *answer;
+
+ PJ_LOG(4,(THIS_FILE, "Pending answering call %d upon completion "
+ "of media transport", call_id));
+
+ answer = PJ_POOL_ZALLOC_T(call->inv->pool_prov, struct call_answer);
+ answer->code = code;
+ if (opt) {
+ answer->opt = PJ_POOL_ZALLOC_T(call->inv->pool_prov,
+ pjsua_call_setting);
+ *answer->opt = *opt;
+ }
+ if (reason) {
+ pj_strdup(call->inv->pool_prov, answer->reason, reason);
+ }
+ if (msg_data) {
+ answer->msg_data = pjsua_msg_data_clone(call->inv->pool_prov,
+ msg_data);
+ }
+ pj_list_push_back(&call->async_call.call_var.inc_call.answers,
+ answer);
+
+ PJSUA_UNLOCK();
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+ }
+
+ PJSUA_UNLOCK();
+
+ if (call->res_time.sec == 0)
+ pj_gettimeofday(&call->res_time);
+
+ if (reason && reason->slen == 0)
+ reason = NULL;
+
+ /* Create response message */
+ status = pjsip_inv_answer(call->inv, code, reason, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating response",
+ status);
+ goto on_return;
+ }
+
+ /* Call might have been disconnected if application is answering with
+ * 200/OK and the media failed to start.
+ */
+ if (call->inv == NULL)
+ goto on_return;
+
+ /* Add additional headers etc */
+ pjsua_process_msg_data( tdata, msg_data);
+
+ /* Send the message */
+ status = pjsip_inv_send_msg(call->inv, tdata);
+ if (status != PJ_SUCCESS)
+ pjsua_perror(THIS_FILE, "Error sending response",
+ status);
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Hangup call by using method that is appropriate according to the
+ * call state.
+ */
+PJ_DEF(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id,
+ unsigned code,
+ const pj_str_t *reason,
+ const pjsua_msg_data *msg_data)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+
+ if (call_id<0 || call_id>=(int)pjsua_var.ua_cfg.max_calls) {
+ PJ_LOG(1,(THIS_FILE, "pjsua_call_hangup(): invalid call id %d",
+ call_id));
+ }
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Call %d hanging up: code=%d..", call_id, code));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_hangup()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ if (code==0) {
+ if (call->inv->state == PJSIP_INV_STATE_CONFIRMED)
+ code = PJSIP_SC_OK;
+ else if (call->inv->role == PJSIP_ROLE_UAS)
+ code = PJSIP_SC_DECLINE;
+ else
+ code = PJSIP_SC_REQUEST_TERMINATED;
+ }
+
+ status = pjsip_inv_end_session(call->inv, code, reason, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Failed to create end session message",
+ status);
+ goto on_return;
+ }
+
+ /* pjsip_inv_end_session may return PJ_SUCCESS with NULL
+ * as p_tdata when INVITE transaction has not been answered
+ * with any provisional responses.
+ */
+ if (tdata == NULL)
+ goto on_return;
+
+ /* Add additional headers etc */
+ pjsua_process_msg_data( tdata, msg_data);
+
+ /* Send the message */
+ status = pjsip_inv_send_msg(call->inv, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Failed to send end session message",
+ status);
+ goto on_return;
+ }
+
+ /* Stop lock codec timer, if it is active */
+ if (call->lock_codec.reinv_timer.id) {
+ pjsip_endpt_cancel_timer(pjsua_var.endpt,
+ &call->lock_codec.reinv_timer);
+ call->lock_codec.reinv_timer.id = PJ_FALSE;
+ }
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Accept or reject redirection.
+ */
+PJ_DEF(pj_status_t) pjsua_call_process_redirect( pjsua_call_id call_id,
+ pjsip_redirect_op cmd)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ status = acquire_call("pjsua_call_process_redirect()", call_id,
+ &call, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = pjsip_inv_process_redirect(call->inv, cmd, NULL);
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return status;
+}
+
+
+/*
+ * Put the specified call on hold.
+ */
+PJ_DEF(pj_status_t) pjsua_call_set_hold(pjsua_call_id call_id,
+ const pjsua_msg_data *msg_data)
+{
+ pjmedia_sdp_session *sdp;
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Putting call %d on hold", call_id));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_set_hold()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
+ PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed"));
+ status = PJSIP_ESESSIONSTATE;
+ goto on_return;
+ }
+
+ status = create_sdp_of_call_hold(call, &sdp);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create re-INVITE with new offer */
+ status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
+ goto on_return;
+ }
+
+ /* Add additional headers etc */
+ pjsua_process_msg_data( tdata, msg_data);
+
+ /* Record the tx_data to keep track the operation */
+ call->hold_msg = (void*) tdata;
+
+ /* Send the request */
+ status = pjsip_inv_send_msg( call->inv, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
+ call->hold_msg = NULL;
+ goto on_return;
+ }
+
+ /* Set flag that local put the call on hold */
+ call->local_hold = PJ_TRUE;
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Send re-INVITE (to release hold).
+ */
+PJ_DEF(pj_status_t) pjsua_call_reinvite( pjsua_call_id call_id,
+ unsigned options,
+ const pjsua_msg_data *msg_data)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pj_status_t status;
+
+ status = acquire_call("pjsua_call_reinvite()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ if (options != call->opt.flag)
+ call->opt.flag = options;
+
+ status = pjsua_call_reinvite2(call_id, NULL, msg_data);
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ return status;
+}
+
+
+/*
+ * Send re-INVITE (to release hold).
+ */
+PJ_DEF(pj_status_t) pjsua_call_reinvite2(pjsua_call_id call_id,
+ const pjsua_call_setting *opt,
+ const pjsua_msg_data *msg_data)
+{
+ pjmedia_sdp_session *sdp;
+ pj_str_t *new_contact = NULL;
+ pjsip_tx_data *tdata;
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pj_status_t status;
+
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Sending re-INVITE on call %d", call_id));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_reinvite2()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
+ PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
+ status = PJSIP_ESESSIONSTATE;
+ goto on_return;
+ }
+
+ status = apply_call_setting(call, opt, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Failed to apply call setting", status);
+ goto on_return;
+ }
+
+ /* Create SDP */
+ if (call->local_hold && (call->opt.flag & PJSUA_CALL_UNHOLD)==0) {
+ status = create_sdp_of_call_hold(call, &sdp);
+ } else {
+ status = pjsua_media_channel_create_sdp(call->index,
+ call->inv->pool_prov,
+ NULL, &sdp, NULL);
+ call->local_hold = PJ_FALSE;
+ }
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint",
+ status);
+ goto on_return;
+ }
+
+ if ((call->opt.flag & PJSUA_CALL_UPDATE_CONTACT) &
+ pjsua_acc_is_valid(call->acc_id))
+ {
+ new_contact = &pjsua_var.acc[call->acc_id].contact;
+ }
+
+ /* Create re-INVITE with new offer */
+ status = pjsip_inv_reinvite( call->inv, new_contact, sdp, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
+ goto on_return;
+ }
+
+ /* Add additional headers etc */
+ pjsua_process_msg_data( tdata, msg_data);
+
+ /* Send the request */
+ status = pjsip_inv_send_msg( call->inv, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
+ goto on_return;
+ }
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Send UPDATE request.
+ */
+PJ_DEF(pj_status_t) pjsua_call_update( pjsua_call_id call_id,
+ unsigned options,
+ const pjsua_msg_data *msg_data)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pj_status_t status;
+
+ status = acquire_call("pjsua_call_update()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ if (options != call->opt.flag)
+ call->opt.flag = options;
+
+ status = pjsua_call_update2(call_id, NULL, msg_data);
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ return status;
+}
+
+
+/*
+ * Send UPDATE request.
+ */
+PJ_DEF(pj_status_t) pjsua_call_update2(pjsua_call_id call_id,
+ const pjsua_call_setting *opt,
+ const pjsua_msg_data *msg_data)
+{
+ pjmedia_sdp_session *sdp;
+ pj_str_t *new_contact = NULL;
+ pjsip_tx_data *tdata;
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Sending UPDATE on call %d", call_id));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_update2()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ status = apply_call_setting(call, opt, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Failed to apply call setting", status);
+ goto on_return;
+ }
+
+ /* Create SDP */
+ status = pjsua_media_channel_create_sdp(call->index,
+ call->inv->pool_prov,
+ NULL, &sdp, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint",
+ status);
+ goto on_return;
+ }
+
+ if ((call->opt.flag & PJSUA_CALL_UPDATE_CONTACT) &
+ pjsua_acc_is_valid(call->acc_id))
+ {
+ new_contact = &pjsua_var.acc[call->acc_id].contact;
+ }
+
+ /* Create UPDATE with new offer */
+ status = pjsip_inv_update(call->inv, new_contact, sdp, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create UPDATE request", status);
+ goto on_return;
+ }
+
+ /* Add additional headers etc */
+ pjsua_process_msg_data( tdata, msg_data);
+
+ /* Send the request */
+ status = pjsip_inv_send_msg( call->inv, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send UPDATE request", status);
+ goto on_return;
+ }
+
+ call->local_hold = PJ_FALSE;
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Initiate call transfer to the specified address.
+ */
+PJ_DEF(pj_status_t) pjsua_call_xfer( pjsua_call_id call_id,
+ const pj_str_t *dest,
+ const pjsua_msg_data *msg_data)
+{
+ pjsip_evsub *sub;
+ pjsip_tx_data *tdata;
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pjsip_generic_string_hdr *gs_hdr;
+ const pj_str_t str_ref_by = { "Referred-By", 11 };
+ struct pjsip_evsub_user xfer_cb;
+ pj_status_t status;
+
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls &&
+ dest, PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Transfering call %d to %.*s", call_id,
+ (int)dest->slen, dest->ptr));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_xfer()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create xfer client subscription. */
+ pj_bzero(&xfer_cb, sizeof(xfer_cb));
+ xfer_cb.on_evsub_state = &xfer_client_on_evsub_state;
+
+ status = pjsip_xfer_create_uac(call->inv->dlg, &xfer_cb, &sub);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create xfer", status);
+ goto on_return;
+ }
+
+ /* Associate this call with the client subscription */
+ pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, call);
+
+ /*
+ * Create REFER request.
+ */
+ status = pjsip_xfer_initiate(sub, dest, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create REFER request", status);
+ goto on_return;
+ }
+
+ /* Add Referred-By header */
+ gs_hdr = pjsip_generic_string_hdr_create(tdata->pool, &str_ref_by,
+ &dlg->local.info_str);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)gs_hdr);
+
+
+ /* Add additional headers etc */
+ pjsua_process_msg_data( tdata, msg_data);
+
+ /* Send. */
+ status = pjsip_xfer_send_request(sub, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send REFER request", status);
+ goto on_return;
+ }
+
+ /* For simplicity (that's what this program is intended to be!),
+ * leave the original invite session as it is. More advanced application
+ * may want to hold the INVITE, or terminate the invite, or whatever.
+ */
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+
+}
+
+
+/*
+ * Initiate attended call transfer to the specified address.
+ */
+PJ_DEF(pj_status_t) pjsua_call_xfer_replaces( pjsua_call_id call_id,
+ pjsua_call_id dest_call_id,
+ unsigned options,
+ const pjsua_msg_data *msg_data)
+{
+ pjsua_call *dest_call;
+ pjsip_dialog *dest_dlg;
+ char str_dest_buf[PJSIP_MAX_URL_SIZE*2];
+ pj_str_t str_dest;
+ int len;
+ pjsip_uri *uri;
+ pj_status_t status;
+
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(dest_call_id>=0 &&
+ dest_call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Transfering call %d replacing with call %d",
+ call_id, dest_call_id));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_xfer_replaces()", dest_call_id,
+ &dest_call, &dest_dlg);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ return status;
+ }
+
+ /*
+ * Create REFER destination URI with Replaces field.
+ */
+
+ /* Make sure we have sufficient buffer's length */
+ PJ_ASSERT_ON_FAIL(dest_dlg->remote.info_str.slen +
+ dest_dlg->call_id->id.slen +
+ dest_dlg->remote.info->tag.slen +
+ dest_dlg->local.info->tag.slen + 32
+ < (long)sizeof(str_dest_buf),
+ { status=PJSIP_EURITOOLONG; goto on_error; });
+
+ /* Print URI */
+ str_dest_buf[0] = '<';
+ str_dest.slen = 1;
+
+ uri = (pjsip_uri*) pjsip_uri_get_uri(dest_dlg->remote.info->uri);
+ len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri,
+ str_dest_buf+1, sizeof(str_dest_buf)-1);
+ if (len < 0) {
+ status = PJSIP_EURITOOLONG;
+ goto on_error;
+ }
+
+ str_dest.slen += len;
+
+
+ /* Build the URI */
+ len = pj_ansi_snprintf(str_dest_buf + str_dest.slen,
+ sizeof(str_dest_buf) - str_dest.slen,
+ "?%s"
+ "Replaces=%.*s"
+ "%%3Bto-tag%%3D%.*s"
+ "%%3Bfrom-tag%%3D%.*s>",
+ ((options&PJSUA_XFER_NO_REQUIRE_REPLACES) ?
+ "" : "Require=replaces&"),
+ (int)dest_dlg->call_id->id.slen,
+ dest_dlg->call_id->id.ptr,
+ (int)dest_dlg->remote.info->tag.slen,
+ dest_dlg->remote.info->tag.ptr,
+ (int)dest_dlg->local.info->tag.slen,
+ dest_dlg->local.info->tag.ptr);
+
+ PJ_ASSERT_ON_FAIL(len > 0 && len <= (int)sizeof(str_dest_buf)-str_dest.slen,
+ { status=PJSIP_EURITOOLONG; goto on_error; });
+
+ str_dest.ptr = str_dest_buf;
+ str_dest.slen += len;
+
+ pjsip_dlg_dec_lock(dest_dlg);
+
+ status = pjsua_call_xfer(call_id, &str_dest, msg_data);
+
+ pj_log_pop_indent();
+ return status;
+
+on_error:
+ if (dest_dlg) pjsip_dlg_dec_lock(dest_dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/**
+ * Send instant messaging inside INVITE session.
+ */
+PJ_DEF(pj_status_t) pjsua_call_send_im( pjsua_call_id call_id,
+ const pj_str_t *mime_type,
+ const pj_str_t *content,
+ const pjsua_msg_data *msg_data,
+ void *user_data)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ const pj_str_t mime_text_plain = pj_str("text/plain");
+ pjsip_media_type ctype;
+ pjsua_im_data *im_data;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Call %d sending %d bytes MESSAGE..",
+ call_id, (int)content->slen));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_send_im()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Set default media type if none is specified */
+ if (mime_type == NULL) {
+ mime_type = &mime_text_plain;
+ }
+
+ /* Create request message. */
+ status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method,
+ -1, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status);
+ goto on_return;
+ }
+
+ /* Add accept header. */
+ pjsip_msg_add_hdr( tdata->msg,
+ (pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
+
+ /* Parse MIME type */
+ pjsua_parse_media_type(tdata->pool, mime_type, &ctype);
+
+ /* Create "text/plain" message body. */
+ tdata->msg->body = pjsip_msg_body_create( tdata->pool, &ctype.type,
+ &ctype.subtype, content);
+ if (tdata->msg->body == NULL) {
+ pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM);
+ pjsip_tx_data_dec_ref(tdata);
+ goto on_return;
+ }
+
+ /* Add additional headers etc */
+ pjsua_process_msg_data( tdata, msg_data);
+
+ /* Create IM data and attach to the request. */
+ im_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsua_im_data);
+ im_data->acc_id = call->acc_id;
+ im_data->call_id = call_id;
+ im_data->to = call->inv->dlg->remote.info_str;
+ pj_strdup_with_null(tdata->pool, &im_data->body, content);
+ im_data->user_data = user_data;
+
+
+ /* Send the request. */
+ status = pjsip_dlg_send_request( call->inv->dlg, tdata,
+ pjsua_var.mod.id, im_data);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status);
+ goto on_return;
+ }
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Send IM typing indication inside INVITE session.
+ */
+PJ_DEF(pj_status_t) pjsua_call_send_typing_ind( pjsua_call_id call_id,
+ pj_bool_t is_typing,
+ const pjsua_msg_data*msg_data)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Call %d sending typing indication..",
+ call_id));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_send_typing_ind", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create request message. */
+ status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method,
+ -1, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status);
+ goto on_return;
+ }
+
+ /* Create "application/im-iscomposing+xml" msg body. */
+ tdata->msg->body = pjsip_iscomposing_create_body(tdata->pool, is_typing,
+ NULL, NULL, -1);
+
+ /* Add additional headers etc */
+ pjsua_process_msg_data( tdata, msg_data);
+
+ /* Send the request. */
+ status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status);
+ goto on_return;
+ }
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Send arbitrary request.
+ */
+PJ_DEF(pj_status_t) pjsua_call_send_request(pjsua_call_id call_id,
+ const pj_str_t *method_str,
+ const pjsua_msg_data *msg_data)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg = NULL;
+ pjsip_method method;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Call %d sending %.*s request..",
+ call_id, (int)method_str->slen, method_str->ptr));
+ pj_log_push_indent();
+
+ status = acquire_call("pjsua_call_send_request", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Init method */
+ pjsip_method_init_np(&method, (pj_str_t*)method_str);
+
+ /* Create request message. */
+ status = pjsip_dlg_create_request( call->inv->dlg, &method, -1, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create request", status);
+ goto on_return;
+ }
+
+ /* Add additional headers etc */
+ pjsua_process_msg_data( tdata, msg_data);
+
+ /* Send the request. */
+ status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send request", status);
+ goto on_return;
+ }
+
+on_return:
+ if (dlg) pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/*
+ * Terminate all calls.
+ */
+PJ_DEF(void) pjsua_call_hangup_all(void)
+{
+ unsigned i;
+
+ PJ_LOG(4,(THIS_FILE, "Hangup all calls.."));
+ pj_log_push_indent();
+
+ // This may deadlock, see https://trac.pjsip.org/repos/ticket/1305
+ //PJSUA_LOCK();
+
+ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+ if (pjsua_var.calls[i].inv)
+ pjsua_call_hangup(i, 0, NULL, NULL);
+ }
+
+ //PJSUA_UNLOCK();
+ pj_log_pop_indent();
+}
+
+
+/* Proto */
+static pj_status_t perform_lock_codec(pjsua_call *call);
+
+/* Timer callback to send re-INVITE or UPDATE to lock codec */
+static void reinv_timer_cb(pj_timer_heap_t *th,
+ pj_timer_entry *entry)
+{
+ pjsua_call_id call_id = (pjsua_call_id)(pj_size_t)entry->user_data;
+ pjsip_dialog *dlg;
+ pjsua_call *call;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(th);
+
+ pjsua_var.calls[call_id].lock_codec.reinv_timer.id = PJ_FALSE;
+
+ status = acquire_call("reinv_timer_cb()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ return;
+
+ status = perform_lock_codec(call);
+
+ pjsip_dlg_dec_lock(dlg);
+}
+
+
+/* Check if the specified format can be skipped in counting codecs */
+static pj_bool_t is_non_av_fmt(const pjmedia_sdp_media *m,
+ const pj_str_t *fmt)
+{
+ const pj_str_t STR_TEL = {"telephone-event", 15};
+ unsigned pt;
+
+ pt = pj_strtoul(fmt);
+
+ /* Check for comfort noise */
+ if (pt == PJMEDIA_RTP_PT_CN)
+ return PJ_TRUE;
+
+ /* Dynamic PT, check the format name */
+ if (pt >= 96) {
+ pjmedia_sdp_attr *a;
+ pjmedia_sdp_rtpmap rtpmap;
+
+ /* Get the format name */
+ a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt);
+ if (a && pjmedia_sdp_attr_get_rtpmap(a, &rtpmap)==PJ_SUCCESS) {
+ /* Check for telephone-event */
+ if (pj_stricmp(&rtpmap.enc_name, &STR_TEL)==0)
+ return PJ_TRUE;
+ } else {
+ /* Invalid SDP, should not reach here */
+ pj_assert(!"SDP should have been validated!");
+ return PJ_TRUE;
+ }
+ }
+
+ return PJ_FALSE;
+}
+
+
+/* Send re-INVITE or UPDATE with new SDP offer to select only one codec
+ * out of several codecs presented by callee in his answer.
+ */
+static pj_status_t perform_lock_codec(pjsua_call *call)
+{
+ const pj_str_t STR_UPDATE = {"UPDATE", 6};
+ const pjmedia_sdp_session *local_sdp = NULL, *new_sdp;
+ unsigned i;
+ pj_bool_t rem_can_update;
+ pj_bool_t need_lock_codec = PJ_FALSE;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call->lock_codec.reinv_timer.id==PJ_FALSE,
+ PJ_EINVALIDOP);
+
+ /* Verify if another SDP negotiation is in progress, e.g: session timer
+ * or another re-INVITE.
+ */
+ if (call->inv==NULL || call->inv->neg==NULL ||
+ pjmedia_sdp_neg_get_state(call->inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE)
+ {
+ return PJMEDIA_SDPNEG_EINSTATE;
+ }
+
+ /* Don't do this if call is disconnecting! */
+ if (call->inv->state > PJSIP_INV_STATE_CONFIRMED ||
+ call->inv->cause >= 200)
+ {
+ return PJ_EINVALIDOP;
+ }
+
+ /* Verify if another SDP negotiation has been completed by comparing
+ * the SDP version.
+ */
+ status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp);
+ if (status != PJ_SUCCESS)
+ return status;
+ if (local_sdp->origin.version > call->lock_codec.sdp_ver)
+ return PJMEDIA_SDP_EINVER;
+
+ PJ_LOG(3, (THIS_FILE, "Updating media session to use only one codec.."));
+
+ /* Update the new offer so it contains only a codec. Note that formats
+ * order in the offer should have been matched to the answer, so we can
+ * just directly update the offer without looking-up the answer.
+ */
+ new_sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, local_sdp);
+
+ for (i = 0; i < call->med_cnt; ++i) {
+ unsigned j = 0, codec_cnt = 0;
+ const pjmedia_sdp_media *ref_m;
+ pjmedia_sdp_media *m;
+ pjsua_call_media *call_med = &call->media[i];
+
+ /* Verify if media is deactivated */
+ if (call_med->state == PJSUA_CALL_MEDIA_NONE ||
+ call_med->state == PJSUA_CALL_MEDIA_ERROR ||
+ call_med->dir == PJMEDIA_DIR_NONE)
+ {
+ continue;
+ }
+
+ ref_m = local_sdp->media[i];
+ m = new_sdp->media[i];
+
+ /* Verify that media must be active. */
+ pj_assert(ref_m->desc.port);
+
+ while (j < m->desc.fmt_count) {
+ pjmedia_sdp_attr *a;
+ pj_str_t *fmt = &m->desc.fmt[j];
+
+ if (is_non_av_fmt(m, fmt) || (++codec_cnt == 1)) {
+ ++j;
+ continue;
+ }
+
+ /* Remove format */
+ a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt);
+ if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a);
+ a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "fmtp", fmt);
+ if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a);
+ pj_array_erase(m->desc.fmt, sizeof(m->desc.fmt[0]),
+ m->desc.fmt_count, j);
+ --m->desc.fmt_count;
+ }
+
+ need_lock_codec |= (ref_m->desc.fmt_count > m->desc.fmt_count);
+ }
+
+ /* Last check if SDP trully needs to be updated. It is possible that OA
+ * negotiations have completed and SDP has changed but we didn't
+ * increase the SDP version (should not happen!).
+ */
+ if (!need_lock_codec)
+ return PJ_SUCCESS;
+
+ /* Send UPDATE or re-INVITE */
+ rem_can_update = pjsip_dlg_remote_has_cap(call->inv->dlg,
+ PJSIP_H_ALLOW,
+ NULL, &STR_UPDATE) ==
+ PJSIP_DIALOG_CAP_SUPPORTED;
+ if (rem_can_update) {
+ status = pjsip_inv_update(call->inv, NULL, new_sdp, &tdata);
+ } else {
+ status = pjsip_inv_reinvite(call->inv, NULL, new_sdp, &tdata);
+ }
+
+ if (status==PJ_EINVALIDOP &&
+ ++call->lock_codec.retry_cnt <= LOCK_CODEC_MAX_RETRY)
+ {
+ /* Ups, let's reschedule again */
+ pj_time_val delay = {0, LOCK_CODEC_RETRY_INTERVAL};
+ pj_time_val_normalize(&delay);
+ call->lock_codec.reinv_timer.id = PJ_TRUE;
+ pjsip_endpt_schedule_timer(pjsua_var.endpt,
+ &call->lock_codec.reinv_timer, &delay);
+ return status;
+ } else if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating UPDATE/re-INVITE to lock codec",
+ status);
+ return status;
+ }
+
+ /* Send the UPDATE/re-INVITE request */
+ status = pjsip_inv_send_msg(call->inv, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error sending UPDATE/re-INVITE in lock codec",
+ status);
+ return status;
+ }
+
+ return status;
+}
+
+/* Check if remote answerer has given us more than one codecs. If so,
+ * create another offer with one codec only to lock down the codec.
+ */
+static pj_status_t lock_codec(pjsua_call *call)
+{
+ pjsip_inv_session *inv = call->inv;
+ const pjmedia_sdp_session *local_sdp, *remote_sdp;
+ pj_time_val delay = {0, 0};
+ const pj_str_t st_update = {"UPDATE", 6};
+ unsigned i;
+ pj_bool_t has_mult_fmt = PJ_FALSE;
+ pj_status_t status;
+
+ /* Stop lock codec timer, if it is active */
+ if (call->lock_codec.reinv_timer.id) {
+ pjsip_endpt_cancel_timer(pjsua_var.endpt,
+ &call->lock_codec.reinv_timer);
+ call->lock_codec.reinv_timer.id = PJ_FALSE;
+ }
+
+ /* Skip this if we are the answerer */
+ if (!inv->neg || !pjmedia_sdp_neg_was_answer_remote(inv->neg)) {
+ return PJ_SUCCESS;
+ }
+
+ /* Delay this when the SDP negotiation done in call state EARLY and
+ * remote does not support UPDATE method.
+ */
+ if (inv->state == PJSIP_INV_STATE_EARLY &&
+ pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, &st_update)!=
+ PJSIP_DIALOG_CAP_SUPPORTED)
+ {
+ call->lock_codec.pending = PJ_TRUE;
+ return PJ_SUCCESS;
+ }
+
+ status = pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
+ if (status != PJ_SUCCESS)
+ return status;
+ status = pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Find multiple codecs answer in all media */
+ for (i = 0; i < call->med_cnt; ++i) {
+ pjsua_call_media *call_med = &call->media[i];
+ const pjmedia_sdp_media *rem_m, *loc_m;
+ unsigned codec_cnt = 0;
+
+ /* Skip this if the media is inactive or error */
+ if (call_med->state == PJSUA_CALL_MEDIA_NONE ||
+ call_med->state == PJSUA_CALL_MEDIA_ERROR ||
+ call_med->dir == PJMEDIA_DIR_NONE)
+ {
+ continue;
+ }
+
+ /* Remote may answer with less media lines. */
+ if (i >= remote_sdp->media_count)
+ continue;
+
+ rem_m = remote_sdp->media[i];
+ loc_m = local_sdp->media[i];
+
+ /* Verify that media must be active. */
+ pj_assert(loc_m->desc.port && rem_m->desc.port);
+
+ /* Count the formats in the answer. */
+ if (rem_m->desc.fmt_count==1) {
+ codec_cnt = 1;
+ } else {
+ unsigned j;
+ for (j=0; j<rem_m->desc.fmt_count && codec_cnt <= 1; ++j) {
+ if (!is_non_av_fmt(rem_m, &rem_m->desc.fmt[j]))
+ ++codec_cnt;
+ }
+ }
+
+ if (codec_cnt > 1) {
+ has_mult_fmt = PJ_TRUE;
+ break;
+ }
+ }
+
+ /* Each media in the answer already contains single codec. */
+ if (!has_mult_fmt) {
+ call->lock_codec.retry_cnt = 0;
+ return PJ_SUCCESS;
+ }
+
+ /* Remote keeps answering with multiple codecs, let's just give up
+ * locking codec to avoid infinite retry loop.
+ */
+ if (++call->lock_codec.retry_cnt > LOCK_CODEC_MAX_RETRY)
+ return PJ_SUCCESS;
+
+ PJ_LOG(4, (THIS_FILE, "Got answer with multiple codecs, scheduling "
+ "updating media session to use only one codec.."));
+
+ call->lock_codec.sdp_ver = local_sdp->origin.version;
+
+ /* Can't send UPDATE or re-INVITE now, so just schedule it immediately.
+ * See: https://trac.pjsip.org/repos/ticket/1149
+ */
+ pj_timer_entry_init(&call->lock_codec.reinv_timer, PJ_TRUE,
+ (void*)(pj_size_t)call->index,
+ &reinv_timer_cb);
+ pjsip_endpt_schedule_timer(pjsua_var.endpt,
+ &call->lock_codec.reinv_timer, &delay);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * This callback receives notification from invite session when the
+ * session state has changed.
+ */
+static void pjsua_call_on_state_changed(pjsip_inv_session *inv,
+ pjsip_event *e)
+{
+ pjsua_call *call;
+
+ pj_log_push_indent();
+
+ call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
+
+ if (!call) {
+ pj_log_pop_indent();
+ return;
+ }
+
+
+ /* Get call times */
+ switch (inv->state) {
+ case PJSIP_INV_STATE_EARLY:
+ case PJSIP_INV_STATE_CONNECTING:
+ if (call->res_time.sec == 0)
+ pj_gettimeofday(&call->res_time);
+ call->last_code = (pjsip_status_code)
+ e->body.tsx_state.tsx->status_code;
+ pj_strncpy(&call->last_text,
+ &e->body.tsx_state.tsx->status_text,
+ sizeof(call->last_text_buf_));
+ break;
+ case PJSIP_INV_STATE_CONFIRMED:
+ pj_gettimeofday(&call->conn_time);
+
+ /* See if lock codec was pended as media update was done in the
+ * EARLY state and remote does not support UPDATE.
+ */
+ if (call->lock_codec.pending) {
+ pj_status_t status;
+ status = lock_codec(call);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to lock codec", status);
+ }
+ call->lock_codec.pending = PJ_FALSE;
+ }
+ break;
+ case PJSIP_INV_STATE_DISCONNECTED:
+ pj_gettimeofday(&call->dis_time);
+ if (call->res_time.sec == 0)
+ pj_gettimeofday(&call->res_time);
+ if (e->type == PJSIP_EVENT_TSX_STATE &&
+ e->body.tsx_state.tsx->status_code > call->last_code)
+ {
+ call->last_code = (pjsip_status_code)
+ e->body.tsx_state.tsx->status_code;
+ pj_strncpy(&call->last_text,
+ &e->body.tsx_state.tsx->status_text,
+ sizeof(call->last_text_buf_));
+ } else {
+ call->last_code = PJSIP_SC_REQUEST_TERMINATED;
+ pj_strncpy(&call->last_text,
+ pjsip_get_status_text(call->last_code),
+ sizeof(call->last_text_buf_));
+ }
+
+ /* Stop lock codec timer, if it is active */
+ if (call->lock_codec.reinv_timer.id) {
+ pjsip_endpt_cancel_timer(pjsua_var.endpt,
+ &call->lock_codec.reinv_timer);
+ call->lock_codec.reinv_timer.id = PJ_FALSE;
+ }
+ break;
+ default:
+ call->last_code = (pjsip_status_code)
+ e->body.tsx_state.tsx->status_code;
+ pj_strncpy(&call->last_text,
+ &e->body.tsx_state.tsx->status_text,
+ sizeof(call->last_text_buf_));
+ break;
+ }
+
+ /* If this is an outgoing INVITE that was created because of
+ * REFER/transfer, send NOTIFY to transferer.
+ */
+ if (call->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) {
+ int st_code = -1;
+ pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE;
+
+
+ switch (call->inv->state) {
+ case PJSIP_INV_STATE_NULL:
+ case PJSIP_INV_STATE_CALLING:
+ /* Do nothing */
+ break;
+
+ case PJSIP_INV_STATE_EARLY:
+ case PJSIP_INV_STATE_CONNECTING:
+ st_code = e->body.tsx_state.tsx->status_code;
+ if (call->inv->state == PJSIP_INV_STATE_CONNECTING)
+ ev_state = PJSIP_EVSUB_STATE_TERMINATED;
+ else
+ ev_state = PJSIP_EVSUB_STATE_ACTIVE;
+ break;
+
+ case PJSIP_INV_STATE_CONFIRMED:
+#if 0
+/* We don't need this, as we've terminated the subscription in
+ * CONNECTING state.
+ */
+ /* When state is confirmed, send the final 200/OK and terminate
+ * subscription.
+ */
+ st_code = e->body.tsx_state.tsx->status_code;
+ ev_state = PJSIP_EVSUB_STATE_TERMINATED;
+#endif
+ break;
+
+ case PJSIP_INV_STATE_DISCONNECTED:
+ st_code = e->body.tsx_state.tsx->status_code;
+ ev_state = PJSIP_EVSUB_STATE_TERMINATED;
+ break;
+
+ case PJSIP_INV_STATE_INCOMING:
+ /* Nothing to do. Just to keep gcc from complaining about
+ * unused enums.
+ */
+ break;
+ }
+
+ if (st_code != -1) {
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_xfer_notify( call->xfer_sub,
+ ev_state, st_code,
+ NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status);
+ } else {
+ status = pjsip_xfer_send_request(call->xfer_sub, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status);
+ }
+ }
+ }
+ }
+
+
+ if (pjsua_var.ua_cfg.cb.on_call_state)
+ (*pjsua_var.ua_cfg.cb.on_call_state)(call->index, e);
+
+ /* call->inv may be NULL now */
+
+ /* Destroy media session when invite session is disconnected. */
+ if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+
+ PJSUA_LOCK();
+
+ pjsua_media_channel_deinit(call->index);
+
+ /* Free call */
+ call->inv = NULL;
+
+ pj_assert(pjsua_var.call_cnt > 0);
+ --pjsua_var.call_cnt;
+
+ /* Reset call */
+ reset_call(call->index);
+
+ pjsua_check_snd_dev_idle();
+
+ PJSUA_UNLOCK();
+ }
+ pj_log_pop_indent();
+}
+
+/*
+ * This callback is called by invite session framework when UAC session
+ * has forked.
+ */
+static void pjsua_call_on_forked( pjsip_inv_session *inv,
+ pjsip_event *e)
+{
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(e);
+
+ PJ_TODO(HANDLE_FORKED_DIALOG);
+}
+
+
+/*
+ * Callback from UA layer when forked dialog response is received.
+ */
+pjsip_dialog* on_dlg_forked(pjsip_dialog *dlg, pjsip_rx_data *res)
+{
+ if (dlg->uac_has_2xx &&
+ res->msg_info.cseq->method.id == PJSIP_INVITE_METHOD &&
+ pjsip_rdata_get_tsx(res) == NULL &&
+ res->msg_info.msg->line.status.code/100 == 2)
+ {
+ pjsip_dialog *forked_dlg;
+ pjsip_tx_data *bye;
+ pj_status_t status;
+
+ /* Create forked dialog */
+ status = pjsip_dlg_fork(dlg, res, &forked_dlg);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ pjsip_dlg_inc_lock(forked_dlg);
+
+ /* Disconnect the call */
+ status = pjsip_dlg_create_request(forked_dlg, &pjsip_bye_method,
+ -1, &bye);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_dlg_send_request(forked_dlg, bye, -1, NULL);
+ }
+
+ pjsip_dlg_dec_lock(forked_dlg);
+
+ if (status != PJ_SUCCESS) {
+ return NULL;
+ }
+
+ return forked_dlg;
+
+ } else {
+ return dlg;
+ }
+}
+
+/*
+ * Disconnect call upon error.
+ */
+static void call_disconnect( pjsip_inv_session *inv,
+ int code )
+{
+ pjsua_call *call;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
+
+ status = pjsip_inv_end_session(inv, code, NULL, &tdata);
+ if (status != PJ_SUCCESS)
+ return;
+
+ /* Add SDP in 488 status */
+#if DISABLED_FOR_TICKET_1185
+ if (call && call->tp && tdata->msg->type==PJSIP_RESPONSE_MSG &&
+ code==PJSIP_SC_NOT_ACCEPTABLE_HERE)
+ {
+ pjmedia_sdp_session *local_sdp;
+ pjmedia_transport_info ti;
+
+ pjmedia_transport_info_init(&ti);
+ pjmedia_transport_get_info(call->med_tp, &ti);
+ status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, tdata->pool,
+ 1, &ti.sock_info, &local_sdp);
+ if (status == PJ_SUCCESS) {
+ pjsip_create_sdp_body(tdata->pool, local_sdp,
+ &tdata->msg->body);
+ }
+ }
+#endif
+
+ pjsip_inv_send_msg(inv, tdata);
+}
+
+/*
+ * Callback to be called when SDP offer/answer negotiation has just completed
+ * in the session. This function will start/update media if negotiation
+ * has succeeded.
+ */
+static void pjsua_call_on_media_update(pjsip_inv_session *inv,
+ pj_status_t status)
+{
+ pjsua_call *call;
+ const pjmedia_sdp_session *local_sdp;
+ const pjmedia_sdp_session *remote_sdp;
+ //const pj_str_t st_update = {"UPDATE", 6};
+
+ pj_log_push_indent();
+
+ call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
+
+ if (status != PJ_SUCCESS) {
+
+ pjsua_perror(THIS_FILE, "SDP negotiation has failed", status);
+
+ /* Clean up provisional media */
+ pjsua_media_prov_clean_up(call->index);
+
+ /* Do not deinitialize media since this may be a re-INVITE or
+ * UPDATE (which in this case the media should not get affected
+ * by the failed re-INVITE/UPDATE). The media will be shutdown
+ * when call is disconnected anyway.
+ */
+ /* Stop/destroy media, if any */
+ /*pjsua_media_channel_deinit(call->index);*/
+
+ /* Disconnect call if we're not in the middle of initializing an
+ * UAS dialog and if this is not a re-INVITE
+ */
+ if (inv->state != PJSIP_INV_STATE_NULL &&
+ inv->state != PJSIP_INV_STATE_CONFIRMED)
+ {
+ call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+ }
+
+ goto on_return;
+ }
+
+
+ /* Get local and remote SDP */
+ status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Unable to retrieve currently active local SDP",
+ status);
+ //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+ goto on_return;
+ }
+
+ status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Unable to retrieve currently active remote SDP",
+ status);
+ //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+ goto on_return;
+ }
+
+ /* Update remote's NAT type */
+ if (pjsua_var.ua_cfg.nat_type_in_sdp) {
+ update_remote_nat_type(call, remote_sdp);
+ }
+
+ /* Update media channel with the new SDP */
+ status = pjsua_media_channel_update(call->index, local_sdp, remote_sdp);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create media session",
+ status);
+ call_disconnect(inv, PJSIP_SC_NOT_ACCEPTABLE_HERE);
+ /* No need to deinitialize; media will be shutdown when call
+ * state is disconnected anyway.
+ */
+ /*pjsua_media_channel_deinit(call->index);*/
+ goto on_return;
+ }
+
+ /* Ticket #476: make sure only one codec is specified in the answer. */
+ status = lock_codec(call);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to lock codec", status);
+ }
+
+ /* Call application callback, if any */
+ if (pjsua_var.ua_cfg.cb.on_call_media_state)
+ pjsua_var.ua_cfg.cb.on_call_media_state(call->index);
+
+on_return:
+ pj_log_pop_indent();
+}
+
+
+/* Modify SDP for call hold. */
+static pj_status_t modify_sdp_of_call_hold(pjsua_call *call,
+ pj_pool_t *pool,
+ pjmedia_sdp_session *sdp)
+{
+ unsigned mi;
+
+ /* Call-hold is done by set the media direction to 'sendonly'
+ * (PJMEDIA_DIR_ENCODING), except when current media direction is
+ * 'inactive' (PJMEDIA_DIR_NONE).
+ * (See RFC 3264 Section 8.4 and RFC 4317 Section 3.1)
+ */
+ /* http://trac.pjsip.org/repos/ticket/880
+ if (call->dir != PJMEDIA_DIR_ENCODING) {
+ */
+ /* https://trac.pjsip.org/repos/ticket/1142:
+ * configuration to use c=0.0.0.0 for call hold.
+ */
+
+ for (mi=0; mi<sdp->media_count; ++mi) {
+ pjmedia_sdp_media *m = sdp->media[mi];
+
+ if (call->call_hold_type == PJSUA_CALL_HOLD_TYPE_RFC2543) {
+ pjmedia_sdp_conn *conn;
+ pjmedia_sdp_attr *attr;
+
+ /* Get SDP media connection line */
+ conn = m->conn;
+ if (!conn)
+ conn = sdp->conn;
+
+ /* Modify address */
+ conn->addr = pj_str("0.0.0.0");
+
+ /* Remove existing directions attributes */
+ pjmedia_sdp_media_remove_all_attr(m, "sendrecv");
+ pjmedia_sdp_media_remove_all_attr(m, "sendonly");
+ pjmedia_sdp_media_remove_all_attr(m, "recvonly");
+ pjmedia_sdp_media_remove_all_attr(m, "inactive");
+
+ /* Add inactive attribute */
+ attr = pjmedia_sdp_attr_create(pool, "inactive", NULL);
+ pjmedia_sdp_media_add_attr(m, attr);
+
+
+ } else {
+ pjmedia_sdp_attr *attr;
+
+ /* Remove existing directions attributes */
+ pjmedia_sdp_media_remove_all_attr(m, "sendrecv");
+ pjmedia_sdp_media_remove_all_attr(m, "sendonly");
+ pjmedia_sdp_media_remove_all_attr(m, "recvonly");
+ pjmedia_sdp_media_remove_all_attr(m, "inactive");
+
+ if (call->media[mi].dir & PJMEDIA_DIR_ENCODING) {
+ /* Add sendonly attribute */
+ attr = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
+ pjmedia_sdp_media_add_attr(m, attr);
+ } else {
+ /* Add inactive attribute */
+ attr = pjmedia_sdp_attr_create(pool, "inactive", NULL);
+ pjmedia_sdp_media_add_attr(m, attr);
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Create SDP for call hold. */
+static pj_status_t create_sdp_of_call_hold(pjsua_call *call,
+ pjmedia_sdp_session **p_sdp)
+{
+ pj_status_t status;
+ pj_pool_t *pool;
+ pjmedia_sdp_session *sdp;
+
+ /* Use call's provisional pool */
+ pool = call->inv->pool_prov;
+
+ /* Create new offer */
+ status = pjsua_media_channel_create_sdp(call->index, pool, NULL, &sdp,
+ NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
+ return status;
+ }
+
+ status = modify_sdp_of_call_hold(call, pool, sdp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ *p_sdp = sdp;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Called when session received new offer.
+ */
+static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
+ const pjmedia_sdp_session *offer)
+{
+ pjsua_call *call;
+ pjmedia_sdp_session *answer;
+ unsigned i;
+ pj_status_t status;
+
+ call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
+
+ /* Supply candidate answer */
+ PJ_LOG(4,(THIS_FILE, "Call %d: received updated media offer",
+ call->index));
+ pj_log_push_indent();
+
+ if (pjsua_var.ua_cfg.cb.on_call_rx_offer) {
+ pjsip_status_code code = PJSIP_SC_OK;
+ pjsua_call_setting opt = call->opt;
+
+ (*pjsua_var.ua_cfg.cb.on_call_rx_offer)(call->index, offer, NULL,
+ &code, &opt);
+
+ if (code != PJSIP_SC_OK) {
+ PJ_LOG(4,(THIS_FILE, "Rejecting updated media offer on call %d",
+ call->index));
+ goto on_return;
+ }
+
+ call->opt = opt;
+ }
+
+ /* Re-init media for the new remote offer before creating SDP */
+ status = apply_call_setting(call, &call->opt, offer);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ status = pjsua_media_channel_create_sdp(call->index,
+ call->inv->pool_prov,
+ offer, &answer, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
+ goto on_return;
+ }
+
+ /* Validate media count in the generated answer */
+ pj_assert(answer->media_count == offer->media_count);
+
+ /* Check if offer's conn address is zero */
+ for (i = 0; i < answer->media_count; ++i) {
+ pjmedia_sdp_conn *conn;
+
+ conn = offer->media[i]->conn;
+ if (!conn)
+ conn = offer->conn;
+
+ if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 ||
+ pj_strcmp2(&conn->addr, "0")==0)
+ {
+ pjmedia_sdp_conn *a_conn = answer->media[i]->conn;
+
+ /* Modify answer address */
+ if (a_conn) {
+ a_conn->addr = pj_str("0.0.0.0");
+ } else if (answer->conn == NULL ||
+ pj_strcmp2(&answer->conn->addr, "0.0.0.0") != 0)
+ {
+ a_conn = PJ_POOL_ZALLOC_T(call->inv->pool_prov,
+ pjmedia_sdp_conn);
+ a_conn->net_type = pj_str("IN");
+ a_conn->addr_type = pj_str("IP4");
+ a_conn->addr = pj_str("0.0.0.0");
+ answer->media[i]->conn = a_conn;
+ }
+ }
+ }
+
+ /* Check if call is on-hold */
+ if (call->local_hold) {
+ modify_sdp_of_call_hold(call, call->inv->pool_prov, answer);
+ }
+
+ status = pjsip_inv_set_sdp_answer(call->inv, answer);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to set answer", status);
+ goto on_return;
+ }
+
+on_return:
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Called to generate new offer.
+ */
+static void pjsua_call_on_create_offer(pjsip_inv_session *inv,
+ pjmedia_sdp_session **offer)
+{
+ pjsua_call *call;
+ pj_status_t status;
+
+ pj_log_push_indent();
+
+ call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
+
+ /* See if we've put call on hold. */
+ if (call->local_hold) {
+ PJ_LOG(4,(THIS_FILE,
+ "Call %d: call is on-hold locally, creating call-hold SDP ",
+ call->index));
+ status = create_sdp_of_call_hold( call, offer );
+ } else {
+ PJ_LOG(4,(THIS_FILE, "Call %d: asked to send a new offer",
+ call->index));
+
+ status = pjsua_media_channel_create_sdp(call->index,
+ call->inv->pool_prov,
+ NULL, offer, NULL);
+ }
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
+ goto on_return;
+ }
+
+on_return:
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Callback called by event framework when the xfer subscription state
+ * has changed.
+ */
+static void xfer_client_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
+{
+
+ PJ_UNUSED_ARG(event);
+
+ pj_log_push_indent();
+
+ /*
+ * When subscription is accepted (got 200/OK to REFER), check if
+ * subscription suppressed.
+ */
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACCEPTED) {
+
+ pjsip_rx_data *rdata;
+ pjsip_generic_string_hdr *refer_sub;
+ const pj_str_t REFER_SUB = { "Refer-Sub", 9 };
+ pjsua_call *call;
+
+ call = (pjsua_call*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+
+ /* Must be receipt of response message */
+ pj_assert(event->type == PJSIP_EVENT_TSX_STATE &&
+ event->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
+ rdata = event->body.tsx_state.src.rdata;
+
+ /* Find Refer-Sub header */
+ refer_sub = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
+ &REFER_SUB, NULL);
+
+ /* Check if subscription is suppressed */
+ if (refer_sub && pj_stricmp2(&refer_sub->hvalue, "false")==0) {
+ /* Since no subscription is desired, assume that call has been
+ * transfered successfully.
+ */
+ if (call && pjsua_var.ua_cfg.cb.on_call_transfer_status) {
+ const pj_str_t ACCEPTED = { "Accepted", 8 };
+ pj_bool_t cont = PJ_FALSE;
+ (*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index,
+ 200,
+ &ACCEPTED,
+ PJ_TRUE,
+ &cont);
+ }
+
+ /* Yes, subscription is suppressed.
+ * Terminate our subscription now.
+ */
+ PJ_LOG(4,(THIS_FILE, "Xfer subscription suppressed, terminating "
+ "event subcription..."));
+ pjsip_evsub_terminate(sub, PJ_TRUE);
+
+ } else {
+ /* Notify application about call transfer progress.
+ * Initially notify with 100/Accepted status.
+ */
+ if (call && pjsua_var.ua_cfg.cb.on_call_transfer_status) {
+ const pj_str_t ACCEPTED = { "Accepted", 8 };
+ pj_bool_t cont = PJ_FALSE;
+ (*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index,
+ 100,
+ &ACCEPTED,
+ PJ_FALSE,
+ &cont);
+ }
+ }
+ }
+ /*
+ * On incoming NOTIFY, notify application about call transfer progress.
+ */
+ else if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACTIVE ||
+ pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED)
+ {
+ pjsua_call *call;
+ pjsip_msg *msg;
+ pjsip_msg_body *body;
+ pjsip_status_line status_line;
+ pj_bool_t is_last;
+ pj_bool_t cont;
+ pj_status_t status;
+
+ call = (pjsua_call*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+
+ /* When subscription is terminated, clear the xfer_sub member of
+ * the inv_data.
+ */
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
+ PJ_LOG(4,(THIS_FILE, "Xfer client subscription terminated"));
+
+ }
+
+ if (!call || !event || !pjsua_var.ua_cfg.cb.on_call_transfer_status) {
+ /* Application is not interested with call progress status */
+ goto on_return;
+ }
+
+ /* This better be a NOTIFY request */
+ if (event->type == PJSIP_EVENT_TSX_STATE &&
+ event->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
+ {
+ pjsip_rx_data *rdata;
+
+ rdata = event->body.tsx_state.src.rdata;
+
+ /* Check if there's body */
+ msg = rdata->msg_info.msg;
+ body = msg->body;
+ if (!body) {
+ PJ_LOG(2,(THIS_FILE,
+ "Warning: received NOTIFY without message body"));
+ goto on_return;
+ }
+
+ /* Check for appropriate content */
+ if (pj_stricmp2(&body->content_type.type, "message") != 0 ||
+ pj_stricmp2(&body->content_type.subtype, "sipfrag") != 0)
+ {
+ PJ_LOG(2,(THIS_FILE,
+ "Warning: received NOTIFY with non message/sipfrag "
+ "content"));
+ goto on_return;
+ }
+
+ /* Try to parse the content */
+ status = pjsip_parse_status_line((char*)body->data, body->len,
+ &status_line);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(2,(THIS_FILE,
+ "Warning: received NOTIFY with invalid "
+ "message/sipfrag content"));
+ goto on_return;
+ }
+
+ } else {
+ status_line.code = 500;
+ status_line.reason = *pjsip_get_status_text(500);
+ }
+
+ /* Notify application */
+ is_last = (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED);
+ cont = !is_last;
+ (*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index,
+ status_line.code,
+ &status_line.reason,
+ is_last, &cont);
+
+ if (!cont) {
+ pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
+ }
+
+ /* If the call transfer has completed but the subscription is
+ * not terminated, terminate it now.
+ */
+ if (status_line.code/100 == 2 && !is_last) {
+ pjsip_tx_data *tdata;
+
+ status = pjsip_evsub_initiate(sub, &pjsip_subscribe_method,
+ 0, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_evsub_send_request(sub, tdata);
+ }
+ }
+
+on_return:
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Callback called by event framework when the xfer subscription state
+ * has changed.
+ */
+static void xfer_server_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
+{
+ PJ_UNUSED_ARG(event);
+
+ pj_log_push_indent();
+
+ /*
+ * When subscription is terminated, clear the xfer_sub member of
+ * the inv_data.
+ */
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ pjsua_call *call;
+
+ call = (pjsua_call*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+ if (!call)
+ goto on_return;
+
+ pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
+ call->xfer_sub = NULL;
+
+ PJ_LOG(4,(THIS_FILE, "Xfer server subscription terminated"));
+ }
+
+on_return:
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Follow transfer (REFER) request.
+ */
+static void on_call_transfered( pjsip_inv_session *inv,
+ pjsip_rx_data *rdata )
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+ pjsua_call *existing_call;
+ int new_call;
+ const pj_str_t str_refer_to = { "Refer-To", 8};
+ const pj_str_t str_refer_sub = { "Refer-Sub", 9 };
+ const pj_str_t str_ref_by = { "Referred-By", 11 };
+ pjsip_generic_string_hdr *refer_to;
+ pjsip_generic_string_hdr *refer_sub;
+ pjsip_hdr *ref_by_hdr;
+ pj_bool_t no_refer_sub = PJ_FALSE;
+ char *uri;
+ pjsua_msg_data msg_data;
+ pj_str_t tmp;
+ pjsip_status_code code;
+ pjsip_evsub *sub;
+ pjsua_call_setting call_opt;
+
+ pj_log_push_indent();
+
+ existing_call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
+
+ /* Find the Refer-To header */
+ refer_to = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL);
+
+ if (refer_to == NULL) {
+ /* Invalid Request.
+ * No Refer-To header!
+ */
+ PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!"));
+ pjsip_dlg_respond( inv->dlg, rdata, 400, NULL, NULL, NULL);
+ goto on_return;
+ }
+
+ /* Find optional Refer-Sub header */
+ refer_sub = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_sub, NULL);
+
+ if (refer_sub) {
+ if (!pj_strnicmp2(&refer_sub->hvalue, "true", 4)==0)
+ no_refer_sub = PJ_TRUE;
+ }
+
+ /* Find optional Referred-By header (to be copied onto outgoing INVITE
+ * request.
+ */
+ ref_by_hdr = (pjsip_hdr*)
+ pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_ref_by,
+ NULL);
+
+ /* Notify callback */
+ code = PJSIP_SC_ACCEPTED;
+ if (pjsua_var.ua_cfg.cb.on_call_transfer_request) {
+ (*pjsua_var.ua_cfg.cb.on_call_transfer_request)(existing_call->index,
+ &refer_to->hvalue,
+ &code);
+ }
+
+ call_opt = existing_call->opt;
+ if (pjsua_var.ua_cfg.cb.on_call_transfer_request2) {
+ (*pjsua_var.ua_cfg.cb.on_call_transfer_request2)(existing_call->index,
+ &refer_to->hvalue,
+ &code,
+ &call_opt);
+ }
+
+ if (code < 200)
+ code = PJSIP_SC_ACCEPTED;
+ if (code >= 300) {
+ /* Application rejects call transfer request */
+ pjsip_dlg_respond( inv->dlg, rdata, code, NULL, NULL, NULL);
+ goto on_return;
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s",
+ (int)inv->dlg->remote.info_str.slen,
+ inv->dlg->remote.info_str.ptr,
+ (int)refer_to->hvalue.slen,
+ refer_to->hvalue.ptr));
+
+ if (no_refer_sub) {
+ /*
+ * Always answer with 2xx.
+ */
+ pjsip_tx_data *tdata;
+ const pj_str_t str_false = { "false", 5};
+ pjsip_hdr *hdr;
+
+ status = pjsip_dlg_create_response(inv->dlg, rdata, code, NULL,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create 2xx response to REFER",
+ status);
+ goto on_return;
+ }
+
+ /* Add Refer-Sub header */
+ hdr = (pjsip_hdr*)
+ pjsip_generic_string_hdr_create(tdata->pool, &str_refer_sub,
+ &str_false);
+ pjsip_msg_add_hdr(tdata->msg, hdr);
+
+
+ /* Send answer */
+ status = pjsip_dlg_send_response(inv->dlg, pjsip_rdata_get_tsx(rdata),
+ tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create 2xx response to REFER",
+ status);
+ goto on_return;
+ }
+
+ /* Don't have subscription */
+ sub = NULL;
+
+ } else {
+ struct pjsip_evsub_user xfer_cb;
+ pjsip_hdr hdr_list;
+
+ /* Init callback */
+ pj_bzero(&xfer_cb, sizeof(xfer_cb));
+ xfer_cb.on_evsub_state = &xfer_server_on_evsub_state;
+
+ /* Init additional header list to be sent with REFER response */
+ pj_list_init(&hdr_list);
+
+ /* Create transferee event subscription */
+ status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create xfer uas", status);
+ pjsip_dlg_respond( inv->dlg, rdata, 500, NULL, NULL, NULL);
+ goto on_return;
+ }
+
+ /* If there's Refer-Sub header and the value is "true", send back
+ * Refer-Sub in the response with value "true" too.
+ */
+ if (refer_sub) {
+ const pj_str_t str_true = { "true", 4 };
+ pjsip_hdr *hdr;
+
+ hdr = (pjsip_hdr*)
+ pjsip_generic_string_hdr_create(inv->dlg->pool,
+ &str_refer_sub,
+ &str_true);
+ pj_list_push_back(&hdr_list, hdr);
+
+ }
+
+ /* Accept the REFER request, send 2xx. */
+ pjsip_xfer_accept(sub, rdata, code, &hdr_list);
+
+ /* Create initial NOTIFY request */
+ status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE,
+ 100, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER",
+ status);
+ goto on_return;
+ }
+
+ /* Send initial NOTIFY request */
+ status = pjsip_xfer_send_request( sub, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status);
+ goto on_return;
+ }
+ }
+
+ /* We're cheating here.
+ * We need to get a null terminated string from a pj_str_t.
+ * So grab the pointer from the hvalue and NULL terminate it, knowing
+ * that the NULL position will be occupied by a newline.
+ */
+ uri = refer_to->hvalue.ptr;
+ uri[refer_to->hvalue.slen] = '\0';
+
+ /* Init msg_data */
+ pjsua_msg_data_init(&msg_data);
+
+ /* If Referred-By header is present in the REFER request, copy this
+ * to the outgoing INVITE request.
+ */
+ if (ref_by_hdr != NULL) {
+ pjsip_hdr *dup = (pjsip_hdr*)
+ pjsip_hdr_clone(rdata->tp_info.pool, ref_by_hdr);
+ pj_list_push_back(&msg_data.hdr_list, dup);
+ }
+
+ /* Now make the outgoing call. */
+ tmp = pj_str(uri);
+ status = pjsua_call_make_call(existing_call->acc_id, &tmp, &call_opt,
+ existing_call->user_data, &msg_data,
+ &new_call);
+ if (status != PJ_SUCCESS) {
+
+ /* Notify xferer about the error (if we have subscription) */
+ if (sub) {
+ status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
+ 500, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER",
+ status);
+ goto on_return;
+ }
+ status = pjsip_xfer_send_request(sub, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER",
+ status);
+ goto on_return;
+ }
+ }
+ goto on_return;
+ }
+
+ if (sub) {
+ /* Put the server subscription in inv_data.
+ * Subsequent state changed in pjsua_inv_on_state_changed() will be
+ * reported back to the server subscription.
+ */
+ pjsua_var.calls[new_call].xfer_sub = sub;
+
+ /* Put the invite_data in the subscription. */
+ pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id,
+ &pjsua_var.calls[new_call]);
+ }
+
+on_return:
+ pj_log_pop_indent();
+}
+
+
+
+/*
+ * This callback is called when transaction state has changed in INVITE
+ * session. We use this to trap:
+ * - incoming REFER request.
+ * - incoming MESSAGE request.
+ */
+static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
+ pjsip_transaction *tsx,
+ pjsip_event *e)
+{
+ pjsua_call *call;
+
+ pj_log_push_indent();
+
+ call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
+
+ if (call == NULL)
+ goto on_return;
+
+ if (call->inv == NULL) {
+ /* Shouldn't happen. It happens only when we don't terminate the
+ * server subscription caused by REFER after the call has been
+ * transfered (and this call has been disconnected), and we
+ * receive another REFER for this call.
+ */
+ goto on_return;
+ }
+
+ /* https://trac.pjsip.org/repos/ticket/1452:
+ * If a request is retried due to 401/407 challenge, don't process the
+ * transaction first but wait until we've retried it.
+ */
+ if (tsx->role == PJSIP_ROLE_UAC &&
+ (tsx->status_code==401 || tsx->status_code==407) &&
+ tsx->last_tx && tsx->last_tx->auth_retry)
+ {
+ goto on_return;
+ }
+
+ /* Notify application callback first */
+ if (pjsua_var.ua_cfg.cb.on_call_tsx_state) {
+ (*pjsua_var.ua_cfg.cb.on_call_tsx_state)(call->index, tsx, e);
+ }
+
+ if (tsx->role==PJSIP_ROLE_UAS &&
+ tsx->state==PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, pjsip_get_refer_method())==0)
+ {
+ /*
+ * Incoming REFER request.
+ */
+ on_call_transfered(call->inv, e->body.tsx_state.src.rdata);
+
+ }
+ else if (tsx->role==PJSIP_ROLE_UAS &&
+ tsx->state==PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0)
+ {
+ /*
+ * Incoming MESSAGE request!
+ */
+ pjsip_rx_data *rdata;
+ pjsip_msg *msg;
+ pjsip_accept_hdr *accept_hdr;
+ pj_status_t status;
+
+ rdata = e->body.tsx_state.src.rdata;
+ msg = rdata->msg_info.msg;
+
+ /* Request MUST have message body, with Content-Type equal to
+ * "text/plain".
+ */
+ if (pjsua_im_accept_pager(rdata, &accept_hdr) == PJ_FALSE) {
+
+ pjsip_hdr hdr_list;
+
+ pj_list_init(&hdr_list);
+ pj_list_push_back(&hdr_list, accept_hdr);
+
+ pjsip_dlg_respond( inv->dlg, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE,
+ NULL, &hdr_list, NULL );
+ goto on_return;
+ }
+
+ /* Respond with 200 first, so that remote doesn't retransmit in case
+ * the UI takes too long to process the message.
+ */
+ status = pjsip_dlg_respond( inv->dlg, rdata, 200, NULL, NULL, NULL);
+
+ /* Process MESSAGE request */
+ pjsua_im_process_pager(call->index, &inv->dlg->remote.info_str,
+ &inv->dlg->local.info_str, rdata);
+
+ }
+ else if (tsx->role == PJSIP_ROLE_UAC &&
+ pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0)
+ {
+ /* Handle outgoing pager status */
+ if (tsx->status_code >= 200) {
+ pjsua_im_data *im_data;
+
+ im_data = (pjsua_im_data*) tsx->mod_data[pjsua_var.mod.id];
+ /* im_data can be NULL if this is typing indication */
+
+ if (im_data && pjsua_var.ua_cfg.cb.on_pager_status) {
+ pjsua_var.ua_cfg.cb.on_pager_status(im_data->call_id,
+ &im_data->to,
+ &im_data->body,
+ im_data->user_data,
+ (pjsip_status_code)
+ tsx->status_code,
+ &tsx->status_text);
+ }
+ }
+ } else if (tsx->role == PJSIP_ROLE_UAC &&
+ tsx->last_tx == (pjsip_tx_data*)call->hold_msg &&
+ tsx->state >= PJSIP_TSX_STATE_COMPLETED)
+ {
+ /* Monitor the status of call hold request */
+ call->hold_msg = NULL;
+ if (tsx->status_code/100 != 2) {
+ /* Outgoing call hold failed */
+ call->local_hold = PJ_FALSE;
+ PJ_LOG(3,(THIS_FILE, "Error putting call %d on hold (reason=%d)",
+ call->index, tsx->status_code));
+ }
+ } else if (tsx->role==PJSIP_ROLE_UAS &&
+ tsx->state==PJSIP_TSX_STATE_TRYING &&
+ pjsip_method_cmp(&tsx->method, &pjsip_info_method)==0)
+ {
+ /*
+ * Incoming INFO request for media control.
+ */
+ const pj_str_t STR_APPLICATION = { "application", 11};
+ const pj_str_t STR_MEDIA_CONTROL_XML = { "media_control+xml", 17 };
+ pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+ pjsip_msg_body *body = rdata->msg_info.msg->body;
+
+ if (body && body->len &&
+ pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 &&
+ pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML)==0)
+ {
+ pjsip_tx_data *tdata;
+ pj_str_t control_st;
+ pj_status_t status;
+
+ /* Apply and answer the INFO request */
+ pj_strset(&control_st, (char*)body->data, body->len);
+ status = pjsua_media_apply_xml_control(call->index, &control_st);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_endpt_create_response(tsx->endpt, rdata,
+ 200, NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_tsx_send_msg(tsx, tdata);
+ } else {
+ status = pjsip_endpt_create_response(tsx->endpt, rdata,
+ 400, NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_tsx_send_msg(tsx, tdata);
+ }
+ }
+ }
+
+on_return:
+ pj_log_pop_indent();
+}
+
+
+/* Redirection handler */
+static pjsip_redirect_op pjsua_call_on_redirected(pjsip_inv_session *inv,
+ const pjsip_uri *target,
+ const pjsip_event *e)
+{
+ pjsua_call *call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
+ pjsip_redirect_op op;
+
+ pj_log_push_indent();
+
+ if (pjsua_var.ua_cfg.cb.on_call_redirected) {
+ op = (*pjsua_var.ua_cfg.cb.on_call_redirected)(call->index,
+ target, e);
+ } else {
+ PJ_LOG(4,(THIS_FILE, "Unhandled redirection for call %d "
+ "(callback not implemented by application). Disconnecting "
+ "call.",
+ call->index));
+ op = PJSIP_REDIRECT_STOP;
+ }
+
+ pj_log_pop_indent();
+
+ return op;
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
new file mode 100644
index 0000000..f4f9508
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -0,0 +1,2909 @@
+/* $Id: pjsua_core.c 4173 2012-06-20 10:39:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+
+#define THIS_FILE "pjsua_core.c"
+
+
+/* Internal prototypes */
+static void resolve_stun_entry(pjsua_stun_resolve *sess);
+
+
+/* PJSUA application instance. */
+struct pjsua_data pjsua_var;
+
+
+PJ_DEF(struct pjsua_data*) pjsua_get_var(void)
+{
+ return &pjsua_var;
+}
+
+
+/* Display error */
+PJ_DEF(void) pjsua_perror( const char *sender, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(sender, "%s: %s [status=%d]", title, errmsg, status));
+}
+
+
+static void init_data()
+{
+ unsigned i;
+
+ pj_bzero(&pjsua_var, sizeof(pjsua_var));
+
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i)
+ pjsua_var.acc[i].index = i;
+
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.tpdata); ++i)
+ pjsua_var.tpdata[i].index = i;
+
+ pjsua_var.stun_status = PJ_EUNKNOWN;
+ pjsua_var.nat_status = PJ_EPENDING;
+ pj_list_init(&pjsua_var.stun_res);
+ pj_list_init(&pjsua_var.outbound_proxy);
+
+ pjsua_config_default(&pjsua_var.ua_cfg);
+
+ for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+ pjsua_vid_win_reset(i);
+ }
+}
+
+
+PJ_DEF(void) pjsua_logging_config_default(pjsua_logging_config *cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+
+ cfg->msg_logging = PJ_TRUE;
+ cfg->level = 5;
+ cfg->console_level = 4;
+ cfg->decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME |
+ PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE |
+ PJ_LOG_HAS_SPACE | PJ_LOG_HAS_THREAD_SWC |
+ PJ_LOG_HAS_INDENT;
+#if defined(PJ_WIN32) && PJ_WIN32 != 0
+ cfg->decor |= PJ_LOG_HAS_COLOR;
+#endif
+}
+
+PJ_DEF(void) pjsua_logging_config_dup(pj_pool_t *pool,
+ pjsua_logging_config *dst,
+ const pjsua_logging_config *src)
+{
+ pj_memcpy(dst, src, sizeof(*src));
+ pj_strdup_with_null(pool, &dst->log_filename, &src->log_filename);
+}
+
+PJ_DEF(void) pjsua_config_default(pjsua_config *cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+
+ cfg->max_calls = ((PJSUA_MAX_CALLS) < 4) ? (PJSUA_MAX_CALLS) : 4;
+ cfg->thread_cnt = 1;
+ cfg->nat_type_in_sdp = 1;
+ cfg->stun_ignore_failure = PJ_TRUE;
+ cfg->force_lr = PJ_TRUE;
+ cfg->enable_unsolicited_mwi = PJ_TRUE;
+ cfg->use_srtp = PJSUA_DEFAULT_USE_SRTP;
+ cfg->srtp_secure_signaling = PJSUA_DEFAULT_SRTP_SECURE_SIGNALING;
+ cfg->hangup_forked_call = PJ_TRUE;
+
+ cfg->use_timer = PJSUA_SIP_TIMER_OPTIONAL;
+ pjsip_timer_setting_default(&cfg->timer_setting);
+}
+
+PJ_DEF(void) pjsua_config_dup(pj_pool_t *pool,
+ pjsua_config *dst,
+ const pjsua_config *src)
+{
+ unsigned i;
+
+ pj_memcpy(dst, src, sizeof(*src));
+
+ for (i=0; i<src->outbound_proxy_cnt; ++i) {
+ pj_strdup_with_null(pool, &dst->outbound_proxy[i],
+ &src->outbound_proxy[i]);
+ }
+
+ for (i=0; i<src->cred_count; ++i) {
+ pjsip_cred_dup(pool, &dst->cred_info[i], &src->cred_info[i]);
+ }
+
+ pj_strdup_with_null(pool, &dst->user_agent, &src->user_agent);
+ pj_strdup_with_null(pool, &dst->stun_domain, &src->stun_domain);
+ pj_strdup_with_null(pool, &dst->stun_host, &src->stun_host);
+
+ for (i=0; i<src->stun_srv_cnt; ++i) {
+ pj_strdup_with_null(pool, &dst->stun_srv[i], &src->stun_srv[i]);
+ }
+}
+
+PJ_DEF(void) pjsua_msg_data_init(pjsua_msg_data *msg_data)
+{
+ pj_bzero(msg_data, sizeof(*msg_data));
+ pj_list_init(&msg_data->hdr_list);
+ pjsip_media_type_init(&msg_data->multipart_ctype, NULL, NULL);
+ pj_list_init(&msg_data->multipart_parts);
+}
+
+PJ_DEF(pjsua_msg_data*) pjsua_msg_data_clone(pj_pool_t *pool,
+ const pjsua_msg_data *rhs)
+{
+ pjsua_msg_data *msg_data;
+ const pjsip_hdr *hdr;
+ const pjsip_multipart_part *mpart;
+
+ PJ_ASSERT_RETURN(pool && rhs, NULL);
+
+ msg_data = PJ_POOL_ZALLOC_T(pool, pjsua_msg_data);
+ PJ_ASSERT_RETURN(msg_data != NULL, NULL);
+
+ pj_list_init(&msg_data->hdr_list);
+ hdr = rhs->hdr_list.next;
+ while (hdr != &rhs->hdr_list) {
+ pj_list_push_back(&msg_data->hdr_list, pjsip_hdr_clone(pool, hdr));
+ hdr = hdr->next;
+ }
+
+ pj_strdup(pool, &msg_data->content_type, &rhs->content_type);
+ pj_strdup(pool, &msg_data->msg_body, &rhs->msg_body);
+
+ pjsip_media_type_cp(pool, &msg_data->multipart_ctype,
+ &rhs->multipart_ctype);
+
+ pj_list_init(&msg_data->multipart_parts);
+ mpart = rhs->multipart_parts.next;
+ while (mpart != &rhs->multipart_parts) {
+ pj_list_push_back(&msg_data->multipart_parts,
+ pjsip_multipart_clone_part(pool, mpart));
+ mpart = mpart->next;
+ }
+
+ return msg_data;
+}
+
+PJ_DEF(void) pjsua_transport_config_default(pjsua_transport_config *cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+ pjsip_tls_setting_default(&cfg->tls_setting);
+}
+
+PJ_DEF(void) pjsua_transport_config_dup(pj_pool_t *pool,
+ pjsua_transport_config *dst,
+ const pjsua_transport_config *src)
+{
+ PJ_UNUSED_ARG(pool);
+ pj_memcpy(dst, src, sizeof(*src));
+}
+
+PJ_DEF(void) pjsua_acc_config_default(pjsua_acc_config *cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+
+ cfg->reg_timeout = PJSUA_REG_INTERVAL;
+ cfg->reg_delay_before_refresh = PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH;
+ cfg->unreg_timeout = PJSUA_UNREG_TIMEOUT;
+ pjsip_publishc_opt_default(&cfg->publish_opt);
+ cfg->unpublish_max_wait_time_msec = PJSUA_UNPUBLISH_MAX_WAIT_TIME_MSEC;
+ cfg->transport_id = PJSUA_INVALID_ID;
+ cfg->allow_contact_rewrite = PJ_TRUE;
+ cfg->allow_via_rewrite = PJ_TRUE;
+ cfg->require_100rel = pjsua_var.ua_cfg.require_100rel;
+ cfg->use_timer = pjsua_var.ua_cfg.use_timer;
+ cfg->timer_setting = pjsua_var.ua_cfg.timer_setting;
+ cfg->ka_interval = 15;
+ cfg->ka_data = pj_str("\r\n");
+ cfg->vid_cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+ cfg->vid_rend_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV;
+#if PJMEDIA_HAS_VIDEO
+ pjmedia_vid_stream_rc_config_default(&cfg->vid_stream_rc_cfg);
+#endif
+ pjsua_transport_config_default(&cfg->rtp_cfg);
+ cfg->use_srtp = pjsua_var.ua_cfg.use_srtp;
+ cfg->srtp_secure_signaling = pjsua_var.ua_cfg.srtp_secure_signaling;
+ cfg->srtp_optional_dup_offer = pjsua_var.ua_cfg.srtp_optional_dup_offer;
+ cfg->reg_retry_interval = PJSUA_REG_RETRY_INTERVAL;
+ cfg->contact_rewrite_method = PJSUA_CONTACT_REWRITE_METHOD;
+ cfg->use_rfc5626 = PJ_TRUE;
+ cfg->reg_use_proxy = PJSUA_REG_USE_OUTBOUND_PROXY |
+ PJSUA_REG_USE_ACC_PROXY;
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ cfg->use_stream_ka = (PJMEDIA_STREAM_ENABLE_KA != 0);
+#endif
+ pj_list_init(&cfg->reg_hdr_list);
+ pj_list_init(&cfg->sub_hdr_list);
+ cfg->call_hold_type = PJSUA_CALL_HOLD_TYPE_DEFAULT;
+ cfg->register_on_acc_add = PJ_TRUE;
+ cfg->mwi_expires = PJSIP_MWI_DEFAULT_EXPIRES;
+}
+
+PJ_DEF(void) pjsua_buddy_config_default(pjsua_buddy_config *cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+}
+
+PJ_DEF(void) pjsua_media_config_default(pjsua_media_config *cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+
+ cfg->clock_rate = PJSUA_DEFAULT_CLOCK_RATE;
+ cfg->snd_clock_rate = 0;
+ cfg->channel_count = 1;
+ cfg->audio_frame_ptime = PJSUA_DEFAULT_AUDIO_FRAME_PTIME;
+ cfg->max_media_ports = PJSUA_MAX_CONF_PORTS;
+ cfg->has_ioqueue = PJ_TRUE;
+ cfg->thread_cnt = 1;
+ cfg->quality = PJSUA_DEFAULT_CODEC_QUALITY;
+ cfg->ilbc_mode = PJSUA_DEFAULT_ILBC_MODE;
+ cfg->ec_tail_len = PJSUA_DEFAULT_EC_TAIL_LEN;
+ cfg->snd_rec_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ cfg->snd_play_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+ cfg->jb_init = cfg->jb_min_pre = cfg->jb_max_pre = cfg->jb_max = -1;
+ cfg->snd_auto_close_time = 1;
+
+ cfg->ice_max_host_cands = -1;
+ pj_ice_sess_options_default(&cfg->ice_opt);
+
+ cfg->turn_conn_type = PJ_TURN_TP_UDP;
+ cfg->vid_preview_enable_native = PJ_TRUE;
+}
+
+/*****************************************************************************
+ * This is a very simple PJSIP module, whose sole purpose is to display
+ * incoming and outgoing messages to log. This module will have priority
+ * higher than transport layer, which means:
+ *
+ * - incoming messages will come to this module first before reaching
+ * transaction layer.
+ *
+ * - outgoing messages will come to this module last, after the message
+ * has been 'printed' to contiguous buffer by transport layer and
+ * appropriate transport instance has been decided for this message.
+ *
+ */
+
+/* Notification on incoming messages */
+static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata)
+{
+ PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ rdata->msg_info.len,
+ pjsip_rx_data_get_info(rdata),
+ rdata->tp_info.transport->type_name,
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ (int)rdata->msg_info.len,
+ rdata->msg_info.msg_buf));
+
+ /* Always return false, otherwise messages will not get processed! */
+ return PJ_FALSE;
+}
+
+/* Notification on outgoing messages */
+static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata)
+{
+
+ /* Important note:
+ * tp_info field is only valid after outgoing messages has passed
+ * transport layer. So don't try to access tp_info when the module
+ * has lower priority than transport layer.
+ */
+
+ PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ (tdata->buf.cur - tdata->buf.start),
+ pjsip_tx_data_get_info(tdata),
+ tdata->tp_info.transport->type_name,
+ tdata->tp_info.dst_name,
+ tdata->tp_info.dst_port,
+ (int)(tdata->buf.cur - tdata->buf.start),
+ tdata->buf.start));
+
+ /* Always return success, otherwise message will not get sent! */
+ return PJ_SUCCESS;
+}
+
+/* The module instance. */
+static pjsip_module pjsua_msg_logger =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-pjsua-log", 13 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &logging_on_rx_msg, /* on_rx_request() */
+ &logging_on_rx_msg, /* on_rx_response() */
+ &logging_on_tx_msg, /* on_tx_request. */
+ &logging_on_tx_msg, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+
+/*****************************************************************************
+ * Another simple module to handle incoming OPTIONS request
+ */
+
+/* Notification on incoming request */
+static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_response_addr res_addr;
+ const pjsip_hdr *cap_hdr;
+ pj_status_t status;
+
+ /* Only want to handle OPTIONS requests */
+ if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
+ pjsip_get_options_method()) != 0)
+ {
+ return PJ_FALSE;
+ }
+
+ /* Don't want to handle if shutdown is in progress */
+ if (pjsua_var.thread_quit_flag) {
+ pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
+ PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL,
+ NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ /* Create basic response. */
+ status = pjsip_endpt_create_response(pjsua_var.endpt, rdata, 200, NULL,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create OPTIONS response", status);
+ return PJ_TRUE;
+ }
+
+ /* Add Allow header */
+ cap_hdr = pjsip_endpt_get_capability(pjsua_var.endpt, PJSIP_H_ALLOW, NULL);
+ if (cap_hdr) {
+ pjsip_msg_add_hdr(tdata->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr));
+ }
+
+ /* Add Accept header */
+ cap_hdr = pjsip_endpt_get_capability(pjsua_var.endpt, PJSIP_H_ACCEPT, NULL);
+ if (cap_hdr) {
+ pjsip_msg_add_hdr(tdata->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr));
+ }
+
+ /* Add Supported header */
+ cap_hdr = pjsip_endpt_get_capability(pjsua_var.endpt, PJSIP_H_SUPPORTED, NULL);
+ if (cap_hdr) {
+ pjsip_msg_add_hdr(tdata->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr));
+ }
+
+ /* Add Allow-Events header from the evsub module */
+ cap_hdr = pjsip_evsub_get_allow_events_hdr(NULL);
+ if (cap_hdr) {
+ pjsip_msg_add_hdr(tdata->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr));
+ }
+
+ /* Add User-Agent header */
+ if (pjsua_var.ua_cfg.user_agent.slen) {
+ const pj_str_t USER_AGENT = { "User-Agent", 10};
+ pjsip_hdr *h;
+
+ h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool,
+ &USER_AGENT,
+ &pjsua_var.ua_cfg.user_agent);
+ pjsip_msg_add_hdr(tdata->msg, h);
+ }
+
+ /* Get media socket info, make sure transport is ready */
+#if DISABLED_FOR_TICKET_1185
+ if (pjsua_var.calls[0].med_tp) {
+ pjmedia_transport_info tpinfo;
+ pjmedia_sdp_session *sdp;
+
+ pjmedia_transport_info_init(&tpinfo);
+ pjmedia_transport_get_info(pjsua_var.calls[0].med_tp, &tpinfo);
+
+ /* Add SDP body, using call0's RTP address */
+ status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, tdata->pool, 1,
+ &tpinfo.sock_info, &sdp);
+ if (status == PJ_SUCCESS) {
+ pjsip_create_sdp_body(tdata->pool, sdp, &tdata->msg->body);
+ }
+ }
+#endif
+
+ /* Send response */
+ pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
+ status = pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, tdata, NULL, NULL);
+ if (status != PJ_SUCCESS)
+ pjsip_tx_data_dec_ref(tdata);
+
+ return PJ_TRUE;
+}
+
+
+/* The module instance. */
+static pjsip_module pjsua_options_handler =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-pjsua-options", 17 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &options_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+
+/*****************************************************************************
+ * These two functions are the main callbacks registered to PJSIP stack
+ * to receive SIP request and response messages that are outside any
+ * dialogs and any transactions.
+ */
+
+/*
+ * Handler for receiving incoming requests.
+ *
+ * This handler serves multiple purposes:
+ * - it receives requests outside dialogs.
+ * - it receives requests inside dialogs, when the requests are
+ * unhandled by other dialog usages. Example of these
+ * requests are: MESSAGE.
+ */
+static pj_bool_t mod_pjsua_on_rx_request(pjsip_rx_data *rdata)
+{
+ pj_bool_t processed = PJ_FALSE;
+
+ PJSUA_LOCK();
+
+ if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) {
+
+ processed = pjsua_call_on_incoming(rdata);
+ }
+
+ PJSUA_UNLOCK();
+
+ return processed;
+}
+
+
+/*
+ * Handler for receiving incoming responses.
+ *
+ * This handler serves multiple purposes:
+ * - it receives strayed responses (i.e. outside any dialog and
+ * outside any transactions).
+ * - it receives responses coming to a transaction, when pjsua
+ * module is set as transaction user for the transaction.
+ * - it receives responses inside a dialog, when these responses
+ * are unhandled by other dialog usages.
+ */
+static pj_bool_t mod_pjsua_on_rx_response(pjsip_rx_data *rdata)
+{
+ PJ_UNUSED_ARG(rdata);
+ return PJ_FALSE;
+}
+
+
+/*****************************************************************************
+ * Logging.
+ */
+
+/* Log callback */
+static void log_writer(int level, const char *buffer, int len)
+{
+ /* Write to file, stdout or application callback. */
+
+ if (pjsua_var.log_file) {
+ pj_ssize_t size = len;
+ pj_file_write(pjsua_var.log_file, buffer, &size);
+ /* This will slow things down considerably! Don't do it!
+ pj_file_flush(pjsua_var.log_file);
+ */
+ }
+
+ if (level <= (int)pjsua_var.log_cfg.console_level) {
+ if (pjsua_var.log_cfg.cb)
+ (*pjsua_var.log_cfg.cb)(level, buffer, len);
+ else
+ pj_log_write(level, buffer, len);
+ }
+}
+
+
+/*
+ * Application can call this function at any time (after pjsua_create(), of
+ * course) to change logging settings.
+ */
+PJ_DEF(pj_status_t) pjsua_reconfigure_logging(const pjsua_logging_config *cfg)
+{
+ pj_status_t status;
+
+ /* Save config. */
+ pjsua_logging_config_dup(pjsua_var.pool, &pjsua_var.log_cfg, cfg);
+
+ /* Redirect log function to ours */
+ pj_log_set_log_func( &log_writer );
+
+ /* Set decor */
+ pj_log_set_decor(pjsua_var.log_cfg.decor);
+
+ /* Set log level */
+ pj_log_set_level(pjsua_var.log_cfg.level);
+
+ /* Close existing file, if any */
+ if (pjsua_var.log_file) {
+ pj_file_close(pjsua_var.log_file);
+ pjsua_var.log_file = NULL;
+ }
+
+ /* If output log file is desired, create the file: */
+ if (pjsua_var.log_cfg.log_filename.slen) {
+ unsigned flags = PJ_O_WRONLY;
+ flags |= pjsua_var.log_cfg.log_file_flags;
+ status = pj_file_open(pjsua_var.pool,
+ pjsua_var.log_cfg.log_filename.ptr,
+ flags,
+ &pjsua_var.log_file);
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating log file", status);
+ return status;
+ }
+ }
+
+ /* Unregister msg logging if it's previously registered */
+ if (pjsua_msg_logger.id >= 0) {
+ pjsip_endpt_unregister_module(pjsua_var.endpt, &pjsua_msg_logger);
+ pjsua_msg_logger.id = -1;
+ }
+
+ /* Enable SIP message logging */
+ if (pjsua_var.log_cfg.msg_logging)
+ pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_msg_logger);
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * PJSUA Base API.
+ */
+
+/* Worker thread function. */
+static int worker_thread(void *arg)
+{
+ enum { TIMEOUT = 10 };
+
+ PJ_UNUSED_ARG(arg);
+
+ while (!pjsua_var.thread_quit_flag) {
+ int count;
+
+ count = pjsua_handle_events(TIMEOUT);
+ if (count < 0)
+ pj_thread_sleep(TIMEOUT);
+ }
+
+ return 0;
+}
+
+
+/* Init random seed */
+static void init_random_seed(void)
+{
+ pj_sockaddr addr;
+ const pj_str_t *hostname;
+ pj_uint32_t pid;
+ pj_time_val t;
+ unsigned seed=0;
+
+ /* Add hostname */
+ hostname = pj_gethostname();
+ seed = pj_hash_calc(seed, hostname->ptr, (int)hostname->slen);
+
+ /* Add primary IP address */
+ if (pj_gethostip(pj_AF_INET(), &addr)==PJ_SUCCESS)
+ seed = pj_hash_calc(seed, &addr.ipv4.sin_addr, 4);
+
+ /* Get timeofday */
+ pj_gettimeofday(&t);
+ seed = pj_hash_calc(seed, &t, sizeof(t));
+
+ /* Add PID */
+ pid = pj_getpid();
+ seed = pj_hash_calc(seed, &pid, sizeof(pid));
+
+ /* Init random seed */
+ pj_srand(seed);
+}
+
+/*
+ * Instantiate pjsua application.
+ */
+PJ_DEF(pj_status_t) pjsua_create(void)
+{
+ pj_status_t status;
+
+ /* Init pjsua data */
+ init_data();
+
+ /* Set default logging settings */
+ pjsua_logging_config_default(&pjsua_var.log_cfg);
+
+ /* Init PJLIB: */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ pj_log_push_indent();
+
+ /* Init random seed */
+ init_random_seed();
+
+ /* Init PJLIB-UTIL: */
+ status = pjlib_util_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Init PJNATH */
+ status = pjnath_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Set default sound device ID */
+ pjsua_var.cap_dev = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV;
+ pjsua_var.play_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV;
+
+ /* Set default video device ID */
+ pjsua_var.vcap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+ pjsua_var.vrdr_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV;
+
+ /* Init caching pool. */
+ pj_caching_pool_init(&pjsua_var.cp, NULL, 0);
+
+ /* Create memory pool for application. */
+ pjsua_var.pool = pjsua_pool_create("pjsua", 1000, 1000);
+
+ PJ_ASSERT_RETURN(pjsua_var.pool, PJ_ENOMEM);
+
+ /* Create mutex */
+ status = pj_mutex_create_recursive(pjsua_var.pool, "pjsua",
+ &pjsua_var.mutex);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ pjsua_perror(THIS_FILE, "Unable to create mutex", status);
+ return status;
+ }
+
+ /* Must create SIP endpoint to initialize SIP parser. The parser
+ * is needed for example when application needs to call pjsua_verify_url().
+ */
+ status = pjsip_endpt_create(&pjsua_var.cp.factory,
+ pj_gethostname()->ptr,
+ &pjsua_var.endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Init timer entry list */
+ pj_list_init(&pjsua_var.timer_list);
+
+ /* Create timer mutex */
+ status = pj_mutex_create_recursive(pjsua_var.pool, "pjsua_timer",
+ &pjsua_var.timer_mutex);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ pjsua_perror(THIS_FILE, "Unable to create mutex", status);
+ return status;
+ }
+
+ pjsua_set_state(PJSUA_STATE_CREATED);
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Initialize pjsua with the specified settings. All the settings are
+ * optional, and the default values will be used when the config is not
+ * specified.
+ */
+PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg,
+ const pjsua_logging_config *log_cfg,
+ const pjsua_media_config *media_cfg)
+{
+ pjsua_config default_cfg;
+ pjsua_media_config default_media_cfg;
+ const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };
+ pjsip_ua_init_param ua_init_param;
+ unsigned i;
+ pj_status_t status;
+
+ pj_log_push_indent();
+
+ /* Create default configurations when the config is not supplied */
+
+ if (ua_cfg == NULL) {
+ pjsua_config_default(&default_cfg);
+ ua_cfg = &default_cfg;
+ }
+
+ if (media_cfg == NULL) {
+ pjsua_media_config_default(&default_media_cfg);
+ media_cfg = &default_media_cfg;
+ }
+
+ /* Initialize logging first so that info/errors can be captured */
+ if (log_cfg) {
+ status = pjsua_reconfigure_logging(log_cfg);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \
+ PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0
+ if (!(pj_get_sys_info()->flags & PJ_SYS_HAS_IOS_BG)) {
+ PJ_LOG(5, (THIS_FILE, "Device does not support "
+ "background mode"));
+ pj_activesock_enable_iphone_os_bg(PJ_FALSE);
+ }
+#endif
+
+ /* If nameserver is configured, create DNS resolver instance and
+ * set it to be used by SIP resolver.
+ */
+ if (ua_cfg->nameserver_count) {
+#if PJSIP_HAS_RESOLVER
+ unsigned i;
+
+ /* Create DNS resolver */
+ status = pjsip_endpt_create_resolver(pjsua_var.endpt,
+ &pjsua_var.resolver);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating resolver", status);
+ goto on_error;
+ }
+
+ /* Configure nameserver for the DNS resolver */
+ status = pj_dns_resolver_set_ns(pjsua_var.resolver,
+ ua_cfg->nameserver_count,
+ ua_cfg->nameserver, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error setting nameserver", status);
+ goto on_error;
+ }
+
+ /* Set this DNS resolver to be used by the SIP resolver */
+ status = pjsip_endpt_set_resolver(pjsua_var.endpt, pjsua_var.resolver);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error setting DNS resolver", status);
+ goto on_error;
+ }
+
+ /* Print nameservers */
+ for (i=0; i<ua_cfg->nameserver_count; ++i) {
+ PJ_LOG(4,(THIS_FILE, "Nameserver %.*s added",
+ (int)ua_cfg->nameserver[i].slen,
+ ua_cfg->nameserver[i].ptr));
+ }
+#else
+ PJ_LOG(2,(THIS_FILE,
+ "DNS resolver is disabled (PJSIP_HAS_RESOLVER==0)"));
+#endif
+ }
+
+ /* Init SIP UA: */
+
+ /* Initialize transaction layer: */
+ status = pjsip_tsx_layer_init_module(pjsua_var.endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Initialize UA layer module: */
+ pj_bzero(&ua_init_param, sizeof(ua_init_param));
+ if (ua_cfg->hangup_forked_call) {
+ ua_init_param.on_dlg_forked = &on_dlg_forked;
+ }
+ status = pjsip_ua_init_module( pjsua_var.endpt, &ua_init_param);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Initialize Replaces support. */
+ status = pjsip_replaces_init_module( pjsua_var.endpt );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Initialize 100rel support */
+ status = pjsip_100rel_init_module(pjsua_var.endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Initialize session timer support */
+ status = pjsip_timer_init_module(pjsua_var.endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Initialize and register PJSUA application module. */
+ {
+ const pjsip_module mod_initializer =
+ {
+ NULL, NULL, /* prev, next. */
+ { "mod-pjsua", 9 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &mod_pjsua_on_rx_request, /* on_rx_request() */
+ &mod_pjsua_on_rx_response, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+ };
+
+ pjsua_var.mod = mod_initializer;
+
+ status = pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_var.mod);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ }
+
+ /* Parse outbound proxies */
+ for (i=0; i<ua_cfg->outbound_proxy_cnt; ++i) {
+ pj_str_t tmp;
+ pj_str_t hname = { "Route", 5};
+ pjsip_route_hdr *r;
+
+ pj_strdup_with_null(pjsua_var.pool, &tmp, &ua_cfg->outbound_proxy[i]);
+
+ r = (pjsip_route_hdr*)
+ pjsip_parse_hdr(pjsua_var.pool, &hname, tmp.ptr,
+ (unsigned)tmp.slen, NULL);
+ if (r == NULL) {
+ pjsua_perror(THIS_FILE, "Invalid outbound proxy URI",
+ PJSIP_EINVALIDURI);
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ if (pjsua_var.ua_cfg.force_lr) {
+ pjsip_sip_uri *sip_url;
+ if (!PJSIP_URI_SCHEME_IS_SIP(r->name_addr.uri) &&
+ !PJSIP_URI_SCHEME_IS_SIP(r->name_addr.uri))
+ {
+ status = PJSIP_EINVALIDSCHEME;
+ goto on_error;
+ }
+ sip_url = (pjsip_sip_uri*)r->name_addr.uri;
+ sip_url->lr_param = 1;
+ }
+
+ pj_list_push_back(&pjsua_var.outbound_proxy, r);
+ }
+
+
+ /* Initialize PJSUA call subsystem: */
+ status = pjsua_call_subsys_init(ua_cfg);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Convert deprecated STUN settings */
+ if (pjsua_var.ua_cfg.stun_srv_cnt==0) {
+ if (pjsua_var.ua_cfg.stun_domain.slen) {
+ pjsua_var.ua_cfg.stun_srv[pjsua_var.ua_cfg.stun_srv_cnt++] =
+ pjsua_var.ua_cfg.stun_domain;
+ }
+ if (pjsua_var.ua_cfg.stun_host.slen) {
+ pjsua_var.ua_cfg.stun_srv[pjsua_var.ua_cfg.stun_srv_cnt++] =
+ pjsua_var.ua_cfg.stun_host;
+ }
+ }
+
+ /* Start resolving STUN server */
+ status = resolve_stun_server(PJ_FALSE);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
+ goto on_error;
+ }
+
+ /* Initialize PJSUA media subsystem */
+ status = pjsua_media_subsys_init(media_cfg);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Init core SIMPLE module : */
+ status = pjsip_evsub_init_module(pjsua_var.endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+ /* Init presence module: */
+ status = pjsip_pres_init_module( pjsua_var.endpt, pjsip_evsub_instance());
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Initialize MWI support */
+ status = pjsip_mwi_init_module(pjsua_var.endpt, pjsip_evsub_instance());
+
+ /* Init PUBLISH module */
+ pjsip_publishc_init_module(pjsua_var.endpt);
+
+ /* Init xfer/REFER module */
+ status = pjsip_xfer_init_module( pjsua_var.endpt );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /* Init pjsua presence handler: */
+ status = pjsua_pres_init();
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init out-of-dialog MESSAGE request handler. */
+ status = pjsua_im_init();
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Register OPTIONS handler */
+ pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_options_handler);
+
+ /* Add OPTIONS in Allow header */
+ pjsip_endpt_add_capability(pjsua_var.endpt, NULL, PJSIP_H_ALLOW,
+ NULL, 1, &STR_OPTIONS);
+
+ /* Start worker thread if needed. */
+ if (pjsua_var.ua_cfg.thread_cnt) {
+ unsigned i;
+
+ if (pjsua_var.ua_cfg.thread_cnt > PJ_ARRAY_SIZE(pjsua_var.thread))
+ pjsua_var.ua_cfg.thread_cnt = PJ_ARRAY_SIZE(pjsua_var.thread);
+
+ for (i=0; i<pjsua_var.ua_cfg.thread_cnt; ++i) {
+ status = pj_thread_create(pjsua_var.pool, "pjsua", &worker_thread,
+ NULL, 0, 0, &pjsua_var.thread[i]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+ PJ_LOG(4,(THIS_FILE, "%d SIP worker threads created",
+ pjsua_var.ua_cfg.thread_cnt));
+ } else {
+ PJ_LOG(4,(THIS_FILE, "No SIP worker threads created"));
+ }
+
+ /* Done! */
+
+ PJ_LOG(3,(THIS_FILE, "pjsua version %s for %s initialized",
+ pj_get_version(), pj_get_sys_info()->info.ptr));
+
+ pjsua_set_state(PJSUA_STATE_INIT);
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ pjsua_destroy();
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/* Sleep with polling */
+static void busy_sleep(unsigned msec)
+{
+ pj_time_val timeout, now;
+
+ pj_gettimeofday(&timeout);
+ timeout.msec += msec;
+ pj_time_val_normalize(&timeout);
+
+ do {
+ int i;
+ i = msec / 10;
+ while (pjsua_handle_events(10) > 0 && i > 0)
+ --i;
+ pj_gettimeofday(&now);
+ } while (PJ_TIME_VAL_LT(now, timeout));
+}
+
+/* Internal function to destroy STUN resolution session
+ * (pj_stun_resolve).
+ */
+static void destroy_stun_resolve(pjsua_stun_resolve *sess)
+{
+ PJSUA_LOCK();
+ pj_list_erase(sess);
+ PJSUA_UNLOCK();
+
+ pj_assert(sess->stun_sock==NULL);
+ pj_pool_release(sess->pool);
+}
+
+/* This is the internal function to be called when STUN resolution
+ * session (pj_stun_resolve) has completed.
+ */
+static void stun_resolve_complete(pjsua_stun_resolve *sess)
+{
+ pj_stun_resolve_result result;
+
+ pj_bzero(&result, sizeof(result));
+ result.token = sess->token;
+ result.status = sess->status;
+ result.name = sess->srv[sess->idx];
+ pj_memcpy(&result.addr, &sess->addr, sizeof(result.addr));
+
+ if (result.status == PJ_SUCCESS) {
+ char addr[PJ_INET6_ADDRSTRLEN+10];
+ pj_sockaddr_print(&result.addr, addr, sizeof(addr), 3);
+ PJ_LOG(4,(THIS_FILE,
+ "STUN resolution success, using %.*s, address is %s",
+ (int)sess->srv[sess->idx].slen,
+ sess->srv[sess->idx].ptr,
+ addr));
+ } else {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(result.status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(THIS_FILE, "STUN resolution failed: %s", errmsg));
+ }
+
+ sess->cb(&result);
+
+ if (!sess->blocking) {
+ destroy_stun_resolve(sess);
+ }
+}
+
+/* This is the callback called by the STUN socket (pj_stun_sock)
+ * to report it's state. We use this as part of testing the
+ * STUN server.
+ */
+static pj_bool_t test_stun_on_status(pj_stun_sock *stun_sock,
+ pj_stun_sock_op op,
+ pj_status_t status)
+{
+ pjsua_stun_resolve *sess;
+
+ sess = (pjsua_stun_resolve*) pj_stun_sock_get_user_data(stun_sock);
+ pj_assert(stun_sock == sess->stun_sock);
+
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(4,(THIS_FILE, "STUN resolution for %.*s failed: %s",
+ (int)sess->srv[sess->idx].slen,
+ sess->srv[sess->idx].ptr, errmsg));
+
+ sess->status = status;
+
+ pj_stun_sock_destroy(stun_sock);
+ sess->stun_sock = NULL;
+
+ ++sess->idx;
+ resolve_stun_entry(sess);
+
+ return PJ_FALSE;
+
+ } else if (op == PJ_STUN_SOCK_BINDING_OP) {
+ pj_stun_sock_info ssi;
+
+ pj_stun_sock_get_info(stun_sock, &ssi);
+ pj_memcpy(&sess->addr, &ssi.srv_addr, sizeof(sess->addr));
+
+ sess->status = PJ_SUCCESS;
+ pj_stun_sock_destroy(stun_sock);
+ sess->stun_sock = NULL;
+
+ stun_resolve_complete(sess);
+
+ return PJ_FALSE;
+
+ } else
+ return PJ_TRUE;
+
+}
+
+/* This is an internal function to resolve and test current
+ * server entry in pj_stun_resolve session. It is called by
+ * pjsua_resolve_stun_servers() and test_stun_on_status() above
+ */
+static void resolve_stun_entry(pjsua_stun_resolve *sess)
+{
+ /* Loop while we have entry to try */
+ for (; sess->idx < sess->count; ++sess->idx) {
+ const int af = pj_AF_INET();
+ pj_str_t hostpart;
+ pj_uint16_t port;
+ pj_stun_sock_cb stun_sock_cb;
+
+ pj_assert(sess->idx < sess->count);
+
+ /* Parse the server entry into host:port */
+ sess->status = pj_sockaddr_parse2(af, 0, &sess->srv[sess->idx],
+ &hostpart, &port, NULL);
+ if (sess->status != PJ_SUCCESS) {
+ PJ_LOG(2,(THIS_FILE, "Invalid STUN server entry %.*s",
+ (int)sess->srv[sess->idx].slen,
+ sess->srv[sess->idx].ptr));
+ continue;
+ }
+
+ /* Use default port if not specified */
+ if (port == 0)
+ port = PJ_STUN_PORT;
+
+ pj_assert(sess->stun_sock == NULL);
+
+ PJ_LOG(4,(THIS_FILE, "Trying STUN server %.*s (%d of %d)..",
+ (int)sess->srv[sess->idx].slen,
+ sess->srv[sess->idx].ptr,
+ sess->idx+1, sess->count));
+
+ /* Use STUN_sock to test this entry */
+ pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb));
+ stun_sock_cb.on_status = &test_stun_on_status;
+ sess->status = pj_stun_sock_create(&pjsua_var.stun_cfg, "stunresolve",
+ pj_AF_INET(), &stun_sock_cb,
+ NULL, sess, &sess->stun_sock);
+ if (sess->status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(sess->status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(THIS_FILE,
+ "Error creating STUN socket for %.*s: %s",
+ (int)sess->srv[sess->idx].slen,
+ sess->srv[sess->idx].ptr, errmsg));
+
+ continue;
+ }
+
+ sess->status = pj_stun_sock_start(sess->stun_sock, &hostpart,
+ port, pjsua_var.resolver);
+ if (sess->status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(sess->status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(THIS_FILE,
+ "Error starting STUN socket for %.*s: %s",
+ (int)sess->srv[sess->idx].slen,
+ sess->srv[sess->idx].ptr, errmsg));
+
+ pj_stun_sock_destroy(sess->stun_sock);
+ sess->stun_sock = NULL;
+ continue;
+ }
+
+ /* Done for now, testing will resume/complete asynchronously in
+ * stun_sock_cb()
+ */
+ return;
+ }
+
+ if (sess->idx >= sess->count) {
+ /* No more entries to try */
+ PJ_ASSERT_ON_FAIL(sess->status != PJ_SUCCESS,
+ sess->status = PJ_EUNKNOWN);
+ stun_resolve_complete(sess);
+ }
+}
+
+
+/*
+ * Resolve STUN server.
+ */
+PJ_DEF(pj_status_t) pjsua_resolve_stun_servers( unsigned count,
+ pj_str_t srv[],
+ pj_bool_t wait,
+ void *token,
+ pj_stun_resolve_cb cb)
+{
+ pj_pool_t *pool;
+ pjsua_stun_resolve *sess;
+ pj_status_t status;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(count && srv && cb, PJ_EINVAL);
+
+ pool = pjsua_pool_create("stunres", 256, 256);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ sess = PJ_POOL_ZALLOC_T(pool, pjsua_stun_resolve);
+ sess->pool = pool;
+ sess->token = token;
+ sess->cb = cb;
+ sess->count = count;
+ sess->blocking = wait;
+ sess->status = PJ_EPENDING;
+ sess->srv = (pj_str_t*) pj_pool_calloc(pool, count, sizeof(pj_str_t));
+ for (i=0; i<count; ++i) {
+ pj_strdup(pool, &sess->srv[i], &srv[i]);
+ }
+
+ PJSUA_LOCK();
+ pj_list_push_back(&pjsua_var.stun_res, sess);
+ PJSUA_UNLOCK();
+
+ resolve_stun_entry(sess);
+
+ if (!wait)
+ return PJ_SUCCESS;
+
+ while (sess->status == PJ_EPENDING) {
+ pjsua_handle_events(50);
+ }
+
+ status = sess->status;
+ destroy_stun_resolve(sess);
+
+ return status;
+}
+
+/*
+ * Cancel pending STUN resolution.
+ */
+PJ_DEF(pj_status_t) pjsua_cancel_stun_resolution( void *token,
+ pj_bool_t notify_cb)
+{
+ pjsua_stun_resolve *sess;
+ unsigned cancelled_count = 0;
+
+ PJSUA_LOCK();
+ sess = pjsua_var.stun_res.next;
+ while (sess != &pjsua_var.stun_res) {
+ pjsua_stun_resolve *next = sess->next;
+
+ if (sess->token == token) {
+ if (notify_cb) {
+ pj_stun_resolve_result result;
+
+ pj_bzero(&result, sizeof(result));
+ result.token = token;
+ result.status = PJ_ECANCELLED;
+
+ sess->cb(&result);
+ }
+
+ destroy_stun_resolve(sess);
+ ++cancelled_count;
+ }
+
+ sess = next;
+ }
+ PJSUA_UNLOCK();
+
+ return cancelled_count ? PJ_SUCCESS : PJ_ENOTFOUND;
+}
+
+static void internal_stun_resolve_cb(const pj_stun_resolve_result *result)
+{
+ pjsua_var.stun_status = result->status;
+ if (result->status == PJ_SUCCESS) {
+ pj_memcpy(&pjsua_var.stun_srv, &result->addr, sizeof(result->addr));
+ }
+}
+
+/*
+ * Resolve STUN server.
+ */
+pj_status_t resolve_stun_server(pj_bool_t wait)
+{
+ if (pjsua_var.stun_status == PJ_EUNKNOWN) {
+ pj_status_t status;
+
+ /* Initialize STUN configuration */
+ pj_stun_config_init(&pjsua_var.stun_cfg, &pjsua_var.cp.factory, 0,
+ pjsip_endpt_get_ioqueue(pjsua_var.endpt),
+ pjsip_endpt_get_timer_heap(pjsua_var.endpt));
+
+ /* Start STUN server resolution */
+ if (pjsua_var.ua_cfg.stun_srv_cnt) {
+ pjsua_var.stun_status = PJ_EPENDING;
+ status = pjsua_resolve_stun_servers(pjsua_var.ua_cfg.stun_srv_cnt,
+ pjsua_var.ua_cfg.stun_srv,
+ wait, NULL,
+ &internal_stun_resolve_cb);
+ if (wait || status != PJ_SUCCESS) {
+ pjsua_var.stun_status = status;
+ }
+ } else {
+ pjsua_var.stun_status = PJ_SUCCESS;
+ }
+
+ } else if (pjsua_var.stun_status == PJ_EPENDING) {
+ /* STUN server resolution has been started, wait for the
+ * result.
+ */
+ if (wait) {
+ while (pjsua_var.stun_status == PJ_EPENDING) {
+ if (pjsua_var.thread[0] == NULL)
+ pjsua_handle_events(10);
+ else
+ pj_thread_sleep(10);
+ }
+ }
+ }
+
+ if (pjsua_var.stun_status != PJ_EPENDING &&
+ pjsua_var.stun_status != PJ_SUCCESS &&
+ pjsua_var.ua_cfg.stun_ignore_failure)
+ {
+ PJ_LOG(2,(THIS_FILE,
+ "Ignoring STUN resolution failure (by setting)"));
+ pjsua_var.stun_status = PJ_SUCCESS;
+ }
+
+ return pjsua_var.stun_status;
+}
+
+/*
+ * Destroy pjsua.
+ */
+PJ_DEF(pj_status_t) pjsua_destroy2(unsigned flags)
+{
+ int i; /* Must be signed */
+
+ if (pjsua_var.endpt) {
+ PJ_LOG(4,(THIS_FILE, "Shutting down, flags=%d...", flags));
+ }
+
+ if (pjsua_var.state > PJSUA_STATE_NULL &&
+ pjsua_var.state < PJSUA_STATE_CLOSING)
+ {
+ pjsua_set_state(PJSUA_STATE_CLOSING);
+ }
+
+ /* Signal threads to quit: */
+ pjsua_var.thread_quit_flag = 1;
+
+ /* Wait worker threads to quit: */
+ for (i=0; i<(int)pjsua_var.ua_cfg.thread_cnt; ++i) {
+ if (pjsua_var.thread[i]) {
+ pj_status_t status;
+ status = pj_thread_join(pjsua_var.thread[i]);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(4,(THIS_FILE, status, "Error joining worker thread"));
+ pj_thread_sleep(1000);
+ }
+ pj_thread_destroy(pjsua_var.thread[i]);
+ pjsua_var.thread[i] = NULL;
+ }
+ }
+
+ if (pjsua_var.endpt) {
+ unsigned max_wait;
+
+ pj_log_push_indent();
+
+ /* Terminate all calls. */
+ if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
+ pjsua_call_hangup_all();
+ }
+
+ /* Set all accounts to offline */
+ for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (!pjsua_var.acc[i].valid)
+ continue;
+ pjsua_var.acc[i].online_status = PJ_FALSE;
+ pj_bzero(&pjsua_var.acc[i].rpid, sizeof(pjrpid_element));
+ }
+
+ /* Terminate all presence subscriptions. */
+ pjsua_pres_shutdown(flags);
+
+ /* Destroy media (to shutdown media transports etc) */
+ pjsua_media_subsys_destroy(flags);
+
+ /* Wait for sometime until all publish client sessions are done
+ * (ticket #364)
+ */
+ /* First stage, get the maximum wait time */
+ max_wait = 100;
+ for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (!pjsua_var.acc[i].valid)
+ continue;
+ if (pjsua_var.acc[i].cfg.unpublish_max_wait_time_msec > max_wait)
+ max_wait = pjsua_var.acc[i].cfg.unpublish_max_wait_time_msec;
+ }
+
+ /* No waiting if RX is disabled */
+ if (flags & PJSUA_DESTROY_NO_RX_MSG) {
+ max_wait = 0;
+ }
+
+ /* Second stage, wait for unpublications to complete */
+ for (i=0; i<(int)(max_wait/50); ++i) {
+ unsigned j;
+ for (j=0; j<PJ_ARRAY_SIZE(pjsua_var.acc); ++j) {
+ if (!pjsua_var.acc[j].valid)
+ continue;
+
+ if (pjsua_var.acc[j].publish_sess)
+ break;
+ }
+ if (j != PJ_ARRAY_SIZE(pjsua_var.acc))
+ busy_sleep(50);
+ else
+ break;
+ }
+
+ /* Third stage, forcefully destroy unfinished unpublications */
+ for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (pjsua_var.acc[i].publish_sess) {
+ pjsip_publishc_destroy(pjsua_var.acc[i].publish_sess);
+ pjsua_var.acc[i].publish_sess = NULL;
+ }
+ }
+
+ /* Unregister all accounts */
+ for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (!pjsua_var.acc[i].valid)
+ continue;
+
+ if (pjsua_var.acc[i].regc && (flags & PJSUA_DESTROY_NO_TX_MSG)==0)
+ {
+ pjsua_acc_set_registration(i, PJ_FALSE);
+ }
+ }
+
+ /* Terminate any pending STUN resolution */
+ if (!pj_list_empty(&pjsua_var.stun_res)) {
+ pjsua_stun_resolve *sess = pjsua_var.stun_res.next;
+ while (sess != &pjsua_var.stun_res) {
+ pjsua_stun_resolve *next = sess->next;
+ destroy_stun_resolve(sess);
+ sess = next;
+ }
+ }
+
+ /* Wait until all unregistrations are done (ticket #364) */
+ /* First stage, get the maximum wait time */
+ max_wait = 100;
+ for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (!pjsua_var.acc[i].valid)
+ continue;
+ if (pjsua_var.acc[i].cfg.unreg_timeout > max_wait)
+ max_wait = pjsua_var.acc[i].cfg.unreg_timeout;
+ }
+
+ /* No waiting if RX is disabled */
+ if (flags & PJSUA_DESTROY_NO_RX_MSG) {
+ max_wait = 0;
+ }
+
+ /* Second stage, wait for unregistrations to complete */
+ for (i=0; i<(int)(max_wait/50); ++i) {
+ unsigned j;
+ for (j=0; j<PJ_ARRAY_SIZE(pjsua_var.acc); ++j) {
+ if (!pjsua_var.acc[j].valid)
+ continue;
+
+ if (pjsua_var.acc[j].regc)
+ break;
+ }
+ if (j != PJ_ARRAY_SIZE(pjsua_var.acc))
+ busy_sleep(50);
+ else
+ break;
+ }
+ /* Note variable 'i' is used below */
+
+ /* Wait for some time to allow unregistration and ICE/TURN
+ * transports shutdown to complete:
+ */
+ if (i < 20 && (flags & PJSUA_DESTROY_NO_RX_MSG) == 0) {
+ busy_sleep(1000 - i*50);
+ }
+
+ PJ_LOG(4,(THIS_FILE, "Destroying..."));
+
+ /* Must destroy endpoint first before destroying pools in
+ * buddies or accounts, since shutting down transaction layer
+ * may emit events which trigger some buddy or account callbacks
+ * to be called.
+ */
+ pjsip_endpt_destroy(pjsua_var.endpt);
+ pjsua_var.endpt = NULL;
+
+ /* Destroy pool in the buddy object */
+ for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+ if (pjsua_var.buddy[i].pool) {
+ pj_pool_release(pjsua_var.buddy[i].pool);
+ pjsua_var.buddy[i].pool = NULL;
+ }
+ }
+
+ /* Destroy accounts */
+ for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (pjsua_var.acc[i].pool) {
+ pj_pool_release(pjsua_var.acc[i].pool);
+ pjsua_var.acc[i].pool = NULL;
+ }
+ }
+ }
+
+ /* Destroy mutex */
+ if (pjsua_var.mutex) {
+ pj_mutex_destroy(pjsua_var.mutex);
+ pjsua_var.mutex = NULL;
+ }
+
+ /* Destroy pool and pool factory. */
+ if (pjsua_var.pool) {
+ pj_pool_release(pjsua_var.pool);
+ pjsua_var.pool = NULL;
+ pj_caching_pool_destroy(&pjsua_var.cp);
+
+ pjsua_set_state(PJSUA_STATE_NULL);
+
+ PJ_LOG(4,(THIS_FILE, "PJSUA destroyed..."));
+
+ /* End logging */
+ if (pjsua_var.log_file) {
+ pj_file_close(pjsua_var.log_file);
+ pjsua_var.log_file = NULL;
+ }
+
+ pj_log_pop_indent();
+
+ /* Shutdown PJLIB */
+ pj_shutdown();
+ }
+
+ /* Clear pjsua_var */
+ pj_bzero(&pjsua_var, sizeof(pjsua_var));
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+void pjsua_set_state(pjsua_state new_state)
+{
+ const char *state_name[] = {
+ "NULL",
+ "CREATED",
+ "INIT",
+ "STARTING",
+ "RUNNING",
+ "CLOSING"
+ };
+ pjsua_state old_state = pjsua_var.state;
+
+ pjsua_var.state = new_state;
+ PJ_LOG(4,(THIS_FILE, "PJSUA state changed: %s --> %s",
+ state_name[old_state], state_name[new_state]));
+}
+
+/* Get state */
+PJ_DEF(pjsua_state) pjsua_get_state(void)
+{
+ return pjsua_var.state;
+}
+
+PJ_DEF(pj_status_t) pjsua_destroy(void)
+{
+ return pjsua_destroy2(0);
+}
+
+
+/**
+ * Application is recommended to call this function after all initialization
+ * is done, so that the library can do additional checking set up
+ * additional
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DEF(pj_status_t) pjsua_start(void)
+{
+ pj_status_t status;
+
+ pjsua_set_state(PJSUA_STATE_STARTING);
+ pj_log_push_indent();
+
+ status = pjsua_call_subsys_start();
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ status = pjsua_media_subsys_start();
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ status = pjsua_pres_start();
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ pjsua_set_state(PJSUA_STATE_RUNNING);
+
+on_return:
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/**
+ * Poll pjsua for events, and if necessary block the caller thread for
+ * the specified maximum interval (in miliseconds).
+ */
+PJ_DEF(int) pjsua_handle_events(unsigned msec_timeout)
+{
+#if defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0
+
+ return pj_symbianos_poll(-1, msec_timeout);
+
+#else
+
+ unsigned count = 0;
+ pj_time_val tv;
+ pj_status_t status;
+
+ tv.sec = 0;
+ tv.msec = msec_timeout;
+ pj_time_val_normalize(&tv);
+
+ status = pjsip_endpt_handle_events2(pjsua_var.endpt, &tv, &count);
+
+ if (status != PJ_SUCCESS)
+ return -status;
+
+ return count;
+
+#endif
+}
+
+
+/*
+ * Create memory pool.
+ */
+PJ_DEF(pj_pool_t*) pjsua_pool_create( const char *name, pj_size_t init_size,
+ pj_size_t increment)
+{
+ /* Pool factory is thread safe, no need to lock */
+ return pj_pool_create(&pjsua_var.cp.factory, name, init_size, increment,
+ NULL);
+}
+
+
+/*
+ * Internal function to get SIP endpoint instance of pjsua, which is
+ * needed for example to register module, create transports, etc.
+ * Probably is only valid after #pjsua_init() is called.
+ */
+PJ_DEF(pjsip_endpoint*) pjsua_get_pjsip_endpt(void)
+{
+ return pjsua_var.endpt;
+}
+
+/*
+ * Internal function to get media endpoint instance.
+ * Only valid after #pjsua_init() is called.
+ */
+PJ_DEF(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void)
+{
+ return pjsua_var.med_endpt;
+}
+
+/*
+ * Internal function to get PJSUA pool factory.
+ */
+PJ_DEF(pj_pool_factory*) pjsua_get_pool_factory(void)
+{
+ return &pjsua_var.cp.factory;
+}
+
+/*****************************************************************************
+ * PJSUA SIP Transport API.
+ */
+
+/*
+ * Tools to get address string.
+ */
+static const char *addr_string(const pj_sockaddr_t *addr)
+{
+ static char str[128];
+ str[0] = '\0';
+ pj_inet_ntop(((const pj_sockaddr*)addr)->addr.sa_family,
+ pj_sockaddr_get_addr(addr),
+ str, sizeof(str));
+ return str;
+}
+
+void pjsua_acc_on_tp_state_changed(pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info);
+
+/* Callback to receive transport state notifications */
+static void on_tp_state_callback(pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info)
+{
+ if (pjsua_var.ua_cfg.cb.on_transport_state) {
+ (*pjsua_var.ua_cfg.cb.on_transport_state)(tp, state, info);
+ }
+ if (pjsua_var.old_tp_cb) {
+ (*pjsua_var.old_tp_cb)(tp, state, info);
+ }
+ pjsua_acc_on_tp_state_changed(tp, state, info);
+}
+
+/*
+ * Create and initialize SIP socket (and possibly resolve public
+ * address via STUN, depending on config).
+ */
+static pj_status_t create_sip_udp_sock(int af,
+ const pjsua_transport_config *cfg,
+ pj_sock_t *p_sock,
+ pj_sockaddr *p_pub_addr)
+{
+ char stun_ip_addr[PJ_INET6_ADDRSTRLEN];
+ unsigned port = cfg->port;
+ pj_str_t stun_srv;
+ pj_sock_t sock;
+ pj_sockaddr bind_addr;
+ pj_status_t status;
+
+ /* Make sure STUN server resolution has completed */
+ status = resolve_stun_server(PJ_TRUE);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
+ return status;
+ }
+
+ /* Initialize bound address */
+ if (cfg->bound_addr.slen) {
+ status = pj_sockaddr_init(af, &bind_addr, &cfg->bound_addr,
+ (pj_uint16_t)port);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Unable to resolve transport bound address",
+ status);
+ return status;
+ }
+ } else {
+ pj_sockaddr_init(af, &bind_addr, NULL, (pj_uint16_t)port);
+ }
+
+ /* Create socket */
+ status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &sock);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "socket() error", status);
+ return status;
+ }
+
+ /* Apply QoS, if specified */
+ status = pj_sock_apply_qos2(sock, cfg->qos_type,
+ &cfg->qos_params,
+ 2, THIS_FILE, "SIP UDP socket");
+
+ /* Bind socket */
+ status = pj_sock_bind(sock, &bind_addr, pj_sockaddr_get_len(&bind_addr));
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "bind() error", status);
+ pj_sock_close(sock);
+ return status;
+ }
+
+ /* If port is zero, get the bound port */
+ if (port == 0) {
+ pj_sockaddr bound_addr;
+ int namelen = sizeof(bound_addr);
+ status = pj_sock_getsockname(sock, &bound_addr, &namelen);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "getsockname() error", status);
+ pj_sock_close(sock);
+ return status;
+ }
+
+ port = pj_sockaddr_get_port(&bound_addr);
+ }
+
+ if (pjsua_var.stun_srv.addr.sa_family != 0) {
+ pj_ansi_strcpy(stun_ip_addr,pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
+ stun_srv = pj_str(stun_ip_addr);
+ } else {
+ stun_srv.slen = 0;
+ }
+
+ /* Get the published address, either by STUN or by resolving
+ * the name of local host.
+ */
+ if (pj_sockaddr_has_addr(p_pub_addr)) {
+ /*
+ * Public address is already specified, no need to resolve the
+ * address, only set the port.
+ */
+ if (pj_sockaddr_get_port(p_pub_addr) == 0)
+ pj_sockaddr_set_port(p_pub_addr, (pj_uint16_t)port);
+
+ } else if (stun_srv.slen) {
+ /*
+ * STUN is specified, resolve the address with STUN.
+ */
+ if (af != pj_AF_INET()) {
+ pjsua_perror(THIS_FILE, "Cannot use STUN", PJ_EAFNOTSUP);
+ pj_sock_close(sock);
+ return PJ_EAFNOTSUP;
+ }
+
+ status = pjstun_get_mapped_addr(&pjsua_var.cp.factory, 1, &sock,
+ &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
+ &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
+ &p_pub_addr->ipv4);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error contacting STUN server", status);
+ pj_sock_close(sock);
+ return status;
+ }
+
+ } else {
+ pj_bzero(p_pub_addr, sizeof(pj_sockaddr));
+
+ if (pj_sockaddr_has_addr(&bind_addr)) {
+ pj_sockaddr_copy_addr(p_pub_addr, &bind_addr);
+ } else {
+ status = pj_gethostip(af, p_pub_addr);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to get local host IP", status);
+ pj_sock_close(sock);
+ return status;
+ }
+ }
+
+ p_pub_addr->addr.sa_family = (pj_uint16_t)af;
+ pj_sockaddr_set_port(p_pub_addr, (pj_uint16_t)port);
+ }
+
+ *p_sock = sock;
+
+ PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d",
+ addr_string(p_pub_addr),
+ (int)pj_sockaddr_get_port(p_pub_addr)));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create SIP transport.
+ */
+PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type,
+ const pjsua_transport_config *cfg,
+ pjsua_transport_id *p_id)
+{
+ pjsip_transport *tp;
+ unsigned id;
+ pj_status_t status;
+
+ PJSUA_LOCK();
+
+ /* Find empty transport slot */
+ for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) {
+ if (pjsua_var.tpdata[id].data.ptr == NULL)
+ break;
+ }
+
+ if (id == PJ_ARRAY_SIZE(pjsua_var.tpdata)) {
+ status = PJ_ETOOMANY;
+ pjsua_perror(THIS_FILE, "Error creating transport", status);
+ goto on_return;
+ }
+
+ /* Create the transport */
+ if (type==PJSIP_TRANSPORT_UDP || type==PJSIP_TRANSPORT_UDP6) {
+ /*
+ * Create UDP transport (IPv4 or IPv6).
+ */
+ pjsua_transport_config config;
+ char hostbuf[PJ_INET6_ADDRSTRLEN];
+ pj_sock_t sock = PJ_INVALID_SOCKET;
+ pj_sockaddr pub_addr;
+ pjsip_host_port addr_name;
+
+ /* Supply default config if it's not specified */
+ if (cfg == NULL) {
+ pjsua_transport_config_default(&config);
+ cfg = &config;
+ }
+
+ /* Initialize the public address from the config, if any */
+ pj_sockaddr_init(pjsip_transport_type_get_af(type), &pub_addr,
+ NULL, (pj_uint16_t)cfg->port);
+ if (cfg->public_addr.slen) {
+ status = pj_sockaddr_set_str_addr(pjsip_transport_type_get_af(type),
+ &pub_addr, &cfg->public_addr);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Unable to resolve transport public address",
+ status);
+ goto on_return;
+ }
+ }
+
+ /* Create the socket and possibly resolve the address with STUN
+ * (only when public address is not specified).
+ */
+ status = create_sip_udp_sock(pjsip_transport_type_get_af(type),
+ cfg, &sock, &pub_addr);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ pj_ansi_strcpy(hostbuf, addr_string(&pub_addr));
+ addr_name.host = pj_str(hostbuf);
+ addr_name.port = pj_sockaddr_get_port(&pub_addr);
+
+ /* Create UDP transport */
+ status = pjsip_udp_transport_attach2(pjsua_var.endpt, type, sock,
+ &addr_name, 1, &tp);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating SIP UDP transport",
+ status);
+ pj_sock_close(sock);
+ goto on_return;
+ }
+
+
+ /* Save the transport */
+ pjsua_var.tpdata[id].type = type;
+ pjsua_var.tpdata[id].local_name = tp->local_name;
+ pjsua_var.tpdata[id].data.tp = tp;
+
+#if defined(PJ_HAS_TCP) && PJ_HAS_TCP!=0
+
+ } else if (type == PJSIP_TRANSPORT_TCP || type == PJSIP_TRANSPORT_TCP6) {
+ /*
+ * Create TCP transport.
+ */
+ pjsua_transport_config config;
+ pjsip_tpfactory *tcp;
+ pjsip_tcp_transport_cfg tcp_cfg;
+
+ pjsip_tcp_transport_cfg_default(&tcp_cfg, pj_AF_INET());
+
+ /* Supply default config if it's not specified */
+ if (cfg == NULL) {
+ pjsua_transport_config_default(&config);
+ cfg = &config;
+ }
+
+ /* Configure bind address */
+ if (cfg->port)
+ pj_sockaddr_set_port(&tcp_cfg.bind_addr, (pj_uint16_t)cfg->port);
+
+ if (cfg->bound_addr.slen) {
+ status = pj_sockaddr_set_str_addr(tcp_cfg.af,
+ &tcp_cfg.bind_addr,
+ &cfg->bound_addr);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Unable to resolve transport bound address",
+ status);
+ goto on_return;
+ }
+ }
+
+ /* Set published name */
+ if (cfg->public_addr.slen)
+ tcp_cfg.addr_name.host = cfg->public_addr;
+
+ /* Copy the QoS settings */
+ tcp_cfg.qos_type = cfg->qos_type;
+ pj_memcpy(&tcp_cfg.qos_params, &cfg->qos_params,
+ sizeof(cfg->qos_params));
+
+ /* Create the TCP transport */
+ status = pjsip_tcp_transport_start3(pjsua_var.endpt, &tcp_cfg, &tcp);
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating SIP TCP listener",
+ status);
+ goto on_return;
+ }
+
+ /* Save the transport */
+ pjsua_var.tpdata[id].type = type;
+ pjsua_var.tpdata[id].local_name = tcp->addr_name;
+ pjsua_var.tpdata[id].data.factory = tcp;
+
+#endif /* PJ_HAS_TCP */
+
+#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
+ } else if (type == PJSIP_TRANSPORT_TLS) {
+ /*
+ * Create TLS transport.
+ */
+ pjsua_transport_config config;
+ pjsip_host_port a_name;
+ pjsip_tpfactory *tls;
+ pj_sockaddr_in local_addr;
+
+ /* Supply default config if it's not specified */
+ if (cfg == NULL) {
+ pjsua_transport_config_default(&config);
+ config.port = 5061;
+ cfg = &config;
+ }
+
+ /* Init local address */
+ pj_sockaddr_in_init(&local_addr, 0, 0);
+
+ if (cfg->port)
+ local_addr.sin_port = pj_htons((pj_uint16_t)cfg->port);
+
+ if (cfg->bound_addr.slen) {
+ status = pj_sockaddr_in_set_str_addr(&local_addr,&cfg->bound_addr);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Unable to resolve transport bound address",
+ status);
+ goto on_return;
+ }
+ }
+
+ /* Init published name */
+ pj_bzero(&a_name, sizeof(pjsip_host_port));
+ if (cfg->public_addr.slen)
+ a_name.host = cfg->public_addr;
+
+ status = pjsip_tls_transport_start(pjsua_var.endpt,
+ &cfg->tls_setting,
+ &local_addr, &a_name, 1, &tls);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating SIP TLS listener",
+ status);
+ goto on_return;
+ }
+
+ /* Save the transport */
+ pjsua_var.tpdata[id].type = type;
+ pjsua_var.tpdata[id].local_name = tls->addr_name;
+ pjsua_var.tpdata[id].data.factory = tls;
+#endif
+
+ } else {
+ status = PJSIP_EUNSUPTRANSPORT;
+ pjsua_perror(THIS_FILE, "Error creating transport", status);
+ goto on_return;
+ }
+
+ /* Set transport state callback */
+ if (pjsua_var.ua_cfg.cb.on_transport_state) {
+ pjsip_tp_state_callback tpcb;
+ pjsip_tpmgr *tpmgr;
+
+ tpmgr = pjsip_endpt_get_tpmgr(pjsua_var.endpt);
+ tpcb = pjsip_tpmgr_get_state_cb(tpmgr);
+
+ if (tpcb != &on_tp_state_callback) {
+ pjsua_var.old_tp_cb = tpcb;
+ pjsip_tpmgr_set_state_cb(tpmgr, &on_tp_state_callback);
+ }
+ }
+
+ /* Return the ID */
+ if (p_id) *p_id = id;
+
+ status = PJ_SUCCESS;
+
+on_return:
+
+ PJSUA_UNLOCK();
+
+ return status;
+}
+
+
+/*
+ * Register transport that has been created by application.
+ */
+PJ_DEF(pj_status_t) pjsua_transport_register( pjsip_transport *tp,
+ pjsua_transport_id *p_id)
+{
+ unsigned id;
+
+ PJSUA_LOCK();
+
+ /* Find empty transport slot */
+ for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) {
+ if (pjsua_var.tpdata[id].data.ptr == NULL)
+ break;
+ }
+
+ if (id == PJ_ARRAY_SIZE(pjsua_var.tpdata)) {
+ pjsua_perror(THIS_FILE, "Error creating transport", PJ_ETOOMANY);
+ PJSUA_UNLOCK();
+ return PJ_ETOOMANY;
+ }
+
+ /* Save the transport */
+ pjsua_var.tpdata[id].type = (pjsip_transport_type_e) tp->key.type;
+ pjsua_var.tpdata[id].local_name = tp->local_name;
+ pjsua_var.tpdata[id].data.tp = tp;
+
+ /* Return the ID */
+ if (p_id) *p_id = id;
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Enumerate all transports currently created in the system.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_transports( pjsua_transport_id id[],
+ unsigned *p_count )
+{
+ unsigned i, count;
+
+ PJSUA_LOCK();
+
+ for (i=0, count=0; i<PJ_ARRAY_SIZE(pjsua_var.tpdata) && count<*p_count;
+ ++i)
+ {
+ if (!pjsua_var.tpdata[i].data.ptr)
+ continue;
+
+ id[count++] = i;
+ }
+
+ *p_count = count;
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get information about transports.
+ */
+PJ_DEF(pj_status_t) pjsua_transport_get_info( pjsua_transport_id id,
+ pjsua_transport_info *info)
+{
+ pjsua_transport_data *t = &pjsua_var.tpdata[id];
+ pj_status_t status;
+
+ pj_bzero(info, sizeof(*info));
+
+ /* Make sure id is in range. */
+ PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.tpdata),
+ PJ_EINVAL);
+
+ /* Make sure that transport exists */
+ PJ_ASSERT_RETURN(pjsua_var.tpdata[id].data.ptr != NULL, PJ_EINVAL);
+
+ PJSUA_LOCK();
+
+ if (t->type == PJSIP_TRANSPORT_UDP) {
+
+ pjsip_transport *tp = t->data.tp;
+
+ if (tp == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVALIDOP;
+ }
+
+ info->id = id;
+ info->type = (pjsip_transport_type_e) tp->key.type;
+ info->type_name = pj_str(tp->type_name);
+ info->info = pj_str(tp->info);
+ info->flag = tp->flag;
+ info->addr_len = tp->addr_len;
+ info->local_addr = tp->local_addr;
+ info->local_name = tp->local_name;
+ info->usage_count = pj_atomic_get(tp->ref_cnt);
+
+ status = PJ_SUCCESS;
+
+ } else if (t->type == PJSIP_TRANSPORT_TCP ||
+ t->type == PJSIP_TRANSPORT_TLS)
+ {
+
+ pjsip_tpfactory *factory = t->data.factory;
+
+ if (factory == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVALIDOP;
+ }
+
+ info->id = id;
+ info->type = t->type;
+ info->type_name = (t->type==PJSIP_TRANSPORT_TCP)? pj_str("TCP"):
+ pj_str("TLS");
+ info->info = (t->type==PJSIP_TRANSPORT_TCP)? pj_str("TCP transport"):
+ pj_str("TLS transport");
+ info->flag = factory->flag;
+ info->addr_len = sizeof(factory->local_addr);
+ info->local_addr = factory->local_addr;
+ info->local_name = factory->addr_name;
+ info->usage_count = 0;
+
+ status = PJ_SUCCESS;
+
+ } else {
+ pj_assert(!"Unsupported transport");
+ status = PJ_EINVALIDOP;
+ }
+
+
+ PJSUA_UNLOCK();
+
+ return status;
+}
+
+
+/*
+ * Disable a transport or re-enable it.
+ */
+PJ_DEF(pj_status_t) pjsua_transport_set_enable( pjsua_transport_id id,
+ pj_bool_t enabled)
+{
+ /* Make sure id is in range. */
+ PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.tpdata),
+ PJ_EINVAL);
+
+ /* Make sure that transport exists */
+ PJ_ASSERT_RETURN(pjsua_var.tpdata[id].data.ptr != NULL, PJ_EINVAL);
+
+
+ /* To be done!! */
+ PJ_TODO(pjsua_transport_set_enable);
+ PJ_UNUSED_ARG(enabled);
+
+ return PJ_EINVALIDOP;
+}
+
+
+/*
+ * Close the transport.
+ */
+PJ_DEF(pj_status_t) pjsua_transport_close( pjsua_transport_id id,
+ pj_bool_t force )
+{
+ pj_status_t status;
+
+ /* Make sure id is in range. */
+ PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.tpdata),
+ PJ_EINVAL);
+
+ /* Make sure that transport exists */
+ PJ_ASSERT_RETURN(pjsua_var.tpdata[id].data.ptr != NULL, PJ_EINVAL);
+
+ /* Note: destroy() may not work if there are objects still referencing
+ * the transport.
+ */
+ if (force) {
+ switch (pjsua_var.tpdata[id].type) {
+ case PJSIP_TRANSPORT_UDP:
+ status = pjsip_transport_shutdown(pjsua_var.tpdata[id].data.tp);
+ if (status != PJ_SUCCESS)
+ return status;
+ status = pjsip_transport_destroy(pjsua_var.tpdata[id].data.tp);
+ if (status != PJ_SUCCESS)
+ return status;
+ break;
+
+ case PJSIP_TRANSPORT_TLS:
+ case PJSIP_TRANSPORT_TCP:
+ /* This will close the TCP listener, but existing TCP/TLS
+ * connections (if any) will still linger
+ */
+ status = (*pjsua_var.tpdata[id].data.factory->destroy)
+ (pjsua_var.tpdata[id].data.factory);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ break;
+
+ default:
+ return PJ_EINVAL;
+ }
+
+ } else {
+ /* If force is not specified, transports will be closed at their
+ * convenient time. However this will leak PJSUA-API transport
+ * descriptors as PJSUA-API wouldn't know when exactly the
+ * transport is closed thus it can't cleanup PJSUA transport
+ * descriptor.
+ */
+ switch (pjsua_var.tpdata[id].type) {
+ case PJSIP_TRANSPORT_UDP:
+ return pjsip_transport_shutdown(pjsua_var.tpdata[id].data.tp);
+ case PJSIP_TRANSPORT_TLS:
+ case PJSIP_TRANSPORT_TCP:
+ return (*pjsua_var.tpdata[id].data.factory->destroy)
+ (pjsua_var.tpdata[id].data.factory);
+ default:
+ return PJ_EINVAL;
+ }
+ }
+
+ /* Cleanup pjsua data when force is applied */
+ if (force) {
+ pjsua_var.tpdata[id].type = PJSIP_TRANSPORT_UNSPECIFIED;
+ pjsua_var.tpdata[id].data.ptr = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add additional headers etc in msg_data specified by application
+ * when sending requests.
+ */
+void pjsua_process_msg_data(pjsip_tx_data *tdata,
+ const pjsua_msg_data *msg_data)
+{
+ pj_bool_t allow_body;
+ const pjsip_hdr *hdr;
+
+ /* Always add User-Agent */
+ if (pjsua_var.ua_cfg.user_agent.slen &&
+ tdata->msg->type == PJSIP_REQUEST_MSG)
+ {
+ const pj_str_t STR_USER_AGENT = { "User-Agent", 10 };
+ pjsip_hdr *h;
+ h = (pjsip_hdr*)pjsip_generic_string_hdr_create(tdata->pool,
+ &STR_USER_AGENT,
+ &pjsua_var.ua_cfg.user_agent);
+ pjsip_msg_add_hdr(tdata->msg, h);
+ }
+
+ if (!msg_data)
+ return;
+
+ hdr = msg_data->hdr_list.next;
+ while (hdr && hdr != &msg_data->hdr_list) {
+ pjsip_hdr *new_hdr;
+
+ new_hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr);
+ pjsip_msg_add_hdr(tdata->msg, new_hdr);
+
+ hdr = hdr->next;
+ }
+
+ allow_body = (tdata->msg->body == NULL);
+
+ if (allow_body && msg_data->content_type.slen && msg_data->msg_body.slen) {
+ pjsip_media_type ctype;
+ pjsip_msg_body *body;
+
+ pjsua_parse_media_type(tdata->pool, &msg_data->content_type, &ctype);
+ body = pjsip_msg_body_create(tdata->pool, &ctype.type, &ctype.subtype,
+ &msg_data->msg_body);
+ tdata->msg->body = body;
+ }
+
+ /* Multipart */
+ if (!pj_list_empty(&msg_data->multipart_parts) &&
+ msg_data->multipart_ctype.type.slen)
+ {
+ pjsip_msg_body *bodies;
+ pjsip_multipart_part *part;
+ pj_str_t *boundary = NULL;
+
+ bodies = pjsip_multipart_create(tdata->pool,
+ &msg_data->multipart_ctype,
+ boundary);
+ part = msg_data->multipart_parts.next;
+ while (part != &msg_data->multipart_parts) {
+ pjsip_multipart_part *part_copy;
+
+ part_copy = pjsip_multipart_clone_part(tdata->pool, part);
+ pjsip_multipart_add_part(tdata->pool, bodies, part_copy);
+ part = part->next;
+ }
+
+ if (tdata->msg->body) {
+ part = pjsip_multipart_create_part(tdata->pool);
+ part->body = tdata->msg->body;
+ pjsip_multipart_add_part(tdata->pool, bodies, part);
+
+ tdata->msg->body = NULL;
+ }
+
+ tdata->msg->body = bodies;
+ }
+}
+
+
+/*
+ * Add route_set to outgoing requests
+ */
+void pjsua_set_msg_route_set( pjsip_tx_data *tdata,
+ const pjsip_route_hdr *route_set )
+{
+ const pjsip_route_hdr *r;
+
+ r = route_set->next;
+ while (r != route_set) {
+ pjsip_route_hdr *new_r;
+
+ new_r = (pjsip_route_hdr*) pjsip_hdr_clone(tdata->pool, r);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)new_r);
+
+ r = r->next;
+ }
+}
+
+
+/*
+ * Simple version of MIME type parsing (it doesn't support parameters)
+ */
+void pjsua_parse_media_type( pj_pool_t *pool,
+ const pj_str_t *mime,
+ pjsip_media_type *media_type)
+{
+ pj_str_t tmp;
+ char *pos;
+
+ pj_bzero(media_type, sizeof(*media_type));
+
+ pj_strdup_with_null(pool, &tmp, mime);
+
+ pos = pj_strchr(&tmp, '/');
+ if (pos) {
+ media_type->type.ptr = tmp.ptr;
+ media_type->type.slen = (pos-tmp.ptr);
+ media_type->subtype.ptr = pos+1;
+ media_type->subtype.slen = tmp.ptr+tmp.slen-pos-1;
+ } else {
+ media_type->type = tmp;
+ }
+}
+
+
+/*
+ * Internal function to init transport selector from transport id.
+ */
+void pjsua_init_tpselector(pjsua_transport_id tp_id,
+ pjsip_tpselector *sel)
+{
+ pjsua_transport_data *tpdata;
+ unsigned flag;
+
+ pj_bzero(sel, sizeof(*sel));
+ if (tp_id == PJSUA_INVALID_ID)
+ return;
+
+ pj_assert(tp_id >= 0 && tp_id < (int)PJ_ARRAY_SIZE(pjsua_var.tpdata));
+ tpdata = &pjsua_var.tpdata[tp_id];
+
+ flag = pjsip_transport_get_flag_from_type(tpdata->type);
+
+ if (flag & PJSIP_TRANSPORT_DATAGRAM) {
+ sel->type = PJSIP_TPSELECTOR_TRANSPORT;
+ sel->u.transport = tpdata->data.tp;
+ } else {
+ sel->type = PJSIP_TPSELECTOR_LISTENER;
+ sel->u.listener = tpdata->data.factory;
+ }
+}
+
+
+/* Callback upon NAT detection completion */
+static void nat_detect_cb(void *user_data,
+ const pj_stun_nat_detect_result *res)
+{
+ PJ_UNUSED_ARG(user_data);
+
+ pjsua_var.nat_in_progress = PJ_FALSE;
+ pjsua_var.nat_status = res->status;
+ pjsua_var.nat_type = res->nat_type;
+
+ if (pjsua_var.ua_cfg.cb.on_nat_detect) {
+ (*pjsua_var.ua_cfg.cb.on_nat_detect)(res);
+ }
+}
+
+
+/*
+ * Detect NAT type.
+ */
+PJ_DEF(pj_status_t) pjsua_detect_nat_type()
+{
+ pj_status_t status;
+
+ if (pjsua_var.nat_in_progress)
+ return PJ_SUCCESS;
+
+ /* Make sure STUN server resolution has completed */
+ status = resolve_stun_server(PJ_TRUE);
+ if (status != PJ_SUCCESS) {
+ pjsua_var.nat_status = status;
+ pjsua_var.nat_type = PJ_STUN_NAT_TYPE_ERR_UNKNOWN;
+ return status;
+ }
+
+ /* Make sure we have STUN */
+ if (pjsua_var.stun_srv.ipv4.sin_family == 0) {
+ pjsua_var.nat_status = PJNATH_ESTUNINSERVER;
+ return PJNATH_ESTUNINSERVER;
+ }
+
+ status = pj_stun_detect_nat_type(&pjsua_var.stun_srv.ipv4,
+ &pjsua_var.stun_cfg,
+ NULL, &nat_detect_cb);
+
+ if (status != PJ_SUCCESS) {
+ pjsua_var.nat_status = status;
+ pjsua_var.nat_type = PJ_STUN_NAT_TYPE_ERR_UNKNOWN;
+ return status;
+ }
+
+ pjsua_var.nat_in_progress = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get NAT type.
+ */
+PJ_DEF(pj_status_t) pjsua_get_nat_type(pj_stun_nat_type *type)
+{
+ *type = pjsua_var.nat_type;
+ return pjsua_var.nat_status;
+}
+
+/*
+ * Verify that valid url is given.
+ */
+PJ_DEF(pj_status_t) pjsua_verify_url(const char *c_url)
+{
+ pjsip_uri *p;
+ pj_pool_t *pool;
+ char *url;
+ int len = (c_url ? pj_ansi_strlen(c_url) : 0);
+
+ if (!len) return PJSIP_EINVALIDURI;
+
+ pool = pj_pool_create(&pjsua_var.cp.factory, "check%p", 1024, 0, NULL);
+ if (!pool) return PJ_ENOMEM;
+
+ url = (char*) pj_pool_alloc(pool, len+1);
+ pj_ansi_strcpy(url, c_url);
+
+ p = pjsip_parse_uri(pool, url, len, 0);
+
+ pj_pool_release(pool);
+ return p ? 0 : PJSIP_EINVALIDURI;
+}
+
+/*
+ * Verify that valid SIP url is given.
+ */
+PJ_DEF(pj_status_t) pjsua_verify_sip_url(const char *c_url)
+{
+ pjsip_uri *p;
+ pj_pool_t *pool;
+ char *url;
+ int len = (c_url ? pj_ansi_strlen(c_url) : 0);
+
+ if (!len) return PJSIP_EINVALIDURI;
+
+ pool = pj_pool_create(&pjsua_var.cp.factory, "check%p", 1024, 0, NULL);
+ if (!pool) return PJ_ENOMEM;
+
+ url = (char*) pj_pool_alloc(pool, len+1);
+ pj_ansi_strcpy(url, c_url);
+
+ p = pjsip_parse_uri(pool, url, len, 0);
+ if (!p || (pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0 &&
+ pj_stricmp2(pjsip_uri_get_scheme(p), "sips") != 0))
+ {
+ p = NULL;
+ }
+
+ pj_pool_release(pool);
+ return p ? 0 : PJSIP_EINVALIDURI;
+}
+
+/*
+ * Schedule a timer entry.
+ */
+#if PJ_TIMER_DEBUG
+PJ_DEF(pj_status_t) pjsua_schedule_timer_dbg( pj_timer_entry *entry,
+ const pj_time_val *delay,
+ const char *src_file,
+ int src_line)
+{
+ return pjsip_endpt_schedule_timer_dbg(pjsua_var.endpt, entry, delay,
+ src_file, src_line);
+}
+#else
+PJ_DEF(pj_status_t) pjsua_schedule_timer( pj_timer_entry *entry,
+ const pj_time_val *delay)
+{
+ return pjsip_endpt_schedule_timer(pjsua_var.endpt, entry, delay);
+}
+#endif
+
+/* Timer callback */
+static void timer_cb( pj_timer_heap_t *th,
+ pj_timer_entry *entry)
+{
+ pjsua_timer_list *tmr = (pjsua_timer_list *)entry->user_data;
+ void (*cb)(void *user_data) = tmr->cb;
+ void *user_data = tmr->user_data;
+
+ PJ_UNUSED_ARG(th);
+
+ pj_mutex_lock(pjsua_var.timer_mutex);
+ pj_list_push_back(&pjsua_var.timer_list, tmr);
+ pj_mutex_unlock(pjsua_var.timer_mutex);
+
+ if (cb)
+ (*cb)(user_data);
+}
+
+/*
+ * Schedule a timer callback.
+ */
+#if PJ_TIMER_DEBUG
+PJ_DEF(pj_status_t) pjsua_schedule_timer2_dbg( void (*cb)(void *user_data),
+ void *user_data,
+ unsigned msec_delay,
+ const char *src_file,
+ int src_line)
+#else
+PJ_DEF(pj_status_t) pjsua_schedule_timer2( void (*cb)(void *user_data),
+ void *user_data,
+ unsigned msec_delay)
+#endif
+{
+ pjsua_timer_list *tmr = NULL;
+ pj_status_t status;
+ pj_time_val delay;
+
+ pj_mutex_lock(pjsua_var.timer_mutex);
+
+ if (pj_list_empty(&pjsua_var.timer_list)) {
+ tmr = PJ_POOL_ALLOC_T(pjsua_var.pool, pjsua_timer_list);
+ } else {
+ tmr = pjsua_var.timer_list.next;
+ pj_list_erase(tmr);
+ }
+ pj_timer_entry_init(&tmr->entry, 0, tmr, timer_cb);
+ tmr->cb = cb;
+ tmr->user_data = user_data;
+ delay.sec = 0;
+ delay.msec = msec_delay;
+
+#if PJ_TIMER_DEBUG
+ status = pjsip_endpt_schedule_timer_dbg(pjsua_var.endpt, &tmr->entry,
+ &delay, src_file, src_line);
+#else
+ status = pjsip_endpt_schedule_timer(pjsua_var.endpt, &tmr->entry, &delay);
+#endif
+ if (status != PJ_SUCCESS) {
+ pj_list_push_back(&pjsua_var.timer_list, tmr);
+ }
+
+ pj_mutex_unlock(pjsua_var.timer_mutex);
+
+ return status;
+}
+
+/*
+ * Cancel the previously scheduled timer.
+ *
+ */
+PJ_DEF(void) pjsua_cancel_timer(pj_timer_entry *entry)
+{
+ pjsip_endpt_cancel_timer(pjsua_var.endpt, entry);
+}
+
+/**
+ * Normalize route URI (check for ";lr" and append one if it doesn't
+ * exist and pjsua_config.force_lr is set.
+ */
+pj_status_t normalize_route_uri(pj_pool_t *pool, pj_str_t *uri)
+{
+ pj_str_t tmp_uri;
+ pj_pool_t *tmp_pool;
+ pjsip_uri *uri_obj;
+ pjsip_sip_uri *sip_uri;
+
+ tmp_pool = pjsua_pool_create("tmplr%p", 512, 512);
+ if (!tmp_pool)
+ return PJ_ENOMEM;
+
+ pj_strdup_with_null(tmp_pool, &tmp_uri, uri);
+
+ uri_obj = pjsip_parse_uri(tmp_pool, tmp_uri.ptr, tmp_uri.slen, 0);
+ if (!uri_obj) {
+ PJ_LOG(1,(THIS_FILE, "Invalid route URI: %.*s",
+ (int)uri->slen, uri->ptr));
+ pj_pool_release(tmp_pool);
+ return PJSIP_EINVALIDURI;
+ }
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri_obj) &&
+ !PJSIP_URI_SCHEME_IS_SIP(uri_obj))
+ {
+ PJ_LOG(1,(THIS_FILE, "Route URI must be SIP URI: %.*s",
+ (int)uri->slen, uri->ptr));
+ pj_pool_release(tmp_pool);
+ return PJSIP_EINVALIDSCHEME;
+ }
+
+ sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri_obj);
+
+ /* Done if force_lr is disabled or if lr parameter is present */
+ if (!pjsua_var.ua_cfg.force_lr || sip_uri->lr_param) {
+ pj_pool_release(tmp_pool);
+ return PJ_SUCCESS;
+ }
+
+ /* Set lr param */
+ sip_uri->lr_param = 1;
+
+ /* Print the URI */
+ tmp_uri.ptr = (char*) pj_pool_alloc(tmp_pool, PJSIP_MAX_URL_SIZE);
+ tmp_uri.slen = pjsip_uri_print(PJSIP_URI_IN_ROUTING_HDR, uri_obj,
+ tmp_uri.ptr, PJSIP_MAX_URL_SIZE);
+ if (tmp_uri.slen < 1) {
+ PJ_LOG(1,(THIS_FILE, "Route URI is too long: %.*s",
+ (int)uri->slen, uri->ptr));
+ pj_pool_release(tmp_pool);
+ return PJSIP_EURITOOLONG;
+ }
+
+ /* Clone the URI */
+ pj_strdup_with_null(pool, uri, &tmp_uri);
+
+ pj_pool_release(tmp_pool);
+ return PJ_SUCCESS;
+}
+
+/*
+ * This is a utility function to dump the stack states to log, using
+ * verbosity level 3.
+ */
+PJ_DEF(void) pjsua_dump(pj_bool_t detail)
+{
+ unsigned old_decor;
+ unsigned i;
+
+ PJ_LOG(3,(THIS_FILE, "Start dumping application states:"));
+
+ old_decor = pj_log_get_decor();
+ pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
+
+ if (detail)
+ pj_dump_config();
+
+ pjsip_endpt_dump(pjsua_get_pjsip_endpt(), detail);
+
+ pjmedia_endpt_dump(pjsua_get_pjmedia_endpt());
+
+ PJ_LOG(3,(THIS_FILE, "Dumping media transports:"));
+ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+ pjsua_call *call = &pjsua_var.calls[i];
+ pjmedia_transport *tp[PJSUA_MAX_CALL_MEDIA*2];
+ unsigned tp_cnt = 0;
+ unsigned j;
+
+ /* Collect media transports in this call */
+ for (j = 0; j < call->med_cnt; ++j) {
+ if (call->media[j].tp != NULL)
+ tp[tp_cnt++] = call->media[j].tp;
+ }
+ for (j = 0; j < call->med_prov_cnt; ++j) {
+ pjmedia_transport *med_tp = call->media_prov[j].tp;
+ if (med_tp) {
+ unsigned k;
+ pj_bool_t used = PJ_FALSE;
+ for (k = 0; k < tp_cnt; ++k) {
+ if (med_tp == tp[k]) {
+ used = PJ_TRUE;
+ break;
+ }
+ }
+ if (!used)
+ tp[tp_cnt++] = med_tp;
+ }
+ }
+
+ /* Dump the media transports in this call */
+ for (j = 0; j < tp_cnt; ++j) {
+ pjmedia_transport_info tpinfo;
+ char addr_buf[80];
+
+ pjmedia_transport_info_init(&tpinfo);
+ pjmedia_transport_get_info(tp[j], &tpinfo);
+ PJ_LOG(3,(THIS_FILE, " %s: %s",
+ (pjsua_var.media_cfg.enable_ice ? "ICE" : "UDP"),
+ pj_sockaddr_print(&tpinfo.sock_info.rtp_addr_name,
+ addr_buf,
+ sizeof(addr_buf), 3)));
+ }
+ }
+
+ pjsip_tsx_layer_dump(detail);
+ pjsip_ua_dump(detail);
+
+// Dumping complete call states may require a 'large' buffer
+// (about 3KB per call session, including RTCP XR).
+#if 0
+ /* Dump all invite sessions: */
+ PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:"));
+
+ if (pjsua_call_get_count() == 0) {
+
+ PJ_LOG(3,(THIS_FILE, " - no sessions -"));
+
+ } else {
+ unsigned i;
+
+ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+ if (pjsua_call_is_active(i)) {
+ /* Tricky logging, since call states log string tends to be
+ * longer than PJ_LOG_MAX_SIZE.
+ */
+ char buf[1024 * 3];
+ unsigned call_dump_len;
+ unsigned part_len;
+ unsigned part_idx;
+ unsigned log_decor;
+
+ pjsua_call_dump(i, detail, buf, sizeof(buf), " ");
+ call_dump_len = strlen(buf);
+
+ log_decor = pj_log_get_decor();
+ pj_log_set_decor(log_decor & ~(PJ_LOG_HAS_NEWLINE |
+ PJ_LOG_HAS_CR));
+ PJ_LOG(3,(THIS_FILE, "\n"));
+ pj_log_set_decor(0);
+
+ part_idx = 0;
+ part_len = PJ_LOG_MAX_SIZE-80;
+ while (part_idx < call_dump_len) {
+ char p_orig, *p;
+
+ p = &buf[part_idx];
+ if (part_idx + part_len > call_dump_len)
+ part_len = call_dump_len - part_idx;
+ p_orig = p[part_len];
+ p[part_len] = '\0';
+ PJ_LOG(3,(THIS_FILE, "%s", p));
+ p[part_len] = p_orig;
+ part_idx += part_len;
+ }
+ pj_log_set_decor(log_decor);
+ }
+ }
+ }
+#endif
+
+ /* Dump presence status */
+ pjsua_pres_dump(detail);
+
+ pj_log_set_decor(old_decor);
+ PJ_LOG(3,(THIS_FILE, "Dump complete"));
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_dump.c b/pjsip/src/pjsua-lib/pjsua_dump.c
new file mode 100644
index 0000000..0b23a97
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_dump.c
@@ -0,0 +1,974 @@
+/* $Id: pjsua_dump.c 4085 2012-04-25 07:45:22Z nanang $ */
+/*
+ * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+const char *good_number(char *buf, pj_int32_t val)
+{
+ if (val < 1000) {
+ pj_ansi_sprintf(buf, "%d", val);
+ } else if (val < 1000000) {
+ pj_ansi_sprintf(buf, "%d.%dK",
+ val / 1000,
+ (val % 1000) / 100);
+ } else {
+ pj_ansi_sprintf(buf, "%d.%02dM",
+ val / 1000000,
+ (val % 1000000) / 10000);
+ }
+
+ return buf;
+}
+
+static unsigned dump_media_stat(const char *indent,
+ char *buf, unsigned maxlen,
+ const pjmedia_rtcp_stat *stat,
+ const char *rx_info, const char *tx_info)
+{
+ char last_update[64];
+ char packets[32], bytes[32], ipbytes[32], avg_bps[32], avg_ipbps[32];
+ pj_time_val media_duration, now;
+ char *p = buf, *end = buf+maxlen;
+ int len;
+
+ if (stat->rx.update_cnt == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, stat->rx.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ pj_gettimeofday(&media_duration);
+ PJ_TIME_VAL_SUB(media_duration, stat->start);
+ if (PJ_TIME_VAL_MSEC(media_duration) == 0)
+ media_duration.msec = 1;
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RX %s last update:%s\n"
+ "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
+ "%s pkt loss=%d (%3.1f%%), discrd=%d (%3.1f%%), dup=%d (%2.1f%%), reord=%d (%3.1f%%)\n"
+ "%s (msec) min avg max last dev\n"
+ "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+ "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+ "%s raw jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+#endif
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+ "%s IPDV : %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+#endif
+ "%s",
+ indent,
+ rx_info? rx_info : "",
+ last_update,
+
+ indent,
+ good_number(packets, stat->rx.pkt),
+ good_number(bytes, stat->rx.bytes),
+ good_number(ipbytes, stat->rx.bytes + stat->rx.pkt * 40),
+ good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->rx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+ good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->rx.bytes + stat->rx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+ indent,
+ stat->rx.loss,
+ (stat->rx.loss? stat->rx.loss * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+ stat->rx.discard,
+ (stat->rx.discard? stat->rx.discard * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+ stat->rx.dup,
+ (stat->rx.dup? stat->rx.dup * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+ stat->rx.reorder,
+ (stat->rx.reorder? stat->rx.reorder * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0),
+ indent, indent,
+ stat->rx.loss_period.min / 1000.0,
+ stat->rx.loss_period.mean / 1000.0,
+ stat->rx.loss_period.max / 1000.0,
+ stat->rx.loss_period.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->rx.loss_period) / 1000.0,
+ indent,
+ stat->rx.jitter.min / 1000.0,
+ stat->rx.jitter.mean / 1000.0,
+ stat->rx.jitter.max / 1000.0,
+ stat->rx.jitter.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->rx.jitter) / 1000.0,
+#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
+ indent,
+ stat->rx_raw_jitter.min / 1000.0,
+ stat->rx_raw_jitter.mean / 1000.0,
+ stat->rx_raw_jitter.max / 1000.0,
+ stat->rx_raw_jitter.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->rx_raw_jitter) / 1000.0,
+#endif
+#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
+ indent,
+ stat->rx_ipdv.min / 1000.0,
+ stat->rx_ipdv.mean / 1000.0,
+ stat->rx_ipdv.max / 1000.0,
+ stat->rx_ipdv.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->rx_ipdv) / 1000.0,
+#endif
+ ""
+ );
+
+ if (len < 1 || len > end-p) {
+ *p = '\0';
+ return (p-buf);
+ }
+ p += len;
+
+ if (stat->tx.update_cnt == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, stat->tx.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s TX %s last update:%s\n"
+ "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n"
+ "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
+ "%s (msec) min avg max last dev \n"
+ "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n"
+ "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
+ indent,
+ tx_info,
+ last_update,
+
+ indent,
+ good_number(packets, stat->tx.pkt),
+ good_number(bytes, stat->tx.bytes),
+ good_number(ipbytes, stat->tx.bytes + stat->tx.pkt * 40),
+ good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->tx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+ good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->tx.bytes + stat->tx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))),
+
+ indent,
+ stat->tx.loss,
+ (stat->tx.loss? stat->tx.loss * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
+ stat->tx.dup,
+ (stat->tx.dup? stat->tx.dup * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
+ stat->tx.reorder,
+ (stat->tx.reorder? stat->tx.reorder * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0),
+
+ indent, indent,
+ stat->tx.loss_period.min / 1000.0,
+ stat->tx.loss_period.mean / 1000.0,
+ stat->tx.loss_period.max / 1000.0,
+ stat->tx.loss_period.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->tx.loss_period) / 1000.0,
+ indent,
+ stat->tx.jitter.min / 1000.0,
+ stat->tx.jitter.mean / 1000.0,
+ stat->tx.jitter.max / 1000.0,
+ stat->tx.jitter.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->tx.jitter) / 1000.0
+ );
+
+ if (len < 1 || len > end-p) {
+ *p = '\0';
+ return (p-buf);
+ }
+ p += len;
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f\n",
+ indent,
+ stat->rtt.min / 1000.0,
+ stat->rtt.mean / 1000.0,
+ stat->rtt.max / 1000.0,
+ stat->rtt.last / 1000.0,
+ pj_math_stat_get_stddev(&stat->rtt) / 1000.0
+ );
+ if (len < 1 || len > end-p) {
+ *p = '\0';
+ return (p-buf);
+ }
+ p += len;
+
+ return (p-buf);
+}
+
+
+/* Dump media session */
+static void dump_media_session(const char *indent,
+ char *buf, unsigned maxlen,
+ pjsua_call *call)
+{
+ unsigned i;
+ char *p = buf, *end = buf+maxlen;
+ int len;
+
+ for (i=0; i<call->med_cnt; ++i) {
+ pjsua_call_media *call_med = &call->media[i];
+ pjmedia_rtcp_stat stat;
+ pj_bool_t has_stat;
+ pjmedia_transport_info tp_info;
+ char rem_addr_buf[80];
+ char codec_info[32] = {'0'};
+ char rx_info[80] = {'\0'};
+ char tx_info[80] = {'\0'};
+ const char *rem_addr;
+ const char *dir_str;
+ const char *media_type_str;
+
+ switch (call_med->type) {
+ case PJMEDIA_TYPE_AUDIO:
+ media_type_str = "audio";
+ break;
+ case PJMEDIA_TYPE_VIDEO:
+ media_type_str = "video";
+ break;
+ case PJMEDIA_TYPE_APPLICATION:
+ media_type_str = "application";
+ break;
+ default:
+ media_type_str = "unknown";
+ break;
+ }
+
+ /* Check if the stream is deactivated */
+ if (call_med->tp == NULL ||
+ (!call_med->strm.a.stream && !call_med->strm.v.stream))
+ {
+ len = pj_ansi_snprintf(p, end-p,
+ "%s #%d %s deactivated\n",
+ indent, i, media_type_str);
+ if (len < 1 || len > end-p) {
+ *p = '\0';
+ return;
+ }
+
+ p += len;
+ continue;
+ }
+
+ pjmedia_transport_info_init(&tp_info);
+ pjmedia_transport_get_info(call_med->tp, &tp_info);
+
+ // rem_addr will contain actual address of RTP originator, instead of
+ // remote RTP address specified by stream which is fetched from the SDP.
+ // Please note that we are assuming only one stream per call.
+ //rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr,
+ // rem_addr_buf, sizeof(rem_addr_buf), 3);
+ if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) {
+ rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf,
+ sizeof(rem_addr_buf), 3);
+ } else {
+ pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-");
+ rem_addr = rem_addr_buf;
+ }
+
+ if (call_med->dir == PJMEDIA_DIR_NONE) {
+ /* To handle when the stream that is currently being paused
+ * (http://trac.pjsip.org/repos/ticket/1079)
+ */
+ dir_str = "inactive";
+ } else if (call_med->dir == PJMEDIA_DIR_ENCODING)
+ dir_str = "sendonly";
+ else if (call_med->dir == PJMEDIA_DIR_DECODING)
+ dir_str = "recvonly";
+ else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING)
+ dir_str = "sendrecv";
+ else
+ dir_str = "inactive";
+
+ if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+ pjmedia_stream *stream = call_med->strm.a.stream;
+ pjmedia_stream_info info;
+
+ pjmedia_stream_get_stat(stream, &stat);
+ has_stat = PJ_TRUE;
+
+ pjmedia_stream_get_info(stream, &info);
+ pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz",
+ (int)info.fmt.encoding_name.slen,
+ info.fmt.encoding_name.ptr,
+ info.fmt.clock_rate / 1000);
+ pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,",
+ info.rx_pt);
+ pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,",
+ info.tx_pt,
+ info.param->setting.frm_per_pkt*
+ info.param->info.frm_ptime);
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ } else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
+ pjmedia_vid_stream *stream = call_med->strm.v.stream;
+ pjmedia_vid_stream_info info;
+
+ pjmedia_vid_stream_get_stat(stream, &stat);
+ has_stat = PJ_TRUE;
+
+ pjmedia_vid_stream_get_info(stream, &info);
+ pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s",
+ (int)info.codec_info.encoding_name.slen,
+ info.codec_info.encoding_name.ptr);
+ if (call_med->dir & PJMEDIA_DIR_DECODING) {
+ pjmedia_video_format_detail *vfd;
+ vfd = pjmedia_format_get_video_format_detail(
+ &info.codec_param->dec_fmt, PJ_TRUE);
+ pj_ansi_snprintf(rx_info, sizeof(rx_info),
+ "pt=%d, size=%dx%d, fps=%.2f,",
+ info.rx_pt,
+ vfd->size.w, vfd->size.h,
+ vfd->fps.num*1.0/vfd->fps.denum);
+ }
+ if (call_med->dir & PJMEDIA_DIR_ENCODING) {
+ pjmedia_video_format_detail *vfd;
+ vfd = pjmedia_format_get_video_format_detail(
+ &info.codec_param->enc_fmt, PJ_TRUE);
+ pj_ansi_snprintf(tx_info, sizeof(tx_info),
+ "pt=%d, size=%dx%d, fps=%.2f,",
+ info.tx_pt,
+ vfd->size.w, vfd->size.h,
+ vfd->fps.num*1.0/vfd->fps.denum);
+ }
+#endif /* PJMEDIA_HAS_VIDEO */
+
+ } else {
+ has_stat = PJ_FALSE;
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s #%d %s%s, %s, peer=%s\n",
+ indent,
+ call_med->idx,
+ media_type_str,
+ codec_info,
+ dir_str,
+ rem_addr);
+ if (len < 1 || len > end-p) {
+ *p = '\0';
+ return;
+ }
+ p += len;
+
+ /* Get and ICE SRTP status */
+ if (call_med->tp) {
+ pjmedia_transport_info tp_info;
+
+ pjmedia_transport_info_init(&tp_info);
+ pjmedia_transport_get_info(call_med->tp, &tp_info);
+ if (tp_info.specific_info_cnt > 0) {
+ unsigned j;
+ for (j = 0; j < tp_info.specific_info_cnt; ++j) {
+ if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
+ {
+ pjmedia_srtp_info *srtp_info =
+ (pjmedia_srtp_info*) tp_info.spc_info[j].buffer;
+
+ len = pj_ansi_snprintf(p, end-p,
+ " %s SRTP status: %s Crypto-suite: %s",
+ indent,
+ (srtp_info->active?"Active":"Not active"),
+ srtp_info->tx_policy.name.ptr);
+ if (len > 0 && len < end-p) {
+ p += len;
+ *p++ = '\n';
+ *p = '\0';
+ }
+ } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
+ const pjmedia_ice_transport_info *ii;
+ unsigned jj;
+
+ ii = (const pjmedia_ice_transport_info*)
+ tp_info.spc_info[j].buffer;
+
+ len = pj_ansi_snprintf(p, end-p,
+ " %s ICE role: %s, state: %s, comp_cnt: %u",
+ indent,
+ pj_ice_sess_role_name(ii->role),
+ pj_ice_strans_state_name(ii->sess_state),
+ ii->comp_cnt);
+ if (len > 0 && len < end-p) {
+ p += len;
+ *p++ = '\n';
+ *p = '\0';
+ }
+
+ for (jj=0; ii->sess_state==PJ_ICE_STRANS_STATE_RUNNING && jj<2; ++jj) {
+ const char *type1 = pj_ice_get_cand_type_name(ii->comp[jj].lcand_type);
+ const char *type2 = pj_ice_get_cand_type_name(ii->comp[jj].rcand_type);
+ char addr1[PJ_INET6_ADDRSTRLEN+10];
+ char addr2[PJ_INET6_ADDRSTRLEN+10];
+
+ if (pj_sockaddr_has_addr(&ii->comp[jj].lcand_addr))
+ pj_sockaddr_print(&ii->comp[jj].lcand_addr, addr1, sizeof(addr1), 3);
+ else
+ strcpy(addr1, "0.0.0.0:0");
+ if (pj_sockaddr_has_addr(&ii->comp[jj].rcand_addr))
+ pj_sockaddr_print(&ii->comp[jj].rcand_addr, addr2, sizeof(addr2), 3);
+ else
+ strcpy(addr2, "0.0.0.0:0");
+ len = pj_ansi_snprintf(p, end-p,
+ " %s [%d]: L:%s (%c) --> R:%s (%c)\n",
+ indent, jj,
+ addr1, type1[0],
+ addr2, type2[0]);
+ if (len > 0 && len < end-p) {
+ p += len;
+ *p = '\0';
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ if (has_stat) {
+ len = dump_media_stat(indent, p, end-p, &stat,
+ rx_info, tx_info);
+ p += len;
+ }
+
+#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
+# define SAMPLES_TO_USEC(usec, samples, clock_rate) \
+ do { \
+ if (samples <= 4294) \
+ usec = samples * 1000000 / clock_rate; \
+ else { \
+ usec = samples * 1000 / clock_rate; \
+ usec *= 1000; \
+ } \
+ } while(0)
+
+# define PRINT_VOIP_MTC_VAL(s, v) \
+ if (v == 127) \
+ sprintf(s, "(na)"); \
+ else \
+ sprintf(s, "%d", v)
+
+# define VALIDATE_PRINT_BUF() \
+ if (len < 1 || len > end-p) { *p = '\0'; return; } \
+ p += len; *p++ = '\n'; *p = '\0'
+
+
+ if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+ pjmedia_stream_info info;
+ char last_update[64];
+ char loss[16], dup[16];
+ char jitter[80];
+ char toh[80];
+ char plc[16], jba[16], jbr[16];
+ char signal_lvl[16], noise_lvl[16], rerl[16];
+ char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16];
+ pjmedia_rtcp_xr_stat xr_stat;
+ unsigned clock_rate;
+ pj_time_val now;
+
+ if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream,
+ &xr_stat) != PJ_SUCCESS)
+ {
+ continue;
+ }
+
+ if (pjmedia_stream_get_info(call_med->strm.a.stream, &info)
+ != PJ_SUCCESS)
+ {
+ continue;
+ }
+
+ clock_rate = info.fmt.clock_rate;
+ pj_gettimeofday(&now);
+
+ len = pj_ansi_snprintf(p, end-p, "\n%s Extended reports:", indent);
+ VALIDATE_PRINT_BUF();
+
+ /* Statistics Summary */
+ len = pj_ansi_snprintf(p, end-p, "%s Statistics Summary", indent);
+ VALIDATE_PRINT_BUF();
+
+ if (xr_stat.rx.stat_sum.l)
+ sprintf(loss, "%d", xr_stat.rx.stat_sum.lost);
+ else
+ sprintf(loss, "(na)");
+
+ if (xr_stat.rx.stat_sum.d)
+ sprintf(dup, "%d", xr_stat.rx.stat_sum.dup);
+ else
+ sprintf(dup, "(na)");
+
+ if (xr_stat.rx.stat_sum.j) {
+ unsigned jmin, jmax, jmean, jdev;
+
+ SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min,
+ clock_rate);
+ SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max,
+ clock_rate);
+ SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean,
+ clock_rate);
+ SAMPLES_TO_USEC(jdev,
+ pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter),
+ clock_rate);
+ sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
+ jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
+ } else
+ sprintf(jitter, "(report not available)");
+
+ if (xr_stat.rx.stat_sum.t) {
+ sprintf(toh, "%11d %11d %11d %11d",
+ xr_stat.rx.stat_sum.toh.min,
+ xr_stat.rx.stat_sum.toh.mean,
+ xr_stat.rx.stat_sum.toh.max,
+ pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
+ } else
+ sprintf(toh, "(report not available)");
+
+ if (xr_stat.rx.stat_sum.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RX last update: %s\n"
+ "%s begin seq=%d, end seq=%d\n"
+ "%s pkt loss=%s, dup=%s\n"
+ "%s (msec) min avg max dev\n"
+ "%s jitter : %s\n"
+ "%s toh : %s",
+ indent, last_update,
+ indent,
+ xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq,
+ indent, loss, dup,
+ indent,
+ indent, jitter,
+ indent, toh
+ );
+ VALIDATE_PRINT_BUF();
+
+ if (xr_stat.tx.stat_sum.l)
+ sprintf(loss, "%d", xr_stat.tx.stat_sum.lost);
+ else
+ sprintf(loss, "(na)");
+
+ if (xr_stat.tx.stat_sum.d)
+ sprintf(dup, "%d", xr_stat.tx.stat_sum.dup);
+ else
+ sprintf(dup, "(na)");
+
+ if (xr_stat.tx.stat_sum.j) {
+ unsigned jmin, jmax, jmean, jdev;
+
+ SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min,
+ clock_rate);
+ SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max,
+ clock_rate);
+ SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean,
+ clock_rate);
+ SAMPLES_TO_USEC(jdev,
+ pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter),
+ clock_rate);
+ sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f",
+ jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0);
+ } else
+ sprintf(jitter, "(report not available)");
+
+ if (xr_stat.tx.stat_sum.t) {
+ sprintf(toh, "%11d %11d %11d %11d",
+ xr_stat.tx.stat_sum.toh.min,
+ xr_stat.tx.stat_sum.toh.mean,
+ xr_stat.tx.stat_sum.toh.max,
+ pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh));
+ } else
+ sprintf(toh, "(report not available)");
+
+ if (xr_stat.tx.stat_sum.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s TX last update: %s\n"
+ "%s begin seq=%d, end seq=%d\n"
+ "%s pkt loss=%s, dup=%s\n"
+ "%s (msec) min avg max dev\n"
+ "%s jitter : %s\n"
+ "%s toh : %s",
+ indent, last_update,
+ indent,
+ xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq,
+ indent, loss, dup,
+ indent,
+ indent, jitter,
+ indent, toh
+ );
+ VALIDATE_PRINT_BUF();
+
+
+ /* VoIP Metrics */
+ len = pj_ansi_snprintf(p, end-p, "%s VoIP Metrics", indent);
+ VALIDATE_PRINT_BUF();
+
+ PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl);
+ PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl);
+ PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl);
+ PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor);
+ PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor);
+ PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq);
+ PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq);
+
+ switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) {
+ case PJMEDIA_RTCP_XR_PLC_DIS:
+ sprintf(plc, "DISABLED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_ENH:
+ sprintf(plc, "ENHANCED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_STD:
+ sprintf(plc, "STANDARD");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_UNK:
+ default:
+ sprintf(plc, "UNKNOWN");
+ break;
+ }
+
+ switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) {
+ case PJMEDIA_RTCP_XR_JB_FIXED:
+ sprintf(jba, "FIXED");
+ break;
+ case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
+ sprintf(jba, "ADAPTIVE");
+ break;
+ default:
+ sprintf(jba, "UNKNOWN");
+ break;
+ }
+
+ sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F);
+
+ if (xr_stat.rx.voip_mtc.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RX last update: %s\n"
+ "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
+ "%s burst : density=%d (%.2f%%), duration=%d%s\n"
+ "%s gap : density=%d (%.2f%%), duration=%d%s\n"
+ "%s delay : round trip=%d%s, end system=%d%s\n"
+ "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
+ "%s quality : R factor=%s, ext R factor=%s\n"
+ "%s MOS LQ=%s, MOS CQ=%s\n"
+ "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
+ "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s",
+ indent,
+ last_update,
+ /* packets */
+ indent,
+ xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256,
+ xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256,
+ /* burst */
+ indent,
+ xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256,
+ xr_stat.rx.voip_mtc.burst_dur, "ms",
+ /* gap */
+ indent,
+ xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256,
+ xr_stat.rx.voip_mtc.gap_dur, "ms",
+ /* delay */
+ indent,
+ xr_stat.rx.voip_mtc.rnd_trip_delay, "ms",
+ xr_stat.rx.voip_mtc.end_sys_delay, "ms",
+ /* level */
+ indent,
+ signal_lvl, "dB",
+ noise_lvl, "dB",
+ rerl, "",
+ /* quality */
+ indent,
+ r_factor, ext_r_factor,
+ indent,
+ mos_lq, mos_cq,
+ /* config */
+ indent,
+ plc, jba, jbr, xr_stat.rx.voip_mtc.gmin,
+ /* JB delay */
+ indent,
+ xr_stat.rx.voip_mtc.jb_nom, "ms",
+ xr_stat.rx.voip_mtc.jb_max, "ms",
+ xr_stat.rx.voip_mtc.jb_abs_max, "ms"
+ );
+ VALIDATE_PRINT_BUF();
+
+ PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl);
+ PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl);
+ PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl);
+ PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor);
+ PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor);
+ PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq);
+ PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq);
+
+ switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) {
+ case PJMEDIA_RTCP_XR_PLC_DIS:
+ sprintf(plc, "DISABLED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_ENH:
+ sprintf(plc, "ENHANCED");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_STD:
+ sprintf(plc, "STANDARD");
+ break;
+ case PJMEDIA_RTCP_XR_PLC_UNK:
+ default:
+ sprintf(plc, "unknown");
+ break;
+ }
+
+ switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) {
+ case PJMEDIA_RTCP_XR_JB_FIXED:
+ sprintf(jba, "FIXED");
+ break;
+ case PJMEDIA_RTCP_XR_JB_ADAPTIVE:
+ sprintf(jba, "ADAPTIVE");
+ break;
+ default:
+ sprintf(jba, "unknown");
+ break;
+ }
+
+ sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F);
+
+ if (xr_stat.tx.voip_mtc.update.sec == 0)
+ strcpy(last_update, "never");
+ else {
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update);
+ sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+ now.sec / 3600,
+ (now.sec % 3600) / 60,
+ now.sec % 60,
+ now.msec);
+ }
+
+ len = pj_ansi_snprintf(p, end-p,
+ "%s TX last update: %s\n"
+ "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n"
+ "%s burst : density=%d (%.2f%%), duration=%d%s\n"
+ "%s gap : density=%d (%.2f%%), duration=%d%s\n"
+ "%s delay : round trip=%d%s, end system=%d%s\n"
+ "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n"
+ "%s quality : R factor=%s, ext R factor=%s\n"
+ "%s MOS LQ=%s, MOS CQ=%s\n"
+ "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n"
+ "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s",
+ indent,
+ last_update,
+ /* pakcets */
+ indent,
+ xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256,
+ xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256,
+ /* burst */
+ indent,
+ xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256,
+ xr_stat.tx.voip_mtc.burst_dur, "ms",
+ /* gap */
+ indent,
+ xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256,
+ xr_stat.tx.voip_mtc.gap_dur, "ms",
+ /* delay */
+ indent,
+ xr_stat.tx.voip_mtc.rnd_trip_delay, "ms",
+ xr_stat.tx.voip_mtc.end_sys_delay, "ms",
+ /* level */
+ indent,
+ signal_lvl, "dB",
+ noise_lvl, "dB",
+ rerl, "",
+ /* quality */
+ indent,
+ r_factor, ext_r_factor,
+ indent,
+ mos_lq, mos_cq,
+ /* config */
+ indent,
+ plc, jba, jbr, xr_stat.tx.voip_mtc.gmin,
+ /* JB delay */
+ indent,
+ xr_stat.tx.voip_mtc.jb_nom, "ms",
+ xr_stat.tx.voip_mtc.jb_max, "ms",
+ xr_stat.tx.voip_mtc.jb_abs_max, "ms"
+ );
+ VALIDATE_PRINT_BUF();
+
+
+ /* RTT delay (by receiver side) */
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RTT (from recv) min avg max last dev",
+ indent);
+ VALIDATE_PRINT_BUF();
+ len = pj_ansi_snprintf(p, end-p,
+ "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f",
+ indent,
+ xr_stat.rtt.min / 1000.0,
+ xr_stat.rtt.mean / 1000.0,
+ xr_stat.rtt.max / 1000.0,
+ xr_stat.rtt.last / 1000.0,
+ pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0
+ );
+ VALIDATE_PRINT_BUF();
+ } /* if audio */;
+#endif
+
+ }
+}
+
+
+/* Print call info */
+void print_call(const char *title,
+ int call_id,
+ char *buf, pj_size_t size)
+{
+ int len;
+ pjsip_inv_session *inv = pjsua_var.calls[call_id].inv;
+ pjsip_dialog *dlg = inv->dlg;
+ char userinfo[128];
+
+ /* Dump invite sesion info. */
+
+ len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
+ if (len < 0)
+ pj_ansi_strcpy(userinfo, "<--uri too long-->");
+ else
+ userinfo[len] = '\0';
+
+ len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
+ title,
+ pjsip_inv_state_name(inv->state),
+ userinfo);
+ if (len < 1 || len >= (int)size) {
+ pj_ansi_strcpy(buf, "<--uri too long-->");
+ len = 18;
+ } else
+ buf[len] = '\0';
+}
+
+
+/*
+ * Dump call and media statistics to string.
+ */
+PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id,
+ pj_bool_t with_media,
+ char *buffer,
+ unsigned maxlen,
+ const char *indent)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg;
+ pj_time_val duration, res_delay, con_delay;
+ char tmp[128];
+ char *p, *end;
+ pj_status_t status;
+ int len;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ *buffer = '\0';
+ p = buffer;
+ end = buffer + maxlen;
+ len = 0;
+
+ print_call(indent, call_id, tmp, sizeof(tmp));
+
+ len = pj_ansi_strlen(tmp);
+ pj_ansi_strcpy(buffer, tmp);
+
+ p += len;
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Calculate call duration */
+ if (call->conn_time.sec != 0) {
+ pj_gettimeofday(&duration);
+ PJ_TIME_VAL_SUB(duration, call->conn_time);
+ con_delay = call->conn_time;
+ PJ_TIME_VAL_SUB(con_delay, call->start_time);
+ } else {
+ duration.sec = duration.msec = 0;
+ con_delay.sec = con_delay.msec = 0;
+ }
+
+ /* Calculate first response delay */
+ if (call->res_time.sec != 0) {
+ res_delay = call->res_time;
+ PJ_TIME_VAL_SUB(res_delay, call->start_time);
+ } else {
+ res_delay.sec = res_delay.msec = 0;
+ }
+
+ /* Print duration */
+ len = pj_ansi_snprintf(p, end-p,
+ "%s Call time: %02dh:%02dm:%02ds, "
+ "1st res in %d ms, conn in %dms",
+ indent,
+ (int)(duration.sec / 3600),
+ (int)((duration.sec % 3600)/60),
+ (int)(duration.sec % 60),
+ (int)PJ_TIME_VAL_MSEC(res_delay),
+ (int)PJ_TIME_VAL_MSEC(con_delay));
+
+ if (len > 0 && len < end-p) {
+ p += len;
+ *p++ = '\n';
+ *p = '\0';
+ }
+
+ /* Dump session statistics */
+ if (with_media)
+ dump_media_session(indent, p, end-p, call);
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_im.c b/pjsip/src/pjsua-lib/pjsua_im.c
new file mode 100644
index 0000000..fa11282
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_im.c
@@ -0,0 +1,745 @@
+/* $Id: pjsua_im.c 4173 2012-06-20 10:39:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+
+#define THIS_FILE "pjsua_im.h"
+
+
+/* Declare MESSAGE method */
+/* We put PJSIP_MESSAGE_METHOD as the enum here, so that when
+ * somebody add that method into pjsip_method_e in sip_msg.h we
+ * will get an error here.
+ */
+enum
+{
+ PJSIP_MESSAGE_METHOD = PJSIP_OTHER_METHOD
+};
+
+const pjsip_method pjsip_message_method =
+{
+ (pjsip_method_e) PJSIP_MESSAGE_METHOD,
+ { "MESSAGE", 7 }
+};
+
+
+/* Proto */
+static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata);
+
+
+/* The module instance. */
+static pjsip_module mod_pjsua_im =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-pjsua-im", 12 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &im_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+
+/* MIME constants. */
+static const pj_str_t STR_MIME_APP = { "application", 11 };
+static const pj_str_t STR_MIME_ISCOMPOSING = { "im-iscomposing+xml", 18 };
+static const pj_str_t STR_MIME_TEXT = { "text", 4 };
+static const pj_str_t STR_MIME_PLAIN = { "plain", 5 };
+
+
+/* Check if content type is acceptable */
+#if 0
+static pj_bool_t acceptable_message(const pjsip_media_type *mime)
+{
+ return (pj_stricmp(&mime->type, &STR_MIME_TEXT)==0 &&
+ pj_stricmp(&mime->subtype, &STR_MIME_PLAIN)==0)
+ ||
+ (pj_stricmp(&mime->type, &STR_MIME_APP)==0 &&
+ pj_stricmp(&mime->subtype, &STR_MIME_ISCOMPOSING)==0);
+}
+#endif
+
+/**
+ * Create Accept header for MESSAGE.
+ */
+pjsip_accept_hdr* pjsua_im_create_accept(pj_pool_t *pool)
+{
+ /* Create Accept header. */
+ pjsip_accept_hdr *accept;
+
+ accept = pjsip_accept_hdr_create(pool);
+ accept->values[0] = pj_str("text/plain");
+ accept->values[1] = pj_str("application/im-iscomposing+xml");
+ accept->count = 2;
+
+ return accept;
+}
+
+/**
+ * Private: check if we can accept the message.
+ */
+pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata,
+ pjsip_accept_hdr **p_accept_hdr)
+{
+ /* Some UA sends text/html, so this check will break */
+#if 0
+ pjsip_ctype_hdr *ctype;
+ pjsip_msg *msg;
+
+ msg = rdata->msg_info.msg;
+
+ /* Request MUST have message body, with Content-Type equal to
+ * "text/plain".
+ */
+ ctype = (pjsip_ctype_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_CONTENT_TYPE, NULL);
+ if (msg->body == NULL || ctype == NULL ||
+ !acceptable_message(&ctype->media))
+ {
+ /* Create Accept header. */
+ if (p_accept_hdr)
+ *p_accept_hdr = pjsua_im_create_accept(rdata->tp_info.pool);
+
+ return PJ_FALSE;
+ }
+#elif 0
+ pjsip_msg *msg;
+
+ msg = rdata->msg_info.msg;
+ if (msg->body == NULL) {
+ /* Create Accept header. */
+ if (p_accept_hdr)
+ *p_accept_hdr = pjsua_im_create_accept(rdata->tp_info.pool);
+
+ return PJ_FALSE;
+ }
+#else
+ /* Ticket #693: allow incoming MESSAGE without message body */
+ PJ_UNUSED_ARG(rdata);
+ PJ_UNUSED_ARG(p_accept_hdr);
+#endif
+
+ return PJ_TRUE;
+}
+
+/**
+ * Private: process pager message.
+ * This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing().
+ */
+void pjsua_im_process_pager(int call_id, const pj_str_t *from,
+ const pj_str_t *to, pjsip_rx_data *rdata)
+{
+ pjsip_contact_hdr *contact_hdr;
+ pj_str_t contact;
+ pjsip_msg_body *body = rdata->msg_info.msg->body;
+
+#if 0
+ /* Ticket #693: allow incoming MESSAGE without message body */
+ /* Body MUST have been checked before */
+ pj_assert(body != NULL);
+#endif
+
+
+ /* Build remote contact */
+ contact_hdr = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
+ NULL);
+ if (contact_hdr && contact_hdr->uri) {
+ contact.ptr = (char*) pj_pool_alloc(rdata->tp_info.pool,
+ PJSIP_MAX_URL_SIZE);
+ contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR,
+ contact_hdr->uri, contact.ptr,
+ PJSIP_MAX_URL_SIZE);
+ } else {
+ contact.slen = 0;
+ }
+
+ if (body && pj_stricmp(&body->content_type.type, &STR_MIME_APP)==0 &&
+ pj_stricmp(&body->content_type.subtype, &STR_MIME_ISCOMPOSING)==0)
+ {
+ /* Expecting typing indication */
+ pj_status_t status;
+ pj_bool_t is_typing;
+
+ status = pjsip_iscomposing_parse(rdata->tp_info.pool, (char*)body->data,
+ body->len, &is_typing, NULL, NULL,
+ NULL );
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Invalid MESSAGE body", status);
+ return;
+ }
+
+ if (pjsua_var.ua_cfg.cb.on_typing) {
+ (*pjsua_var.ua_cfg.cb.on_typing)(call_id, from, to, &contact,
+ is_typing);
+ }
+
+ if (pjsua_var.ua_cfg.cb.on_typing2) {
+ pjsua_acc_id acc_id;
+
+ if (call_id == PJSUA_INVALID_ID) {
+ acc_id = pjsua_acc_find_for_incoming(rdata);
+ } else {
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ acc_id = call->acc_id;
+ }
+
+
+ (*pjsua_var.ua_cfg.cb.on_typing2)(call_id, from, to, &contact,
+ is_typing, rdata, acc_id);
+ }
+
+ } else {
+ pj_str_t mime_type;
+ char buf[256];
+ pjsip_media_type *m;
+ pj_str_t text_body;
+
+ /* Save text body */
+ if (body) {
+ text_body.ptr = (char*)rdata->msg_info.msg->body->data;
+ text_body.slen = rdata->msg_info.msg->body->len;
+
+ /* Get mime type */
+ m = &rdata->msg_info.msg->body->content_type;
+ mime_type.ptr = buf;
+ mime_type.slen = pj_ansi_snprintf(buf, sizeof(buf),
+ "%.*s/%.*s",
+ (int)m->type.slen,
+ m->type.ptr,
+ (int)m->subtype.slen,
+ m->subtype.ptr);
+ if (mime_type.slen < 1)
+ mime_type.slen = 0;
+
+
+ } else {
+ text_body.ptr = mime_type.ptr = "";
+ text_body.slen = mime_type.slen = 0;
+ }
+
+ if (pjsua_var.ua_cfg.cb.on_pager) {
+ (*pjsua_var.ua_cfg.cb.on_pager)(call_id, from, to, &contact,
+ &mime_type, &text_body);
+ }
+
+ if (pjsua_var.ua_cfg.cb.on_pager2) {
+ pjsua_acc_id acc_id;
+
+ if (call_id == PJSUA_INVALID_ID) {
+ acc_id = pjsua_acc_find_for_incoming(rdata);
+ } else {
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ acc_id = call->acc_id;
+ }
+
+ (*pjsua_var.ua_cfg.cb.on_pager2)(call_id, from, to, &contact,
+ &mime_type, &text_body, rdata,
+ acc_id);
+ }
+ }
+}
+
+
+/*
+ * Handler to receive incoming MESSAGE
+ */
+static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata)
+{
+ pj_str_t from, to;
+ pjsip_accept_hdr *accept_hdr;
+ pjsip_msg *msg;
+ pj_status_t status;
+
+ msg = rdata->msg_info.msg;
+
+ /* Only want to handle MESSAGE requests. */
+ if (pjsip_method_cmp(&msg->line.req.method, &pjsip_message_method) != 0) {
+ return PJ_FALSE;
+ }
+
+
+ /* Should not have any transaction attached to rdata. */
+ PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata)==NULL, PJ_FALSE);
+
+ /* Should not have any dialog attached to rdata. */
+ PJ_ASSERT_RETURN(pjsip_rdata_get_dlg(rdata)==NULL, PJ_FALSE);
+
+ /* Check if we can accept the message. */
+ if (!pjsua_im_accept_pager(rdata, &accept_hdr)) {
+ pjsip_hdr hdr_list;
+
+ pj_list_init(&hdr_list);
+ pj_list_push_back(&hdr_list, accept_hdr);
+
+ pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
+ PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL,
+ &hdr_list, NULL);
+ return PJ_TRUE;
+ }
+
+ /* Respond with 200 first, so that remote doesn't retransmit in case
+ * the UI takes too long to process the message.
+ */
+ status = pjsip_endpt_respond( pjsua_var.endpt, NULL, rdata, 200, NULL,
+ NULL, NULL, NULL);
+
+ /* For the source URI, we use Contact header if present, since
+ * Contact header contains the port number information. If this is
+ * not available, then use From header.
+ */
+ from.ptr = (char*)pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_URL_SIZE);
+ from.slen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
+ rdata->msg_info.from->uri,
+ from.ptr, PJSIP_MAX_URL_SIZE);
+
+ if (from.slen < 1)
+ from = pj_str("<--URI is too long-->");
+
+ /* Build the To text. */
+ to.ptr = (char*) pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_URL_SIZE);
+ to.slen = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR,
+ rdata->msg_info.to->uri,
+ to.ptr, PJSIP_MAX_URL_SIZE);
+ if (to.slen < 1)
+ to = pj_str("<--URI is too long-->");
+
+ /* Process pager. */
+ pjsua_im_process_pager(-1, &from, &to, rdata);
+
+ /* Done. */
+ return PJ_TRUE;
+}
+
+
+/* Outgoing IM callback. */
+static void im_callback(void *token, pjsip_event *e)
+{
+ pjsua_im_data *im_data = (pjsua_im_data*) token;
+
+ if (e->type == PJSIP_EVENT_TSX_STATE) {
+
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+
+ /* Ignore provisional response, if any */
+ if (tsx->status_code < 200)
+ return;
+
+
+ /* Handle authentication challenges */
+ if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
+ (tsx->status_code == 401 || tsx->status_code == 407))
+ {
+ pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+ pjsip_auth_clt_sess auth;
+ pj_status_t status;
+
+ PJ_LOG(4,(THIS_FILE, "Resending IM with authentication"));
+
+ /* Create temporary authentication session */
+ pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0);
+
+ pjsip_auth_clt_set_credentials(&auth,
+ pjsua_var.acc[im_data->acc_id].cred_cnt,
+ pjsua_var.acc[im_data->acc_id].cred);
+
+ pjsip_auth_clt_set_prefs(&auth,
+ &pjsua_var.acc[im_data->acc_id].cfg.auth_pref);
+
+ status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx,
+ &tdata);
+ if (status == PJ_SUCCESS) {
+ pjsua_im_data *im_data2;
+
+ /* Must duplicate im_data */
+ im_data2 = pjsua_im_data_dup(tdata->pool, im_data);
+
+ /* Increment CSeq */
+ PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq++;
+
+ /* Re-send request */
+ status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
+ im_data2, &im_callback);
+ if (status == PJ_SUCCESS) {
+ /* Done */
+ return;
+ }
+ }
+ }
+
+ if (tsx->status_code/100 == 2) {
+ PJ_LOG(4,(THIS_FILE,
+ "Message \'%s\' delivered successfully",
+ im_data->body.ptr));
+ } else {
+ PJ_LOG(3,(THIS_FILE,
+ "Failed to deliver message \'%s\': %d/%.*s",
+ im_data->body.ptr,
+ tsx->status_code,
+ (int)tsx->status_text.slen,
+ tsx->status_text.ptr));
+ }
+
+ if (pjsua_var.ua_cfg.cb.on_pager_status) {
+ pjsua_var.ua_cfg.cb.on_pager_status(im_data->call_id,
+ &im_data->to,
+ &im_data->body,
+ im_data->user_data,
+ (pjsip_status_code)
+ tsx->status_code,
+ &tsx->status_text);
+ }
+
+ if (pjsua_var.ua_cfg.cb.on_pager_status2) {
+ pjsip_rx_data *rdata;
+
+ if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
+ rdata = e->body.tsx_state.src.rdata;
+ else
+ rdata = NULL;
+
+ pjsua_var.ua_cfg.cb.on_pager_status2(im_data->call_id,
+ &im_data->to,
+ &im_data->body,
+ im_data->user_data,
+ (pjsip_status_code)
+ tsx->status_code,
+ &tsx->status_text,
+ tsx->last_tx,
+ rdata, im_data->acc_id);
+ }
+ }
+}
+
+
+/* Outgoing typing indication callback.
+ * (used to reauthenticate request)
+ */
+static void typing_callback(void *token, pjsip_event *e)
+{
+ pjsua_im_data *im_data = (pjsua_im_data*) token;
+
+ if (e->type == PJSIP_EVENT_TSX_STATE) {
+
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+
+ /* Ignore provisional response, if any */
+ if (tsx->status_code < 200)
+ return;
+
+ /* Handle authentication challenges */
+ if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
+ (tsx->status_code == 401 || tsx->status_code == 407))
+ {
+ pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+ pjsip_auth_clt_sess auth;
+ pj_status_t status;
+
+ PJ_LOG(4,(THIS_FILE, "Resending IM with authentication"));
+
+ /* Create temporary authentication session */
+ pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0);
+
+ pjsip_auth_clt_set_credentials(&auth,
+ pjsua_var.acc[im_data->acc_id].cred_cnt,
+ pjsua_var.acc[im_data->acc_id].cred);
+
+ pjsip_auth_clt_set_prefs(&auth,
+ &pjsua_var.acc[im_data->acc_id].cfg.auth_pref);
+
+ status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx,
+ &tdata);
+ if (status == PJ_SUCCESS) {
+ pjsua_im_data *im_data2;
+
+ /* Must duplicate im_data */
+ im_data2 = pjsua_im_data_dup(tdata->pool, im_data);
+
+ /* Increment CSeq */
+ PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq++;
+
+ /* Re-send request */
+ status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
+ im_data2, &typing_callback);
+ if (status == PJ_SUCCESS) {
+ /* Done */
+ return;
+ }
+ }
+ }
+
+ }
+}
+
+
+/*
+ * Send instant messaging outside dialog, using the specified account for
+ * route set and authentication.
+ */
+PJ_DEF(pj_status_t) pjsua_im_send( pjsua_acc_id acc_id,
+ const pj_str_t *to,
+ const pj_str_t *mime_type,
+ const pj_str_t *content,
+ const pjsua_msg_data *msg_data,
+ void *user_data)
+{
+ pjsip_tx_data *tdata;
+ const pj_str_t mime_text_plain = pj_str("text/plain");
+ const pj_str_t STR_CONTACT = { "Contact", 7 };
+ pjsip_media_type media_type;
+ pjsua_im_data *im_data;
+ pjsua_acc *acc;
+ pj_str_t contact;
+ pj_status_t status;
+
+ /* To and message body must be specified. */
+ PJ_ASSERT_RETURN(to && content, PJ_EINVAL);
+
+ acc = &pjsua_var.acc[acc_id];
+
+ /* Create request. */
+ status = pjsip_endpt_create_request(pjsua_var.endpt,
+ &pjsip_message_method, to,
+ &acc->cfg.id,
+ to, NULL, NULL, -1, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create request", status);
+ return status;
+ }
+
+ /* If account is locked to specific transport, then set transport to
+ * the request.
+ */
+ if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
+ pjsip_tpselector tp_sel;
+
+ pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
+ pjsip_tx_data_set_transport(tdata, &tp_sel);
+ }
+
+ /* Add accept header. */
+ pjsip_msg_add_hdr( tdata->msg,
+ (pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
+
+ /* Create suitable Contact header unless a Contact header has been
+ * set in the account.
+ */
+ if (acc->contact.slen) {
+ contact = acc->contact;
+ } else {
+ status = pjsua_acc_create_uac_contact(tdata->pool, &contact, acc_id, to);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to generate Contact header", status);
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+ }
+
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_generic_string_hdr_create(tdata->pool,
+ &STR_CONTACT, &contact));
+
+ /* Create IM data to keep message details and give it back to
+ * application on the callback
+ */
+ im_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsua_im_data);
+ im_data->acc_id = acc_id;
+ im_data->call_id = PJSUA_INVALID_ID;
+ pj_strdup_with_null(tdata->pool, &im_data->to, to);
+ pj_strdup_with_null(tdata->pool, &im_data->body, content);
+ im_data->user_data = user_data;
+
+
+ /* Set default media type if none is specified */
+ if (mime_type == NULL) {
+ mime_type = &mime_text_plain;
+ }
+
+ /* Parse MIME type */
+ pjsua_parse_media_type(tdata->pool, mime_type, &media_type);
+
+ /* Add message body */
+ tdata->msg->body = pjsip_msg_body_create( tdata->pool, &media_type.type,
+ &media_type.subtype,
+ &im_data->body);
+ if (tdata->msg->body == NULL) {
+ pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM);
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_ENOMEM;
+ }
+
+ /* Add additional headers etc. */
+ pjsua_process_msg_data(tdata, msg_data);
+
+ /* Add route set */
+ pjsua_set_msg_route_set(tdata, &acc->route_set);
+
+ /* If via_addr is set, use this address for the Via header. */
+ if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
+ tdata->via_addr = acc->via_addr;
+ tdata->via_tp = acc->via_tp;
+ }
+
+ /* Send request (statefully) */
+ status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
+ im_data, &im_callback);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send request", status);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Send typing indication outside dialog.
+ */
+PJ_DEF(pj_status_t) pjsua_im_typing( pjsua_acc_id acc_id,
+ const pj_str_t *to,
+ pj_bool_t is_typing,
+ const pjsua_msg_data *msg_data)
+{
+ const pj_str_t STR_CONTACT = { "Contact", 7 };
+ pjsua_im_data *im_data;
+ pjsip_tx_data *tdata;
+ pjsua_acc *acc;
+ pj_str_t contact;
+ pj_status_t status;
+
+ acc = &pjsua_var.acc[acc_id];
+
+ /* Create request. */
+ status = pjsip_endpt_create_request( pjsua_var.endpt, &pjsip_message_method,
+ to, &acc->cfg.id,
+ to, NULL, NULL, -1, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create request", status);
+ return status;
+ }
+
+
+ /* If account is locked to specific transport, then set transport to
+ * the request.
+ */
+ if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
+ pjsip_tpselector tp_sel;
+
+ pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
+ pjsip_tx_data_set_transport(tdata, &tp_sel);
+ }
+
+ /* Add accept header. */
+ pjsip_msg_add_hdr( tdata->msg,
+ (pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
+
+
+ /* Create suitable Contact header unless a Contact header has been
+ * set in the account.
+ */
+ if (acc->contact.slen) {
+ contact = acc->contact;
+ } else {
+ status = pjsua_acc_create_uac_contact(tdata->pool, &contact, acc_id, to);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to generate Contact header", status);
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+ }
+
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_generic_string_hdr_create(tdata->pool,
+ &STR_CONTACT, &contact));
+
+
+ /* Create "application/im-iscomposing+xml" msg body. */
+ tdata->msg->body = pjsip_iscomposing_create_body( tdata->pool, is_typing,
+ NULL, NULL, -1);
+
+ /* Add additional headers etc. */
+ pjsua_process_msg_data(tdata, msg_data);
+
+ /* Add route set */
+ pjsua_set_msg_route_set(tdata, &acc->route_set);
+
+ /* If via_addr is set, use this address for the Via header. */
+ if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
+ tdata->via_addr = acc->via_addr;
+ tdata->via_tp = acc->via_tp;
+ }
+
+ /* Create data to reauthenticate */
+ im_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsua_im_data);
+ im_data->acc_id = acc_id;
+
+ /* Send request (statefully) */
+ status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
+ im_data, &typing_callback);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send request", status);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Init pjsua IM module.
+ */
+pj_status_t pjsua_im_init(void)
+{
+ const pj_str_t msg_tag = { "MESSAGE", 7 };
+ const pj_str_t STR_MIME_TEXT_PLAIN = { "text/plain", 10 };
+ const pj_str_t STR_MIME_APP_ISCOMPOSING =
+ { "application/im-iscomposing+xml", 30 };
+ pj_status_t status;
+
+ /* Register module */
+ status = pjsip_endpt_register_module(pjsua_var.endpt, &mod_pjsua_im);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Register support for MESSAGE method. */
+ pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ALLOW,
+ NULL, 1, &msg_tag);
+
+ /* Register support for "application/im-iscomposing+xml" content */
+ pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ACCEPT,
+ NULL, 1, &STR_MIME_APP_ISCOMPOSING);
+
+ /* Register support for "text/plain" content */
+ pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ACCEPT,
+ NULL, 1, &STR_MIME_TEXT_PLAIN);
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
new file mode 100644
index 0000000..8bcb0da
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -0,0 +1,2695 @@
+/* $Id: pjsua_media.c 4182 2012-06-27 07:12:23Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+
+#define THIS_FILE "pjsua_media.c"
+
+#define DEFAULT_RTP_PORT 4000
+
+#ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
+# define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 0
+#endif
+
+/* Next RTP port to be used */
+static pj_uint16_t next_rtp_port;
+
+static void pjsua_media_config_dup(pj_pool_t *pool,
+ pjsua_media_config *dst,
+ const pjsua_media_config *src)
+{
+ pj_memcpy(dst, src, sizeof(*src));
+ pj_strdup(pool, &dst->turn_server, &src->turn_server);
+ pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, &src->turn_auth_cred);
+}
+
+
+/**
+ * Init media subsystems.
+ */
+pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
+{
+ pj_status_t status;
+
+ pj_log_push_indent();
+
+ /* Specify which audio device settings are save-able */
+ pjsua_var.aud_svmask = 0xFFFFFFFF;
+ /* These are not-settable */
+ pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
+ PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER);
+ /* EC settings use different API */
+ pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EC |
+ PJMEDIA_AUD_DEV_CAP_EC_TAIL);
+
+ /* Copy configuration */
+ pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg);
+
+ /* Normalize configuration */
+ if (pjsua_var.media_cfg.snd_clock_rate == 0) {
+ pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate;
+ }
+
+ if (pjsua_var.media_cfg.has_ioqueue &&
+ pjsua_var.media_cfg.thread_cnt == 0)
+ {
+ pjsua_var.media_cfg.thread_cnt = 1;
+ }
+
+ if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
+ pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
+ }
+
+ /* Create media endpoint. */
+ status = pjmedia_endpt_create(&pjsua_var.cp.factory,
+ pjsua_var.media_cfg.has_ioqueue? NULL :
+ pjsip_endpt_get_ioqueue(pjsua_var.endpt),
+ pjsua_var.media_cfg.thread_cnt,
+ &pjsua_var.med_endpt);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Media stack initialization has returned error",
+ status);
+ goto on_error;
+ }
+
+ status = pjsua_aud_subsys_init();
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* Initialize SRTP library (ticket #788). */
+ status = pjmedia_srtp_init_lib(pjsua_var.med_endpt);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error initializing SRTP library",
+ status);
+ goto on_error;
+ }
+#endif
+
+ /* Video */
+#if PJMEDIA_HAS_VIDEO
+ status = pjsua_vid_subsys_init();
+ if (status != PJ_SUCCESS)
+ goto on_error;
+#endif
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ pj_log_pop_indent();
+ return status;
+}
+
+/*
+ * Start pjsua media subsystem.
+ */
+pj_status_t pjsua_media_subsys_start(void)
+{
+ pj_status_t status;
+
+ pj_log_push_indent();
+
+#if DISABLED_FOR_TICKET_1185
+ /* Create media for calls, if none is specified */
+ if (pjsua_var.calls[0].media[0].tp == NULL) {
+ pjsua_transport_config transport_cfg;
+
+ /* Create default transport config */
+ pjsua_transport_config_default(&transport_cfg);
+ transport_cfg.port = DEFAULT_RTP_PORT;
+
+ status = pjsua_media_transports_create(&transport_cfg);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ return status;
+ }
+ }
+#endif
+
+ /* Audio */
+ status = pjsua_aud_subsys_start();
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ return status;
+ }
+
+ /* Video */
+#if PJMEDIA_HAS_VIDEO
+ status = pjsua_vid_subsys_start();
+ if (status != PJ_SUCCESS) {
+ pjsua_aud_subsys_destroy();
+ pj_log_pop_indent();
+ return status;
+ }
+#endif
+
+ /* Perform NAT detection */
+ status = pjsua_detect_nat_type();
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed"));
+ }
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy pjsua media subsystem.
+ */
+pj_status_t pjsua_media_subsys_destroy(unsigned flags)
+{
+ unsigned i;
+
+ PJ_LOG(4,(THIS_FILE, "Shutting down media.."));
+ pj_log_push_indent();
+
+ if (pjsua_var.med_endpt) {
+ pjsua_aud_subsys_destroy();
+ }
+
+ /* Close media transports */
+ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+ /* TODO: check if we're not allowed to send to network in the
+ * "flags", and if so do not do TURN allocation...
+ */
+ PJ_UNUSED_ARG(flags);
+ pjsua_media_channel_deinit(i);
+ }
+
+ /* Destroy media endpoint. */
+ if (pjsua_var.med_endpt) {
+
+# if PJMEDIA_HAS_VIDEO
+ pjsua_vid_subsys_destroy();
+# endif
+
+ pjmedia_endpt_destroy(pjsua_var.med_endpt);
+ pjsua_var.med_endpt = NULL;
+
+ /* Deinitialize sound subsystem */
+ // Not necessary, as pjmedia_snd_deinit() should have been called
+ // in pjmedia_endpt_destroy().
+ //pjmedia_snd_deinit();
+ }
+
+ /* Reset RTP port */
+ next_rtp_port = 0;
+
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create RTP and RTCP socket pair, and possibly resolve their public
+ * address via STUN.
+ */
+static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
+ pjmedia_sock_info *skinfo)
+{
+ enum {
+ RTP_RETRY = 100
+ };
+ int i;
+ pj_sockaddr_in bound_addr;
+ pj_sockaddr_in mapped_addr[2];
+ pj_status_t status = PJ_SUCCESS;
+ char addr_buf[PJ_INET6_ADDRSTRLEN+2];
+ pj_sock_t sock[2];
+
+ /* Make sure STUN server resolution has completed */
+ status = resolve_stun_server(PJ_TRUE);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
+ return status;
+ }
+
+ if (next_rtp_port == 0)
+ next_rtp_port = (pj_uint16_t)cfg->port;
+
+ if (next_rtp_port == 0)
+ next_rtp_port = (pj_uint16_t)40000;
+
+ for (i=0; i<2; ++i)
+ sock[i] = PJ_INVALID_SOCKET;
+
+ bound_addr.sin_addr.s_addr = PJ_INADDR_ANY;
+ if (cfg->bound_addr.slen) {
+ status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to resolve transport bind address",
+ status);
+ return status;
+ }
+ }
+
+ /* Loop retry to bind RTP and RTCP sockets. */
+ for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) {
+
+ /* Create RTP socket. */
+ status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "socket() error", status);
+ return status;
+ }
+
+ /* Apply QoS to RTP socket, if specified */
+ status = pj_sock_apply_qos2(sock[0], cfg->qos_type,
+ &cfg->qos_params,
+ 2, THIS_FILE, "RTP socket");
+
+ /* Bind RTP socket */
+ status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr),
+ next_rtp_port);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock[0]);
+ sock[0] = PJ_INVALID_SOCKET;
+ continue;
+ }
+
+ /* Create RTCP socket. */
+ status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "socket() error", status);
+ pj_sock_close(sock[0]);
+ return status;
+ }
+
+ /* Apply QoS to RTCP socket, if specified */
+ status = pj_sock_apply_qos2(sock[1], cfg->qos_type,
+ &cfg->qos_params,
+ 2, THIS_FILE, "RTCP socket");
+
+ /* Bind RTCP socket */
+ status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr),
+ (pj_uint16_t)(next_rtp_port+1));
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock[0]);
+ sock[0] = PJ_INVALID_SOCKET;
+
+ pj_sock_close(sock[1]);
+ sock[1] = PJ_INVALID_SOCKET;
+ continue;
+ }
+
+ /*
+ * If we're configured to use STUN, then find out the mapped address,
+ * and make sure that the mapped RTCP port is adjacent with the RTP.
+ */
+ if (pjsua_var.stun_srv.addr.sa_family != 0) {
+ char ip_addr[32];
+ pj_str_t stun_srv;
+
+ pj_ansi_strcpy(ip_addr,
+ pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr));
+ stun_srv = pj_str(ip_addr);
+
+ status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
+ &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
+ &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port),
+ mapped_addr);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "STUN resolve error", status);
+ goto on_error;
+ }
+
+#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT
+ if (pj_ntohs(mapped_addr[1].sin_port) ==
+ pj_ntohs(mapped_addr[0].sin_port)+1)
+ {
+ /* Success! */
+ break;
+ }
+
+ pj_sock_close(sock[0]);
+ sock[0] = PJ_INVALID_SOCKET;
+
+ pj_sock_close(sock[1]);
+ sock[1] = PJ_INVALID_SOCKET;
+#else
+ if (pj_ntohs(mapped_addr[1].sin_port) !=
+ pj_ntohs(mapped_addr[0].sin_port)+1)
+ {
+ PJ_LOG(4,(THIS_FILE,
+ "Note: STUN mapped RTCP port %d is not adjacent"
+ " to RTP port %d",
+ pj_ntohs(mapped_addr[1].sin_port),
+ pj_ntohs(mapped_addr[0].sin_port)));
+ }
+ /* Success! */
+ break;
+#endif
+
+ } else if (cfg->public_addr.slen) {
+
+ status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr,
+ (pj_uint16_t)next_rtp_port);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr,
+ (pj_uint16_t)(next_rtp_port+1));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ break;
+
+ } else {
+
+ if (bound_addr.sin_addr.s_addr == 0) {
+ pj_sockaddr addr;
+
+ /* Get local IP address. */
+ status = pj_gethostip(pj_AF_INET(), &addr);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr;
+ }
+
+ for (i=0; i<2; ++i) {
+ pj_sockaddr_in_init(&mapped_addr[i], NULL, 0);
+ mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr;
+ }
+
+ mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port);
+ mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1));
+ break;
+ }
+ }
+
+ if (sock[0] == PJ_INVALID_SOCKET) {
+ PJ_LOG(1,(THIS_FILE,
+ "Unable to find appropriate RTP/RTCP ports combination"));
+ goto on_error;
+ }
+
+
+ skinfo->rtp_sock = sock[0];
+ pj_memcpy(&skinfo->rtp_addr_name,
+ &mapped_addr[0], sizeof(pj_sockaddr_in));
+
+ skinfo->rtcp_sock = sock[1];
+ pj_memcpy(&skinfo->rtcp_addr_name,
+ &mapped_addr[1], sizeof(pj_sockaddr_in));
+
+ PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s",
+ pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf,
+ sizeof(addr_buf), 3)));
+ PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s",
+ pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf,
+ sizeof(addr_buf), 3)));
+
+ next_rtp_port += 2;
+ return PJ_SUCCESS;
+
+on_error:
+ for (i=0; i<2; ++i) {
+ if (sock[i] != PJ_INVALID_SOCKET)
+ pj_sock_close(sock[i]);
+ }
+ return status;
+}
+
+/* Create normal UDP media transports */
+static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg,
+ pjsua_call_media *call_med)
+{
+ pjmedia_sock_info skinfo;
+ pj_status_t status;
+
+ status = create_rtp_rtcp_sock(cfg, &skinfo);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
+ status);
+ goto on_error;
+ }
+
+ status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
+ &skinfo, 0, &call_med->tp);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create media transport",
+ status);
+ goto on_error;
+ }
+
+ pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
+ pjsua_var.media_cfg.tx_drop_pct);
+
+ pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
+ pjsua_var.media_cfg.rx_drop_pct);
+
+ call_med->tp_ready = PJ_SUCCESS;
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (call_med->tp)
+ pjmedia_transport_close(call_med->tp);
+
+ return status;
+}
+
+#if DISABLED_FOR_TICKET_1185
+/* Create normal UDP media transports */
+static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg)
+{
+ unsigned i;
+ pj_status_t status;
+
+ for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
+ pjsua_call *call = &pjsua_var.calls[i];
+ unsigned strm_idx;
+
+ for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+ pjsua_call_media *call_med = &call->media[strm_idx];
+
+ status = create_udp_media_transport(cfg, &call_med->tp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+ }
+
+ return PJ_SUCCESS;
+
+on_error:
+ for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
+ pjsua_call *call = &pjsua_var.calls[i];
+ unsigned strm_idx;
+
+ for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+ pjsua_call_media *call_med = &call->media[strm_idx];
+
+ if (call_med->tp) {
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = NULL;
+ }
+ }
+ }
+ return status;
+}
+#endif
+
+static void med_tp_timer_cb(void *user_data)
+{
+ pjsua_call_media *call_med = (pjsua_call_media*)user_data;
+ pjsua_call *call = NULL;
+ pjsip_dialog *dlg = NULL;
+
+ acquire_call("med_tp_timer_cb", call_med->call->index, &call, &dlg);
+
+ call_med->tp_ready = call_med->tp_result;
+ if (call_med->med_create_cb)
+ (*call_med->med_create_cb)(call_med, call_med->tp_ready,
+ call_med->call->secure_level, NULL);
+
+ if (dlg)
+ pjsip_dlg_dec_lock(dlg);
+}
+
+/* This callback is called when ICE negotiation completes */
+static void on_ice_complete(pjmedia_transport *tp,
+ pj_ice_strans_op op,
+ pj_status_t result)
+{
+ pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data;
+
+ if (!call_med)
+ return;
+
+ switch (op) {
+ case PJ_ICE_STRANS_OP_INIT:
+ call_med->tp_result = result;
+ pjsua_schedule_timer2(&med_tp_timer_cb, call_med, 1);
+ break;
+ case PJ_ICE_STRANS_OP_NEGOTIATION:
+ if (result != PJ_SUCCESS) {
+ call_med->state = PJSUA_CALL_MEDIA_ERROR;
+ call_med->dir = PJMEDIA_DIR_NONE;
+
+ if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) {
+ pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index);
+ }
+ } else if (call_med->call) {
+ /* Send UPDATE if default transport address is different than
+ * what was advertised (ticket #881)
+ */
+ pjmedia_transport_info tpinfo;
+ pjmedia_ice_transport_info *ii = NULL;
+ unsigned i;
+
+ pjmedia_transport_info_init(&tpinfo);
+ pjmedia_transport_get_info(tp, &tpinfo);
+ for (i=0; i<tpinfo.specific_info_cnt; ++i) {
+ if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) {
+ ii = (pjmedia_ice_transport_info*)
+ tpinfo.spc_info[i].buffer;
+ break;
+ }
+ }
+
+ if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING &&
+ pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name,
+ &call_med->rtp_addr))
+ {
+ pj_bool_t use_update;
+ const pj_str_t STR_UPDATE = { "UPDATE", 6 };
+ pjsip_dialog_cap_status support_update;
+ pjsip_dialog *dlg;
+
+ dlg = call_med->call->inv->dlg;
+ support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW,
+ NULL, &STR_UPDATE);
+ use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED);
+
+ PJ_LOG(4,(THIS_FILE,
+ "ICE default transport address has changed for "
+ "call %d, sending %s",
+ call_med->call->index,
+ (use_update ? "UPDATE" : "re-INVITE")));
+
+ if (use_update)
+ pjsua_call_update(call_med->call->index, 0, NULL);
+ else
+ pjsua_call_reinvite(call_med->call->index, 0, NULL);
+ }
+ }
+ break;
+ case PJ_ICE_STRANS_OP_KEEP_ALIVE:
+ if (result != PJ_SUCCESS) {
+ PJ_PERROR(4,(THIS_FILE, result,
+ "ICE keep alive failure for transport %d:%d",
+ call_med->call->index, call_med->idx));
+ }
+ if (pjsua_var.ua_cfg.cb.on_call_media_transport_state) {
+ pjsua_med_tp_state_info info;
+
+ pj_bzero(&info, sizeof(info));
+ info.med_idx = call_med->idx;
+ info.state = call_med->tp_st;
+ info.status = result;
+ info.ext_info = &op;
+ (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)(
+ call_med->call->index, &info);
+ }
+ if (pjsua_var.ua_cfg.cb.on_ice_transport_error) {
+ pjsua_call_id id = call_med->call->index;
+ (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result,
+ NULL);
+ }
+ break;
+ }
+}
+
+
+/* Parse "HOST:PORT" format */
+static pj_status_t parse_host_port(const pj_str_t *host_port,
+ pj_str_t *host, pj_uint16_t *port)
+{
+ pj_str_t str_port;
+
+ str_port.ptr = pj_strchr(host_port, ':');
+ if (str_port.ptr != NULL) {
+ int iport;
+
+ host->ptr = host_port->ptr;
+ host->slen = (str_port.ptr - host->ptr);
+ str_port.ptr++;
+ str_port.slen = host_port->slen - host->slen - 1;
+ iport = (int)pj_strtoul(&str_port);
+ if (iport < 1 || iport > 65535)
+ return PJ_EINVAL;
+ *port = (pj_uint16_t)iport;
+ } else {
+ *host = *host_port;
+ *port = 0;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Create ICE media transports (when ice is enabled) */
+static pj_status_t create_ice_media_transport(
+ const pjsua_transport_config *cfg,
+ pjsua_call_media *call_med,
+ pj_bool_t async)
+{
+ char stunip[PJ_INET6_ADDRSTRLEN];
+ pj_ice_strans_cfg ice_cfg;
+ pjmedia_ice_cb ice_cb;
+ char name[32];
+ unsigned comp_cnt;
+ pj_status_t status;
+
+ /* Make sure STUN server resolution has completed */
+ status = resolve_stun_server(PJ_TRUE);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error resolving STUN server", status);
+ return status;
+ }
+
+ /* Create ICE stream transport configuration */
+ pj_ice_strans_cfg_default(&ice_cfg);
+ pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0,
+ pjsip_endpt_get_ioqueue(pjsua_var.endpt),
+ pjsip_endpt_get_timer_heap(pjsua_var.endpt));
+
+ ice_cfg.af = pj_AF_INET();
+ ice_cfg.resolver = pjsua_var.resolver;
+
+ ice_cfg.opt = pjsua_var.media_cfg.ice_opt;
+
+ /* Configure STUN settings */
+ if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) {
+ pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0);
+ ice_cfg.stun.server = pj_str(stunip);
+ ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv);
+ }
+ if (pjsua_var.media_cfg.ice_max_host_cands >= 0)
+ ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands;
+
+ /* Copy QoS setting to STUN setting */
+ ice_cfg.stun.cfg.qos_type = cfg->qos_type;
+ pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params,
+ sizeof(cfg->qos_params));
+
+ /* Configure TURN settings */
+ if (pjsua_var.media_cfg.enable_turn) {
+ status = parse_host_port(&pjsua_var.media_cfg.turn_server,
+ &ice_cfg.turn.server,
+ &ice_cfg.turn.port);
+ if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) {
+ PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting"));
+ return PJ_EINVAL;
+ }
+ if (ice_cfg.turn.port == 0)
+ ice_cfg.turn.port = 3479;
+ ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type;
+ pj_memcpy(&ice_cfg.turn.auth_cred,
+ &pjsua_var.media_cfg.turn_auth_cred,
+ sizeof(ice_cfg.turn.auth_cred));
+
+ /* Copy QoS setting to TURN setting */
+ ice_cfg.turn.cfg.qos_type = cfg->qos_type;
+ pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params,
+ sizeof(cfg->qos_params));
+ }
+
+ pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb));
+ ice_cb.on_ice_complete = &on_ice_complete;
+ pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx);
+ call_med->tp_ready = PJ_EPENDING;
+
+ comp_cnt = 1;
+ if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp)
+ ++comp_cnt;
+
+ status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt,
+ &ice_cfg, &ice_cb, 0, call_med,
+ &call_med->tp);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create ICE media transport",
+ status);
+ goto on_error;
+ }
+
+ /* Wait until transport is initialized, or time out */
+ if (!async) {
+ pj_bool_t has_pjsua_lock = PJSUA_LOCK_IS_LOCKED();
+ if (has_pjsua_lock)
+ PJSUA_UNLOCK();
+ while (call_med->tp_ready == PJ_EPENDING) {
+ pjsua_handle_events(100);
+ }
+ if (has_pjsua_lock)
+ PJSUA_LOCK();
+ }
+
+ if (async && call_med->tp_ready == PJ_EPENDING) {
+ return PJ_EPENDING;
+ } else if (call_med->tp_ready != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error initializing ICE media transport",
+ call_med->tp_ready);
+ status = call_med->tp_ready;
+ goto on_error;
+ }
+
+ pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
+ pjsua_var.media_cfg.tx_drop_pct);
+
+ pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
+ pjsua_var.media_cfg.rx_drop_pct);
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (call_med->tp != NULL) {
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = NULL;
+ }
+
+ return status;
+}
+
+#if DISABLED_FOR_TICKET_1185
+/* Create ICE media transports (when ice is enabled) */
+static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg)
+{
+ unsigned i;
+ pj_status_t status;
+
+ for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
+ pjsua_call *call = &pjsua_var.calls[i];
+ unsigned strm_idx;
+
+ for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+ pjsua_call_media *call_med = &call->media[strm_idx];
+
+ status = create_ice_media_transport(cfg, call_med);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+ }
+
+ return PJ_SUCCESS;
+
+on_error:
+ for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) {
+ pjsua_call *call = &pjsua_var.calls[i];
+ unsigned strm_idx;
+
+ for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+ pjsua_call_media *call_med = &call->media[strm_idx];
+
+ if (call_med->tp) {
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = NULL;
+ }
+ }
+ }
+ return status;
+}
+#endif
+
+#if DISABLED_FOR_TICKET_1185
+/*
+ * Create media transports for all the calls. This function creates
+ * one UDP media transport for each call.
+ */
+PJ_DEF(pj_status_t) pjsua_media_transports_create(
+ const pjsua_transport_config *app_cfg)
+{
+ pjsua_transport_config cfg;
+ unsigned i;
+ pj_status_t status;
+
+
+ /* Make sure pjsua_init() has been called */
+ PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
+
+ PJSUA_LOCK();
+
+ /* Delete existing media transports */
+ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+ pjsua_call *call = &pjsua_var.calls[i];
+ unsigned strm_idx;
+
+ for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+ pjsua_call_media *call_med = &call->media[strm_idx];
+
+ if (call_med->tp && call_med->tp_auto_del) {
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = NULL;
+ call_med->tp_orig = NULL;
+ }
+ }
+ }
+
+ /* Copy config */
+ pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg);
+
+ /* Create the transports */
+ if (pjsua_var.media_cfg.enable_ice) {
+ status = create_ice_media_transports(&cfg);
+ } else {
+ status = create_udp_media_transports(&cfg);
+ }
+
+ /* Set media transport auto_delete to True */
+ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+ pjsua_call *call = &pjsua_var.calls[i];
+ unsigned strm_idx;
+
+ for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+ pjsua_call_media *call_med = &call->media[strm_idx];
+
+ call_med->tp_auto_del = PJ_TRUE;
+ }
+ }
+
+ PJSUA_UNLOCK();
+
+ return status;
+}
+
+/*
+ * Attach application's created media transports.
+ */
+PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[],
+ unsigned count,
+ pj_bool_t auto_delete)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL);
+
+ /* Assign the media transports */
+ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+ pjsua_call *call = &pjsua_var.calls[i];
+ unsigned strm_idx;
+
+ for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) {
+ pjsua_call_media *call_med = &call->media[strm_idx];
+
+ if (call_med->tp && call_med->tp_auto_del) {
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = NULL;
+ call_med->tp_orig = NULL;
+ }
+ }
+
+ PJ_TODO(remove_pjsua_media_transports_attach);
+
+ call->media[0].tp = tp[i].transport;
+ call->media[0].tp_auto_del = auto_delete;
+ }
+
+ return PJ_SUCCESS;
+}
+#endif
+
+/* Go through the list of media in the SDP, find acceptable media, and
+ * sort them based on the "quality" of the media, and store the indexes
+ * in the specified array. Media with the best quality will be listed
+ * first in the array. The quality factors considered currently is
+ * encryption.
+ */
+static void sort_media(const pjmedia_sdp_session *sdp,
+ const pj_str_t *type,
+ pjmedia_srtp_use use_srtp,
+ pj_uint8_t midx[],
+ unsigned *p_count,
+ unsigned *p_total_count)
+{
+ unsigned i;
+ unsigned count = 0;
+ int score[PJSUA_MAX_CALL_MEDIA];
+
+ pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA);
+ pj_assert(*p_total_count >= PJSUA_MAX_CALL_MEDIA);
+
+ *p_count = 0;
+ *p_total_count = 0;
+ for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i)
+ score[i] = 1;
+
+ /* Score each media */
+ for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) {
+ const pjmedia_sdp_media *m = sdp->media[i];
+ const pjmedia_sdp_conn *c;
+
+ /* Skip different media */
+ if (pj_stricmp(&m->desc.media, type) != 0) {
+ score[count++] = -22000;
+ continue;
+ }
+
+ c = m->conn? m->conn : sdp->conn;
+
+ /* Supported transports */
+ if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) {
+ switch (use_srtp) {
+ case PJMEDIA_SRTP_MANDATORY:
+ case PJMEDIA_SRTP_OPTIONAL:
+ ++score[i];
+ break;
+ case PJMEDIA_SRTP_DISABLED:
+ //--score[i];
+ score[i] -= 5;
+ break;
+ }
+ } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) {
+ switch (use_srtp) {
+ case PJMEDIA_SRTP_MANDATORY:
+ //--score[i];
+ score[i] -= 5;
+ break;
+ case PJMEDIA_SRTP_OPTIONAL:
+ /* No change in score */
+ break;
+ case PJMEDIA_SRTP_DISABLED:
+ ++score[i];
+ break;
+ }
+ } else {
+ score[i] -= 10;
+ }
+
+ /* Is media disabled? */
+ if (m->desc.port == 0)
+ score[i] -= 10;
+
+ /* Is media inactive? */
+ if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) ||
+ pj_strcmp2(&c->addr, "0.0.0.0") == 0)
+ {
+ //score[i] -= 10;
+ score[i] -= 1;
+ }
+
+ ++count;
+ }
+
+ /* Created sorted list based on quality */
+ for (i=0; i<count; ++i) {
+ unsigned j;
+ int best = 0;
+
+ for (j=1; j<count; ++j) {
+ if (score[j] > score[best])
+ best = j;
+ }
+ /* Don't put media with negative score, that media is unacceptable
+ * for us.
+ */
+ midx[i] = (pj_uint8_t)best;
+ if (score[best] >= 0)
+ (*p_count)++;
+ if (score[best] > -22000)
+ (*p_total_count)++;
+
+ score[best] = -22000;
+
+ }
+}
+
+/* Callback to receive media events */
+pj_status_t call_media_on_event(pjmedia_event *event,
+ void *user_data)
+{
+ pjsua_call_media *call_med = (pjsua_call_media*)user_data;
+ pjsua_call *call = call_med->call;
+ pj_status_t status = PJ_SUCCESS;
+
+ switch(event->type) {
+ case PJMEDIA_EVENT_KEYFRAME_MISSING:
+ if (call->opt.req_keyframe_method & PJSUA_VID_REQ_KEYFRAME_SIP_INFO)
+ {
+ pj_timestamp now;
+
+ pj_get_timestamp(&now);
+ if (pj_elapsed_msec(&call_med->last_req_keyframe, &now) >=
+ PJSUA_VID_REQ_KEYFRAME_INTERVAL)
+ {
+ pjsua_msg_data msg_data;
+ const pj_str_t SIP_INFO = {"INFO", 4};
+ const char *BODY_TYPE = "application/media_control+xml";
+ const char *BODY =
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+ "<media_control><vc_primitive><to_encoder>"
+ "<picture_fast_update/>"
+ "</to_encoder></vc_primitive></media_control>";
+
+ PJ_LOG(4,(THIS_FILE,
+ "Sending video keyframe request via SIP INFO"));
+
+ pjsua_msg_data_init(&msg_data);
+ pj_cstr(&msg_data.content_type, BODY_TYPE);
+ pj_cstr(&msg_data.msg_body, BODY);
+ status = pjsua_call_send_request(call->index, &SIP_INFO,
+ &msg_data);
+ if (status != PJ_SUCCESS) {
+ pj_perror(3, THIS_FILE, status,
+ "Failed requesting keyframe via SIP INFO");
+ } else {
+ call_med->last_req_keyframe = now;
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (pjsua_var.ua_cfg.cb.on_call_media_event && call) {
+ (*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index,
+ call_med->idx, event);
+ }
+
+ return status;
+}
+
+/* Set media transport state and notify the application via the callback. */
+void pjsua_set_media_tp_state(pjsua_call_media *call_med,
+ pjsua_med_tp_st tp_st)
+{
+ if (pjsua_var.ua_cfg.cb.on_call_media_transport_state &&
+ call_med->tp_st != tp_st)
+ {
+ pjsua_med_tp_state_info info;
+
+ pj_bzero(&info, sizeof(info));
+ info.med_idx = call_med->idx;
+ info.state = tp_st;
+ info.status = call_med->tp_ready;
+ (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)(
+ call_med->call->index, &info);
+ }
+
+ call_med->tp_st = tp_st;
+}
+
+/* Callback to resume pjsua_call_media_init() after media transport
+ * creation is completed.
+ */
+static pj_status_t call_media_init_cb(pjsua_call_media *call_med,
+ pj_status_t status,
+ int security_level,
+ int *sip_err_code)
+{
+ pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
+ pjmedia_transport_info tpinfo;
+ int err_code = 0;
+
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING,
+ pjsua_var.media_cfg.tx_drop_pct);
+
+ pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING,
+ pjsua_var.media_cfg.rx_drop_pct);
+
+ if (call_med->tp_st == PJSUA_MED_TP_CREATING)
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
+
+ if (!call_med->tp_orig &&
+ pjsua_var.ua_cfg.cb.on_create_media_transport)
+ {
+ call_med->use_custom_med_tp = PJ_TRUE;
+ } else
+ call_med->use_custom_med_tp = PJ_FALSE;
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* This function may be called when SRTP transport already exists
+ * (e.g: in re-invite, update), don't need to destroy/re-create.
+ */
+ if (!call_med->tp_orig) {
+ pjmedia_srtp_setting srtp_opt;
+ pjmedia_transport *srtp = NULL;
+
+ /* Check if SRTP requires secure signaling */
+ if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) {
+ if (security_level < acc->cfg.srtp_secure_signaling) {
+ err_code = PJSIP_SC_NOT_ACCEPTABLE;
+ status = PJSIP_ESESSIONINSECURE;
+ goto on_return;
+ }
+ }
+
+ /* Always create SRTP adapter */
+ pjmedia_srtp_setting_default(&srtp_opt);
+ srtp_opt.close_member_tp = PJ_TRUE;
+
+ /* If media session has been ever established, let's use remote's
+ * preference in SRTP usage policy, especially when it is stricter.
+ */
+ if (call_med->rem_srtp_use > acc->cfg.use_srtp)
+ srtp_opt.use = call_med->rem_srtp_use;
+ else
+ srtp_opt.use = acc->cfg.use_srtp;
+
+ status = pjmedia_transport_srtp_create(pjsua_var.med_endpt,
+ call_med->tp,
+ &srtp_opt, &srtp);
+ if (status != PJ_SUCCESS) {
+ err_code = PJSIP_SC_INTERNAL_SERVER_ERROR;
+ goto on_return;
+ }
+
+ /* Set SRTP as current media transport */
+ call_med->tp_orig = call_med->tp;
+ call_med->tp = srtp;
+ }
+#else
+ call_med->tp_orig = call_med->tp;
+ PJ_UNUSED_ARG(security_level);
+#endif
+
+
+ pjmedia_transport_info_init(&tpinfo);
+ pjmedia_transport_get_info(call_med->tp, &tpinfo);
+
+ pj_sockaddr_cp(&call_med->rtp_addr, &tpinfo.sock_info.rtp_addr_name);
+
+
+on_return:
+ if (status != PJ_SUCCESS && call_med->tp) {
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL);
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = NULL;
+ }
+
+ if (sip_err_code)
+ *sip_err_code = err_code;
+
+ if (call_med->med_init_cb) {
+ pjsua_med_tp_state_info info;
+
+ pj_bzero(&info, sizeof(info));
+ info.status = status;
+ info.state = call_med->tp_st;
+ info.med_idx = call_med->idx;
+ info.sip_err_code = err_code;
+ (*call_med->med_init_cb)(call_med->call->index, &info);
+ }
+
+ return status;
+}
+
+/* Initialize the media line */
+pj_status_t pjsua_call_media_init(pjsua_call_media *call_med,
+ pjmedia_type type,
+ const pjsua_transport_config *tcfg,
+ int security_level,
+ int *sip_err_code,
+ pj_bool_t async,
+ pjsua_med_tp_state_cb cb)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ /*
+ * Note: this function may be called when the media already exists
+ * (e.g. in reinvites, updates, etc.)
+ */
+ call_med->type = type;
+
+ /* Create the media transport for initial call. Here are the possible
+ * media transport state and the action needed:
+ * - PJSUA_MED_TP_NULL or call_med->tp==NULL, create one.
+ * - PJSUA_MED_TP_RUNNING, do nothing.
+ * - PJSUA_MED_TP_DISABLED, re-init (media_create(), etc). Currently,
+ * this won't happen as media_channel_update() will always clean up
+ * the unused transport of a disabled media.
+ */
+ if (call_med->tp == NULL) {
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ /* While in initial call, set default video devices */
+ if (type == PJMEDIA_TYPE_VIDEO) {
+ status = pjsua_vid_channel_init(call_med);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+#endif
+
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_CREATING);
+
+ if (pjsua_var.media_cfg.enable_ice) {
+ status = create_ice_media_transport(tcfg, call_med, async);
+ if (async && status == PJ_EPENDING) {
+ /* We will resume call media initialization in the
+ * on_ice_complete() callback.
+ */
+ call_med->med_create_cb = &call_media_init_cb;
+ call_med->med_init_cb = cb;
+
+ return PJ_EPENDING;
+ }
+ } else {
+ status = create_udp_media_transport(tcfg, call_med);
+ }
+
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport"));
+ return status;
+ }
+
+ /* Media transport creation completed immediately, so
+ * we don't need to call the callback.
+ */
+ call_med->med_init_cb = NULL;
+
+ } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) {
+ /* Media is being reenabled. */
+ //pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
+
+ pj_assert(!"Currently no media transport reuse");
+ }
+
+ return call_media_init_cb(call_med, status, security_level,
+ sip_err_code);
+}
+
+/* Callback to resume pjsua_media_channel_init() after media transport
+ * initialization is completed.
+ */
+static pj_status_t media_channel_init_cb(pjsua_call_id call_id,
+ const pjsua_med_tp_state_info *info)
+{
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ pj_status_t status = (info? info->status : PJ_SUCCESS);
+ unsigned mi;
+
+ if (info) {
+ pj_mutex_lock(call->med_ch_mutex);
+
+ /* Set the callback to NULL to indicate that the async operation
+ * has completed.
+ */
+ call->media_prov[info->med_idx].med_init_cb = NULL;
+
+ /* In case of failure, save the information to be returned
+ * by the last media transport to finish.
+ */
+ if (info->status != PJ_SUCCESS)
+ pj_memcpy(&call->med_ch_info, info, sizeof(info));
+
+ /* Check whether all the call's medias have finished calling their
+ * callbacks.
+ */
+ for (mi=0; mi < call->med_prov_cnt; ++mi) {
+ pjsua_call_media *call_med = &call->media_prov[mi];
+
+ if (call_med->med_init_cb) {
+ pj_mutex_unlock(call->med_ch_mutex);
+ return PJ_SUCCESS;
+ }
+
+ if (call_med->tp_ready != PJ_SUCCESS)
+ status = call_med->tp_ready;
+ }
+
+ /* OK, we are called by the last media transport finished. */
+ pj_mutex_unlock(call->med_ch_mutex);
+ }
+
+ if (call->med_ch_mutex) {
+ pj_mutex_destroy(call->med_ch_mutex);
+ call->med_ch_mutex = NULL;
+ }
+
+ if (status != PJ_SUCCESS) {
+ if (call->med_ch_info.status == PJ_SUCCESS) {
+ call->med_ch_info.status = status;
+ call->med_ch_info.sip_err_code = PJSIP_SC_TEMPORARILY_UNAVAILABLE;
+ }
+ pjsua_media_prov_clean_up(call_id);
+ goto on_return;
+ }
+
+ /* Tell the media transport of a new offer/answer session */
+ for (mi=0; mi < call->med_prov_cnt; ++mi) {
+ pjsua_call_media *call_med = &call->media_prov[mi];
+
+ /* Note: tp may be NULL if this media line is disabled */
+ if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
+ pj_pool_t *tmp_pool = call->async_call.pool_prov;
+
+ if (!tmp_pool) {
+ tmp_pool = (call->inv? call->inv->pool_prov:
+ call->async_call.dlg->pool);
+ }
+
+ if (call_med->use_custom_med_tp) {
+ unsigned custom_med_tp_flags = 0;
+
+ /* Use custom media transport returned by the application */
+ call_med->tp =
+ (*pjsua_var.ua_cfg.cb.on_create_media_transport)
+ (call_id, mi, call_med->tp,
+ custom_med_tp_flags);
+ if (!call_med->tp) {
+ status =
+ PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_TEMPORARILY_UNAVAILABLE);
+ }
+ }
+
+ if (call_med->tp) {
+ status = pjmedia_transport_media_create(
+ call_med->tp, tmp_pool,
+ 0, call->async_call.rem_sdp, mi);
+ }
+ if (status != PJ_SUCCESS) {
+ call->med_ch_info.status = status;
+ call->med_ch_info.med_idx = mi;
+ call->med_ch_info.state = call_med->tp_st;
+ call->med_ch_info.sip_err_code = PJSIP_SC_TEMPORARILY_UNAVAILABLE;
+ pjsua_media_prov_clean_up(call_id);
+ goto on_return;
+ }
+
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
+ }
+ }
+
+ call->med_ch_info.status = PJ_SUCCESS;
+
+on_return:
+ if (call->med_ch_cb)
+ (*call->med_ch_cb)(call->index, &call->med_ch_info);
+
+ return status;
+}
+
+
+/* Clean up media transports in provisional media that is not used
+ * by call media.
+ */
+void pjsua_media_prov_clean_up(pjsua_call_id call_id)
+{
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ unsigned i;
+
+ for (i = 0; i < call->med_prov_cnt; ++i) {
+ pjsua_call_media *call_med = &call->media_prov[i];
+ unsigned j;
+ pj_bool_t used = PJ_FALSE;
+
+ if (call_med->tp == NULL)
+ continue;
+
+ for (j = 0; j < call->med_cnt; ++j) {
+ if (call->media[j].tp == call_med->tp) {
+ used = PJ_TRUE;
+ break;
+ }
+ }
+
+ if (!used) {
+ if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
+ pjmedia_transport_media_stop(call_med->tp);
+ }
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL);
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = call_med->tp_orig = NULL;
+ }
+ }
+}
+
+
+pj_status_t pjsua_media_channel_init(pjsua_call_id call_id,
+ pjsip_role_e role,
+ int security_level,
+ pj_pool_t *tmp_pool,
+ const pjmedia_sdp_session *rem_sdp,
+ int *sip_err_code,
+ pj_bool_t async,
+ pjsua_med_tp_state_cb cb)
+{
+ const pj_str_t STR_AUDIO = { "audio", 5 };
+ const pj_str_t STR_VIDEO = { "video", 5 };
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
+ pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
+ unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
+ unsigned mtotaudcnt = PJ_ARRAY_SIZE(maudidx);
+ pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
+ unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
+ unsigned mtotvidcnt = PJ_ARRAY_SIZE(mvididx);
+ unsigned mi;
+ pj_bool_t pending_med_tp = PJ_FALSE;
+ pj_bool_t reinit = PJ_FALSE;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(role);
+
+ /*
+ * Note: this function may be called when the media already exists
+ * (e.g. in reinvites, updates, etc).
+ */
+
+ if (pjsua_get_state() != PJSUA_STATE_RUNNING)
+ return PJ_EBUSY;
+
+ if (async) {
+ pj_pool_t *tmppool = (call->inv? call->inv->pool_prov:
+ call->async_call.dlg->pool);
+
+ status = pj_mutex_create_simple(tmppool, NULL, &call->med_ch_mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED)
+ reinit = PJ_TRUE;
+
+ PJ_LOG(4,(THIS_FILE, "Call %d: %sinitializing media..",
+ call_id, (reinit?"re-":"") ));
+
+ pj_log_push_indent();
+
+ /* Init provisional media state */
+ if (call->med_cnt == 0) {
+ /* New media session, just copy whole from call media state. */
+ pj_memcpy(call->media_prov, call->media, sizeof(call->media));
+ } else {
+ /* Clean up any unused transports. Note that when local SDP reoffer
+ * is rejected by remote, there may be any initialized transports that
+ * are not used by call media and currently there is no notification
+ * from PJSIP level regarding the reoffer rejection.
+ */
+ pjsua_media_prov_clean_up(call_id);
+
+ /* Updating media session, copy from call media state. */
+ pj_memcpy(call->media_prov, call->media,
+ sizeof(call->media[0]) * call->med_cnt);
+ }
+ call->med_prov_cnt = call->med_cnt;
+
+#if DISABLED_FOR_TICKET_1185
+ /* Return error if media transport has not been created yet
+ * (e.g. application is starting)
+ */
+ for (i=0; i<call->med_cnt; ++i) {
+ if (call->media[i].tp == NULL) {
+ status = PJ_EBUSY;
+ goto on_error;
+ }
+ }
+#endif
+
+ /* Get media count for each media type */
+ if (rem_sdp) {
+ sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp,
+ maudidx, &maudcnt, &mtotaudcnt);
+ if (maudcnt==0) {
+ /* Expecting audio in the offer */
+ if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
+ goto on_error;
+ }
+
+#if PJMEDIA_HAS_VIDEO
+ sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp,
+ mvididx, &mvidcnt, &mtotvidcnt);
+#else
+ mvidcnt = mtotvidcnt = 0;
+ PJ_UNUSED_ARG(STR_VIDEO);
+#endif
+
+ /* Update media count only when remote add any media, this media count
+ * must never decrease. Also note that we shouldn't apply the media
+ * count setting (of the call setting) before the SDP negotiation.
+ */
+ if (call->med_prov_cnt < rem_sdp->media_count)
+ call->med_prov_cnt = PJ_MIN(rem_sdp->media_count,
+ PJSUA_MAX_CALL_MEDIA);
+
+ call->rem_offerer = PJ_TRUE;
+ call->rem_aud_cnt = maudcnt;
+ call->rem_vid_cnt = mvidcnt;
+
+ } else {
+
+ /* If call already established, calculate media count from current
+ * local active SDP and call setting. Otherwise, calculate media
+ * count from the call setting only.
+ */
+ if (reinit) {
+ const pjmedia_sdp_session *sdp;
+
+ status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp);
+ pj_assert(status == PJ_SUCCESS);
+
+ sort_media(sdp, &STR_AUDIO, acc->cfg.use_srtp,
+ maudidx, &maudcnt, &mtotaudcnt);
+ pj_assert(maudcnt > 0);
+
+ sort_media(sdp, &STR_VIDEO, acc->cfg.use_srtp,
+ mvididx, &mvidcnt, &mtotvidcnt);
+
+ /* Call setting may add or remove media. Adding media is done by
+ * enabling any disabled/port-zeroed media first, then adding new
+ * media whenever needed. Removing media is done by disabling
+ * media with the lowest 'quality'.
+ */
+
+ /* Check if we need to add new audio */
+ if (maudcnt < call->opt.aud_cnt &&
+ mtotaudcnt < call->opt.aud_cnt)
+ {
+ for (mi = 0; mi < call->opt.aud_cnt - mtotaudcnt; ++mi)
+ maudidx[maudcnt++] = (pj_uint8_t)call->med_prov_cnt++;
+
+ mtotaudcnt = call->opt.aud_cnt;
+ }
+ maudcnt = call->opt.aud_cnt;
+
+ /* Check if we need to add new video */
+ if (mvidcnt < call->opt.vid_cnt &&
+ mtotvidcnt < call->opt.vid_cnt)
+ {
+ for (mi = 0; mi < call->opt.vid_cnt - mtotvidcnt; ++mi)
+ mvididx[mvidcnt++] = (pj_uint8_t)call->med_prov_cnt++;
+
+ mtotvidcnt = call->opt.vid_cnt;
+ }
+ mvidcnt = call->opt.vid_cnt;
+
+ } else {
+
+ maudcnt = mtotaudcnt = call->opt.aud_cnt;
+ for (mi=0; mi<maudcnt; ++mi) {
+ maudidx[mi] = (pj_uint8_t)mi;
+ }
+ mvidcnt = mtotvidcnt = call->opt.vid_cnt;
+ for (mi=0; mi<mvidcnt; ++mi) {
+ mvididx[mi] = (pj_uint8_t)(maudcnt + mi);
+ }
+ call->med_prov_cnt = maudcnt + mvidcnt;
+
+ /* Need to publish supported media? */
+ if (call->opt.flag & PJSUA_CALL_INCLUDE_DISABLED_MEDIA) {
+ if (mtotaudcnt == 0) {
+ mtotaudcnt = 1;
+ maudidx[0] = (pj_uint8_t)call->med_prov_cnt++;
+ }
+#if PJMEDIA_HAS_VIDEO
+ if (mtotvidcnt == 0) {
+ mtotvidcnt = 1;
+ mvididx[0] = (pj_uint8_t)call->med_prov_cnt++;
+ }
+#endif
+ }
+ }
+
+ call->rem_offerer = PJ_FALSE;
+ }
+
+ if (call->med_prov_cnt == 0) {
+ /* Expecting at least one media */
+ if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE);
+ goto on_error;
+ }
+
+ if (async) {
+ call->med_ch_cb = cb;
+ }
+
+ if (rem_sdp) {
+ call->async_call.rem_sdp =
+ pjmedia_sdp_session_clone(call->inv->pool_prov, rem_sdp);
+ } else {
+ call->async_call.rem_sdp = NULL;
+ }
+
+ call->async_call.pool_prov = tmp_pool;
+
+ /* Initialize each media line */
+ for (mi=0; mi < call->med_prov_cnt; ++mi) {
+ pjsua_call_media *call_med = &call->media_prov[mi];
+ pj_bool_t enabled = PJ_FALSE;
+ pjmedia_type media_type = PJMEDIA_TYPE_UNKNOWN;
+
+ if (pj_memchr(maudidx, mi, mtotaudcnt * sizeof(maudidx[0]))) {
+ media_type = PJMEDIA_TYPE_AUDIO;
+ if (call->opt.aud_cnt &&
+ pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0])))
+ {
+ enabled = PJ_TRUE;
+ }
+ } else if (pj_memchr(mvididx, mi, mtotvidcnt * sizeof(mvididx[0]))) {
+ media_type = PJMEDIA_TYPE_VIDEO;
+ if (call->opt.vid_cnt &&
+ pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0])))
+ {
+ enabled = PJ_TRUE;
+ }
+ }
+
+ if (enabled) {
+ status = pjsua_call_media_init(call_med, media_type,
+ &acc->cfg.rtp_cfg,
+ security_level, sip_err_code,
+ async,
+ (async? &media_channel_init_cb:
+ NULL));
+ if (status == PJ_EPENDING) {
+ pending_med_tp = PJ_TRUE;
+ } else if (status != PJ_SUCCESS) {
+ if (pending_med_tp) {
+ /* Save failure information. */
+ call_med->tp_ready = status;
+ pj_bzero(&call->med_ch_info, sizeof(call->med_ch_info));
+ call->med_ch_info.status = status;
+ call->med_ch_info.state = call_med->tp_st;
+ call->med_ch_info.med_idx = call_med->idx;
+ if (sip_err_code)
+ call->med_ch_info.sip_err_code = *sip_err_code;
+
+ /* We will return failure in the callback later. */
+ return PJ_EPENDING;
+ }
+
+ pjsua_media_prov_clean_up(call_id);
+ goto on_error;
+ }
+ } else {
+ /* By convention, the media is disabled if transport is NULL
+ * or transport state is PJSUA_MED_TP_DISABLED.
+ */
+ if (call_med->tp) {
+ // Don't close transport here, as SDP negotiation has not been
+ // done and stream may be still active. Once SDP negotiation
+ // is done (channel_update() invoked), this transport will be
+ // closed there.
+ //pjmedia_transport_close(call_med->tp);
+ //call_med->tp = NULL;
+ pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT ||
+ call_med->tp_st == PJSUA_MED_TP_RUNNING);
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
+ }
+
+ /* Put media type just for info */
+ call_med->type = media_type;
+ }
+ }
+
+ call->audio_idx = maudidx[0];
+
+ PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d",
+ call->audio_idx, call->index));
+
+ if (pending_med_tp) {
+ /* We shouldn't use temporary pool anymore. */
+ call->async_call.pool_prov = NULL;
+ /* We have a pending media transport initialization. */
+ pj_log_pop_indent();
+ return PJ_EPENDING;
+ }
+
+ /* Media transport initialization completed immediately, so
+ * we don't need to call the callback.
+ */
+ call->med_ch_cb = NULL;
+
+ status = media_channel_init_cb(call_id, NULL);
+ if (status != PJ_SUCCESS && sip_err_code)
+ *sip_err_code = call->med_ch_info.sip_err_code;
+
+ pj_log_pop_indent();
+ return status;
+
+on_error:
+ if (call->med_ch_mutex) {
+ pj_mutex_destroy(call->med_ch_mutex);
+ call->med_ch_mutex = NULL;
+ }
+
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/* Create SDP based on the current media channel. Note that, this function
+ * will not modify the media channel, so when receiving new offer or
+ * updating media count (via call setting), media channel must be reinit'd
+ * (using pjsua_media_channel_init()) first before calling this function.
+ */
+pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id,
+ pj_pool_t *pool,
+ const pjmedia_sdp_session *rem_sdp,
+ pjmedia_sdp_session **p_sdp,
+ int *sip_err_code)
+{
+ enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA };
+ pjmedia_sdp_session *sdp;
+ pj_sockaddr origin;
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL;
+ unsigned mi;
+ unsigned tot_bandw_tias = 0;
+ pj_status_t status;
+
+ if (pjsua_get_state() != PJSUA_STATE_RUNNING)
+ return PJ_EBUSY;
+
+#if 0
+ // This function should not really change the media channel.
+ if (rem_sdp) {
+ /* If this is a re-offer, let's re-initialize media as remote may
+ * add or remove media
+ */
+ if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
+ status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS,
+ call->secure_level, pool,
+ rem_sdp, sip_err_code,
+ PJ_FALSE, NULL);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+ } else {
+ /* Audio is first in our offer, by convention */
+ // The audio_idx should not be changed here, as this function may be
+ // called in generating re-offer and the current active audio index
+ // can be anywhere.
+ //call->audio_idx = 0;
+ }
+#endif
+
+#if 0
+ // Since r3512, old-style hold should have got transport, created by
+ // pjsua_media_channel_init() in initial offer/answer or remote reoffer.
+ /* Create media if it's not created. This could happen when call is
+ * currently on-hold (with the old style hold)
+ */
+ if (call->media[call->audio_idx].tp == NULL) {
+ pjsip_role_e role;
+ role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC);
+ status = pjsua_media_channel_init(call_id, role, call->secure_level,
+ pool, rem_sdp, sip_err_code);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+#endif
+
+ /* Get SDP negotiator state */
+ if (call->inv && call->inv->neg)
+ sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg);
+
+ /* Get one address to use in the origin field */
+ pj_bzero(&origin, sizeof(origin));
+ for (mi=0; mi<call->med_prov_cnt; ++mi) {
+ pjmedia_transport_info tpinfo;
+
+ if (call->media_prov[mi].tp == NULL)
+ continue;
+
+ pjmedia_transport_info_init(&tpinfo);
+ pjmedia_transport_get_info(call->media_prov[mi].tp, &tpinfo);
+ pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name);
+ break;
+ }
+
+ /* Create the base (blank) SDP */
+ status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL,
+ &origin, &sdp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Process each media line */
+ for (mi=0; mi<call->med_prov_cnt; ++mi) {
+ pjsua_call_media *call_med = &call->media_prov[mi];
+ pjmedia_sdp_media *m = NULL;
+ pjmedia_transport_info tpinfo;
+ unsigned i;
+
+ if (rem_sdp && mi >= rem_sdp->media_count) {
+ /* Remote might have removed some media lines. */
+ break;
+ }
+
+ if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED)
+ {
+ /*
+ * This media is disabled. Just create a valid SDP with zero
+ * port.
+ */
+ if (rem_sdp) {
+ /* Just clone the remote media and deactivate it */
+ m = pjmedia_sdp_media_clone_deactivate(pool,
+ rem_sdp->media[mi]);
+ } else {
+ m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media);
+ m->desc.transport = pj_str("RTP/AVP");
+ m->desc.fmt_count = 1;
+ m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn);
+ m->conn->net_type = pj_str("IN");
+ m->conn->addr_type = pj_str("IP4");
+ m->conn->addr = pj_str("127.0.0.1");
+
+ switch (call_med->type) {
+ case PJMEDIA_TYPE_AUDIO:
+ m->desc.media = pj_str("audio");
+ m->desc.fmt[0] = pj_str("0");
+ break;
+ case PJMEDIA_TYPE_VIDEO:
+ m->desc.media = pj_str("video");
+ m->desc.fmt[0] = pj_str("31");
+ break;
+ default:
+ /* This must be us generating re-offer, and some unknown
+ * media may exist, so just clone from active local SDP
+ * (and it should have been deactivated already).
+ */
+ pj_assert(call->inv && call->inv->neg &&
+ sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE);
+ {
+ const pjmedia_sdp_session *s_;
+ pjmedia_sdp_neg_get_active_local(call->inv->neg, &s_);
+
+ pj_assert(mi < s_->media_count);
+ m = pjmedia_sdp_media_clone(pool, s_->media[mi]);
+ m->desc.port = 0;
+ }
+ break;
+ }
+ }
+
+ sdp->media[sdp->media_count++] = m;
+ continue;
+ }
+
+ /* Get transport address info */
+ pjmedia_transport_info_init(&tpinfo);
+ pjmedia_transport_get_info(call_med->tp, &tpinfo);
+
+ /* Ask pjmedia endpoint to create SDP media line */
+ switch (call_med->type) {
+ case PJMEDIA_TYPE_AUDIO:
+ status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool,
+ &tpinfo.sock_info, 0, &m);
+ break;
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ case PJMEDIA_TYPE_VIDEO:
+ status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
+ &tpinfo.sock_info, 0, &m);
+ break;
+#endif
+ default:
+ pj_assert(!"Invalid call_med media type");
+ return PJ_EBUG;
+ }
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ sdp->media[sdp->media_count++] = m;
+
+ /* Give to transport */
+ status = pjmedia_transport_encode_sdp(call_med->tp, pool,
+ sdp, rem_sdp, mi);
+ if (status != PJ_SUCCESS) {
+ if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
+ return status;
+ }
+
+ /* Copy c= line of the first media to session level,
+ * if there's none.
+ */
+ if (sdp->conn == NULL) {
+ sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn);
+ }
+
+
+ /* Find media bandwidth info */
+ for (i = 0; i < m->bandw_count; ++i) {
+ const pj_str_t STR_BANDW_MODIFIER_TIAS = { "TIAS", 4 };
+ if (!pj_stricmp(&m->bandw[i]->modifier, &STR_BANDW_MODIFIER_TIAS))
+ {
+ tot_bandw_tias += m->bandw[i]->value;
+ break;
+ }
+ }
+ }
+
+ /* Add NAT info in the SDP */
+ if (pjsua_var.ua_cfg.nat_type_in_sdp) {
+ pjmedia_sdp_attr *a;
+ pj_str_t value;
+ char nat_info[80];
+
+ value.ptr = nat_info;
+ if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) {
+ value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
+ "%d", pjsua_var.nat_type);
+ } else {
+ const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type);
+ value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info),
+ "%d %s",
+ pjsua_var.nat_type,
+ type_name);
+ }
+
+ a = pjmedia_sdp_attr_create(pool, "X-nat", &value);
+
+ pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a);
+
+ }
+
+
+ /* Add bandwidth info in session level using bandwidth modifier "AS". */
+ if (tot_bandw_tias) {
+ unsigned bandw;
+ const pj_str_t STR_BANDW_MODIFIER_AS = { "AS", 2 };
+ pjmedia_sdp_bandw *b;
+
+ /* AS bandwidth = RTP bitrate + RTCP bitrate.
+ * RTP bitrate = payload bitrate (total TIAS) + overheads (~16kbps).
+ * RTCP bitrate = est. 5% of RTP bitrate.
+ * Note that AS bandwidth is in kbps.
+ */
+ bandw = tot_bandw_tias + 16000;
+ bandw += bandw * 5 / 100;
+ b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw);
+ b->modifier = STR_BANDW_MODIFIER_AS;
+ b->value = bandw / 1000;
+ sdp->bandw[sdp->bandw_count++] = b;
+ }
+
+
+#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+ /* Check if SRTP is in optional mode and configured to use duplicated
+ * media, i.e: secured and unsecured version, in the SDP offer.
+ */
+ if (!rem_sdp &&
+ pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL &&
+ pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer)
+ {
+ unsigned i;
+
+ for (i = 0; i < sdp->media_count; ++i) {
+ pjmedia_sdp_media *m = sdp->media[i];
+
+ /* Check if this media is unsecured but has SDP "crypto"
+ * attribute.
+ */
+ if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 &&
+ pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL)
+ {
+ if (i == (unsigned)call->audio_idx &&
+ sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE)
+ {
+ /* This is a session update, and peer has chosen the
+ * unsecured version, so let's make this unsecured too.
+ */
+ pjmedia_sdp_media_remove_all_attr(m, "crypto");
+ } else {
+ /* This is new offer, duplicate media so we'll have
+ * secured (with "RTP/SAVP" transport) and and unsecured
+ * versions.
+ */
+ pjmedia_sdp_media *new_m;
+
+ /* Duplicate this media and apply secured transport */
+ new_m = pjmedia_sdp_media_clone(pool, m);
+ pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP");
+
+ /* Remove the "crypto" attribute in the unsecured media */
+ pjmedia_sdp_media_remove_all_attr(m, "crypto");
+
+ /* Insert the new media before the unsecured media */
+ if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) {
+ pj_array_insert(sdp->media, sizeof(new_m),
+ sdp->media_count, i, &new_m);
+ ++sdp->media_count;
+ ++i;
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ call->rem_offerer = (rem_sdp != NULL);
+
+ /* Notify application */
+ if (pjsua_var.ua_cfg.cb.on_call_sdp_created) {
+ (*pjsua_var.ua_cfg.cb.on_call_sdp_created)(call_id, sdp,
+ pool, rem_sdp);
+ }
+
+ *p_sdp = sdp;
+ return PJ_SUCCESS;
+}
+
+
+static void stop_media_session(pjsua_call_id call_id)
+{
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ unsigned mi;
+
+ pj_log_push_indent();
+
+ for (mi=0; mi<call->med_cnt; ++mi) {
+ pjsua_call_media *call_med = &call->media[mi];
+
+ if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+ pjsua_aud_stop_stream(call_med);
+ }
+
+#if PJMEDIA_HAS_VIDEO
+ else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
+ pjsua_vid_stop_stream(call_med);
+ }
+#endif
+
+ PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed",
+ call_id, mi));
+ call_med->prev_state = call_med->state;
+ call_med->state = PJSUA_CALL_MEDIA_NONE;
+
+ /* Try to sync recent changes to provisional media */
+ if (mi<call->med_prov_cnt && call->media_prov[mi].tp==call_med->tp)
+ {
+ pjsua_call_media *prov_med = &call->media_prov[mi];
+
+ /* Media state */
+ prov_med->prev_state = call_med->prev_state;
+ prov_med->state = call_med->state;
+
+ /* RTP seq/ts */
+ prov_med->rtp_tx_seq_ts_set = call_med->rtp_tx_seq_ts_set;
+ prov_med->rtp_tx_seq = call_med->rtp_tx_seq;
+ prov_med->rtp_tx_ts = call_med->rtp_tx_ts;
+
+ /* Stream */
+ if (call_med->type == PJMEDIA_TYPE_AUDIO) {
+ prov_med->strm.a.conf_slot = call_med->strm.a.conf_slot;
+ prov_med->strm.a.stream = call_med->strm.a.stream;
+ }
+#if PJMEDIA_HAS_VIDEO
+ else if (call_med->type == PJMEDIA_TYPE_VIDEO) {
+ prov_med->strm.v.cap_win_id = call_med->strm.v.cap_win_id;
+ prov_med->strm.v.rdr_win_id = call_med->strm.v.rdr_win_id;
+ prov_med->strm.v.stream = call_med->strm.v.stream;
+ }
+#endif
+ }
+ }
+
+ pj_log_pop_indent();
+}
+
+pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id)
+{
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ unsigned mi;
+
+ for (mi=0; mi<call->med_cnt; ++mi) {
+ pjsua_call_media *call_med = &call->media[mi];
+
+ if (call_med->tp_st == PJSUA_MED_TP_CREATING) {
+ /* We will do the deinitialization after media transport
+ * creation is completed.
+ */
+ call->async_call.med_ch_deinit = PJ_TRUE;
+ return PJ_SUCCESS;
+ }
+ }
+
+ PJ_LOG(4,(THIS_FILE, "Call %d: deinitializing media..", call_id));
+ pj_log_push_indent();
+
+ stop_media_session(call_id);
+
+ /* Clean up media transports */
+ pjsua_media_prov_clean_up(call_id);
+ call->med_prov_cnt = 0;
+ for (mi=0; mi<call->med_cnt; ++mi) {
+ pjsua_call_media *call_med = &call->media[mi];
+
+ if (call_med->tp_st > PJSUA_MED_TP_IDLE) {
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE);
+ pjmedia_transport_media_stop(call_med->tp);
+ }
+
+ if (call_med->tp) {
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL);
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = call_med->tp_orig = NULL;
+ }
+ call_med->tp_orig = NULL;
+ }
+
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+}
+
+
+pj_status_t pjsua_media_channel_update(pjsua_call_id call_id,
+ const pjmedia_sdp_session *local_sdp,
+ const pjmedia_sdp_session *remote_sdp)
+{
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
+ pj_pool_t *tmp_pool = call->inv->pool_prov;
+ unsigned mi;
+ pj_bool_t got_media = PJ_FALSE;
+ pj_status_t status = PJ_SUCCESS;
+
+ const pj_str_t STR_AUDIO = { "audio", 5 };
+ const pj_str_t STR_VIDEO = { "video", 5 };
+ pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA];
+ unsigned maudcnt = PJ_ARRAY_SIZE(maudidx);
+ unsigned mtotaudcnt = PJ_ARRAY_SIZE(maudidx);
+ pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA];
+ unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx);
+ unsigned mtotvidcnt = PJ_ARRAY_SIZE(mvididx);
+ pj_bool_t need_renego_sdp = PJ_FALSE;
+
+ if (pjsua_get_state() != PJSUA_STATE_RUNNING)
+ return PJ_EBUSY;
+
+ PJ_LOG(4,(THIS_FILE, "Call %d: updating media..", call_id));
+ pj_log_push_indent();
+
+ /* Destroy existing media session, if any. */
+ stop_media_session(call->index);
+
+ /* Call media count must be at least equal to SDP media. Note that
+ * it may not be equal when remote removed any SDP media line.
+ */
+ pj_assert(call->med_prov_cnt >= local_sdp->media_count);
+
+ /* Reset audio_idx first */
+ call->audio_idx = -1;
+
+ /* Sort audio/video based on "quality" */
+ sort_media(local_sdp, &STR_AUDIO, acc->cfg.use_srtp,
+ maudidx, &maudcnt, &mtotaudcnt);
+#if PJMEDIA_HAS_VIDEO
+ sort_media(local_sdp, &STR_VIDEO, acc->cfg.use_srtp,
+ mvididx, &mvidcnt, &mtotvidcnt);
+#else
+ PJ_UNUSED_ARG(STR_VIDEO);
+ mvidcnt = mtotvidcnt = 0;
+#endif
+
+ /* Applying media count limitation. Note that in generating SDP answer,
+ * no media count limitation applied, as we didn't know yet which media
+ * would pass the SDP negotiation.
+ */
+ if (maudcnt > call->opt.aud_cnt || mvidcnt > call->opt.vid_cnt)
+ {
+ pjmedia_sdp_session *local_sdp2;
+
+ maudcnt = PJ_MIN(maudcnt, call->opt.aud_cnt);
+ mvidcnt = PJ_MIN(mvidcnt, call->opt.vid_cnt);
+ local_sdp2 = pjmedia_sdp_session_clone(tmp_pool, local_sdp);
+
+ for (mi=0; mi < local_sdp2->media_count; ++mi) {
+ pjmedia_sdp_media *m = local_sdp2->media[mi];
+
+ if (m->desc.port == 0 ||
+ pj_memchr(maudidx, mi, maudcnt*sizeof(maudidx[0])) ||
+ pj_memchr(mvididx, mi, mvidcnt*sizeof(mvididx[0])))
+ {
+ continue;
+ }
+
+ /* Deactivate this media */
+ pjmedia_sdp_media_deactivate(tmp_pool, m);
+ }
+
+ local_sdp = local_sdp2;
+ need_renego_sdp = PJ_TRUE;
+ }
+
+ /* Process each media stream */
+ for (mi=0; mi < call->med_prov_cnt; ++mi) {
+ pjsua_call_media *call_med = &call->media_prov[mi];
+
+ if (mi >= local_sdp->media_count ||
+ mi >= remote_sdp->media_count)
+ {
+ /* This may happen when remote removed any SDP media lines in
+ * its re-offer.
+ */
+ if (call_med->tp) {
+ /* Close the media transport */
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL);
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = call_med->tp_orig = NULL;
+ }
+ continue;
+#if 0
+ /* Something is wrong */
+ PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: "
+ "invalid media index %d in SDP", call_id, mi));
+ status = PJMEDIA_SDP_EINSDP;
+ goto on_error;
+#endif
+ }
+
+ if (call_med->type==PJMEDIA_TYPE_AUDIO) {
+ pjmedia_stream_info the_si, *si = &the_si;
+
+ status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
+ local_sdp, remote_sdp, mi);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "pjmedia_stream_info_from_sdp() failed "
+ "for call_id %d media %d",
+ call_id, mi));
+ continue;
+ }
+
+ /* Check if no media is active */
+ if (si->dir == PJMEDIA_DIR_NONE) {
+ /* Update call media state and direction */
+ call_med->state = PJSUA_CALL_MEDIA_NONE;
+ call_med->dir = PJMEDIA_DIR_NONE;
+
+ } else {
+ pjmedia_transport_info tp_info;
+
+ /* Start/restart media transport based on info in SDP */
+ status = pjmedia_transport_media_start(call_med->tp,
+ tmp_pool, local_sdp,
+ remote_sdp, mi);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "pjmedia_transport_media_start() failed "
+ "for call_id %d media %d",
+ call_id, mi));
+ continue;
+ }
+
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);
+
+ /* Get remote SRTP usage policy */
+ pjmedia_transport_info_init(&tp_info);
+ pjmedia_transport_get_info(call_med->tp, &tp_info);
+ if (tp_info.specific_info_cnt > 0) {
+ unsigned i;
+ for (i = 0; i < tp_info.specific_info_cnt; ++i) {
+ if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP)
+ {
+ pjmedia_srtp_info *srtp_info =
+ (pjmedia_srtp_info*) tp_info.spc_info[i].buffer;
+
+ call_med->rem_srtp_use = srtp_info->peer_use;
+ break;
+ }
+ }
+ }
+
+ /* Call media direction */
+ call_med->dir = si->dir;
+
+ /* Call media state */
+ if (call->local_hold)
+ call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
+ else if (call_med->dir == PJMEDIA_DIR_DECODING)
+ call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
+ else
+ call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
+ }
+
+ /* Call implementation */
+ status = pjsua_aud_channel_update(call_med, tmp_pool, si,
+ local_sdp, remote_sdp);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "pjsua_aud_channel_update() failed "
+ "for call_id %d media %d",
+ call_id, mi));
+ continue;
+ }
+
+ /* Print info. */
+ if (status == PJ_SUCCESS) {
+ char info[80];
+ int info_len = 0;
+ int len;
+ const char *dir;
+
+ switch (si->dir) {
+ case PJMEDIA_DIR_NONE:
+ dir = "inactive";
+ break;
+ case PJMEDIA_DIR_ENCODING:
+ dir = "sendonly";
+ break;
+ case PJMEDIA_DIR_DECODING:
+ dir = "recvonly";
+ break;
+ case PJMEDIA_DIR_ENCODING_DECODING:
+ dir = "sendrecv";
+ break;
+ default:
+ dir = "unknown";
+ break;
+ }
+ len = pj_ansi_sprintf( info+info_len,
+ ", stream #%d: %.*s (%s)", mi,
+ (int)si->fmt.encoding_name.slen,
+ si->fmt.encoding_name.ptr,
+ dir);
+ if (len > 0)
+ info_len += len;
+ PJ_LOG(4,(THIS_FILE,"Audio updated%s", info));
+ }
+
+
+ if (call->audio_idx==-1 && status==PJ_SUCCESS &&
+ si->dir != PJMEDIA_DIR_NONE)
+ {
+ call->audio_idx = mi;
+ }
+
+#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+ } else if (call_med->type==PJMEDIA_TYPE_VIDEO) {
+ pjmedia_vid_stream_info the_si, *si = &the_si;
+
+ status = pjmedia_vid_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt,
+ local_sdp, remote_sdp, mi);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "pjmedia_vid_stream_info_from_sdp() failed "
+ "for call_id %d media %d",
+ call_id, mi));
+ continue;
+ }
+
+ /* Check if no media is active */
+ if (si->dir == PJMEDIA_DIR_NONE) {
+ /* Update call media state and direction */
+ call_med->state = PJSUA_CALL_MEDIA_NONE;
+ call_med->dir = PJMEDIA_DIR_NONE;
+
+ } else {
+ pjmedia_transport_info tp_info;
+
+ /* Start/restart media transport */
+ status = pjmedia_transport_media_start(call_med->tp,
+ tmp_pool, local_sdp,
+ remote_sdp, mi);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "pjmedia_transport_media_start() failed "
+ "for call_id %d media %d",
+ call_id, mi));
+ continue;
+ }
+
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING);
+
+ /* Get remote SRTP usage policy */
+ pjmedia_transport_info_init(&tp_info);
+ pjmedia_transport_get_info(call_med->tp, &tp_info);
+ if (tp_info.specific_info_cnt > 0) {
+ unsigned i;
+ for (i = 0; i < tp_info.specific_info_cnt; ++i) {
+ if (tp_info.spc_info[i].type ==
+ PJMEDIA_TRANSPORT_TYPE_SRTP)
+ {
+ pjmedia_srtp_info *sri;
+ sri=(pjmedia_srtp_info*)tp_info.spc_info[i].buffer;
+ call_med->rem_srtp_use = sri->peer_use;
+ break;
+ }
+ }
+ }
+
+ /* Call media direction */
+ call_med->dir = si->dir;
+
+ /* Call media state */
+ if (call->local_hold)
+ call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD;
+ else if (call_med->dir == PJMEDIA_DIR_DECODING)
+ call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD;
+ else
+ call_med->state = PJSUA_CALL_MEDIA_ACTIVE;
+ }
+
+ status = pjsua_vid_channel_update(call_med, tmp_pool, si,
+ local_sdp, remote_sdp);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "pjsua_vid_channel_update() failed "
+ "for call_id %d media %d",
+ call_id, mi));
+ continue;
+ }
+
+ /* Print info. */
+ {
+ char info[80];
+ int info_len = 0;
+ int len;
+ const char *dir;
+
+ switch (si->dir) {
+ case PJMEDIA_DIR_NONE:
+ dir = "inactive";
+ break;
+ case PJMEDIA_DIR_ENCODING:
+ dir = "sendonly";
+ break;
+ case PJMEDIA_DIR_DECODING:
+ dir = "recvonly";
+ break;
+ case PJMEDIA_DIR_ENCODING_DECODING:
+ dir = "sendrecv";
+ break;
+ default:
+ dir = "unknown";
+ break;
+ }
+ len = pj_ansi_sprintf( info+info_len,
+ ", stream #%d: %.*s (%s)", mi,
+ (int)si->codec_info.encoding_name.slen,
+ si->codec_info.encoding_name.ptr,
+ dir);
+ if (len > 0)
+ info_len += len;
+ PJ_LOG(4,(THIS_FILE,"Video updated%s", info));
+ }
+
+#endif
+ } else {
+ status = PJMEDIA_EINVALIMEDIATYPE;
+ }
+
+ /* Close the transport of deactivated media, need this here as media
+ * can be deactivated by the SDP negotiation and the max media count
+ * (account) setting.
+ */
+ if (local_sdp->media[mi]->desc.port==0 && call_med->tp) {
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL);
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = call_med->tp_orig = NULL;
+ }
+
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02d:%d",
+ call_id, mi));
+ } else {
+ got_media = PJ_TRUE;
+ }
+ }
+
+ /* Update call media from provisional media */
+ call->med_cnt = call->med_prov_cnt;
+ pj_memcpy(call->media, call->media_prov,
+ sizeof(call->media_prov[0]) * call->med_prov_cnt);
+
+ /* Perform SDP re-negotiation if needed. */
+ if (got_media && need_renego_sdp) {
+ pjmedia_sdp_neg *neg = call->inv->neg;
+
+ /* This should only happen when we are the answerer. */
+ PJ_ASSERT_RETURN(neg && !pjmedia_sdp_neg_was_answer_remote(neg),
+ PJMEDIA_SDPNEG_EINSTATE);
+
+ status = pjmedia_sdp_neg_set_remote_offer(tmp_pool, neg, remote_sdp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pjmedia_sdp_neg_set_local_answer(tmp_pool, neg, local_sdp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ pj_log_pop_indent();
+ return (got_media? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA);
+
+on_error:
+ pj_log_pop_indent();
+ return status;
+}
+
+/*****************************************************************************
+ * Codecs.
+ */
+
+/*
+ * Enum all supported codecs in the system.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
+ unsigned *p_count )
+{
+ pjmedia_codec_mgr *codec_mgr;
+ pjmedia_codec_info info[32];
+ unsigned i, count, prio[32];
+ pj_status_t status;
+
+ codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
+ count = PJ_ARRAY_SIZE(info);
+ status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
+ if (status != PJ_SUCCESS) {
+ *p_count = 0;
+ return status;
+ }
+
+ if (count > *p_count) count = *p_count;
+
+ for (i=0; i<count; ++i) {
+ pj_bzero(&id[i], sizeof(pjsua_codec_info));
+
+ pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
+ id[i].codec_id = pj_str(id[i].buf_);
+ id[i].priority = (pj_uint8_t) prio[i];
+ }
+
+ *p_count = count;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Change codec priority.
+ */
+PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
+ pj_uint8_t priority )
+{
+ const pj_str_t all = { NULL, 0 };
+ pjmedia_codec_mgr *codec_mgr;
+
+ codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
+
+ if (codec_id->slen==1 && *codec_id->ptr=='*')
+ codec_id = &all;
+
+ return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id,
+ priority);
+}
+
+
+/*
+ * Get codec parameters.
+ */
+PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
+ pjmedia_codec_param *param )
+{
+ const pj_str_t all = { NULL, 0 };
+ const pjmedia_codec_info *info;
+ pjmedia_codec_mgr *codec_mgr;
+ unsigned count = 1;
+ pj_status_t status;
+
+ codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
+
+ if (codec_id->slen==1 && *codec_id->ptr=='*')
+ codec_id = &all;
+
+ status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
+ &count, &info, NULL);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (count != 1)
+ return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
+
+ status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
+ return status;
+}
+
+
+/*
+ * Set codec parameters.
+ */
+PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id,
+ const pjmedia_codec_param *param)
+{
+ const pjmedia_codec_info *info[2];
+ pjmedia_codec_mgr *codec_mgr;
+ unsigned count = 2;
+ pj_status_t status;
+
+ codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
+
+ status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
+ &count, info, NULL);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Codec ID should be specific, except for G.722.1 */
+ if (count > 1 &&
+ pj_strnicmp2(codec_id, "G7221/16", 8) != 0 &&
+ pj_strnicmp2(codec_id, "G7221/32", 8) != 0)
+ {
+ pj_assert(!"Codec ID is not specific");
+ return PJ_ETOOMANY;
+ }
+
+ status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param);
+ return status;
+}
+
+
+pj_status_t pjsua_media_apply_xml_control(pjsua_call_id call_id,
+ const pj_str_t *xml_st)
+{
+#if PJMEDIA_HAS_VIDEO
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ const pj_str_t PICT_FAST_UPDATE = {"picture_fast_update", 19};
+
+ if (pj_strstr(xml_st, &PICT_FAST_UPDATE)) {
+ unsigned i;
+
+ PJ_LOG(4,(THIS_FILE, "Received keyframe request via SIP INFO"));
+
+ for (i = 0; i < call->med_cnt; ++i) {
+ pjsua_call_media *cm = &call->media[i];
+ if (cm->type != PJMEDIA_TYPE_VIDEO || !cm->strm.v.stream)
+ continue;
+
+ pjmedia_vid_stream_send_keyframe(cm->strm.v.stream);
+ }
+
+ return PJ_SUCCESS;
+ }
+#endif
+
+ /* Just to avoid compiler warning of unused var */
+ PJ_UNUSED_ARG(call_id);
+ PJ_UNUSED_ARG(xml_st);
+
+ return PJ_ENOTSUP;
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c
new file mode 100644
index 0000000..4551c91
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_pres.c
@@ -0,0 +1,2402 @@
+/* $Id: pjsua_pres.c 4186 2012-06-29 01:37:50Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+
+#define THIS_FILE "pjsua_pres.c"
+
+
+static void subscribe_buddy_presence(pjsua_buddy_id buddy_id);
+static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id);
+
+
+/*
+ * Find buddy.
+ */
+static pjsua_buddy_id find_buddy(const pjsip_uri *uri)
+{
+ const pjsip_sip_uri *sip_uri;
+ unsigned i;
+
+ uri = (const pjsip_uri*) pjsip_uri_get_uri((pjsip_uri*)uri);
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))
+ return PJSUA_INVALID_ID;
+
+ sip_uri = (const pjsip_sip_uri*) uri;
+
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+ const pjsua_buddy *b = &pjsua_var.buddy[i];
+
+ if (!pjsua_buddy_is_valid(i))
+ continue;
+
+ if (pj_stricmp(&sip_uri->user, &b->name)==0 &&
+ pj_stricmp(&sip_uri->host, &b->host)==0 &&
+ (sip_uri->port==(int)b->port || (sip_uri->port==0 && b->port==5060)))
+ {
+ /* Match */
+ return i;
+ }
+ }
+
+ return PJSUA_INVALID_ID;
+}
+
+#define LOCK_DIALOG 1
+#define LOCK_PJSUA 2
+#define LOCK_ALL (LOCK_DIALOG | LOCK_PJSUA)
+
+/* Buddy lock object */
+struct buddy_lock
+{
+ pjsua_buddy *buddy;
+ pjsip_dialog *dlg;
+ pj_uint8_t flag;
+};
+
+/* Acquire lock to the specified buddy_id */
+pj_status_t lock_buddy(const char *title,
+ pjsua_buddy_id buddy_id,
+ struct buddy_lock *lck,
+ unsigned _unused_)
+{
+ enum { MAX_RETRY=50 };
+ pj_bool_t has_pjsua_lock = PJ_FALSE;
+ unsigned retry;
+
+ PJ_UNUSED_ARG(_unused_);
+
+ pj_bzero(lck, sizeof(*lck));
+
+ for (retry=0; retry<MAX_RETRY; ++retry) {
+
+ if (PJSUA_TRY_LOCK() != PJ_SUCCESS) {
+ pj_thread_sleep(retry/10);
+ continue;
+ }
+
+ has_pjsua_lock = PJ_TRUE;
+ lck->flag = LOCK_PJSUA;
+ lck->buddy = &pjsua_var.buddy[buddy_id];
+
+ if (lck->buddy->dlg == NULL)
+ return PJ_SUCCESS;
+
+ if (pjsip_dlg_try_inc_lock(lck->buddy->dlg) != PJ_SUCCESS) {
+ lck->flag = 0;
+ lck->buddy = NULL;
+ has_pjsua_lock = PJ_FALSE;
+ PJSUA_UNLOCK();
+ pj_thread_sleep(retry/10);
+ continue;
+ }
+
+ lck->dlg = lck->buddy->dlg;
+ lck->flag = LOCK_DIALOG;
+ PJSUA_UNLOCK();
+
+ break;
+ }
+
+ if (lck->flag == 0) {
+ if (has_pjsua_lock == PJ_FALSE)
+ PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire PJSUA mutex "
+ "(possibly system has deadlocked) in %s",
+ title));
+ else
+ PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire dialog mutex "
+ "(possibly system has deadlocked) in %s",
+ title));
+ return PJ_ETIMEDOUT;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Release buddy lock */
+static void unlock_buddy(struct buddy_lock *lck)
+{
+ if (lck->flag & LOCK_DIALOG)
+ pjsip_dlg_dec_lock(lck->dlg);
+
+ if (lck->flag & LOCK_PJSUA)
+ PJSUA_UNLOCK();
+}
+
+
+/*
+ * Get total number of buddies.
+ */
+PJ_DEF(unsigned) pjsua_get_buddy_count(void)
+{
+ return pjsua_var.buddy_cnt;
+}
+
+
+/*
+ * Find buddy.
+ */
+PJ_DEF(pjsua_buddy_id) pjsua_buddy_find(const pj_str_t *uri_str)
+{
+ pj_str_t input;
+ pj_pool_t *pool;
+ pjsip_uri *uri;
+ pjsua_buddy_id buddy_id;
+
+ pool = pjsua_pool_create("buddyfind", 512, 512);
+ pj_strdup_with_null(pool, &input, uri_str);
+
+ uri = pjsip_parse_uri(pool, input.ptr, input.slen, 0);
+ if (!uri)
+ buddy_id = PJSUA_INVALID_ID;
+ else {
+ PJSUA_LOCK();
+ buddy_id = find_buddy(uri);
+ PJSUA_UNLOCK();
+ }
+
+ pj_pool_release(pool);
+
+ return buddy_id;
+}
+
+
+/*
+ * Check if buddy ID is valid.
+ */
+PJ_DEF(pj_bool_t) pjsua_buddy_is_valid(pjsua_buddy_id buddy_id)
+{
+ return buddy_id>=0 && buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy) &&
+ pjsua_var.buddy[buddy_id].uri.slen != 0;
+}
+
+
+/*
+ * Enum buddy IDs.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_buddies( pjsua_buddy_id ids[],
+ unsigned *count)
+{
+ unsigned i, c;
+
+ PJ_ASSERT_RETURN(ids && count, PJ_EINVAL);
+
+ PJSUA_LOCK();
+
+ for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+ if (!pjsua_var.buddy[i].uri.slen)
+ continue;
+ ids[c] = i;
+ ++c;
+ }
+
+ *count = c;
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get detailed buddy info.
+ */
+PJ_DEF(pj_status_t) pjsua_buddy_get_info( pjsua_buddy_id buddy_id,
+ pjsua_buddy_info *info)
+{
+ unsigned total=0;
+ struct buddy_lock lck;
+ pjsua_buddy *buddy;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
+
+ pj_bzero(info, sizeof(pjsua_buddy_info));
+
+ status = lock_buddy("pjsua_buddy_get_info()", buddy_id, &lck, 0);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ buddy = lck.buddy;
+ info->id = buddy->index;
+ if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
+ unlock_buddy(&lck);
+ return PJ_SUCCESS;
+ }
+
+ /* uri */
+ info->uri.ptr = info->buf_ + total;
+ pj_strncpy(&info->uri, &buddy->uri, sizeof(info->buf_)-total);
+ total += info->uri.slen;
+
+ /* contact */
+ info->contact.ptr = info->buf_ + total;
+ pj_strncpy(&info->contact, &buddy->contact, sizeof(info->buf_)-total);
+ total += info->contact.slen;
+
+ /* Presence status */
+ pj_memcpy(&info->pres_status, &buddy->status, sizeof(pjsip_pres_status));
+
+ /* status and status text */
+ if (buddy->sub == NULL || buddy->status.info_cnt==0) {
+ info->status = PJSUA_BUDDY_STATUS_UNKNOWN;
+ info->status_text = pj_str("?");
+ } else if (pjsua_var.buddy[buddy_id].status.info[0].basic_open) {
+ info->status = PJSUA_BUDDY_STATUS_ONLINE;
+
+ /* copy RPID information */
+ info->rpid = buddy->status.info[0].rpid;
+
+ if (info->rpid.note.slen)
+ info->status_text = info->rpid.note;
+ else
+ info->status_text = pj_str("Online");
+
+ } else {
+ info->status = PJSUA_BUDDY_STATUS_OFFLINE;
+ info->rpid = buddy->status.info[0].rpid;
+
+ if (info->rpid.note.slen)
+ info->status_text = info->rpid.note;
+ else
+ info->status_text = pj_str("Offline");
+ }
+
+ /* monitor pres */
+ info->monitor_pres = buddy->monitor;
+
+ /* subscription state and termination reason */
+ info->sub_term_code = buddy->term_code;
+ if (buddy->sub) {
+ info->sub_state = pjsip_evsub_get_state(buddy->sub);
+ info->sub_state_name = pjsip_evsub_get_state_name(buddy->sub);
+ if (info->sub_state == PJSIP_EVSUB_STATE_TERMINATED &&
+ total < sizeof(info->buf_))
+ {
+ info->sub_term_reason.ptr = info->buf_ + total;
+ pj_strncpy(&info->sub_term_reason,
+ pjsip_evsub_get_termination_reason(buddy->sub),
+ sizeof(info->buf_) - total);
+ total += info->sub_term_reason.slen;
+ } else {
+ info->sub_term_reason = pj_str("");
+ }
+ } else if (total < sizeof(info->buf_)) {
+ info->sub_state_name = "NULL";
+ info->sub_term_reason.ptr = info->buf_ + total;
+ pj_strncpy(&info->sub_term_reason, &buddy->term_reason,
+ sizeof(info->buf_) - total);
+ total += info->sub_term_reason.slen;
+ } else {
+ info->sub_state_name = "NULL";
+ info->sub_term_reason = pj_str("");
+ }
+
+ unlock_buddy(&lck);
+ return PJ_SUCCESS;
+}
+
+/*
+ * Set the user data associated with the buddy object.
+ */
+PJ_DEF(pj_status_t) pjsua_buddy_set_user_data( pjsua_buddy_id buddy_id,
+ void *user_data)
+{
+ struct buddy_lock lck;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
+
+ status = lock_buddy("pjsua_buddy_set_user_data()", buddy_id, &lck, 0);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pjsua_var.buddy[buddy_id].user_data = user_data;
+
+ unlock_buddy(&lck);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the user data associated with the budy object.
+ */
+PJ_DEF(void*) pjsua_buddy_get_user_data(pjsua_buddy_id buddy_id)
+{
+ struct buddy_lock lck;
+ pj_status_t status;
+ void *user_data;
+
+ PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), NULL);
+
+ status = lock_buddy("pjsua_buddy_get_user_data()", buddy_id, &lck, 0);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ user_data = pjsua_var.buddy[buddy_id].user_data;
+
+ unlock_buddy(&lck);
+
+ return user_data;
+}
+
+
+/*
+ * Reset buddy descriptor.
+ */
+static void reset_buddy(pjsua_buddy_id id)
+{
+ pj_pool_t *pool = pjsua_var.buddy[id].pool;
+ pj_bzero(&pjsua_var.buddy[id], sizeof(pjsua_var.buddy[id]));
+ pjsua_var.buddy[id].pool = pool;
+ pjsua_var.buddy[id].index = id;
+}
+
+
+/*
+ * Add new buddy.
+ */
+PJ_DEF(pj_status_t) pjsua_buddy_add( const pjsua_buddy_config *cfg,
+ pjsua_buddy_id *p_buddy_id)
+{
+ pjsip_name_addr *url;
+ pjsua_buddy *buddy;
+ pjsip_sip_uri *sip_uri;
+ int index;
+ pj_str_t tmp;
+
+ PJ_ASSERT_RETURN(pjsua_var.buddy_cnt <=
+ PJ_ARRAY_SIZE(pjsua_var.buddy),
+ PJ_ETOOMANY);
+
+ PJ_LOG(4,(THIS_FILE, "Adding buddy: %.*s",
+ (int)cfg->uri.slen, cfg->uri.ptr));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ /* Find empty slot */
+ for (index=0; index<(int)PJ_ARRAY_SIZE(pjsua_var.buddy); ++index) {
+ if (pjsua_var.buddy[index].uri.slen == 0)
+ break;
+ }
+
+ /* Expect to find an empty slot */
+ if (index == PJ_ARRAY_SIZE(pjsua_var.buddy)) {
+ PJSUA_UNLOCK();
+ /* This shouldn't happen */
+ pj_assert(!"index < PJ_ARRAY_SIZE(pjsua_var.buddy)");
+ pj_log_pop_indent();
+ return PJ_ETOOMANY;
+ }
+
+ buddy = &pjsua_var.buddy[index];
+
+ /* Create pool for this buddy */
+ if (buddy->pool) {
+ pj_pool_reset(buddy->pool);
+ } else {
+ char name[PJ_MAX_OBJ_NAME];
+ pj_ansi_snprintf(name, sizeof(name), "buddy%03d", index);
+ buddy->pool = pjsua_pool_create(name, 512, 256);
+ }
+
+ /* Init buffers for presence subscription status */
+ buddy->term_reason.ptr = (char*)
+ pj_pool_alloc(buddy->pool,
+ PJSUA_BUDDY_SUB_TERM_REASON_LEN);
+
+ /* Get name and display name for buddy */
+ pj_strdup_with_null(buddy->pool, &tmp, &cfg->uri);
+ url = (pjsip_name_addr*)pjsip_parse_uri(buddy->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+
+ if (url == NULL) {
+ pjsua_perror(THIS_FILE, "Unable to add buddy", PJSIP_EINVALIDURI);
+ pj_pool_release(buddy->pool);
+ buddy->pool = NULL;
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Only support SIP schemes */
+ if (!PJSIP_URI_SCHEME_IS_SIP(url) && !PJSIP_URI_SCHEME_IS_SIPS(url)) {
+ pj_pool_release(buddy->pool);
+ buddy->pool = NULL;
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJSIP_EINVALIDSCHEME;
+ }
+
+ /* Reset buddy, to make sure everything is cleared with default
+ * values
+ */
+ reset_buddy(index);
+
+ /* Save URI */
+ pjsua_var.buddy[index].uri = tmp;
+
+ sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(url->uri);
+ pjsua_var.buddy[index].name = sip_uri->user;
+ pjsua_var.buddy[index].display = url->display;
+ pjsua_var.buddy[index].host = sip_uri->host;
+ pjsua_var.buddy[index].port = sip_uri->port;
+ pjsua_var.buddy[index].monitor = cfg->subscribe;
+ if (pjsua_var.buddy[index].port == 0)
+ pjsua_var.buddy[index].port = 5060;
+
+ /* Save user data */
+ pjsua_var.buddy[index].user_data = (void*)cfg->user_data;
+
+ if (p_buddy_id)
+ *p_buddy_id = index;
+
+ pjsua_var.buddy_cnt++;
+
+ PJSUA_UNLOCK();
+
+ PJ_LOG(4,(THIS_FILE, "Buddy %d added.", index));
+
+ pjsua_buddy_subscribe_pres(index, cfg->subscribe);
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Delete buddy.
+ */
+PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id)
+{
+ struct buddy_lock lck;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(buddy_id>=0 &&
+ buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
+ PJ_EINVAL);
+
+ if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
+ return PJ_SUCCESS;
+ }
+
+ status = lock_buddy("pjsua_buddy_del()", buddy_id, &lck, 0);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ PJ_LOG(4,(THIS_FILE, "Buddy %d: deleting..", buddy_id));
+ pj_log_push_indent();
+
+ /* Unsubscribe presence */
+ pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE);
+
+ /* Not interested with further events for this buddy */
+ if (pjsua_var.buddy[buddy_id].sub) {
+ pjsip_evsub_set_mod_data(pjsua_var.buddy[buddy_id].sub,
+ pjsua_var.mod.id, NULL);
+ }
+
+ /* Remove buddy */
+ pjsua_var.buddy[buddy_id].uri.slen = 0;
+ pjsua_var.buddy_cnt--;
+
+ /* Clear timer */
+ if (pjsua_var.buddy[buddy_id].timer.id) {
+ pjsua_cancel_timer(&pjsua_var.buddy[buddy_id].timer);
+ pjsua_var.buddy[buddy_id].timer.id = PJ_FALSE;
+ }
+
+ /* Reset buddy struct */
+ reset_buddy(buddy_id);
+
+ unlock_buddy(&lck);
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Enable/disable buddy's presence monitoring.
+ */
+PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id buddy_id,
+ pj_bool_t subscribe)
+{
+ struct buddy_lock lck;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
+
+ status = lock_buddy("pjsua_buddy_subscribe_pres()", buddy_id, &lck, 0);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ PJ_LOG(4,(THIS_FILE, "Buddy %d: unsubscribing presence..", buddy_id));
+ pj_log_push_indent();
+
+ lck.buddy->monitor = subscribe;
+
+ pjsua_buddy_update_pres(buddy_id);
+
+ unlock_buddy(&lck);
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Update buddy's presence.
+ */
+PJ_DEF(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id)
+{
+ struct buddy_lock lck;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
+
+ status = lock_buddy("pjsua_buddy_update_pres()", buddy_id, &lck, 0);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ PJ_LOG(4,(THIS_FILE, "Buddy %d: updating presence..", buddy_id));
+ pj_log_push_indent();
+
+ /* Is this an unsubscribe request? */
+ if (!lck.buddy->monitor) {
+ unsubscribe_buddy_presence(buddy_id);
+ unlock_buddy(&lck);
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+ }
+
+ /* Ignore if presence is already active for the buddy */
+ if (lck.buddy->sub) {
+ unlock_buddy(&lck);
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+ }
+
+ /* Initiate presence subscription */
+ subscribe_buddy_presence(buddy_id);
+
+ unlock_buddy(&lck);
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Dump presence subscriptions to log file.
+ */
+PJ_DEF(void) pjsua_pres_dump(pj_bool_t verbose)
+{
+ unsigned acc_id;
+ unsigned i;
+
+
+ PJSUA_LOCK();
+
+ /*
+ * When no detail is required, just dump number of server and client
+ * subscriptions.
+ */
+ if (verbose == PJ_FALSE) {
+
+ int count = 0;
+
+ for (acc_id=0; acc_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
+
+ if (!pjsua_var.acc[acc_id].valid)
+ continue;
+
+ if (!pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) {
+ struct pjsua_srv_pres *uapres;
+
+ uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
+ while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
+ ++count;
+ uapres = uapres->next;
+ }
+ }
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d",
+ count));
+
+ count = 0;
+
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+ if (pjsua_var.buddy[i].uri.slen == 0)
+ continue;
+ if (pjsua_var.buddy[i].sub) {
+ ++count;
+ }
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Number of client/UAC subscriptions: %d",
+ count));
+ PJSUA_UNLOCK();
+ return;
+ }
+
+
+ /*
+ * Dumping all server (UAS) subscriptions
+ */
+ PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
+
+ for (acc_id=0; acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
+
+ if (!pjsua_var.acc[acc_id].valid)
+ continue;
+
+ PJ_LOG(3,(THIS_FILE, " %.*s",
+ (int)pjsua_var.acc[acc_id].cfg.id.slen,
+ pjsua_var.acc[acc_id].cfg.id.ptr));
+
+ if (pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) {
+
+ PJ_LOG(3,(THIS_FILE, " - none - "));
+
+ } else {
+ struct pjsua_srv_pres *uapres;
+
+ uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
+ while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
+
+ PJ_LOG(3,(THIS_FILE, " %10s %s",
+ pjsip_evsub_get_state_name(uapres->sub),
+ uapres->remote));
+
+ uapres = uapres->next;
+ }
+ }
+ }
+
+ /*
+ * Dumping all client (UAC) subscriptions
+ */
+ PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:"));
+
+ if (pjsua_var.buddy_cnt == 0) {
+
+ PJ_LOG(3,(THIS_FILE, " - no buddy list - "));
+
+ } else {
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+
+ if (pjsua_var.buddy[i].uri.slen == 0)
+ continue;
+
+ if (pjsua_var.buddy[i].sub) {
+ PJ_LOG(3,(THIS_FILE, " %10s %.*s",
+ pjsip_evsub_get_state_name(pjsua_var.buddy[i].sub),
+ (int)pjsua_var.buddy[i].uri.slen,
+ pjsua_var.buddy[i].uri.ptr));
+ } else {
+ PJ_LOG(3,(THIS_FILE, " %10s %.*s",
+ "(null)",
+ (int)pjsua_var.buddy[i].uri.slen,
+ pjsua_var.buddy[i].uri.ptr));
+ }
+ }
+ }
+
+ PJSUA_UNLOCK();
+}
+
+
+/***************************************************************************
+ * Server subscription.
+ */
+
+/* Proto */
+static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata);
+
+/* The module instance. */
+static pjsip_module mod_pjsua_pres =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-pjsua-pres", 14 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &pres_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+
+/* Callback called when *server* subscription state has changed. */
+static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event)
+{
+ pjsua_srv_pres *uapres;
+
+ PJ_UNUSED_ARG(event);
+
+ PJSUA_LOCK();
+
+ uapres = (pjsua_srv_pres*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+ if (uapres) {
+ pjsip_evsub_state state;
+
+ PJ_LOG(4,(THIS_FILE, "Server subscription to %s is %s",
+ uapres->remote, pjsip_evsub_get_state_name(sub)));
+ pj_log_push_indent();
+
+ state = pjsip_evsub_get_state(sub);
+
+ if (pjsua_var.ua_cfg.cb.on_srv_subscribe_state) {
+ pj_str_t from;
+
+ from = uapres->dlg->remote.info_str;
+ (*pjsua_var.ua_cfg.cb.on_srv_subscribe_state)(uapres->acc_id,
+ uapres, &from,
+ state, event);
+ }
+
+ if (state == PJSIP_EVSUB_STATE_TERMINATED) {
+ pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
+ pj_list_erase(uapres);
+ }
+ pj_log_pop_indent();
+ }
+
+ PJSUA_UNLOCK();
+}
+
+/* This is called when request is received.
+ * We need to check for incoming SUBSCRIBE request.
+ */
+static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
+{
+ int acc_id;
+ pjsua_acc *acc;
+ pj_str_t contact;
+ pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
+ pjsua_srv_pres *uapres;
+ pjsip_evsub *sub;
+ pjsip_evsub_user pres_cb;
+ pjsip_dialog *dlg;
+ pjsip_status_code st_code;
+ pj_str_t reason;
+ pjsip_expires_hdr *expires_hdr;
+ pjsua_msg_data msg_data;
+ pj_status_t status;
+
+ if (pjsip_method_cmp(req_method, pjsip_get_subscribe_method()) != 0)
+ return PJ_FALSE;
+
+ /* Incoming SUBSCRIBE: */
+
+ /* Don't want to accept the request if shutdown is in progress */
+ if (pjsua_var.thread_quit_flag) {
+ pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
+ PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL,
+ NULL, NULL);
+ return PJ_TRUE;
+ }
+
+ PJSUA_LOCK();
+
+ /* Find which account for the incoming request. */
+ acc_id = pjsua_acc_find_for_incoming(rdata);
+ acc = &pjsua_var.acc[acc_id];
+
+ PJ_LOG(4,(THIS_FILE, "Creating server subscription, using account %d",
+ acc_id));
+ pj_log_push_indent();
+
+ /* Create suitable Contact header */
+ if (acc->contact.slen) {
+ contact = acc->contact;
+ } else {
+ status = pjsua_acc_create_uas_contact(rdata->tp_info.pool, &contact,
+ acc_id, rdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to generate Contact header",
+ status);
+ PJSUA_UNLOCK();
+ pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL,
+ NULL, NULL);
+ pj_log_pop_indent();
+ return PJ_TRUE;
+ }
+ }
+
+ /* Create UAS dialog: */
+ status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata,
+ &contact, &dlg);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Unable to create UAS dialog for subscription",
+ status);
+ PJSUA_UNLOCK();
+ pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL,
+ NULL, NULL);
+ pj_log_pop_indent();
+ return PJ_TRUE;
+ }
+
+ if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0)
+ pjsip_dlg_set_via_sent_by(dlg, &acc->via_addr, acc->via_tp);
+
+ /* Set credentials and preference. */
+ pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->cred_cnt, acc->cred);
+ pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref);
+
+ /* Init callback: */
+ pj_bzero(&pres_cb, sizeof(pres_cb));
+ pres_cb.on_evsub_state = &pres_evsub_on_srv_state;
+
+ /* Create server presence subscription: */
+ status = pjsip_pres_create_uas( dlg, &pres_cb, rdata, &sub);
+ if (status != PJ_SUCCESS) {
+ int code = PJSIP_ERRNO_TO_SIP_STATUS(status);
+ pjsip_tx_data *tdata;
+
+ pjsua_perror(THIS_FILE, "Unable to create server subscription",
+ status);
+
+ if (code==599 || code > 699 || code < 300) {
+ code = 400;
+ }
+
+ status = pjsip_dlg_create_response(dlg, rdata, code, NULL, &tdata);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata),
+ tdata);
+ }
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_TRUE;
+ }
+
+ /* If account is locked to specific transport, then lock dialog
+ * to this transport too.
+ */
+ if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
+ pjsip_tpselector tp_sel;
+
+ pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
+ pjsip_dlg_set_transport(dlg, &tp_sel);
+ }
+
+ /* Attach our data to the subscription: */
+ uapres = PJ_POOL_ALLOC_T(dlg->pool, pjsua_srv_pres);
+ uapres->sub = sub;
+ uapres->remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE);
+ uapres->acc_id = acc_id;
+ uapres->dlg = dlg;
+ status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri,
+ uapres->remote, PJSIP_MAX_URL_SIZE);
+ if (status < 1)
+ pj_ansi_strcpy(uapres->remote, "<-- url is too long-->");
+ else
+ uapres->remote[status] = '\0';
+
+ pjsip_evsub_add_header(sub, &acc->cfg.sub_hdr_list);
+ pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, uapres);
+
+ /* Add server subscription to the list: */
+ pj_list_push_back(&pjsua_var.acc[acc_id].pres_srv_list, uapres);
+
+
+ /* Capture the value of Expires header. */
+ expires_hdr = (pjsip_expires_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES,
+ NULL);
+ if (expires_hdr)
+ uapres->expires = expires_hdr->ivalue;
+ else
+ uapres->expires = -1;
+
+ st_code = (pjsip_status_code)200;
+ reason = pj_str("OK");
+ pjsua_msg_data_init(&msg_data);
+
+ /* Notify application callback, if any */
+ if (pjsua_var.ua_cfg.cb.on_incoming_subscribe) {
+ pjsua_buddy_id buddy_id;
+
+ buddy_id = find_buddy(rdata->msg_info.from->uri);
+
+ (*pjsua_var.ua_cfg.cb.on_incoming_subscribe)(acc_id, uapres, buddy_id,
+ &dlg->remote.info_str,
+ rdata, &st_code, &reason,
+ &msg_data);
+ }
+
+ /* Handle rejection case */
+ if (st_code >= 300) {
+ pjsip_tx_data *tdata;
+
+ /* Create response */
+ status = pjsip_dlg_create_response(dlg, rdata, st_code,
+ &reason, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating response", status);
+ pj_list_erase(uapres);
+ pjsip_pres_terminate(sub, PJ_FALSE);
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_FALSE;
+ }
+
+ /* Add header list, if any */
+ pjsua_process_msg_data(tdata, &msg_data);
+
+ /* Send the response */
+ status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata),
+ tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error sending response", status);
+ /* This is not fatal */
+ }
+
+ /* Terminate presence subscription */
+ pj_list_erase(uapres);
+ pjsip_pres_terminate(sub, PJ_FALSE);
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_TRUE;
+ }
+
+ /* Create and send 2xx response to the SUBSCRIBE request: */
+ status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to accept presence subscription",
+ status);
+ pj_list_erase(uapres);
+ pjsip_pres_terminate(sub, PJ_FALSE);
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_FALSE;
+ }
+
+ /* If code is 200, send NOTIFY now */
+ if (st_code == 200) {
+ pjsua_pres_notify(acc_id, uapres, PJSIP_EVSUB_STATE_ACTIVE,
+ NULL, NULL, PJ_TRUE, &msg_data);
+ }
+
+ /* Done: */
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_TRUE;
+}
+
+
+/*
+ * Send NOTIFY.
+ */
+PJ_DEF(pj_status_t) pjsua_pres_notify( pjsua_acc_id acc_id,
+ pjsua_srv_pres *srv_pres,
+ pjsip_evsub_state ev_state,
+ const pj_str_t *state_str,
+ const pj_str_t *reason,
+ pj_bool_t with_body,
+ const pjsua_msg_data *msg_data)
+{
+ pjsua_acc *acc;
+ pjsip_pres_status pres_status;
+ pjsua_buddy_id buddy_id;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Check parameters */
+ PJ_ASSERT_RETURN(acc_id!=-1 && srv_pres, PJ_EINVAL);
+
+ /* Check that account ID is valid */
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+ PJ_EINVAL);
+ /* Check that account is valid */
+ PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+ PJ_LOG(4,(THIS_FILE, "Acc %d: sending NOTIFY for srv_pres=0x%p..",
+ acc_id, (int)(long)srv_pres));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ acc = &pjsua_var.acc[acc_id];
+
+ /* Check that the server presence subscription is still valid */
+ if (pj_list_find_node(&acc->pres_srv_list, srv_pres) == NULL) {
+ /* Subscription has been terminated */
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_EINVALIDOP;
+ }
+
+ /* Set our online status: */
+ pj_bzero(&pres_status, sizeof(pres_status));
+ pres_status.info_cnt = 1;
+ pres_status.info[0].basic_open = acc->online_status;
+ pres_status.info[0].id = acc->cfg.pidf_tuple_id;
+ //Both pjsua_var.local_uri and pjsua_var.contact_uri are enclosed in "<" and ">"
+ //causing XML parsing to fail.
+ //pres_status.info[0].contact = pjsua_var.local_uri;
+ /* add RPID information */
+ pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
+ sizeof(pjrpid_element));
+
+ pjsip_pres_set_status(srv_pres->sub, &pres_status);
+
+ /* Check expires value. If it's zero, send our presense state but
+ * set subscription state to TERMINATED.
+ */
+ if (srv_pres->expires == 0)
+ ev_state = PJSIP_EVSUB_STATE_TERMINATED;
+
+ /* Create and send the NOTIFY to active subscription: */
+ status = pjsip_pres_notify(srv_pres->sub, ev_state, state_str,
+ reason, &tdata);
+ if (status == PJ_SUCCESS) {
+ /* Force removal of message body if msg_body==FALSE */
+ if (!with_body) {
+ tdata->msg->body = NULL;
+ }
+ pjsua_process_msg_data(tdata, msg_data);
+ status = pjsip_pres_send_request( srv_pres->sub, tdata);
+ }
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create/send NOTIFY",
+ status);
+ pj_list_erase(srv_pres);
+ pjsip_pres_terminate(srv_pres->sub, PJ_FALSE);
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+ }
+
+
+ /* Subscribe to buddy's presence if we're not subscribed */
+ buddy_id = find_buddy(srv_pres->dlg->remote.info->uri);
+ if (buddy_id != PJSUA_INVALID_ID) {
+ pjsua_buddy *b = &pjsua_var.buddy[buddy_id];
+ if (b->monitor && b->sub == NULL) {
+ PJ_LOG(4,(THIS_FILE, "Received SUBSCRIBE from buddy %d, "
+ "activating outgoing subscription", buddy_id));
+ subscribe_buddy_presence(buddy_id);
+ }
+ }
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Client presence publication callback.
+ */
+static void publish_cb(struct pjsip_publishc_cbparam *param)
+{
+ pjsua_acc *acc = (pjsua_acc*) param->token;
+
+ if (param->code/100 != 2 || param->status != PJ_SUCCESS) {
+
+ pjsip_publishc_destroy(param->pubc);
+ acc->publish_sess = NULL;
+
+ if (param->status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(param->status, errmsg, sizeof(errmsg));
+ PJ_LOG(1,(THIS_FILE,
+ "Client publication (PUBLISH) failed, status=%d, msg=%s",
+ param->status, errmsg));
+ } else if (param->code == 412) {
+ /* 412 (Conditional Request Failed)
+ * The PUBLISH refresh has failed, retry with new one.
+ */
+ pjsua_pres_init_publish_acc(acc->index);
+
+ } else {
+ PJ_LOG(1,(THIS_FILE,
+ "Client publication (PUBLISH) failed (%d/%.*s)",
+ param->code, (int)param->reason.slen,
+ param->reason.ptr));
+ }
+
+ } else {
+ if (param->expiration < 1) {
+ /* Could happen if server "forgot" to include Expires header
+ * in the response. We will not renew, so destroy the pubc.
+ */
+ pjsip_publishc_destroy(param->pubc);
+ acc->publish_sess = NULL;
+ }
+ }
+}
+
+
+/*
+ * Send PUBLISH request.
+ */
+static pj_status_t send_publish(int acc_id, pj_bool_t active)
+{
+ pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+ pjsip_pres_status pres_status;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_LOG(5,(THIS_FILE, "Acc %d: sending %sPUBLISH..",
+ acc_id, (active ? "" : "un-")));
+ pj_log_push_indent();
+
+ /* Create PUBLISH request */
+ if (active) {
+ char *bpos;
+ pj_str_t entity;
+
+ status = pjsip_publishc_publish(acc->publish_sess, PJ_TRUE, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status);
+ goto on_error;
+ }
+
+ /* Set our online status: */
+ pj_bzero(&pres_status, sizeof(pres_status));
+ pres_status.info_cnt = 1;
+ pres_status.info[0].basic_open = acc->online_status;
+ pres_status.info[0].id = acc->cfg.pidf_tuple_id;
+ /* .. including RPID information */
+ pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
+ sizeof(pjrpid_element));
+
+ /* Be careful not to send PIDF with presence entity ID containing
+ * "<" character.
+ */
+ if ((bpos=pj_strchr(&acc_cfg->id, '<')) != NULL) {
+ char *epos = pj_strchr(&acc_cfg->id, '>');
+ if (epos - bpos < 2) {
+ pj_assert(!"Unexpected invalid URI");
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+ entity.ptr = bpos+1;
+ entity.slen = epos - bpos - 1;
+ } else {
+ entity = acc_cfg->id;
+ }
+
+ /* Create and add PIDF message body */
+ status = pjsip_pres_create_pidf(tdata->pool, &pres_status,
+ &entity, &tdata->msg->body);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating PIDF for PUBLISH request",
+ status);
+ pjsip_tx_data_dec_ref(tdata);
+ goto on_error;
+ }
+ } else {
+ status = pjsip_publishc_unpublish(acc->publish_sess, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status);
+ goto on_error;
+ }
+ }
+
+ /* Add headers etc */
+ pjsua_process_msg_data(tdata, NULL);
+
+ /* Set Via sent-by */
+ if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
+ pjsip_publishc_set_via_sent_by(acc->publish_sess, &acc->via_addr,
+ acc->via_tp);
+ }
+
+ /* Send the PUBLISH request */
+ status = pjsip_publishc_send(acc->publish_sess, tdata);
+ if (status == PJ_EPENDING) {
+ PJ_LOG(3,(THIS_FILE, "Previous request is in progress, "
+ "PUBLISH request is queued"));
+ } else if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error sending PUBLISH request", status);
+ goto on_error;
+ }
+
+ acc->publish_state = acc->online_status;
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ if (acc->publish_sess) {
+ pjsip_publishc_destroy(acc->publish_sess);
+ acc->publish_sess = NULL;
+ }
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/* Create client publish session */
+pj_status_t pjsua_pres_init_publish_acc(int acc_id)
+{
+ const pj_str_t STR_PRESENCE = { "presence", 8 };
+ pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+ pj_status_t status;
+
+ /* Create and init client publication session */
+ if (acc_cfg->publish_enabled) {
+
+ /* Create client publication */
+ status = pjsip_publishc_create(pjsua_var.endpt, &acc_cfg->publish_opt,
+ acc, &publish_cb,
+ &acc->publish_sess);
+ if (status != PJ_SUCCESS) {
+ acc->publish_sess = NULL;
+ return status;
+ }
+
+ /* Initialize client publication */
+ status = pjsip_publishc_init(acc->publish_sess, &STR_PRESENCE,
+ &acc_cfg->id, &acc_cfg->id,
+ &acc_cfg->id,
+ PJSUA_PUBLISH_EXPIRATION);
+ if (status != PJ_SUCCESS) {
+ acc->publish_sess = NULL;
+ return status;
+ }
+
+ /* Add credential for authentication */
+ if (acc->cred_cnt) {
+ pjsip_publishc_set_credentials(acc->publish_sess, acc->cred_cnt,
+ acc->cred);
+ }
+
+ /* Set route-set */
+ pjsip_publishc_set_route_set(acc->publish_sess, &acc->route_set);
+
+ /* Send initial PUBLISH request */
+ if (acc->online_status != 0) {
+ status = send_publish(acc_id, PJ_TRUE);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ } else {
+ acc->publish_sess = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Init presence for account */
+pj_status_t pjsua_pres_init_acc(int acc_id)
+{
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+
+ /* Init presence subscription */
+ pj_list_init(&acc->pres_srv_list);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Unpublish presence publication */
+void pjsua_pres_unpublish(pjsua_acc *acc, unsigned flags)
+{
+ if (acc->publish_sess) {
+ pjsua_acc_config *acc_cfg = &acc->cfg;
+
+ acc->online_status = PJ_FALSE;
+
+ if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
+ send_publish(acc->index, PJ_FALSE);
+ }
+
+ /* By ticket #364, don't destroy the session yet (let the callback
+ destroy it)
+ if (acc->publish_sess) {
+ pjsip_publishc_destroy(acc->publish_sess);
+ acc->publish_sess = NULL;
+ }
+ */
+ acc_cfg->publish_enabled = PJ_FALSE;
+ }
+}
+
+/* Terminate server subscription for the account */
+void pjsua_pres_delete_acc(int acc_id, unsigned flags)
+{
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+ pjsua_srv_pres *uapres;
+
+ uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
+
+ /* Notify all subscribers that we're no longer available */
+ while (uapres != &acc->pres_srv_list) {
+
+ pjsip_pres_status pres_status;
+ pj_str_t reason = { "noresource", 10 };
+ pjsua_srv_pres *next;
+ pjsip_tx_data *tdata;
+
+ next = uapres->next;
+
+ pjsip_pres_get_status(uapres->sub, &pres_status);
+
+ pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status;
+ pjsip_pres_set_status(uapres->sub, &pres_status);
+
+ if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
+ if (pjsip_pres_notify(uapres->sub,
+ PJSIP_EVSUB_STATE_TERMINATED, NULL,
+ &reason, &tdata)==PJ_SUCCESS)
+ {
+ pjsip_pres_send_request(uapres->sub, tdata);
+ }
+ } else {
+ pjsip_pres_terminate(uapres->sub, PJ_FALSE);
+ }
+
+ uapres = next;
+ }
+
+ /* Clear server presence subscription list because account might be reused
+ * later. */
+ pj_list_init(&acc->pres_srv_list);
+
+ /* Terminate presence publication, if any */
+ pjsua_pres_unpublish(acc, flags);
+}
+
+
+/* Update server subscription (e.g. when our online status has changed) */
+void pjsua_pres_update_acc(int acc_id, pj_bool_t force)
+{
+ pjsua_acc *acc = &pjsua_var.acc[acc_id];
+ pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
+ pjsua_srv_pres *uapres;
+
+ uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
+
+ while (uapres != &acc->pres_srv_list) {
+
+ pjsip_pres_status pres_status;
+ pjsip_tx_data *tdata;
+
+ pjsip_pres_get_status(uapres->sub, &pres_status);
+
+ /* Only send NOTIFY once subscription is active. Some subscriptions
+ * may still be in NULL (when app is adding a new buddy while in the
+ * on_incoming_subscribe() callback) or PENDING (when user approval is
+ * being requested) state and we don't send NOTIFY to these subs until
+ * the user accepted the request.
+ */
+ if (pjsip_evsub_get_state(uapres->sub)==PJSIP_EVSUB_STATE_ACTIVE &&
+ (force || pres_status.info[0].basic_open != acc->online_status))
+ {
+
+ pres_status.info[0].basic_open = acc->online_status;
+ pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
+ sizeof(pjrpid_element));
+
+ pjsip_pres_set_status(uapres->sub, &pres_status);
+
+ if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS) {
+ pjsua_process_msg_data(tdata, NULL);
+ pjsip_pres_send_request(uapres->sub, tdata);
+ }
+ }
+
+ uapres = uapres->next;
+ }
+
+ /* Send PUBLISH if required. We only do this when we have a PUBLISH
+ * session. If we don't have a PUBLISH session, then it could be
+ * that we're waiting until registration has completed before we
+ * send the first PUBLISH.
+ */
+ if (acc_cfg->publish_enabled && acc->publish_sess) {
+ if (force || acc->publish_state != acc->online_status) {
+ send_publish(acc_id, PJ_TRUE);
+ }
+ }
+}
+
+
+
+/***************************************************************************
+ * Client subscription.
+ */
+
+static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry)
+{
+ pjsua_buddy *buddy = (pjsua_buddy*)entry->user_data;
+
+ PJ_UNUSED_ARG(th);
+
+ entry->id = PJ_FALSE;
+ pjsua_buddy_update_pres(buddy->index);
+}
+
+/* Reschedule subscription refresh timer or terminate the subscription
+ * refresh timer for the specified buddy.
+ */
+static void buddy_resubscribe(pjsua_buddy *buddy, pj_bool_t resched,
+ unsigned msec_interval)
+{
+ if (buddy->timer.id) {
+ pjsua_cancel_timer(&buddy->timer);
+ buddy->timer.id = PJ_FALSE;
+ }
+
+ if (resched) {
+ pj_time_val delay;
+
+ PJ_LOG(4,(THIS_FILE,
+ "Resubscribing buddy id %u in %u ms (reason: %.*s)",
+ buddy->index, msec_interval,
+ (int)buddy->term_reason.slen,
+ buddy->term_reason.ptr));
+
+ pj_timer_entry_init(&buddy->timer, 0, buddy, &buddy_timer_cb);
+ delay.sec = 0;
+ delay.msec = msec_interval;
+ pj_time_val_normalize(&delay);
+
+ if (pjsua_schedule_timer(&buddy->timer, &delay)==PJ_SUCCESS)
+ buddy->timer.id = PJ_TRUE;
+ }
+}
+
+/* Callback called when *client* subscription state has changed. */
+static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
+{
+ pjsua_buddy *buddy;
+
+ PJ_UNUSED_ARG(event);
+
+ /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
+ * a dialog attached to it, lock_buddy() will use the dialog
+ * lock, which we are currently holding!
+ */
+ buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+ if (buddy) {
+ PJ_LOG(4,(THIS_FILE,
+ "Presence subscription to %.*s is %s",
+ (int)pjsua_var.buddy[buddy->index].uri.slen,
+ pjsua_var.buddy[buddy->index].uri.ptr,
+ pjsip_evsub_get_state_name(sub)));
+ pj_log_push_indent();
+
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ int resub_delay = -1;
+
+ if (buddy->term_reason.ptr == NULL) {
+ buddy->term_reason.ptr = (char*)
+ pj_pool_alloc(buddy->pool,
+ PJSUA_BUDDY_SUB_TERM_REASON_LEN);
+ }
+ pj_strncpy(&buddy->term_reason,
+ pjsip_evsub_get_termination_reason(sub),
+ PJSUA_BUDDY_SUB_TERM_REASON_LEN);
+
+ buddy->term_code = 200;
+
+ /* Determine whether to resubscribe automatically */
+ if (event && event->type==PJSIP_EVENT_TSX_STATE) {
+ const pjsip_transaction *tsx = event->body.tsx_state.tsx;
+ if (pjsip_method_cmp(&tsx->method,
+ &pjsip_subscribe_method)==0)
+ {
+ buddy->term_code = tsx->status_code;
+ switch (tsx->status_code) {
+ case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST:
+ /* 481: we refreshed too late? resubscribe
+ * immediately.
+ */
+ /* But this must only happen when the 481 is received
+ * on subscription refresh request. We MUST NOT try to
+ * resubscribe automatically if the 481 is received
+ * on the initial SUBSCRIBE (if server returns this
+ * response for some reason).
+ */
+ if (buddy->dlg->remote.contact)
+ resub_delay = 500;
+ break;
+ }
+ } else if (pjsip_method_cmp(&tsx->method,
+ &pjsip_notify_method)==0)
+ {
+ if (pj_stricmp2(&buddy->term_reason, "deactivated")==0 ||
+ pj_stricmp2(&buddy->term_reason, "timeout")==0) {
+ /* deactivated: The subscription has been terminated,
+ * but the subscriber SHOULD retry immediately with
+ * a new subscription.
+ */
+ /* timeout: The subscription has been terminated
+ * because it was not refreshed before it expired.
+ * Clients MAY re-subscribe immediately. The
+ * "retry-after" parameter has no semantics for
+ * "timeout".
+ */
+ resub_delay = 500;
+ }
+ else if (pj_stricmp2(&buddy->term_reason, "probation")==0||
+ pj_stricmp2(&buddy->term_reason, "giveup")==0) {
+ /* probation: The subscription has been terminated,
+ * but the client SHOULD retry at some later time.
+ * If a "retry-after" parameter is also present, the
+ * client SHOULD wait at least the number of seconds
+ * specified by that parameter before attempting to re-
+ * subscribe.
+ */
+ /* giveup: The subscription has been terminated because
+ * the notifier could not obtain authorization in a
+ * timely fashion. If a "retry-after" parameter is
+ * also present, the client SHOULD wait at least the
+ * number of seconds specified by that parameter before
+ * attempting to re-subscribe; otherwise, the client
+ * MAY retry immediately, but will likely get put back
+ * into pending state.
+ */
+ const pjsip_sub_state_hdr *sub_hdr;
+ pj_str_t sub_state = { "Subscription-State", 18 };
+ const pjsip_msg *msg;
+
+ msg = event->body.tsx_state.src.rdata->msg_info.msg;
+ sub_hdr = (const pjsip_sub_state_hdr*)
+ pjsip_msg_find_hdr_by_name(msg, &sub_state,
+ NULL);
+ if (sub_hdr && sub_hdr->retry_after > 0)
+ resub_delay = sub_hdr->retry_after * 1000;
+ }
+
+ }
+ }
+
+ /* For other cases of subscription termination, if resubscribe
+ * timer is not set, schedule with default expiration (plus minus
+ * some random value, to avoid sending SUBSCRIBEs all at once)
+ */
+ if (resub_delay == -1) {
+ pj_assert(PJSUA_PRES_TIMER >= 3);
+ resub_delay = PJSUA_PRES_TIMER*1000 - 2500 + (pj_rand()%5000);
+ }
+
+ buddy_resubscribe(buddy, PJ_TRUE, resub_delay);
+
+ } else {
+ /* This will clear the last termination code/reason */
+ buddy->term_code = 0;
+ buddy->term_reason.slen = 0;
+ }
+
+ /* Call callbacks */
+ if (pjsua_var.ua_cfg.cb.on_buddy_evsub_state)
+ (*pjsua_var.ua_cfg.cb.on_buddy_evsub_state)(buddy->index, sub,
+ event);
+
+ if (pjsua_var.ua_cfg.cb.on_buddy_state)
+ (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index);
+
+ /* Clear subscription */
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ buddy->sub = NULL;
+ buddy->status.info_cnt = 0;
+ buddy->dlg = NULL;
+ pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
+ }
+
+ pj_log_pop_indent();
+ }
+}
+
+
+/* Callback when transaction state has changed. */
+static void pjsua_evsub_on_tsx_state(pjsip_evsub *sub,
+ pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pjsua_buddy *buddy;
+ pjsip_contact_hdr *contact_hdr;
+
+ /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
+ * a dialog attached to it, lock_buddy() will use the dialog
+ * lock, which we are currently holding!
+ */
+ buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+ if (!buddy) {
+ return;
+ }
+
+ /* We only use this to update buddy's Contact, when it's not
+ * set.
+ */
+ if (buddy->contact.slen != 0) {
+ /* Contact already set */
+ return;
+ }
+
+ /* Only care about 2xx response to outgoing SUBSCRIBE */
+ if (tsx->status_code/100 != 2 ||
+ tsx->role != PJSIP_UAC_ROLE ||
+ event->type != PJSIP_EVENT_RX_MSG ||
+ pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method())!=0)
+ {
+ return;
+ }
+
+ /* Find contact header. */
+ contact_hdr = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg,
+ PJSIP_H_CONTACT, NULL);
+ if (!contact_hdr || !contact_hdr->uri) {
+ return;
+ }
+
+ buddy->contact.ptr = (char*)
+ pj_pool_alloc(buddy->pool, PJSIP_MAX_URL_SIZE);
+ buddy->contact.slen = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR,
+ contact_hdr->uri,
+ buddy->contact.ptr,
+ PJSIP_MAX_URL_SIZE);
+ if (buddy->contact.slen < 0)
+ buddy->contact.slen = 0;
+}
+
+
+/* Callback called when we receive NOTIFY */
+static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsua_buddy *buddy;
+
+ /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
+ * a dialog attached to it, lock_buddy() will use the dialog
+ * lock, which we are currently holding!
+ */
+ buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+ if (buddy) {
+ /* Update our info. */
+ pjsip_pres_get_status(sub, &buddy->status);
+ }
+
+ /* The default is to send 200 response to NOTIFY.
+ * Just leave it there..
+ */
+ PJ_UNUSED_ARG(rdata);
+ PJ_UNUSED_ARG(p_st_code);
+ PJ_UNUSED_ARG(p_st_text);
+ PJ_UNUSED_ARG(res_hdr);
+ PJ_UNUSED_ARG(p_body);
+}
+
+
+/* It does what it says.. */
+static void subscribe_buddy_presence(pjsua_buddy_id buddy_id)
+{
+ pjsip_evsub_user pres_callback;
+ pj_pool_t *tmp_pool = NULL;
+ pjsua_buddy *buddy;
+ int acc_id;
+ pjsua_acc *acc;
+ pj_str_t contact;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Event subscription callback. */
+ pj_bzero(&pres_callback, sizeof(pres_callback));
+ pres_callback.on_evsub_state = &pjsua_evsub_on_state;
+ pres_callback.on_tsx_state = &pjsua_evsub_on_tsx_state;
+ pres_callback.on_rx_notify = &pjsua_evsub_on_rx_notify;
+
+ buddy = &pjsua_var.buddy[buddy_id];
+ acc_id = pjsua_acc_find_for_outgoing(&buddy->uri);
+
+ acc = &pjsua_var.acc[acc_id];
+
+ PJ_LOG(4,(THIS_FILE, "Buddy %d: subscribing presence,using account %d..",
+ buddy_id, acc_id));
+ pj_log_push_indent();
+
+ /* Generate suitable Contact header unless one is already set in
+ * the account
+ */
+ if (acc->contact.slen) {
+ contact = acc->contact;
+ } else {
+ tmp_pool = pjsua_pool_create("tmpbuddy", 512, 256);
+
+ status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
+ acc_id, &buddy->uri);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to generate Contact header",
+ status);
+ pj_pool_release(tmp_pool);
+ pj_log_pop_indent();
+ return;
+ }
+ }
+
+ /* Create UAC dialog */
+ status = pjsip_dlg_create_uac( pjsip_ua_instance(),
+ &acc->cfg.id,
+ &contact,
+ &buddy->uri,
+ NULL, &buddy->dlg);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create dialog",
+ status);
+ if (tmp_pool) pj_pool_release(tmp_pool);
+ pj_log_pop_indent();
+ return;
+ }
+
+ /* Increment the dialog's lock otherwise when presence session creation
+ * fails the dialog will be destroyed prematurely.
+ */
+ pjsip_dlg_inc_lock(buddy->dlg);
+
+ if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0)
+ pjsip_dlg_set_via_sent_by(buddy->dlg, &acc->via_addr, acc->via_tp);
+
+ status = pjsip_pres_create_uac( buddy->dlg, &pres_callback,
+ PJSIP_EVSUB_NO_EVENT_ID, &buddy->sub);
+ if (status != PJ_SUCCESS) {
+ buddy->sub = NULL;
+ pjsua_perror(THIS_FILE, "Unable to create presence client",
+ status);
+ /* This should destroy the dialog since there's no session
+ * referencing it
+ */
+ if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
+ if (tmp_pool) pj_pool_release(tmp_pool);
+ pj_log_pop_indent();
+ return;
+ }
+
+ /* If account is locked to specific transport, then lock dialog
+ * to this transport too.
+ */
+ if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
+ pjsip_tpselector tp_sel;
+
+ pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
+ pjsip_dlg_set_transport(buddy->dlg, &tp_sel);
+ }
+
+ /* Set route-set */
+ if (!pj_list_empty(&acc->route_set)) {
+ pjsip_dlg_set_route_set(buddy->dlg, &acc->route_set);
+ }
+
+ /* Set credentials */
+ if (acc->cred_cnt) {
+ pjsip_auth_clt_set_credentials( &buddy->dlg->auth_sess,
+ acc->cred_cnt, acc->cred);
+ }
+
+ /* Set authentication preference */
+ pjsip_auth_clt_set_prefs(&buddy->dlg->auth_sess, &acc->cfg.auth_pref);
+
+ pjsip_evsub_set_mod_data(buddy->sub, pjsua_var.mod.id, buddy);
+
+ status = pjsip_pres_initiate(buddy->sub, -1, &tdata);
+ if (status != PJ_SUCCESS) {
+ if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
+ if (buddy->sub) {
+ pjsip_pres_terminate(buddy->sub, PJ_FALSE);
+ }
+ buddy->sub = NULL;
+ pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE",
+ status);
+ if (tmp_pool) pj_pool_release(tmp_pool);
+ pj_log_pop_indent();
+ return;
+ }
+
+ pjsua_process_msg_data(tdata, NULL);
+
+ status = pjsip_pres_send_request(buddy->sub, tdata);
+ if (status != PJ_SUCCESS) {
+ if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
+ if (buddy->sub) {
+ pjsip_pres_terminate(buddy->sub, PJ_FALSE);
+ }
+ buddy->sub = NULL;
+ pjsua_perror(THIS_FILE, "Unable to send initial SUBSCRIBE",
+ status);
+ if (tmp_pool) pj_pool_release(tmp_pool);
+ pj_log_pop_indent();
+ return;
+ }
+
+ pjsip_dlg_dec_lock(buddy->dlg);
+ if (tmp_pool) pj_pool_release(tmp_pool);
+ pj_log_pop_indent();
+}
+
+
+/* It does what it says... */
+static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id)
+{
+ pjsua_buddy *buddy;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ buddy = &pjsua_var.buddy[buddy_id];
+
+ if (buddy->sub == NULL)
+ return;
+
+ if (pjsip_evsub_get_state(buddy->sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ buddy->sub = NULL;
+ return;
+ }
+
+ PJ_LOG(5,(THIS_FILE, "Buddy %d: unsubscribing..", buddy_id));
+ pj_log_push_indent();
+
+ status = pjsip_pres_initiate( buddy->sub, 0, &tdata);
+ if (status == PJ_SUCCESS) {
+ pjsua_process_msg_data(tdata, NULL);
+ status = pjsip_pres_send_request( buddy->sub, tdata );
+ }
+
+ if (status != PJ_SUCCESS && buddy->sub) {
+ pjsip_pres_terminate(buddy->sub, PJ_FALSE);
+ buddy->sub = NULL;
+ pjsua_perror(THIS_FILE, "Unable to unsubscribe presence",
+ status);
+ }
+
+ pj_log_pop_indent();
+}
+
+/* It does what it says.. */
+static pj_status_t refresh_client_subscriptions(void)
+{
+ unsigned i;
+ pj_status_t status;
+
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+ struct buddy_lock lck;
+
+ if (!pjsua_buddy_is_valid(i))
+ continue;
+
+ status = lock_buddy("refresh_client_subscriptions()", i, &lck, 0);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (pjsua_var.buddy[i].monitor && !pjsua_var.buddy[i].sub) {
+ subscribe_buddy_presence(i);
+
+ } else if (!pjsua_var.buddy[i].monitor && pjsua_var.buddy[i].sub) {
+ unsubscribe_buddy_presence(i);
+
+ }
+
+ unlock_buddy(&lck);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/***************************************************************************
+ * MWI
+ */
+/* Callback called when *client* subscription state has changed. */
+static void mwi_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
+{
+ pjsua_acc *acc;
+
+ PJ_UNUSED_ARG(event);
+
+ /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
+ * a dialog attached to it, lock_buddy() will use the dialog
+ * lock, which we are currently holding!
+ */
+ acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+ if (!acc)
+ return;
+
+ PJ_LOG(4,(THIS_FILE,
+ "MWI subscription for %.*s is %s",
+ (int)acc->cfg.id.slen, acc->cfg.id.ptr,
+ pjsip_evsub_get_state_name(sub)));
+
+ /* Call callback */
+ if (pjsua_var.ua_cfg.cb.on_mwi_state) {
+ (*pjsua_var.ua_cfg.cb.on_mwi_state)(acc->index, sub);
+ }
+
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ /* Clear subscription */
+ acc->mwi_dlg = NULL;
+ acc->mwi_sub = NULL;
+ pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
+
+ }
+}
+
+/* Callback called when we receive NOTIFY */
+static void mwi_evsub_on_rx_notify(pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsua_mwi_info mwi_info;
+ pjsua_acc *acc;
+
+ PJ_UNUSED_ARG(p_st_code);
+ PJ_UNUSED_ARG(p_st_text);
+ PJ_UNUSED_ARG(res_hdr);
+ PJ_UNUSED_ARG(p_body);
+
+ acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+ if (!acc)
+ return;
+
+ /* Construct mwi_info */
+ pj_bzero(&mwi_info, sizeof(mwi_info));
+ mwi_info.evsub = sub;
+ mwi_info.rdata = rdata;
+
+ PJ_LOG(4,(THIS_FILE, "MWI got NOTIFY.."));
+ pj_log_push_indent();
+
+ /* Call callback */
+ if (pjsua_var.ua_cfg.cb.on_mwi_info) {
+ (*pjsua_var.ua_cfg.cb.on_mwi_info)(acc->index, &mwi_info);
+ }
+
+ pj_log_pop_indent();
+}
+
+
+/* Event subscription callback. */
+static pjsip_evsub_user mwi_cb =
+{
+ &mwi_evsub_on_state,
+ NULL, /* on_tsx_state: not interested */
+ NULL, /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless
+ * we want to authenticate
+ */
+
+ &mwi_evsub_on_rx_notify,
+
+ NULL, /* on_client_refresh: Use default behaviour, which is to
+ * refresh client subscription. */
+
+ NULL, /* on_server_timeout: Use default behaviour, which is to send
+ * NOTIFY to terminate.
+ */
+};
+
+pj_status_t pjsua_start_mwi(pjsua_acc_id acc_id, pj_bool_t force_renew)
+{
+ pjsua_acc *acc;
+ pj_pool_t *tmp_pool = NULL;
+ pj_str_t contact;
+ pjsip_tx_data *tdata;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc)
+ && pjsua_var.acc[acc_id].valid, PJ_EINVAL);
+
+ acc = &pjsua_var.acc[acc_id];
+
+ if (!acc->cfg.mwi_enabled) {
+ if (acc->mwi_sub) {
+ /* Terminate MWI subscription */
+ pjsip_evsub *sub = acc->mwi_sub;
+
+ /* Detach sub from this account */
+ acc->mwi_sub = NULL;
+ acc->mwi_dlg = NULL;
+ pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
+
+ /* Unsubscribe */
+ status = pjsip_mwi_initiate(sub, 0, &tdata);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_mwi_send_request(sub, tdata);
+ }
+ }
+ return status;
+ }
+
+ /* Subscription is already active */
+ if (acc->mwi_sub) {
+ if (!force_renew)
+ return PJ_SUCCESS;
+
+ /* Update MWI subscription */
+ pj_assert(acc->mwi_dlg);
+ pjsip_dlg_inc_lock(acc->mwi_dlg);
+
+ status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata);
+ if (status == PJ_SUCCESS) {
+ pjsua_process_msg_data(tdata, NULL);
+ status = pjsip_pres_send_request(acc->mwi_sub, tdata);
+ }
+
+ pjsip_dlg_dec_lock(acc->mwi_dlg);
+ return status;
+ }
+
+ PJ_LOG(4,(THIS_FILE, "Starting MWI subscription.."));
+ pj_log_push_indent();
+
+ /* Generate suitable Contact header unless one is already set in
+ * the account
+ */
+ if (acc->contact.slen) {
+ contact = acc->contact;
+ } else {
+ tmp_pool = pjsua_pool_create("tmpmwi", 512, 256);
+ status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
+ acc->index, &acc->cfg.id);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to generate Contact header",
+ status);
+ goto on_return;
+ }
+ }
+
+ /* Create UAC dialog */
+ status = pjsip_dlg_create_uac( pjsip_ua_instance(),
+ &acc->cfg.id,
+ &contact,
+ &acc->cfg.id,
+ NULL, &acc->mwi_dlg);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create dialog", status);
+ goto on_return;
+ }
+
+ /* Increment the dialog's lock otherwise when presence session creation
+ * fails the dialog will be destroyed prematurely.
+ */
+ pjsip_dlg_inc_lock(acc->mwi_dlg);
+
+ if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0)
+ pjsip_dlg_set_via_sent_by(acc->mwi_dlg, &acc->via_addr, acc->via_tp);
+
+ /* Create UAC subscription */
+ status = pjsip_mwi_create_uac(acc->mwi_dlg, &mwi_cb,
+ PJSIP_EVSUB_NO_EVENT_ID, &acc->mwi_sub);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating MWI subscription", status);
+ if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
+ goto on_return;
+ }
+
+ /* If account is locked to specific transport, then lock dialog
+ * to this transport too.
+ */
+ if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
+ pjsip_tpselector tp_sel;
+
+ pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
+ pjsip_dlg_set_transport(acc->mwi_dlg, &tp_sel);
+ }
+
+ /* Set route-set */
+ if (!pj_list_empty(&acc->route_set)) {
+ pjsip_dlg_set_route_set(acc->mwi_dlg, &acc->route_set);
+ }
+
+ /* Set credentials */
+ if (acc->cred_cnt) {
+ pjsip_auth_clt_set_credentials( &acc->mwi_dlg->auth_sess,
+ acc->cred_cnt, acc->cred);
+ }
+
+ /* Set authentication preference */
+ pjsip_auth_clt_set_prefs(&acc->mwi_dlg->auth_sess, &acc->cfg.auth_pref);
+
+ pjsip_evsub_set_mod_data(acc->mwi_sub, pjsua_var.mod.id, acc);
+
+ status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata);
+ if (status != PJ_SUCCESS) {
+ if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
+ if (acc->mwi_sub) {
+ pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE);
+ }
+ acc->mwi_sub = NULL;
+ acc->mwi_dlg = NULL;
+ pjsua_perror(THIS_FILE, "Unable to create initial MWI SUBSCRIBE",
+ status);
+ goto on_return;
+ }
+
+ pjsua_process_msg_data(tdata, NULL);
+
+ status = pjsip_pres_send_request(acc->mwi_sub, tdata);
+ if (status != PJ_SUCCESS) {
+ if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
+ if (acc->mwi_sub) {
+ pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE);
+ }
+ acc->mwi_sub = NULL;
+ acc->mwi_dlg = NULL;
+ pjsua_perror(THIS_FILE, "Unable to send initial MWI SUBSCRIBE",
+ status);
+ goto on_return;
+ }
+
+ pjsip_dlg_dec_lock(acc->mwi_dlg);
+
+on_return:
+ if (tmp_pool) pj_pool_release(tmp_pool);
+
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/***************************************************************************
+ * Unsolicited MWI
+ */
+static pj_bool_t unsolicited_mwi_on_rx_request(pjsip_rx_data *rdata)
+{
+ pjsip_msg *msg = rdata->msg_info.msg;
+ pj_str_t EVENT_HDR = { "Event", 5 };
+ pj_str_t MWI = { "message-summary", 15 };
+ pjsip_event_hdr *eh;
+
+ if (pjsip_method_cmp(&msg->line.req.method, &pjsip_notify_method)!=0) {
+ /* Only interested with NOTIFY request */
+ return PJ_FALSE;
+ }
+
+ eh = (pjsip_event_hdr*) pjsip_msg_find_hdr_by_name(msg, &EVENT_HDR, NULL);
+ if (!eh) {
+ /* Something wrong with the request, it has no Event hdr */
+ return PJ_FALSE;
+ }
+
+ if (pj_stricmp(&eh->event_type, &MWI) != 0) {
+ /* Not MWI event */
+ return PJ_FALSE;
+ }
+
+ PJ_LOG(4,(THIS_FILE, "Got unsolicited NOTIFY from %s:%d..",
+ rdata->pkt_info.src_name, rdata->pkt_info.src_port));
+ pj_log_push_indent();
+
+ /* Got unsolicited MWI request, respond with 200/OK first */
+ pjsip_endpt_respond(pjsua_get_pjsip_endpt(), NULL, rdata, 200, NULL,
+ NULL, NULL, NULL);
+
+
+ /* Call callback */
+ if (pjsua_var.ua_cfg.cb.on_mwi_info) {
+ pjsua_acc_id acc_id;
+ pjsua_mwi_info mwi_info;
+
+ acc_id = pjsua_acc_find_for_incoming(rdata);
+
+ pj_bzero(&mwi_info, sizeof(mwi_info));
+ mwi_info.rdata = rdata;
+
+ (*pjsua_var.ua_cfg.cb.on_mwi_info)(acc_id, &mwi_info);
+ }
+
+ pj_log_pop_indent();
+ return PJ_TRUE;
+}
+
+/* The module instance. */
+static pjsip_module pjsua_unsolicited_mwi_mod =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-unsolicited-mwi", 19 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &unsolicited_mwi_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+static pj_status_t enable_unsolicited_mwi(void)
+{
+ pj_status_t status;
+
+ status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(),
+ &pjsua_unsolicited_mwi_mod);
+ if (status != PJ_SUCCESS)
+ pjsua_perror(THIS_FILE, "Error registering unsolicited MWI module",
+ status);
+
+ return status;
+}
+
+
+
+/***************************************************************************/
+
+/* Timer callback to re-create client subscription */
+static void pres_timer_cb(pj_timer_heap_t *th,
+ pj_timer_entry *entry)
+{
+ unsigned i;
+ pj_time_val delay = { PJSUA_PRES_TIMER, 0 };
+
+ entry->id = PJ_FALSE;
+
+ /* Retry failed PUBLISH and MWI SUBSCRIBE requests */
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ pjsua_acc *acc = &pjsua_var.acc[i];
+
+ /* Acc may not be ready yet, otherwise assertion will happen */
+ if (!pjsua_acc_is_valid(i))
+ continue;
+
+ /* Retry PUBLISH */
+ if (acc->cfg.publish_enabled && acc->publish_sess==NULL)
+ pjsua_pres_init_publish_acc(acc->index);
+
+ /* Re-subscribe MWI subscription if it's terminated prematurely */
+ if (acc->cfg.mwi_enabled && !acc->mwi_sub)
+ pjsua_start_mwi(acc->index, PJ_FALSE);
+ }
+
+ /* #937: No need to do bulk client refresh, as buddies have their
+ * own individual timer now.
+ */
+ //refresh_client_subscriptions();
+
+ pjsip_endpt_schedule_timer(pjsua_var.endpt, entry, &delay);
+ entry->id = PJ_TRUE;
+
+ PJ_UNUSED_ARG(th);
+}
+
+
+/*
+ * Init presence
+ */
+pj_status_t pjsua_pres_init()
+{
+ unsigned i;
+ pj_status_t status;
+
+ status = pjsip_endpt_register_module( pjsua_var.endpt, &mod_pjsua_pres);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to register pjsua presence module",
+ status);
+ }
+
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+ reset_buddy(i);
+ }
+
+ return status;
+}
+
+
+/*
+ * Start presence subsystem.
+ */
+pj_status_t pjsua_pres_start(void)
+{
+ /* Start presence timer to re-subscribe to buddy's presence when
+ * subscription has failed.
+ */
+ if (pjsua_var.pres_timer.id == PJ_FALSE) {
+ pj_time_val pres_interval = {PJSUA_PRES_TIMER, 0};
+
+ pjsua_var.pres_timer.cb = &pres_timer_cb;
+ pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.pres_timer,
+ &pres_interval);
+ pjsua_var.pres_timer.id = PJ_TRUE;
+ }
+
+ if (pjsua_var.ua_cfg.enable_unsolicited_mwi) {
+ pj_status_t status = enable_unsolicited_mwi();
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Shutdown presence.
+ */
+void pjsua_pres_shutdown(unsigned flags)
+{
+ unsigned i;
+
+ PJ_LOG(4,(THIS_FILE, "Shutting down presence.."));
+ pj_log_push_indent();
+
+ if (pjsua_var.pres_timer.id != 0) {
+ pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.pres_timer);
+ pjsua_var.pres_timer.id = PJ_FALSE;
+ }
+
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (!pjsua_var.acc[i].valid)
+ continue;
+ pjsua_pres_delete_acc(i, flags);
+ }
+
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+ pjsua_var.buddy[i].monitor = 0;
+ }
+
+ if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
+ refresh_client_subscriptions();
+
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (pjsua_var.acc[i].valid)
+ pjsua_pres_update_acc(i, PJ_FALSE);
+ }
+ }
+
+ pj_log_pop_indent();
+}
diff --git a/pjsip/src/pjsua-lib/pjsua_vid.c b/pjsip/src/pjsua-lib/pjsua_vid.c
new file mode 100644
index 0000000..eb74ee1
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_vid.c
@@ -0,0 +1,2166 @@
+/* $Id: pjsua_vid.c 4071 2012-04-24 05:40:32Z nanang $ */
+/*
+ * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_internal.h>
+
+#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0
+
+#define THIS_FILE "pjsua_vid.c"
+
+#if PJSUA_HAS_VIDEO
+
+#define ENABLE_EVENT 1
+#define VID_TEE_MAX_PORT (PJSUA_MAX_CALLS + 1)
+
+#define PJSUA_SHOW_WINDOW 1
+#define PJSUA_HIDE_WINDOW 0
+
+
+static void free_vid_win(pjsua_vid_win_id wid);
+
+/*****************************************************************************
+ * pjsua video subsystem.
+ */
+pj_status_t pjsua_vid_subsys_init(void)
+{
+ unsigned i;
+ pj_status_t status;
+
+ PJ_LOG(4,(THIS_FILE, "Initializing video subsystem.."));
+ pj_log_push_indent();
+
+ status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error creating PJMEDIA video format manager"));
+ goto on_error;
+ }
+
+ status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error creating PJMEDIA converter manager"));
+ goto on_error;
+ }
+
+ status = pjmedia_event_mgr_create(pjsua_var.pool, 0, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error creating PJMEDIA event manager"));
+ goto on_error;
+ }
+
+ status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error creating PJMEDIA video codec manager"));
+ goto on_error;
+ }
+
+#if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_VID_CODEC
+ status = pjmedia_codec_ffmpeg_vid_init(NULL, &pjsua_var.cp.factory);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error initializing ffmpeg library"));
+ goto on_error;
+ }
+#endif
+
+ status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error creating PJMEDIA video subsystem"));
+ goto on_error;
+ }
+
+ for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+ if (pjsua_var.win[i].pool == NULL) {
+ pjsua_var.win[i].pool = pjsua_pool_create("win%p", 512, 512);
+ if (pjsua_var.win[i].pool == NULL) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ }
+ }
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ pj_log_pop_indent();
+ return status;
+}
+
+pj_status_t pjsua_vid_subsys_start(void)
+{
+ return PJ_SUCCESS;
+}
+
+pj_status_t pjsua_vid_subsys_destroy(void)
+{
+ unsigned i;
+
+ PJ_LOG(4,(THIS_FILE, "Destroying video subsystem.."));
+ pj_log_push_indent();
+
+ for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+ if (pjsua_var.win[i].pool) {
+ free_vid_win(i);
+ pj_pool_release(pjsua_var.win[i].pool);
+ pjsua_var.win[i].pool = NULL;
+ }
+ }
+
+ pjmedia_vid_dev_subsys_shutdown();
+
+#if PJMEDIA_HAS_FFMPEG_VID_CODEC
+ pjmedia_codec_ffmpeg_vid_deinit();
+#endif
+
+ if (pjmedia_vid_codec_mgr_instance())
+ pjmedia_vid_codec_mgr_destroy(NULL);
+
+ if (pjmedia_converter_mgr_instance())
+ pjmedia_converter_mgr_destroy(NULL);
+
+ if (pjmedia_event_mgr_instance())
+ pjmedia_event_mgr_destroy(NULL);
+
+ if (pjmedia_video_format_mgr_instance())
+ pjmedia_video_format_mgr_destroy(NULL);
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(const char*) pjsua_vid_win_type_name(pjsua_vid_win_type wt)
+{
+ const char *win_type_names[] = {
+ "none",
+ "preview",
+ "stream"
+ };
+
+ return (wt < PJ_ARRAY_SIZE(win_type_names)) ? win_type_names[wt] : "??";
+}
+
+PJ_DEF(void)
+pjsua_call_vid_strm_op_param_default(pjsua_call_vid_strm_op_param *param)
+{
+ pj_bzero(param, sizeof(*param));
+ param->med_idx = -1;
+ param->dir = PJMEDIA_DIR_ENCODING_DECODING;
+ param->cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV;
+}
+
+PJ_DEF(void) pjsua_vid_preview_param_default(pjsua_vid_preview_param *p)
+{
+ p->rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV;
+ p->show = PJ_TRUE;
+ p->wnd_flags = 0;
+}
+
+
+/*****************************************************************************
+ * Devices.
+ */
+
+/*
+ * Get the number of video devices installed in the system.
+ */
+PJ_DEF(unsigned) pjsua_vid_dev_count(void)
+{
+ return pjmedia_vid_dev_count();
+}
+
+/*
+ * Retrieve the video device info for the specified device index.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_dev_get_info(pjmedia_vid_dev_index id,
+ pjmedia_vid_dev_info *vdi)
+{
+ return pjmedia_vid_dev_get_info(id, vdi);
+}
+
+/*
+ * Enum all video devices installed in the system.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[],
+ unsigned *count)
+{
+ unsigned i, dev_count;
+
+ dev_count = pjmedia_vid_dev_count();
+
+ if (dev_count > *count) dev_count = *count;
+
+ for (i=0; i<dev_count; ++i) {
+ pj_status_t status;
+
+ status = pjmedia_vid_dev_get_info(i, &info[i]);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ *count = dev_count;
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Codecs.
+ */
+
+static pj_status_t find_codecs_with_rtp_packing(
+ const pj_str_t *codec_id,
+ unsigned *count,
+ const pjmedia_vid_codec_info *p_info[])
+{
+ const pjmedia_vid_codec_info *info[32];
+ unsigned i, j, count_ = PJ_ARRAY_SIZE(info);
+ pj_status_t status;
+
+ status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id,
+ &count_, info, NULL);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ for (i = 0, j = 0; i < count_ && j<*count; ++i) {
+ if ((info[i]->packings & PJMEDIA_VID_PACKING_PACKETS) == 0)
+ continue;
+ p_info[j++] = info[i];
+ }
+ *count = j;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Enum all supported video codecs in the system.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[],
+ unsigned *p_count )
+{
+ pjmedia_vid_codec_info info[32];
+ unsigned i, j, count, prio[32];
+ pj_status_t status;
+
+ count = PJ_ARRAY_SIZE(info);
+ status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio);
+ if (status != PJ_SUCCESS) {
+ *p_count = 0;
+ return status;
+ }
+
+ for (i=0, j=0; i<count && j<*p_count; ++i) {
+ if (info[i].packings & PJMEDIA_VID_PACKING_PACKETS) {
+ pj_bzero(&id[j], sizeof(pjsua_codec_info));
+
+ pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_));
+ id[j].codec_id = pj_str(id[j].buf_);
+ id[j].priority = (pj_uint8_t) prio[i];
+
+ if (id[j].codec_id.slen < sizeof(id[j].buf_)) {
+ id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1;
+ pj_strncpy(&id[j].desc, &info[i].encoding_desc,
+ sizeof(id[j].buf_) - id[j].codec_id.slen - 1);
+ }
+
+ ++j;
+ }
+ }
+
+ *p_count = j;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Change video codec priority.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id,
+ pj_uint8_t priority )
+{
+ const pj_str_t all = { NULL, 0 };
+
+ if (codec_id->slen==1 && *codec_id->ptr=='*')
+ codec_id = &all;
+
+ return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id,
+ priority);
+}
+
+
+/*
+ * Get video codec parameters.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_codec_get_param(
+ const pj_str_t *codec_id,
+ pjmedia_vid_codec_param *param)
+{
+ const pjmedia_vid_codec_info *info[2];
+ unsigned count = 2;
+ pj_status_t status;
+
+ status = find_codecs_with_rtp_packing(codec_id, &count, info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (count != 1)
+ return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
+
+ status = pjmedia_vid_codec_mgr_get_default_param(NULL, info[0], param);
+ return status;
+}
+
+
+/*
+ * Set video codec parameters.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_codec_set_param(
+ const pj_str_t *codec_id,
+ const pjmedia_vid_codec_param *param)
+{
+ const pjmedia_vid_codec_info *info[2];
+ unsigned count = 2;
+ pj_status_t status;
+
+ status = find_codecs_with_rtp_packing(codec_id, &count, info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (count != 1)
+ return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND);
+
+ status = pjmedia_vid_codec_mgr_set_default_param(NULL, info[0], param);
+ return status;
+}
+
+
+/*****************************************************************************
+ * Preview
+ */
+
+static pjsua_vid_win_id vid_preview_get_win(pjmedia_vid_dev_index id,
+ pj_bool_t running_only)
+{
+ pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+ unsigned i;
+
+ PJSUA_LOCK();
+
+ /* Get real capture ID, if set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV */
+ if (id == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
+ pjmedia_vid_dev_info info;
+ pjmedia_vid_dev_get_info(id, &info);
+ id = info.id;
+ }
+
+ for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+ pjsua_vid_win *w = &pjsua_var.win[i];
+ if (w->type == PJSUA_WND_TYPE_PREVIEW && w->preview_cap_id == id) {
+ wid = i;
+ break;
+ }
+ }
+
+ if (wid != PJSUA_INVALID_ID && running_only) {
+ pjsua_vid_win *w = &pjsua_var.win[wid];
+ wid = w->preview_running ? wid : PJSUA_INVALID_ID;
+ }
+
+ PJSUA_UNLOCK();
+
+ return wid;
+}
+
+/*
+ * NOTE: internal function don't use this!!! Use vid_preview_get_win()
+ * instead. This is because this function will only return window ID
+ * if preview is currently running.
+ */
+PJ_DEF(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id)
+{
+ return vid_preview_get_win(id, PJ_TRUE);
+}
+
+PJ_DEF(void) pjsua_vid_win_reset(pjsua_vid_win_id wid)
+{
+ pjsua_vid_win *w = &pjsua_var.win[wid];
+ pj_pool_t *pool = w->pool;
+
+ pj_bzero(w, sizeof(*w));
+ if (pool) pj_pool_reset(pool);
+ w->ref_cnt = 0;
+ w->pool = pool;
+ w->preview_cap_id = PJMEDIA_VID_INVALID_DEV;
+}
+
+/* Allocate and initialize pjsua video window:
+ * - If the type is preview, video capture, tee, and render
+ * will be instantiated.
+ * - If the type is stream, only renderer will be created.
+ */
+static pj_status_t create_vid_win(pjsua_vid_win_type type,
+ const pjmedia_format *fmt,
+ pjmedia_vid_dev_index rend_id,
+ pjmedia_vid_dev_index cap_id,
+ pj_bool_t show,
+ unsigned wnd_flags,
+ pjsua_vid_win_id *id)
+{
+ pj_bool_t enable_native_preview;
+ pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+ pjsua_vid_win *w = NULL;
+ pjmedia_vid_port_param vp_param;
+ pjmedia_format fmt_;
+ pj_status_t status;
+ unsigned i;
+
+ enable_native_preview = pjsua_var.media_cfg.vid_preview_enable_native;
+
+ PJ_LOG(4,(THIS_FILE,
+ "Creating video window: type=%s, cap_id=%d, rend_id=%d",
+ pjsua_vid_win_type_name(type), cap_id, rend_id));
+ pj_log_push_indent();
+
+ /* If type is preview, check if it exists already */
+ if (type == PJSUA_WND_TYPE_PREVIEW) {
+ wid = vid_preview_get_win(cap_id, PJ_FALSE);
+ if (wid != PJSUA_INVALID_ID) {
+ /* Yes, it exists */
+ /* Show/hide window */
+ pjmedia_vid_dev_stream *strm;
+ pj_bool_t hide = !show;
+
+ w = &pjsua_var.win[wid];
+
+ PJ_LOG(4,(THIS_FILE,
+ "Window already exists for cap_dev=%d, returning wid=%d",
+ cap_id, wid));
+
+
+ if (w->is_native) {
+ strm = pjmedia_vid_port_get_stream(w->vp_cap);
+ } else {
+ strm = pjmedia_vid_port_get_stream(w->vp_rend);
+ }
+
+ pj_assert(strm);
+ status = pjmedia_vid_dev_stream_set_cap(
+ strm, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE,
+ &hide);
+
+ pjmedia_vid_dev_stream_set_cap(
+ strm, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS,
+ &wnd_flags);
+
+ /* Done */
+ *id = wid;
+ pj_log_pop_indent();
+
+ return status;
+ }
+ }
+
+ /* Allocate window */
+ for (i=0; i<PJSUA_MAX_VID_WINS; ++i) {
+ w = &pjsua_var.win[i];
+ if (w->type == PJSUA_WND_TYPE_NONE) {
+ wid = i;
+ w->type = type;
+ break;
+ }
+ }
+ if (i == PJSUA_MAX_VID_WINS) {
+ pj_log_pop_indent();
+ return PJ_ETOOMANY;
+ }
+
+ /* Initialize window */
+ pjmedia_vid_port_param_default(&vp_param);
+
+ if (w->type == PJSUA_WND_TYPE_PREVIEW) {
+ pjmedia_vid_dev_info vdi;
+
+ /*
+ * Determine if the device supports native preview.
+ */
+ status = pjmedia_vid_dev_get_info(cap_id, &vdi);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (enable_native_preview &&
+ (vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW))
+ {
+ /* Device supports native preview! */
+ w->is_native = PJ_TRUE;
+ }
+
+ status = pjmedia_vid_dev_default_param(w->pool, cap_id,
+ &vp_param.vidparam);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (w->is_native) {
+ vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
+ vp_param.vidparam.window_hide = !show;
+ vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
+ vp_param.vidparam.window_flags = wnd_flags;
+ }
+
+ /* Normalize capture ID, in case it was set to
+ * PJMEDIA_VID_DEFAULT_CAPTURE_DEV
+ */
+ cap_id = vp_param.vidparam.cap_id;
+
+ /* Assign preview capture device ID */
+ w->preview_cap_id = cap_id;
+
+ /* Create capture video port */
+ vp_param.active = PJ_TRUE;
+ vp_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+ if (fmt)
+ vp_param.vidparam.fmt = *fmt;
+
+ status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_cap);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Update format info */
+ fmt_ = vp_param.vidparam.fmt;
+ fmt = &fmt_;
+
+ /* Create video tee */
+ status = pjmedia_vid_tee_create(w->pool, fmt, VID_TEE_MAX_PORT,
+ &w->tee);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Connect capturer to the video tee */
+ status = pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* If device supports native preview, enable it */
+ if (w->is_native) {
+ pjmedia_vid_dev_stream *cap_dev;
+ pj_bool_t enabled = PJ_TRUE;
+
+ cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
+ status = pjmedia_vid_dev_stream_set_cap(
+ cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW,
+ &enabled);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error activating native preview, falling back "
+ "to software preview.."));
+ w->is_native = PJ_FALSE;
+ }
+ }
+ }
+
+ /* Create renderer video port, only if it's not a native preview */
+ if (!w->is_native) {
+ status = pjmedia_vid_dev_default_param(w->pool, rend_id,
+ &vp_param.vidparam);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ vp_param.active = (w->type == PJSUA_WND_TYPE_STREAM);
+ vp_param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ vp_param.vidparam.fmt = *fmt;
+ vp_param.vidparam.disp_size = fmt->det.vid.size;
+ vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
+ vp_param.vidparam.window_hide = !show;
+ vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
+ vp_param.vidparam.window_flags = wnd_flags;
+
+ status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_rend);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* For preview window, connect capturer & renderer (via tee) */
+ if (w->type == PJSUA_WND_TYPE_PREVIEW) {
+ pjmedia_port *rend_port;
+
+ rend_port = pjmedia_vid_port_get_passive_port(w->vp_rend);
+ status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, rend_port);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ PJ_LOG(4,(THIS_FILE,
+ "%s window id %d created for cap_dev=%d rend_dev=%d",
+ pjsua_vid_win_type_name(type), wid, cap_id, rend_id));
+ } else {
+ PJ_LOG(4,(THIS_FILE,
+ "Preview window id %d created for cap_dev %d, "
+ "using built-in preview!",
+ wid, cap_id));
+ }
+
+
+ /* Done */
+ *id = wid;
+
+ PJ_LOG(4,(THIS_FILE, "Window %d created", wid));
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ free_vid_win(wid);
+ pj_log_pop_indent();
+ return status;
+}
+
+
+static void free_vid_win(pjsua_vid_win_id wid)
+{
+ pjsua_vid_win *w = &pjsua_var.win[wid];
+
+ PJ_LOG(4,(THIS_FILE, "Window %d: destroying..", wid));
+ pj_log_push_indent();
+
+ if (w->vp_cap) {
+ pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL,
+ w->vp_cap);
+ pjmedia_vid_port_stop(w->vp_cap);
+ pjmedia_vid_port_disconnect(w->vp_cap);
+ pjmedia_vid_port_destroy(w->vp_cap);
+ }
+ if (w->vp_rend) {
+ pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL,
+ w->vp_rend);
+ pjmedia_vid_port_stop(w->vp_rend);
+ pjmedia_vid_port_destroy(w->vp_rend);
+ }
+ if (w->tee) {
+ pjmedia_port_destroy(w->tee);
+ }
+ pjsua_vid_win_reset(wid);
+
+ pj_log_pop_indent();
+}
+
+
+static void inc_vid_win(pjsua_vid_win_id wid)
+{
+ pjsua_vid_win *w;
+
+ pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS);
+
+ w = &pjsua_var.win[wid];
+ pj_assert(w->type != PJSUA_WND_TYPE_NONE);
+ ++w->ref_cnt;
+}
+
+static void dec_vid_win(pjsua_vid_win_id wid)
+{
+ pjsua_vid_win *w;
+
+ pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS);
+
+ w = &pjsua_var.win[wid];
+ pj_assert(w->type != PJSUA_WND_TYPE_NONE);
+ if (--w->ref_cnt == 0)
+ free_vid_win(wid);
+}
+
+/* Initialize video call media */
+pj_status_t pjsua_vid_channel_init(pjsua_call_media *call_med)
+{
+ pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
+
+ call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev;
+ call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev;
+ if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) {
+ pjmedia_vid_dev_info info;
+ pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info);
+ call_med->strm.v.rdr_dev = info.id;
+ }
+ if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
+ pjmedia_vid_dev_info info;
+ pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info);
+ call_med->strm.v.cap_dev = info.id;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Internal function: update video channel after SDP negotiation */
+pj_status_t pjsua_vid_channel_update(pjsua_call_media *call_med,
+ pj_pool_t *tmp_pool,
+ pjmedia_vid_stream_info *si,
+ const pjmedia_sdp_session *local_sdp,
+ const pjmedia_sdp_session *remote_sdp)
+{
+ pjsua_call *call = call_med->call;
+ pjsua_acc *acc = &pjsua_var.acc[call->acc_id];
+ pjmedia_port *media_port;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(tmp_pool);
+ PJ_UNUSED_ARG(local_sdp);
+ PJ_UNUSED_ARG(remote_sdp);
+
+ PJ_LOG(4,(THIS_FILE, "Video channel update.."));
+ pj_log_push_indent();
+
+ si->rtcp_sdes_bye_disabled = PJ_TRUE;
+
+ /* Check if no media is active */
+ if (si->dir != PJMEDIA_DIR_NONE) {
+ /* Optionally, application may modify other stream settings here
+ * (such as jitter buffer parameters, codec ptime, etc.)
+ */
+ si->jb_init = pjsua_var.media_cfg.jb_init;
+ si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre;
+ si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre;
+ si->jb_max = pjsua_var.media_cfg.jb_max;
+
+ /* Set SSRC */
+ si->ssrc = call_med->ssrc;
+
+ /* Set RTP timestamp & sequence, normally these value are intialized
+ * automatically when stream session created, but for some cases (e.g:
+ * call reinvite, call update) timestamp and sequence need to be kept
+ * contigue.
+ */
+ si->rtp_ts = call_med->rtp_tx_ts;
+ si->rtp_seq = call_med->rtp_tx_seq;
+ si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set;
+
+ /* Set rate control config from account setting */
+ si->rc_cfg = acc->cfg.vid_stream_rc_cfg;
+
+#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0
+ /* Enable/disable stream keep-alive and NAT hole punch. */
+ si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka;
+#endif
+
+ /* Try to get shared format ID between the capture device and
+ * the encoder to avoid format conversion in the capture device.
+ */
+ if (si->dir & PJMEDIA_DIR_ENCODING) {
+ pjmedia_vid_dev_info dev_info;
+ pjmedia_vid_codec_info *codec_info = &si->codec_info;
+ unsigned i, j;
+
+ status = pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev,
+ &dev_info);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Find matched format ID */
+ for (i = 0; i < codec_info->dec_fmt_id_cnt; ++i) {
+ for (j = 0; j < dev_info.fmt_cnt; ++j) {
+ if (codec_info->dec_fmt_id[i] ==
+ (pjmedia_format_id)dev_info.fmt[j].id)
+ {
+ /* Apply the matched format ID to the codec */
+ si->codec_param->dec_fmt.id =
+ codec_info->dec_fmt_id[i];
+
+ /* Force outer loop to break */
+ i = codec_info->dec_fmt_id_cnt;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Create session based on session info. */
+ status = pjmedia_vid_stream_create(pjsua_var.med_endpt, NULL, si,
+ call_med->tp, NULL,
+ &call_med->strm.v.stream);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Start stream */
+ status = pjmedia_vid_stream_start(call_med->strm.v.stream);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Setup decoding direction */
+ if (si->dir & PJMEDIA_DIR_DECODING)
+ {
+ pjsua_vid_win_id wid;
+ pjsua_vid_win *w;
+
+ PJ_LOG(4,(THIS_FILE, "Setting up RX.."));
+ pj_log_push_indent();
+
+ status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
+ PJMEDIA_DIR_DECODING,
+ &media_port);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ goto on_error;
+ }
+
+ /* Create stream video window */
+ status = create_vid_win(PJSUA_WND_TYPE_STREAM,
+ &media_port->info.fmt,
+ call_med->strm.v.rdr_dev,
+ //acc->cfg.vid_rend_dev,
+ PJSUA_INVALID_ID,
+ acc->cfg.vid_in_auto_show,
+ acc->cfg.vid_wnd_flags,
+ &wid);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ goto on_error;
+ }
+
+ w = &pjsua_var.win[wid];
+
+#if ENABLE_EVENT
+ /* Register to video events */
+ pjmedia_event_subscribe(NULL, &call_media_on_event,
+ call_med, w->vp_rend);
+#endif
+
+ /* Connect renderer to stream */
+ status = pjmedia_vid_port_connect(w->vp_rend, media_port,
+ PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ goto on_error;
+ }
+
+ /* Start renderer */
+ status = pjmedia_vid_port_start(w->vp_rend);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ goto on_error;
+ }
+
+ /* Done */
+ inc_vid_win(wid);
+ call_med->strm.v.rdr_win_id = wid;
+ pj_log_pop_indent();
+ }
+
+ /* Setup encoding direction */
+ if (si->dir & PJMEDIA_DIR_ENCODING && !call->local_hold)
+ {
+ pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
+ pjsua_vid_win *w;
+ pjsua_vid_win_id wid;
+ pj_bool_t just_created = PJ_FALSE;
+
+ PJ_LOG(4,(THIS_FILE, "Setting up TX.."));
+ pj_log_push_indent();
+
+ status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
+ PJMEDIA_DIR_ENCODING,
+ &media_port);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ goto on_error;
+ }
+
+ /* Note: calling pjsua_vid_preview_get_win() even though
+ * create_vid_win() will automatically create the window
+ * if it doesn't exist, because create_vid_win() will modify
+ * existing window SHOW/HIDE value.
+ */
+ wid = vid_preview_get_win(call_med->strm.v.cap_dev, PJ_FALSE);
+ if (wid == PJSUA_INVALID_ID) {
+ /* Create preview video window */
+ status = create_vid_win(PJSUA_WND_TYPE_PREVIEW,
+ &media_port->info.fmt,
+ call_med->strm.v.rdr_dev,
+ call_med->strm.v.cap_dev,
+ //acc->cfg.vid_rend_dev,
+ //acc->cfg.vid_cap_dev,
+ PJSUA_HIDE_WINDOW,
+ acc->cfg.vid_wnd_flags,
+ &wid);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ return status;
+ }
+ just_created = PJ_TRUE;
+ }
+
+ w = &pjsua_var.win[wid];
+#if ENABLE_EVENT
+ pjmedia_event_subscribe(NULL, &call_media_on_event,
+ call_med, w->vp_cap);
+#endif
+
+ /* Connect stream to capturer (via video window tee) */
+ status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ goto on_error;
+ }
+
+ /* Start capturer */
+ if (just_created) {
+ status = pjmedia_vid_port_start(w->vp_cap);
+ if (status != PJ_SUCCESS) {
+ pj_log_pop_indent();
+ goto on_error;
+ }
+ }
+
+ /* Done */
+ inc_vid_win(wid);
+ call_med->strm.v.cap_win_id = wid;
+ pj_log_pop_indent();
+ }
+
+ }
+
+ if (!acc->cfg.vid_out_auto_transmit && call_med->strm.v.stream) {
+ status = pjmedia_vid_stream_pause(call_med->strm.v.stream,
+ PJMEDIA_DIR_ENCODING);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ pj_log_pop_indent();
+ return status;
+}
+
+
+/* Internal function to stop video stream */
+void pjsua_vid_stop_stream(pjsua_call_media *call_med)
+{
+ pjmedia_vid_stream *strm = call_med->strm.v.stream;
+ pjmedia_rtcp_stat stat;
+
+ pj_assert(call_med->type == PJMEDIA_TYPE_VIDEO);
+
+ if (!strm)
+ return;
+
+ PJ_LOG(4,(THIS_FILE, "Stopping video stream.."));
+ pj_log_push_indent();
+
+ if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) {
+ pjmedia_port *media_port;
+ pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.cap_win_id];
+ pj_status_t status;
+
+ /* Stop the capture before detaching stream and unsubscribing event */
+ pjmedia_vid_port_stop(w->vp_cap);
+
+ /* Disconnect video stream from capture device */
+ status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
+ PJMEDIA_DIR_ENCODING,
+ &media_port);
+ if (status == PJ_SUCCESS) {
+ pjmedia_vid_tee_remove_dst_port(w->tee, media_port);
+ }
+
+ /* Unsubscribe event */
+ pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
+ w->vp_cap);
+
+ /* Re-start capture again, if it is used by other stream */
+ if (w->ref_cnt > 1)
+ pjmedia_vid_port_start(w->vp_cap);
+
+ dec_vid_win(call_med->strm.v.cap_win_id);
+ call_med->strm.v.cap_win_id = PJSUA_INVALID_ID;
+ }
+
+ if (call_med->strm.v.rdr_win_id != PJSUA_INVALID_ID) {
+ pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.rdr_win_id];
+
+ /* Stop the render before unsubscribing event */
+ pjmedia_vid_port_stop(w->vp_rend);
+ pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
+ w->vp_rend);
+
+ dec_vid_win(call_med->strm.v.rdr_win_id);
+ call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID;
+ }
+
+ if ((call_med->dir & PJMEDIA_DIR_ENCODING) &&
+ (pjmedia_vid_stream_get_stat(strm, &stat) == PJ_SUCCESS))
+ {
+ /* Save RTP timestamp & sequence, so when media session is
+ * restarted, those values will be restored as the initial
+ * RTP timestamp & sequence of the new media session. So in
+ * the same call session, RTP timestamp and sequence are
+ * guaranteed to be contigue.
+ */
+ call_med->rtp_tx_seq_ts_set = 1 | (1 << 1);
+ call_med->rtp_tx_seq = stat.rtp_tx_last_seq;
+ call_med->rtp_tx_ts = stat.rtp_tx_last_ts;
+ }
+
+ pjmedia_vid_stream_destroy(strm);
+ call_med->strm.v.stream = NULL;
+
+ pj_log_pop_indent();
+}
+
+/*
+ * Does it have built-in preview support.
+ */
+PJ_DEF(pj_bool_t) pjsua_vid_preview_has_native(pjmedia_vid_dev_index id)
+{
+ pjmedia_vid_dev_info vdi;
+
+ return (pjmedia_vid_dev_get_info(id, &vdi)==PJ_SUCCESS) ?
+ ((vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW)!=0) : PJ_FALSE;
+}
+
+/*
+ * Start video preview window for the specified capture device.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id,
+ const pjsua_vid_preview_param *prm)
+{
+ pjsua_vid_win_id wid;
+ pjsua_vid_win *w;
+ pjmedia_vid_dev_index rend_id;
+ pjsua_vid_preview_param default_param;
+ pj_status_t status;
+
+ if (!prm) {
+ pjsua_vid_preview_param_default(&default_param);
+ prm = &default_param;
+ }
+
+ PJ_LOG(4,(THIS_FILE, "Starting preview for cap_dev=%d, show=%d",
+ id, prm->show));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ rend_id = prm->rend_id;
+
+ status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, NULL, rend_id, id,
+ prm->show, prm->wnd_flags, &wid);
+ if (status != PJ_SUCCESS) {
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+ }
+
+ w = &pjsua_var.win[wid];
+ if (w->preview_running) {
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+ }
+
+ /* Start renderer, unless it's native preview */
+ if (w->is_native && !pjmedia_vid_port_is_running(w->vp_cap)) {
+ pjmedia_vid_dev_stream *cap_dev;
+ pj_bool_t enabled = PJ_TRUE;
+
+ cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
+ status = pjmedia_vid_dev_stream_set_cap(
+ cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW,
+ &enabled);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status,
+ "Error activating native preview, falling back "
+ "to software preview.."));
+ w->is_native = PJ_FALSE;
+ }
+ }
+
+ if (!w->is_native && !pjmedia_vid_port_is_running(w->vp_rend)) {
+ status = pjmedia_vid_port_start(w->vp_rend);
+ if (status != PJ_SUCCESS) {
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+ }
+ }
+
+ /* Start capturer */
+ if (!pjmedia_vid_port_is_running(w->vp_cap)) {
+ status = pjmedia_vid_port_start(w->vp_cap);
+ if (status != PJ_SUCCESS) {
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+ }
+ }
+
+ inc_vid_win(wid);
+ w->preview_running = PJ_TRUE;
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+/*
+ * Stop video preview.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_preview_stop(pjmedia_vid_dev_index id)
+{
+ pjsua_vid_win_id wid = PJSUA_INVALID_ID;
+ pjsua_vid_win *w;
+ pj_status_t status;
+
+ PJSUA_LOCK();
+ wid = pjsua_vid_preview_get_win(id);
+ if (wid == PJSUA_INVALID_ID) {
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return PJ_ENOTFOUND;
+ }
+
+ PJ_LOG(4,(THIS_FILE, "Stopping preview for cap_dev=%d", id));
+ pj_log_push_indent();
+
+ w = &pjsua_var.win[wid];
+ if (w->preview_running) {
+ if (w->is_native) {
+ pjmedia_vid_dev_stream *cap_dev;
+ pj_bool_t enabled = PJ_FALSE;
+
+ cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
+ status = pjmedia_vid_dev_stream_set_cap(
+ cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW,
+ &enabled);
+ } else {
+ status = pjmedia_vid_port_stop(w->vp_rend);
+ }
+
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(1,(THIS_FILE, status, "Error stopping %spreview",
+ (w->is_native ? "native " : "")));
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+ return status;
+ }
+
+ dec_vid_win(wid);
+ w->preview_running = PJ_FALSE;
+ }
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Window
+ */
+
+
+/*
+ * Enumerates all video windows.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_enum_wins( pjsua_vid_win_id wids[],
+ unsigned *count)
+{
+ unsigned i, cnt;
+
+ cnt = 0;
+
+ for (i=0; i<PJSUA_MAX_VID_WINS && cnt <*count; ++i) {
+ pjsua_vid_win *w = &pjsua_var.win[i];
+ if (w->type != PJSUA_WND_TYPE_NONE)
+ wids[cnt++] = i;
+ }
+
+ *count = cnt;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get window info.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_get_info( pjsua_vid_win_id wid,
+ pjsua_vid_win_info *wi)
+{
+ pjsua_vid_win *w;
+ pjmedia_vid_dev_stream *s;
+ pjmedia_vid_dev_param vparam;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && wi, PJ_EINVAL);
+
+ pj_bzero(wi, sizeof(*wi));
+
+ PJSUA_LOCK();
+ w = &pjsua_var.win[wid];
+
+ wi->is_native = w->is_native;
+
+ if (w->is_native) {
+ pjmedia_vid_dev_stream *cap_strm;
+ pjmedia_vid_dev_cap cap = PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
+
+ cap_strm = pjmedia_vid_port_get_stream(w->vp_cap);
+ if (!cap_strm) {
+ status = PJ_EINVAL;
+ } else {
+ status = pjmedia_vid_dev_stream_get_cap(cap_strm, cap, &wi->hwnd);
+ }
+
+ PJSUA_UNLOCK();
+ return status;
+ }
+
+ if (w->vp_rend == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ s = pjmedia_vid_port_get_stream(w->vp_rend);
+ if (s == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ status = pjmedia_vid_dev_stream_get_param(s, &vparam);
+ if (status != PJ_SUCCESS) {
+ PJSUA_UNLOCK();
+ return status;
+ }
+
+ wi->rdr_dev = vparam.rend_id;
+ wi->hwnd = vparam.window;
+ wi->show = !vparam.window_hide;
+ wi->pos = vparam.window_pos;
+ wi->size = vparam.disp_size;
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Show or hide window.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_set_show( pjsua_vid_win_id wid,
+ pj_bool_t show)
+{
+ pjsua_vid_win *w;
+ pjmedia_vid_dev_stream *s;
+ pj_bool_t hide;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL);
+
+ PJSUA_LOCK();
+ w = &pjsua_var.win[wid];
+ if (w->vp_rend == NULL) {
+ /* Native window */
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ s = pjmedia_vid_port_get_stream(w->vp_rend);
+ if (s == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ /* Make sure that renderer gets started before shown up */
+ if (show && !pjmedia_vid_port_is_running(w->vp_rend))
+ status = pjmedia_vid_port_start(w->vp_rend);
+
+ hide = !show;
+ status = pjmedia_vid_dev_stream_set_cap(s,
+ PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, &hide);
+
+ PJSUA_UNLOCK();
+
+ return status;
+}
+
+/*
+ * Set video window position.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_set_pos( pjsua_vid_win_id wid,
+ const pjmedia_coord *pos)
+{
+ pjsua_vid_win *w;
+ pjmedia_vid_dev_stream *s;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && pos, PJ_EINVAL);
+
+ PJSUA_LOCK();
+ w = &pjsua_var.win[wid];
+ if (w->vp_rend == NULL) {
+ /* Native window */
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ s = pjmedia_vid_port_get_stream(w->vp_rend);
+ if (s == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ status = pjmedia_vid_dev_stream_set_cap(s,
+ PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION, pos);
+
+ PJSUA_UNLOCK();
+
+ return status;
+}
+
+/*
+ * Resize window.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_set_size( pjsua_vid_win_id wid,
+ const pjmedia_rect_size *size)
+{
+ pjsua_vid_win *w;
+ pjmedia_vid_dev_stream *s;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && size, PJ_EINVAL);
+
+ PJSUA_LOCK();
+ w = &pjsua_var.win[wid];
+ if (w->vp_rend == NULL) {
+ /* Native window */
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ s = pjmedia_vid_port_get_stream(w->vp_rend);
+ if (s == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ status = pjmedia_vid_dev_stream_set_cap(s,
+ PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE, size);
+
+ PJSUA_UNLOCK();
+
+ return status;
+}
+
+/*
+ * Set video orientation.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_win_rotate( pjsua_vid_win_id wid,
+ int angle)
+{
+ pjsua_vid_win *w;
+ pjmedia_vid_dev_stream *s;
+ pjmedia_orient orient;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL);
+ PJ_ASSERT_RETURN((angle % 90) == 0, PJ_EINVAL);
+
+ /* Normalize angle, so it must be 0, 90, 180, or 270. */
+ angle %= 360;
+ if (angle < 0)
+ angle += 360;
+
+ /* Convert angle to pjmedia_orient */
+ switch(angle) {
+ case 0:
+ /* No rotation */
+ return PJ_SUCCESS;
+ case 90:
+ orient = PJMEDIA_ORIENT_ROTATE_90DEG;
+ break;
+ case 180:
+ orient = PJMEDIA_ORIENT_ROTATE_180DEG;
+ break;
+ case 270:
+ orient = PJMEDIA_ORIENT_ROTATE_270DEG;
+ break;
+ default:
+ pj_assert(!"Angle must have been validated");
+ return PJ_EBUG;
+ }
+
+ PJSUA_LOCK();
+ w = &pjsua_var.win[wid];
+ if (w->vp_rend == NULL) {
+ /* Native window */
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ s = pjmedia_vid_port_get_stream(w->vp_rend);
+ if (s == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
+ }
+
+ status = pjmedia_vid_dev_stream_set_cap(s,
+ PJMEDIA_VID_DEV_CAP_ORIENTATION, &orient);
+
+ PJSUA_UNLOCK();
+
+ return status;
+}
+
+
+static void call_get_vid_strm_info(pjsua_call *call,
+ int *first_active,
+ int *first_inactive,
+ unsigned *active_cnt,
+ unsigned *cnt)
+{
+ unsigned i, var_cnt = 0;
+
+ if (first_active && ++var_cnt)
+ *first_active = -1;
+ if (first_inactive && ++var_cnt)
+ *first_inactive = -1;
+ if (active_cnt && ++var_cnt)
+ *active_cnt = 0;
+ if (cnt && ++var_cnt)
+ *cnt = 0;
+
+ for (i = 0; i < call->med_cnt && var_cnt; ++i) {
+ if (call->media[i].type == PJMEDIA_TYPE_VIDEO) {
+ if (call->media[i].dir != PJMEDIA_DIR_NONE)
+ {
+ if (first_active && *first_active == -1) {
+ *first_active = i;
+ --var_cnt;
+ }
+ if (active_cnt)
+ ++(*active_cnt);
+ } else if (first_inactive && *first_inactive == -1) {
+ *first_inactive = i;
+ --var_cnt;
+ }
+ if (cnt)
+ ++(*cnt);
+ }
+ }
+}
+
+
+/* Send SDP reoffer. */
+static pj_status_t call_reoffer_sdp(pjsua_call_id call_id,
+ const pjmedia_sdp_session *sdp)
+{
+ pjsua_call *call;
+ pjsip_tx_data *tdata;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ status = acquire_call("call_reoffer_sdp()", call_id, &call, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
+ PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
+ pjsip_dlg_dec_lock(dlg);
+ return PJSIP_ESESSIONSTATE;
+ }
+
+ /* Create re-INVITE with new offer */
+ status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+ }
+
+ /* Send the request */
+ status = pjsip_inv_send_msg( call->inv, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
+/* Add a new video stream into a call */
+static pj_status_t call_add_video(pjsua_call *call,
+ pjmedia_vid_dev_index cap_dev,
+ pjmedia_dir dir)
+{
+ pj_pool_t *pool = call->inv->pool_prov;
+ pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
+ pjsua_call_media *call_med;
+ const pjmedia_sdp_session *current_sdp;
+ pjmedia_sdp_session *sdp;
+ pjmedia_sdp_media *sdp_m;
+ pjmedia_transport_info tpinfo;
+ pj_status_t status;
+
+ /* Verify media slot availability */
+ if (call->med_cnt == PJSUA_MAX_CALL_MEDIA)
+ return PJ_ETOOMANY;
+
+ /* Get active local SDP and clone it */
+ status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &current_sdp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, current_sdp);
+
+ /* Clean up provisional media before using it */
+ pjsua_media_prov_clean_up(call->index);
+
+ /* Update provisional media from call media */
+ call->med_prov_cnt = call->med_cnt;
+ pj_memcpy(call->media_prov, call->media,
+ sizeof(call->media[0]) * call->med_cnt);
+
+ /* Initialize call media */
+ call_med = &call->media_prov[call->med_prov_cnt++];
+ status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO,
+ &acc_cfg->rtp_cfg, call->secure_level,
+ NULL, PJ_FALSE, NULL);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Override default capture device setting */
+ call_med->strm.v.cap_dev = cap_dev;
+
+ /* Init transport media */
+ status = pjmedia_transport_media_create(call_med->tp, pool, 0,
+ NULL, call_med->idx);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT);
+
+ /* Get transport address info */
+ pjmedia_transport_info_init(&tpinfo);
+ pjmedia_transport_get_info(call_med->tp, &tpinfo);
+
+ /* Create SDP media line */
+ status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
+ &tpinfo.sock_info, 0, &sdp_m);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ sdp->media[sdp->media_count++] = sdp_m;
+
+ /* Update media direction, if it is not 'sendrecv' */
+ if (dir != PJMEDIA_DIR_ENCODING_DECODING) {
+ pjmedia_sdp_attr *a;
+
+ /* Remove sendrecv direction attribute, if any */
+ pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv");
+
+ if (dir == PJMEDIA_DIR_ENCODING)
+ a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
+ else if (dir == PJMEDIA_DIR_DECODING)
+ a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
+ else
+ a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
+
+ pjmedia_sdp_media_add_attr(sdp_m, a);
+ }
+
+ /* Update SDP media line by media transport */
+ status = pjmedia_transport_encode_sdp(call_med->tp, pool,
+ sdp, NULL, call_med->idx);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = call_reoffer_sdp(call->index, sdp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ call->opt.vid_cnt++;
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (call_med->tp) {
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL);
+ pjmedia_transport_close(call_med->tp);
+ call_med->tp = call_med->tp_orig = NULL;
+ }
+
+ return status;
+}
+
+
+/* Modify a video stream from a call, i.e: update direction,
+ * remove/disable.
+ */
+static pj_status_t call_modify_video(pjsua_call *call,
+ int med_idx,
+ pjmedia_dir dir,
+ pj_bool_t remove)
+{
+ pjsua_call_media *call_med;
+ const pjmedia_sdp_session *current_sdp;
+ pjmedia_sdp_session *sdp;
+ pj_status_t status;
+
+ /* Verify and normalize media index */
+ if (med_idx == -1) {
+ int first_active;
+
+ call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
+ if (first_active == -1)
+ return PJ_ENOTFOUND;
+
+ med_idx = first_active;
+ }
+
+ /* Clean up provisional media before using it */
+ pjsua_media_prov_clean_up(call->index);
+
+ /* Update provisional media from call media */
+ call->med_prov_cnt = call->med_cnt;
+ pj_memcpy(call->media_prov, call->media,
+ sizeof(call->media[0]) * call->med_cnt);
+
+ call_med = &call->media_prov[med_idx];
+
+ /* Verify if the stream media type is video */
+ if (call_med->type != PJMEDIA_TYPE_VIDEO)
+ return PJ_EINVAL;
+
+ /* Verify if the stream dir is not changed */
+ if ((!remove && call_med->dir == dir) ||
+ ( remove && (call_med->tp_st == PJSUA_MED_TP_DISABLED ||
+ call_med->tp == NULL)))
+ {
+ return PJ_SUCCESS;
+ }
+
+ /* Get active local SDP and clone it */
+ status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &current_sdp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, current_sdp);
+
+ pj_assert(med_idx < (int)sdp->media_count);
+
+ if (!remove) {
+ pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
+ pj_pool_t *pool = call->inv->pool_prov;
+ pjmedia_sdp_media *sdp_m;
+
+ /* Enabling video */
+ if (call_med->dir == PJMEDIA_DIR_NONE) {
+ unsigned i, vid_cnt = 0;
+
+ /* Check if vid_cnt in call option needs to be increased */
+ for (i = 0; i < call->med_cnt; ++i) {
+ if (call->media[i].type == PJMEDIA_TYPE_VIDEO &&
+ call->media[i].dir != PJMEDIA_DIR_NONE)
+ {
+ ++vid_cnt;
+ }
+ }
+ if (call->opt.vid_cnt <= vid_cnt)
+ call->opt.vid_cnt++;
+ }
+
+ status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO,
+ &acc_cfg->rtp_cfg, call->secure_level,
+ NULL, PJ_FALSE, NULL);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init transport media */
+ if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) {
+ status = pjmedia_transport_media_create(call_med->tp, pool, 0,
+ NULL, call_med->idx);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ sdp_m = sdp->media[med_idx];
+
+ /* Create new SDP media line if the stream is disabled */
+ if (sdp->media[med_idx]->desc.port == 0) {
+ pjmedia_transport_info tpinfo;
+
+ /* Get transport address info */
+ pjmedia_transport_info_init(&tpinfo);
+ pjmedia_transport_get_info(call_med->tp, &tpinfo);
+
+ status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool,
+ &tpinfo.sock_info, 0, &sdp_m);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ {
+ pjmedia_sdp_attr *a;
+
+ /* Remove any direction attributes */
+ pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv");
+ pjmedia_sdp_media_remove_all_attr(sdp_m, "sendonly");
+ pjmedia_sdp_media_remove_all_attr(sdp_m, "recvonly");
+ pjmedia_sdp_media_remove_all_attr(sdp_m, "inactive");
+
+ /* Update media direction */
+ if (dir == PJMEDIA_DIR_ENCODING_DECODING)
+ a = pjmedia_sdp_attr_create(pool, "sendrecv", NULL);
+ else if (dir == PJMEDIA_DIR_ENCODING)
+ a = pjmedia_sdp_attr_create(pool, "sendonly", NULL);
+ else if (dir == PJMEDIA_DIR_DECODING)
+ a = pjmedia_sdp_attr_create(pool, "recvonly", NULL);
+ else
+ a = pjmedia_sdp_attr_create(pool, "inactive", NULL);
+
+ pjmedia_sdp_media_add_attr(sdp_m, a);
+ }
+
+ sdp->media[med_idx] = sdp_m;
+
+ /* Update SDP media line by media transport */
+ status = pjmedia_transport_encode_sdp(call_med->tp, pool,
+ sdp, NULL, call_med->idx);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+on_error:
+ if (status != PJ_SUCCESS) {
+ pjsua_media_prov_clean_up(call->index);
+ return status;
+ }
+
+ } else {
+
+ pj_pool_t *pool = call->inv->pool_prov;
+
+ /* Mark media transport to disabled */
+ // Don't close this here, as SDP negotiation has not been
+ // done and stream may be still active.
+ pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED);
+
+ /* Deactivate the stream */
+ pjmedia_sdp_media_deactivate(pool, sdp->media[med_idx]);
+
+ call->opt.vid_cnt--;
+ }
+
+ status = call_reoffer_sdp(call->index, sdp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Change capture device of a video stream in a call */
+static pj_status_t call_change_cap_dev(pjsua_call *call,
+ int med_idx,
+ pjmedia_vid_dev_index cap_dev)
+{
+ pjsua_call_media *call_med;
+ pjmedia_vid_dev_stream *old_dev;
+ pjmedia_vid_dev_switch_param switch_prm;
+ pjmedia_vid_dev_info info;
+ pjsua_vid_win *w, *new_w = NULL;
+ pjsua_vid_win_id wid, new_wid;
+ pjmedia_port *media_port;
+ pj_status_t status;
+
+ /* Verify and normalize media index */
+ if (med_idx == -1) {
+ int first_active;
+
+ call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
+ if (first_active == -1)
+ return PJ_ENOTFOUND;
+
+ med_idx = first_active;
+ }
+
+ call_med = &call->media[med_idx];
+
+ /* Verify if the stream media type is video */
+ if (call_med->type != PJMEDIA_TYPE_VIDEO)
+ return PJ_EINVAL;
+
+ /* Verify the capture device */
+ status = pjmedia_vid_dev_get_info(cap_dev, &info);
+ if (status != PJ_SUCCESS || info.dir != PJMEDIA_DIR_CAPTURE)
+ return PJ_EINVAL;
+
+ /* The specified capture device is being used already */
+ if (call_med->strm.v.cap_dev == cap_dev)
+ return PJ_SUCCESS;
+
+ /* == Apply the new capture device == */
+
+ wid = call_med->strm.v.cap_win_id;
+ w = &pjsua_var.win[wid];
+ pj_assert(w->type == PJSUA_WND_TYPE_PREVIEW && w->vp_cap);
+
+ /* If the old device supports fast switching, then that's excellent! */
+ old_dev = pjmedia_vid_port_get_stream(w->vp_cap);
+ pjmedia_vid_dev_switch_param_default(&switch_prm);
+ switch_prm.target_id = cap_dev;
+ status = pjmedia_vid_dev_stream_set_cap(old_dev,
+ PJMEDIA_VID_DEV_CAP_SWITCH,
+ &switch_prm);
+ if (status == PJ_SUCCESS) {
+ w->preview_cap_id = cap_dev;
+ call_med->strm.v.cap_dev = cap_dev;
+ return PJ_SUCCESS;
+ }
+
+ /* No it doesn't support fast switching. Do slow switching then.. */
+ status = pjmedia_vid_stream_get_port(call_med->strm.v.stream,
+ PJMEDIA_DIR_ENCODING, &media_port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
+ w->vp_cap);
+
+ /* temporarily disconnect while we operate on the tee. */
+ pjmedia_vid_port_disconnect(w->vp_cap);
+
+ /* = Detach stream port from the old capture device's tee = */
+ status = pjmedia_vid_tee_remove_dst_port(w->tee, media_port);
+ if (status != PJ_SUCCESS) {
+ /* Something wrong, assume that media_port has been removed
+ * and continue.
+ */
+ PJ_PERROR(4,(THIS_FILE, status,
+ "Warning: call %d: unable to remove video from tee",
+ call->index));
+ }
+
+ /* Reconnect again immediately. We're done with w->tee */
+ pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
+
+ /* = Attach stream port to the new capture device = */
+
+ /* Note: calling pjsua_vid_preview_get_win() even though
+ * create_vid_win() will automatically create the window
+ * if it doesn't exist, because create_vid_win() will modify
+ * existing window SHOW/HIDE value.
+ */
+ new_wid = vid_preview_get_win(cap_dev, PJ_FALSE);
+ if (new_wid == PJSUA_INVALID_ID) {
+ pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id];
+
+ /* Create preview video window */
+ status = create_vid_win(PJSUA_WND_TYPE_PREVIEW,
+ &media_port->info.fmt,
+ call_med->strm.v.rdr_dev,
+ cap_dev,
+ PJSUA_HIDE_WINDOW,
+ acc->cfg.vid_wnd_flags,
+ &new_wid);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ inc_vid_win(new_wid);
+ new_w = &pjsua_var.win[new_wid];
+
+ /* Connect stream to capturer (via video window tee) */
+ status = pjmedia_vid_tee_add_dst_port2(new_w->tee, 0, media_port);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (w->vp_rend) {
+ /* Start renderer */
+ status = pjmedia_vid_port_start(new_w->vp_rend);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+#if ENABLE_EVENT
+ pjmedia_event_subscribe(NULL, &call_media_on_event,
+ call_med, new_w->vp_cap);
+#endif
+
+ /* Start capturer */
+ if (!pjmedia_vid_port_is_running(new_w->vp_cap)) {
+ status = pjmedia_vid_port_start(new_w->vp_cap);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Finally */
+ call_med->strm.v.cap_dev = cap_dev;
+ call_med->strm.v.cap_win_id = new_wid;
+ dec_vid_win(wid);
+
+ return PJ_SUCCESS;
+
+on_error:
+ PJ_PERROR(4,(THIS_FILE, status,
+ "Call %d: error changing capture device to %d",
+ call->index, cap_dev));
+
+ if (new_w) {
+ /* Unsubscribe, just in case */
+ pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med,
+ new_w->vp_cap);
+ /* Disconnect media port from the new capturer */
+ pjmedia_vid_tee_remove_dst_port(new_w->tee, media_port);
+ /* Release the new capturer */
+ dec_vid_win(new_wid);
+ }
+
+ /* Revert back to the old capturer */
+ pjmedia_vid_port_disconnect(w->vp_cap);
+ status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port);
+ pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE);
+ if (status != PJ_SUCCESS)
+ return status;
+
+#if ENABLE_EVENT
+ /* Resubscribe */
+ pjmedia_event_subscribe(NULL, &call_media_on_event,
+ call_med, w->vp_cap);
+#endif
+
+ return status;
+}
+
+
+/* Start/stop transmitting video stream in a call */
+static pj_status_t call_set_tx_video(pjsua_call *call,
+ int med_idx,
+ pj_bool_t enable)
+{
+ pjsua_call_media *call_med;
+ pj_status_t status;
+
+ /* Verify and normalize media index */
+ if (med_idx == -1) {
+ int first_active;
+
+ call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
+ if (first_active == -1)
+ return PJ_ENOTFOUND;
+
+ med_idx = first_active;
+ }
+
+ call_med = &call->media[med_idx];
+
+ /* Verify if the stream is transmitting video */
+ if (call_med->type != PJMEDIA_TYPE_VIDEO ||
+ (enable && (call_med->dir & PJMEDIA_DIR_ENCODING) == 0))
+ {
+ return PJ_EINVAL;
+ }
+
+ if (enable) {
+ /* Start stream in encoding direction */
+ status = pjmedia_vid_stream_resume(call_med->strm.v.stream,
+ PJMEDIA_DIR_ENCODING);
+ } else {
+ /* Pause stream in encoding direction */
+ status = pjmedia_vid_stream_pause( call_med->strm.v.stream,
+ PJMEDIA_DIR_ENCODING);
+ }
+
+ return status;
+}
+
+
+static pj_status_t call_send_vid_keyframe(pjsua_call *call,
+ int med_idx)
+{
+ pjsua_call_media *call_med;
+
+ /* Verify and normalize media index */
+ if (med_idx == -1) {
+ int first_active;
+
+ call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL);
+ if (first_active == -1)
+ return PJ_ENOTFOUND;
+
+ med_idx = first_active;
+ }
+
+ call_med = &call->media[med_idx];
+
+ /* Verify media type and stream instance. */
+ if (call_med->type != PJMEDIA_TYPE_VIDEO || !call_med->strm.v.stream)
+ return PJ_EINVAL;
+
+ return pjmedia_vid_stream_send_keyframe(call_med->strm.v.stream);
+}
+
+
+/*
+ * Start, stop, and/or manipulate video transmission for the specified call.
+ */
+PJ_DEF(pj_status_t) pjsua_call_set_vid_strm (
+ pjsua_call_id call_id,
+ pjsua_call_vid_strm_op op,
+ const pjsua_call_vid_strm_op_param *param)
+{
+ pjsua_call *call;
+ pjsua_call_vid_strm_op_param param_;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(op != PJSUA_CALL_VID_STRM_NO_OP, PJ_EINVAL);
+
+ PJ_LOG(4,(THIS_FILE, "Call %d: set video stream, op=%d",
+ call_id, op));
+ pj_log_push_indent();
+
+ PJSUA_LOCK();
+
+ call = &pjsua_var.calls[call_id];
+
+ if (param) {
+ param_ = *param;
+ } else {
+ pjsua_call_vid_strm_op_param_default(&param_);
+ }
+
+ /* If set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with
+ * account default video capture device.
+ */
+ if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
+ pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg;
+ param_.cap_dev = acc_cfg->vid_cap_dev;
+
+ /* If the account default video capture device is
+ * PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with
+ * global default video capture device.
+ */
+ if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) {
+ pjmedia_vid_dev_info info;
+ pjmedia_vid_dev_get_info(param_.cap_dev, &info);
+ pj_assert(info.dir == PJMEDIA_DIR_CAPTURE);
+ param_.cap_dev = info.id;
+ }
+ }
+
+ switch (op) {
+ case PJSUA_CALL_VID_STRM_ADD:
+ status = call_add_video(call, param_.cap_dev, param_.dir);
+ break;
+ case PJSUA_CALL_VID_STRM_REMOVE:
+ status = call_modify_video(call, param_.med_idx, PJMEDIA_DIR_NONE,
+ PJ_TRUE);
+ break;
+ case PJSUA_CALL_VID_STRM_CHANGE_DIR:
+ status = call_modify_video(call, param_.med_idx, param_.dir, PJ_FALSE);
+ break;
+ case PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV:
+ status = call_change_cap_dev(call, param_.med_idx, param_.cap_dev);
+ break;
+ case PJSUA_CALL_VID_STRM_START_TRANSMIT:
+ status = call_set_tx_video(call, param_.med_idx, PJ_TRUE);
+ break;
+ case PJSUA_CALL_VID_STRM_STOP_TRANSMIT:
+ status = call_set_tx_video(call, param_.med_idx, PJ_FALSE);
+ break;
+ case PJSUA_CALL_VID_STRM_SEND_KEYFRAME:
+ status = call_send_vid_keyframe(call, param_.med_idx);
+ break;
+ default:
+ status = PJ_EINVALIDOP;
+ break;
+ }
+
+ PJSUA_UNLOCK();
+ pj_log_pop_indent();
+
+ return status;
+}
+
+
+/*
+ * Get the media stream index of the default video stream in the call.
+ */
+PJ_DEF(int) pjsua_call_get_vid_stream_idx(pjsua_call_id call_id)
+{
+ pjsua_call *call;
+ int first_active, first_inactive;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ PJSUA_LOCK();
+ call = &pjsua_var.calls[call_id];
+ call_get_vid_strm_info(call, &first_active, &first_inactive, NULL, NULL);
+ PJSUA_UNLOCK();
+
+ if (first_active == -1)
+ return first_inactive;
+
+ return first_active;
+}
+
+
+/*
+ * Determine if video stream for the specified call is currently running
+ * for the specified direction.
+ */
+PJ_DEF(pj_bool_t) pjsua_call_vid_stream_is_running( pjsua_call_id call_id,
+ int med_idx,
+ pjmedia_dir dir)
+{
+ pjsua_call *call;
+ pjsua_call_media *call_med;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ /* Verify and normalize media index */
+ if (med_idx == -1) {
+ med_idx = pjsua_call_get_vid_stream_idx(call_id);
+ }
+
+ call = &pjsua_var.calls[call_id];
+ PJ_ASSERT_RETURN(med_idx >= 0 && med_idx < (int)call->med_cnt, PJ_EINVAL);
+
+ call_med = &call->media[med_idx];
+
+ /* Verify if the stream is transmitting video */
+ if (call_med->type != PJMEDIA_TYPE_VIDEO || (call_med->dir & dir) == 0 ||
+ !call_med->strm.v.stream)
+ {
+ return PJ_FALSE;
+ }
+
+ return pjmedia_vid_stream_is_running(call_med->strm.v.stream, dir);
+}
+
+#endif /* PJSUA_HAS_VIDEO */
+
+#endif /* PJSUA_MEDIA_HAS_PJMEDIA */
diff --git a/pjsip/src/test/dlg_core_test.c b/pjsip/src/test/dlg_core_test.c
new file mode 100644
index 0000000..ef2f35b
--- /dev/null
+++ b/pjsip/src/test/dlg_core_test.c
@@ -0,0 +1,23 @@
+/* $Id: dlg_core_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip.h>
+
diff --git a/pjsip/src/test/dns_test.c b/pjsip/src/test/dns_test.c
new file mode 100644
index 0000000..7f6f3b0
--- /dev/null
+++ b/pjsip/src/test/dns_test.c
@@ -0,0 +1,618 @@
+/* $Id: dns_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+
+/* For logging purpose. */
+#define THIS_FILE "dns_test.c"
+
+struct result
+{
+ pj_status_t status;
+ pjsip_server_addresses servers;
+};
+
+
+static void cb(pj_status_t status,
+ void *token,
+ const struct pjsip_server_addresses *addr)
+{
+ struct result *result = (struct result*) token;
+
+ result->status = status;
+ if (status == PJ_SUCCESS)
+ pj_memcpy(&result->servers, addr, sizeof(*addr));
+}
+
+
+static void add_dns_entries(pj_dns_resolver *resv)
+{
+ /* Inject DNS SRV entry */
+ pj_dns_parsed_packet pkt;
+ pj_dns_parsed_query q;
+ pj_dns_parsed_rr ans[4];
+ pj_dns_parsed_rr ar[5];
+ pj_str_t tmp;
+ unsigned i;
+
+ /*
+ * This is answer to SRV query to "example.com" domain, and
+ * the answer contains full reference to the A records of
+ * the server. The full DNS records is :
+
+ _sip._udp.example.com 3600 IN SRV 0 0 5060 sip01.example.com.
+ _sip._udp.example.com 3600 IN SRV 0 20 5060 sip02.example.com.
+ _sip._udp.example.com 3600 IN SRV 0 10 5060 sip03.example.com.
+ _sip._udp.example.com 3600 IN SRV 1 0 5060 sip04.example.com.
+
+ sip01.example.com. 3600 IN A 1.1.1.1
+ sip02.example.com. 3600 IN A 2.2.2.2
+ sip03.example.com. 3600 IN A 3.3.3.3
+ sip04.example.com. 3600 IN A 4.4.4.4
+
+ ; Additionally, add A record for "example.com"
+ example.com. 3600 IN A 5.5.5.5
+
+ */
+ pj_bzero(&pkt, sizeof(pkt));
+ pj_bzero(ans, sizeof(ans));
+ pj_bzero(ar, sizeof(ar));
+
+ pkt.hdr.flags = PJ_DNS_SET_QR(1);
+ pkt.hdr.anscount = PJ_ARRAY_SIZE(ans);
+ pkt.hdr.arcount = 0;
+ pkt.ans = ans;
+ pkt.arr = ar;
+
+ ans[0].name = pj_str("_sip._udp.example.com");
+ ans[0].type = PJ_DNS_TYPE_SRV;
+ ans[0].dnsclass = PJ_DNS_CLASS_IN;
+ ans[0].ttl = 3600;
+ ans[0].rdata.srv.prio = 0;
+ ans[0].rdata.srv.weight = 0;
+ ans[0].rdata.srv.port = 5060;
+ ans[0].rdata.srv.target = pj_str("sip01.example.com");
+
+ ans[1].name = pj_str("_sip._udp.example.com");
+ ans[1].type = PJ_DNS_TYPE_SRV;
+ ans[1].dnsclass = PJ_DNS_CLASS_IN;
+ ans[1].ttl = 3600;
+ ans[1].rdata.srv.prio = 0;
+ ans[1].rdata.srv.weight = 20;
+ ans[1].rdata.srv.port = 5060;
+ ans[1].rdata.srv.target = pj_str("sip02.example.com");
+
+ ans[2].name = pj_str("_sip._udp.example.com");
+ ans[2].type = PJ_DNS_TYPE_SRV;
+ ans[2].dnsclass = PJ_DNS_CLASS_IN;
+ ans[2].ttl = 3600;
+ ans[2].rdata.srv.prio = 0;
+ ans[2].rdata.srv.weight = 10;
+ ans[2].rdata.srv.port = 5060;
+ ans[2].rdata.srv.target = pj_str("sip03.example.com");
+
+ ans[3].name = pj_str("_sip._udp.example.com");
+ ans[3].type = PJ_DNS_TYPE_SRV;
+ ans[3].dnsclass = PJ_DNS_CLASS_IN;
+ ans[3].ttl = 3600;
+ ans[3].rdata.srv.prio = 1;
+ ans[3].rdata.srv.weight = 0;
+ ans[3].rdata.srv.port = 5060;
+ ans[3].rdata.srv.target = pj_str("sip04.example.com");
+
+ pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
+
+ ar[0].name = pj_str("sip01.example.com");
+ ar[0].type = PJ_DNS_TYPE_A;
+ ar[0].dnsclass = PJ_DNS_CLASS_IN;
+ ar[0].ttl = 3600;
+ ar[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "1.1.1.1"));
+
+ ar[1].name = pj_str("sip02.example.com");
+ ar[1].type = PJ_DNS_TYPE_A;
+ ar[1].dnsclass = PJ_DNS_CLASS_IN;
+ ar[1].ttl = 3600;
+ ar[1].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "2.2.2.2"));
+
+ ar[2].name = pj_str("sip03.example.com");
+ ar[2].type = PJ_DNS_TYPE_A;
+ ar[2].dnsclass = PJ_DNS_CLASS_IN;
+ ar[2].ttl = 3600;
+ ar[2].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "3.3.3.3"));
+
+ ar[3].name = pj_str("sip04.example.com");
+ ar[3].type = PJ_DNS_TYPE_A;
+ ar[3].dnsclass = PJ_DNS_CLASS_IN;
+ ar[3].ttl = 3600;
+ ar[3].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "4.4.4.4"));
+
+ ar[4].name = pj_str("example.com");
+ ar[4].type = PJ_DNS_TYPE_A;
+ ar[4].dnsclass = PJ_DNS_CLASS_IN;
+ ar[4].ttl = 3600;
+ ar[4].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "5.5.5.5"));
+
+ /*
+ * Create individual A records for all hosts in "example.com" domain.
+ */
+ for (i=0; i<PJ_ARRAY_SIZE(ar); ++i) {
+ pj_bzero(&pkt, sizeof(pkt));
+ pkt.hdr.flags = PJ_DNS_SET_QR(1);
+ pkt.hdr.qdcount = 1;
+ pkt.q = &q;
+ q.name = ar[i].name;
+ q.type = ar[i].type;
+ q.dnsclass = PJ_DNS_CLASS_IN;
+ pkt.hdr.anscount = 1;
+ pkt.ans = &ar[i];
+
+ pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
+ }
+
+ /*
+ * Simulate DNS error response by creating these answers.
+ * Sample of invalid SRV records: _sip._udp.sip01.example.com.
+ */
+ for (i=0; i<PJ_ARRAY_SIZE(ans); ++i) {
+ pj_dns_parsed_query q;
+ char buf[128];
+ char *services[] = { "_sip._udp.", "_sip._tcp.", "_sips._tcp."};
+ unsigned j;
+
+ for (j=0; j<PJ_ARRAY_SIZE(services); ++j) {
+ q.dnsclass = PJ_DNS_CLASS_IN;
+ q.type = PJ_DNS_TYPE_SRV;
+
+ q.name.ptr = buf;
+ pj_bzero(buf, sizeof(buf));
+ pj_strcpy2(&q.name, services[j]);
+ pj_strcat(&q.name, &ans[i].rdata.srv.target);
+
+ pj_bzero(&pkt, sizeof(pkt));
+ pkt.hdr.qdcount = 1;
+ pkt.hdr.flags = PJ_DNS_SET_QR(1) |
+ PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN);
+ pkt.q = &q;
+
+ pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
+ }
+ }
+
+
+ /*
+ * ANOTHER DOMAIN.
+ *
+ * This time we let SRV and A get answered in different DNS
+ * query.
+ */
+
+ /* The "domain.com" DNS records (note the different the port):
+
+ _sip._tcp.domain.com 3600 IN SRV 1 0 50060 sip06.domain.com.
+ _sip._tcp.domain.com 3600 IN SRV 2 0 50060 sip07.domain.com.
+
+ sip06.domain.com. 3600 IN A 6.6.6.6
+ sip07.domain.com. 3600 IN A 7.7.7.7
+ */
+
+ pj_bzero(&pkt, sizeof(pkt));
+ pj_bzero(&ans, sizeof(ans));
+ pkt.hdr.flags = PJ_DNS_SET_QR(1);
+ pkt.hdr.anscount = 2;
+ pkt.ans = ans;
+
+ /* Add the SRV records, with reverse priority (to test that sorting
+ * works.
+ */
+ ans[0].name = pj_str("_sip._tcp.domain.com");
+ ans[0].type = PJ_DNS_TYPE_SRV;
+ ans[0].dnsclass = PJ_DNS_CLASS_IN;
+ ans[0].ttl = 3600;
+ ans[0].rdata.srv.prio = 2;
+ ans[0].rdata.srv.weight = 0;
+ ans[0].rdata.srv.port = 50060;
+ ans[0].rdata.srv.target = pj_str("SIP07.DOMAIN.COM");
+
+ ans[1].name = pj_str("_sip._tcp.domain.com");
+ ans[1].type = PJ_DNS_TYPE_SRV;
+ ans[1].dnsclass = PJ_DNS_CLASS_IN;
+ ans[1].ttl = 3600;
+ ans[1].rdata.srv.prio = 1;
+ ans[1].rdata.srv.weight = 0;
+ ans[1].rdata.srv.port = 50060;
+ ans[1].rdata.srv.target = pj_str("SIP06.DOMAIN.COM");
+
+ pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
+
+ /* From herein there is only one answer */
+ pkt.hdr.anscount = 1;
+
+ /* Add a single SRV for UDP */
+ ans[0].name = pj_str("_sip._udp.domain.com");
+ ans[0].type = PJ_DNS_TYPE_SRV;
+ ans[0].dnsclass = PJ_DNS_CLASS_IN;
+ ans[0].ttl = 3600;
+ ans[0].rdata.srv.prio = 0;
+ ans[0].rdata.srv.weight = 0;
+ ans[0].rdata.srv.port = 50060;
+ ans[0].rdata.srv.target = pj_str("SIP06.DOMAIN.COM");
+
+ pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
+
+
+ /* Add the A record for sip06.domain.com */
+ ans[0].name = pj_str("sip06.domain.com");
+ ans[0].type = PJ_DNS_TYPE_A;
+ ans[0].dnsclass = PJ_DNS_CLASS_IN;
+ ans[0].ttl = 3600;
+ ans[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "6.6.6.6"));
+
+ pkt.hdr.qdcount = 1;
+ pkt.q = &q;
+ q.name = ans[0].name;
+ q.type = ans[0].type;
+ q.dnsclass = ans[0].dnsclass;
+
+ pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
+
+ /* Add the A record for sip07.domain.com */
+ ans[0].name = pj_str("sip07.domain.com");
+ ans[0].type = PJ_DNS_TYPE_A;
+ ans[0].dnsclass = PJ_DNS_CLASS_IN;
+ ans[0].ttl = 3600;
+ ans[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "7.7.7.7"));
+
+ pkt.hdr.qdcount = 1;
+ pkt.q = &q;
+ q.name = ans[0].name;
+ q.type = ans[0].type;
+ q.dnsclass = ans[0].dnsclass;
+
+ pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
+
+ pkt.hdr.qdcount = 0;
+}
+
+
+/*
+ * Perform server resolution where the results are expected to
+ * come in strict order.
+ */
+static int test_resolve(const char *title,
+ pj_pool_t *pool,
+ pjsip_transport_type_e type,
+ char *name,
+ int port,
+ pjsip_server_addresses *ref)
+{
+ pjsip_host_info dest;
+ struct result result;
+
+ PJ_LOG(3,(THIS_FILE, " test_resolve(): %s", title));
+
+ dest.type = type;
+ dest.flag = pjsip_transport_get_flag_from_type(type);
+ dest.addr.host = pj_str(name);
+ dest.addr.port = port;
+
+ result.status = 0x12345678;
+
+ pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb);
+
+ while (result.status == 0x12345678) {
+ int i = 0;
+ pj_time_val timeout = { 1, 0 };
+ pjsip_endpt_handle_events(endpt, &timeout);
+ if (i == 1)
+ pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE);
+ }
+
+ if (result.status != PJ_SUCCESS) {
+ app_perror(" pjsip_endpt_resolve() error", result.status);
+ return result.status;
+ }
+
+ if (ref) {
+ unsigned i;
+
+ if (ref->count != result.servers.count) {
+ PJ_LOG(3,(THIS_FILE, " test_resolve() error 10: result count mismatch"));
+ return 10;
+ }
+
+ for (i=0; i<ref->count; ++i) {
+ pj_sockaddr_in *ra = (pj_sockaddr_in *)&ref->entry[i].addr;
+ pj_sockaddr_in *rb = (pj_sockaddr_in *)&result.servers.entry[i].addr;
+
+ if (ra->sin_addr.s_addr != rb->sin_addr.s_addr) {
+ PJ_LOG(3,(THIS_FILE, " test_resolve() error 20: IP address mismatch"));
+ return 20;
+ }
+ if (ra->sin_port != rb->sin_port) {
+ PJ_LOG(3,(THIS_FILE, " test_resolve() error 30: port mismatch"));
+ return 30;
+ }
+ if (ref->entry[i].addr_len != result.servers.entry[i].addr_len) {
+ PJ_LOG(3,(THIS_FILE, " test_resolve() error 40: addr_len mismatch"));
+ return 40;
+ }
+ if (ref->entry[i].type != result.servers.entry[i].type) {
+ PJ_LOG(3,(THIS_FILE, " test_resolve() error 50: transport type mismatch"));
+ return 50;
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Perform round-robin/load balance test.
+ */
+static int round_robin_test(pj_pool_t *pool)
+{
+ enum { COUNT = 400, PCT_ALLOWANCE = 5 };
+ unsigned i;
+ struct server_hit
+ {
+ char *ip_addr;
+ unsigned percent;
+ unsigned hits;
+ } server_hit[] =
+ {
+ { "1.1.1.1", 3, 0 },
+ { "2.2.2.2", 65, 0 },
+ { "3.3.3.3", 32, 0 },
+ { "4.4.4.4", 0, 0 }
+ };
+
+ PJ_LOG(3,(THIS_FILE, " Performing round-robin/load-balance test.."));
+
+ /* Do multiple resolve request to "example.com".
+ * The resolver should select the server based on the weight proportion
+ * the the servers in the SRV entry.
+ */
+ for (i=0; i<COUNT; ++i) {
+ pjsip_host_info dest;
+ struct result result;
+ unsigned j;
+
+ dest.type = PJSIP_TRANSPORT_UDP;
+ dest.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP);
+ dest.addr.host = pj_str("example.com");
+ dest.addr.port = 0;
+
+ result.status = 0x12345678;
+
+ pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb);
+
+ while (result.status == 0x12345678) {
+ int i = 0;
+ pj_time_val timeout = { 1, 0 };
+ pjsip_endpt_handle_events(endpt, &timeout);
+ if (i == 1)
+ pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE);
+ }
+
+ /* Find which server was "hit" */
+ for (j=0; j<PJ_ARRAY_SIZE(server_hit); ++j) {
+ pj_str_t tmp;
+ pj_in_addr a1;
+ pj_sockaddr_in *a2;
+
+ tmp = pj_str(server_hit[j].ip_addr);
+ a1 = pj_inet_addr(&tmp);
+ a2 = (pj_sockaddr_in*) &result.servers.entry[0].addr;
+
+ if (a1.s_addr == a2->sin_addr.s_addr) {
+ server_hit[j].hits++;
+ break;
+ }
+ }
+
+ if (j == PJ_ARRAY_SIZE(server_hit)) {
+ PJ_LOG(1,(THIS_FILE, "..round_robin_test() error 10: returned address mismatch"));
+ return 10;
+ }
+ }
+
+ /* Print the actual hit rate */
+ for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) {
+ PJ_LOG(3,(THIS_FILE, " ..Server %s: weight=%d%%, hit %d%% times",
+ server_hit[i].ip_addr, server_hit[i].percent,
+ (server_hit[i].hits * 100) / COUNT));
+ }
+
+ /* Compare the actual hit with the weight proportion */
+ for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) {
+ int actual_pct = (server_hit[i].hits * 100) / COUNT;
+
+ if (actual_pct + PCT_ALLOWANCE < (int)server_hit[i].percent ||
+ actual_pct - PCT_ALLOWANCE > (int)server_hit[i].percent)
+ {
+ PJ_LOG(1,(THIS_FILE,
+ "..round_robin_test() error 20: "
+ "hit rate difference for server %s (%d%%) is more than "
+ "tolerable allowance (%d%%)",
+ server_hit[i].ip_addr,
+ actual_pct - server_hit[i].percent,
+ PCT_ALLOWANCE));
+ return 20;
+ }
+ }
+
+ PJ_LOG(3,(THIS_FILE,
+ " Load balance test success, hit-rate is "
+ "within %d%% allowance", PCT_ALLOWANCE));
+ return PJ_SUCCESS;
+}
+
+
+#define C(expr) status = expr; \
+ if (status != PJ_SUCCESS) app_perror(THIS_FILE, "Error", status);
+
+static void add_ref(pjsip_server_addresses *r,
+ pjsip_transport_type_e type,
+ char *addr,
+ int port)
+{
+ pj_sockaddr_in *a;
+ pj_str_t tmp;
+
+ r->entry[r->count].type = type;
+ r->entry[r->count].priority = 0;
+ r->entry[r->count].weight = 0;
+ r->entry[r->count].addr_len = sizeof(pj_sockaddr_in);
+
+ a = (pj_sockaddr_in *)&r->entry[r->count].addr;
+ a->sin_family = pj_AF_INET();
+ tmp = pj_str(addr);
+ a->sin_addr = pj_inet_addr(&tmp);
+ a->sin_port = pj_htons((pj_uint16_t)port);
+
+ r->count++;
+}
+
+static void create_ref(pjsip_server_addresses *r,
+ pjsip_transport_type_e type,
+ char *addr,
+ int port)
+{
+ r->count = 0;
+ add_ref(r, type, addr, port);
+}
+
+
+/*
+ * Main test entry.
+ */
+int resolve_test(void)
+{
+ pj_pool_t *pool;
+ pj_dns_resolver *resv;
+ pj_str_t nameserver;
+ pj_uint16_t port = 5353;
+ pj_status_t status;
+
+ pool = pjsip_endpt_create_pool(endpt, NULL, 4000, 4000);
+
+ status = pjsip_endpt_create_resolver(endpt, &resv);
+
+ nameserver = pj_str("192.168.0.106");
+ pj_dns_resolver_set_ns(resv, 1, &nameserver, &port);
+ pjsip_endpt_set_resolver(endpt, resv);
+
+ add_dns_entries(resv);
+
+ /* These all should be resolved as IP addresses (DNS A query) */
+ {
+ pjsip_server_addresses ref;
+ create_ref(&ref, PJSIP_TRANSPORT_UDP, "1.1.1.1", 5060);
+ status = test_resolve("IP address without transport and port", pool, PJSIP_TRANSPORT_UNSPECIFIED, "1.1.1.1", 0, &ref);
+ if (status != PJ_SUCCESS)
+ return -100;
+ }
+ {
+ pjsip_server_addresses ref;
+ create_ref(&ref, PJSIP_TRANSPORT_UDP, "1.1.1.1", 5060);
+ status = test_resolve("IP address with explicit port", pool, PJSIP_TRANSPORT_UNSPECIFIED, "1.1.1.1", 5060, &ref);
+ if (status != PJ_SUCCESS)
+ return -110;
+ }
+ {
+ pjsip_server_addresses ref;
+ create_ref(&ref, PJSIP_TRANSPORT_TCP, "1.1.1.1", 5060);
+ status = test_resolve("IP address without port (TCP)", pool, PJSIP_TRANSPORT_TCP,"1.1.1.1", 0, &ref);
+ if (status != PJ_SUCCESS)
+ return -120;
+ }
+ {
+ pjsip_server_addresses ref;
+ create_ref(&ref, PJSIP_TRANSPORT_TLS, "1.1.1.1", 5061);
+ status = test_resolve("IP address without port (TLS)", pool, PJSIP_TRANSPORT_TLS, "1.1.1.1", 0, &ref);
+ if (status != PJ_SUCCESS)
+ return -130;
+ }
+
+ /* This should be resolved as DNS A record (because port is present) */
+ {
+ pjsip_server_addresses ref;
+ create_ref(&ref, PJSIP_TRANSPORT_UDP, "5.5.5.5", 5060);
+ status = test_resolve("domain name with port should resolve to A record", pool, PJSIP_TRANSPORT_UNSPECIFIED, "example.com", 5060, &ref);
+ if (status != PJ_SUCCESS)
+ return -140;
+ }
+
+ /* This will fail to be resolved as SRV, resolver should fallback to
+ * resolving to A record.
+ */
+ {
+ pjsip_server_addresses ref;
+ create_ref(&ref, PJSIP_TRANSPORT_UDP, "2.2.2.2", 5060);
+ status = test_resolve("failure with SRV fallback to A record", pool, PJSIP_TRANSPORT_UNSPECIFIED, "sip02.example.com", 0, &ref);
+ if (status != PJ_SUCCESS)
+ return -150;
+ }
+
+ /* Same as above, but explicitly for TLS. */
+ {
+ pjsip_server_addresses ref;
+ create_ref(&ref, PJSIP_TRANSPORT_TLS, "2.2.2.2", 5061);
+ status = test_resolve("failure with SRV fallback to A record (for TLS)", pool, PJSIP_TRANSPORT_TLS, "sip02.example.com", 0, &ref);
+ if (status != PJ_SUCCESS)
+ return -150;
+ }
+
+ /* Standard DNS SRV followed by A recolution */
+ {
+ pjsip_server_addresses ref;
+ create_ref(&ref, PJSIP_TRANSPORT_UDP, "6.6.6.6", 50060);
+ status = test_resolve("standard SRV resolution", pool, PJSIP_TRANSPORT_UNSPECIFIED, "domain.com", 0, &ref);
+ if (status != PJ_SUCCESS)
+ return -155;
+ }
+
+ /* Standard DNS SRV followed by A recolution (explicit transport) */
+ {
+ pjsip_server_addresses ref;
+ create_ref(&ref, PJSIP_TRANSPORT_TCP, "6.6.6.6", 50060);
+ add_ref(&ref, PJSIP_TRANSPORT_TCP, "7.7.7.7", 50060);
+ status = test_resolve("standard SRV resolution with explicit transport (TCP)", pool, PJSIP_TRANSPORT_TCP, "domain.com", 0, &ref);
+ if (status != PJ_SUCCESS)
+ return -160;
+ }
+
+
+ /* Round robin/load balance test */
+ if (round_robin_test(pool) != 0)
+ return -170;
+
+ /* Timeout test */
+ {
+ status = test_resolve("timeout test", pool, PJSIP_TRANSPORT_UNSPECIFIED, "an.invalid.address", 0, NULL);
+ if (status == PJ_SUCCESS)
+ return -150;
+ }
+
+ return 0;
+}
+
diff --git a/pjsip/src/test/inv_offer_answer_test.c b/pjsip/src/test/inv_offer_answer_test.c
new file mode 100644
index 0000000..7fdb11e
--- /dev/null
+++ b/pjsip/src/test/inv_offer_answer_test.c
@@ -0,0 +1,691 @@
+/* $Id: inv_offer_answer_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip_ua.h>
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "inv_offer_answer_test.c"
+#define PORT 5068
+#define CONTACT "sip:127.0.0.1:5068"
+#define TRACE_(x) PJ_LOG(3,x)
+
+static struct oa_sdp_t
+{
+ const char *offer;
+ const char *answer;
+ unsigned pt_result;
+} oa_sdp[] =
+{
+ {
+ /* Offer: */
+ "v=0\r\n"
+ "o=alice 1 1 IN IP4 host.anywhere.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n",
+
+ /* Answer: */
+ "v=0\r\n"
+ "o=bob 1 1 IN IP4 host.example.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49920 RTP/AVP 0\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n"
+ "m=video 0 RTP/AVP 31\r\n",
+
+ 0
+ },
+
+ {
+ /* Offer: */
+ "v=0\r\n"
+ "o=alice 2 2 IN IP4 host.anywhere.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 8\r\n"
+ "a=rtpmap:0 PCMA/8000\r\n",
+
+ /* Answer: */
+ "v=0\r\n"
+ "o=bob 2 2 IN IP4 host.example.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49920 RTP/AVP 8\r\n"
+ "a=rtpmap:0 PCMA/8000\r\n",
+
+ 8
+ },
+
+ {
+ /* Offer: */
+ "v=0\r\n"
+ "o=alice 3 3 IN IP4 host.anywhere.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 3\r\n",
+
+ /* Answer: */
+ "v=0\r\n"
+ "o=bob 3 3 IN IP4 host.example.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49920 RTP/AVP 3\r\n",
+
+ 3
+ },
+
+ {
+ /* Offer: */
+ "v=0\r\n"
+ "o=alice 4 4 IN IP4 host.anywhere.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.anywhere.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49170 RTP/AVP 4\r\n",
+
+ /* Answer: */
+ "v=0\r\n"
+ "o=bob 4 4 IN IP4 host.example.com\r\n"
+ "s= \r\n"
+ "c=IN IP4 host.example.com\r\n"
+ "t=0 0\r\n"
+ "m=audio 49920 RTP/AVP 4\r\n",
+
+ 4
+ }
+};
+
+
+
+typedef enum oa_t
+{
+ OFFERER_NONE,
+ OFFERER_UAC,
+ OFFERER_UAS
+} oa_t;
+
+typedef struct inv_test_param_t
+{
+ char *title;
+ unsigned inv_option;
+ pj_bool_t need_established;
+ unsigned count;
+ oa_t oa[4];
+} inv_test_param_t;
+
+typedef struct inv_test_t
+{
+ inv_test_param_t param;
+ pjsip_inv_session *uac;
+ pjsip_inv_session *uas;
+
+ pj_bool_t complete;
+ pj_bool_t uas_complete,
+ uac_complete;
+
+ unsigned oa_index;
+ unsigned uac_update_cnt,
+ uas_update_cnt;
+} inv_test_t;
+
+
+/**************** GLOBALS ******************/
+static inv_test_t inv_test;
+static unsigned job_cnt;
+
+typedef enum job_type
+{
+ SEND_OFFER,
+ ESTABLISH_CALL
+} job_type;
+
+typedef struct job_t
+{
+ job_type type;
+ pjsip_role_e who;
+} job_t;
+
+static job_t jobs[128];
+
+
+/**************** UTILS ******************/
+static pjmedia_sdp_session *create_sdp(pj_pool_t *pool, const char *body)
+{
+ pjmedia_sdp_session *sdp;
+ pj_str_t dup;
+ pj_status_t status;
+
+ pj_strdup2_with_null(pool, &dup, body);
+ status = pjmedia_sdp_parse(pool, dup.ptr, dup.slen, &sdp);
+ pj_assert(status == PJ_SUCCESS);
+
+ return sdp;
+}
+
+/**************** INVITE SESSION CALLBACKS ******************/
+static void on_rx_offer(pjsip_inv_session *inv,
+ const pjmedia_sdp_session *offer)
+{
+ pjmedia_sdp_session *sdp;
+
+ PJ_UNUSED_ARG(offer);
+
+ sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].answer);
+ pjsip_inv_set_sdp_answer(inv, sdp);
+
+ if (inv_test.oa_index == inv_test.param.count-1 &&
+ inv_test.param.need_established)
+ {
+ jobs[job_cnt].type = ESTABLISH_CALL;
+ jobs[job_cnt].who = PJSIP_ROLE_UAS;
+ job_cnt++;
+ }
+}
+
+
+static void on_create_offer(pjsip_inv_session *inv,
+ pjmedia_sdp_session **p_offer)
+{
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(p_offer);
+
+ pj_assert(!"Should not happen");
+}
+
+static void on_media_update(pjsip_inv_session *inv_ses,
+ pj_status_t status)
+{
+ PJ_UNUSED_ARG(status);
+
+ if (inv_ses == inv_test.uas) {
+ inv_test.uas_update_cnt++;
+ pj_assert(inv_test.uas_update_cnt - inv_test.uac_update_cnt <= 1);
+ TRACE_((THIS_FILE, " Callee media is established"));
+ } else if (inv_ses == inv_test.uac) {
+ inv_test.uac_update_cnt++;
+ pj_assert(inv_test.uac_update_cnt - inv_test.uas_update_cnt <= 1);
+ TRACE_((THIS_FILE, " Caller media is established"));
+
+ } else {
+ pj_assert(!"Unknown session!");
+ }
+
+ if (inv_test.uac_update_cnt == inv_test.uas_update_cnt) {
+ inv_test.oa_index++;
+
+ if (inv_test.oa_index < inv_test.param.count) {
+ switch (inv_test.param.oa[inv_test.oa_index]) {
+ case OFFERER_UAC:
+ jobs[job_cnt].type = SEND_OFFER;
+ jobs[job_cnt].who = PJSIP_ROLE_UAC;
+ job_cnt++;
+ break;
+ case OFFERER_UAS:
+ jobs[job_cnt].type = SEND_OFFER;
+ jobs[job_cnt].who = PJSIP_ROLE_UAS;
+ job_cnt++;
+ break;
+ default:
+ pj_assert(!"Invalid oa");
+ }
+ }
+
+ pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs));
+ }
+}
+
+static void on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
+{
+ const char *who = NULL;
+
+ PJ_UNUSED_ARG(e);
+
+ if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+ TRACE_((THIS_FILE, " %s call disconnected",
+ (inv==inv_test.uas ? "Callee" : "Caller")));
+ return;
+ }
+
+ if (inv->state != PJSIP_INV_STATE_CONFIRMED)
+ return;
+
+ if (inv == inv_test.uas) {
+ inv_test.uas_complete = PJ_TRUE;
+ who = "Callee";
+ } else if (inv == inv_test.uac) {
+ inv_test.uac_complete = PJ_TRUE;
+ who = "Caller";
+ } else
+ pj_assert(!"No session");
+
+ TRACE_((THIS_FILE, " %s call is confirmed", who));
+
+ if (inv_test.uac_complete && inv_test.uas_complete)
+ inv_test.complete = PJ_TRUE;
+}
+
+
+/**************** MODULE TO RECEIVE INITIAL INVITE ******************/
+
+static pj_bool_t on_rx_request(pjsip_rx_data *rdata)
+{
+ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG &&
+ rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD)
+ {
+ pjsip_dialog *dlg;
+ pjmedia_sdp_session *sdp = NULL;
+ pj_str_t uri;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /*
+ * Create UAS
+ */
+ uri = pj_str(CONTACT);
+ status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata,
+ &uri, &dlg);
+ pj_assert(status == PJ_SUCCESS);
+
+ if (inv_test.param.oa[0] == OFFERER_UAC)
+ sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].answer);
+ else if (inv_test.param.oa[0] == OFFERER_UAS)
+ sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].offer);
+ else
+ pj_assert(!"Invalid offerer type");
+
+ status = pjsip_inv_create_uas(dlg, rdata, sdp, inv_test.param.inv_option, &inv_test.uas);
+ pj_assert(status == PJ_SUCCESS);
+
+ TRACE_((THIS_FILE, " Sending 183 with SDP"));
+
+ /*
+ * Answer with 183
+ */
+ status = pjsip_inv_initial_answer(inv_test.uas, rdata, 183, NULL,
+ NULL, &tdata);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjsip_inv_send_msg(inv_test.uas, tdata);
+ pj_assert(status == PJ_SUCCESS);
+
+ return PJ_TRUE;
+ }
+
+ return PJ_FALSE;
+}
+
+static pjsip_module mod_inv_oa_test =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-inv-oa-test", 15 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+/**************** THE TEST ******************/
+static void run_job(job_t *j)
+{
+ pjsip_inv_session *inv;
+ pjsip_tx_data *tdata;
+ pjmedia_sdp_session *sdp;
+ pj_status_t status;
+
+ if (j->who == PJSIP_ROLE_UAC)
+ inv = inv_test.uac;
+ else
+ inv = inv_test.uas;
+
+ switch (j->type) {
+ case SEND_OFFER:
+ sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].offer);
+
+ TRACE_((THIS_FILE, " Sending UPDATE with offer"));
+ status = pjsip_inv_update(inv, NULL, sdp, &tdata);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjsip_inv_send_msg(inv, tdata);
+ pj_assert(status == PJ_SUCCESS);
+ break;
+ case ESTABLISH_CALL:
+ TRACE_((THIS_FILE, " Sending 200/OK"));
+ status = pjsip_inv_answer(inv, 200, NULL, NULL, &tdata);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjsip_inv_send_msg(inv, tdata);
+ pj_assert(status == PJ_SUCCESS);
+ break;
+ }
+}
+
+
+static int perform_test(inv_test_param_t *param)
+{
+ pj_str_t uri;
+ pjsip_dialog *dlg;
+ pjmedia_sdp_session *sdp;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " %s", param->title));
+
+ pj_bzero(&inv_test, sizeof(inv_test));
+ pj_memcpy(&inv_test.param, param, sizeof(*param));
+ job_cnt = 0;
+
+ uri = pj_str(CONTACT);
+
+ /*
+ * Create UAC
+ */
+ status = pjsip_dlg_create_uac(pjsip_ua_instance(),
+ &uri, &uri, &uri, &uri, &dlg);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, -10);
+
+ if (inv_test.param.oa[0] == OFFERER_UAC)
+ sdp = create_sdp(dlg->pool, oa_sdp[0].offer);
+ else
+ sdp = NULL;
+
+ status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20);
+
+ TRACE_((THIS_FILE, " Sending INVITE %s offer", (sdp ? "with" : "without")));
+
+ /*
+ * Make call!
+ */
+ status = pjsip_inv_invite(inv_test.uac, &tdata);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
+
+ status = pjsip_inv_send_msg(inv_test.uac, tdata);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
+
+ /*
+ * Wait until test completes
+ */
+ while (!inv_test.complete) {
+ pj_time_val delay = {0, 20};
+
+ pjsip_endpt_handle_events(endpt, &delay);
+
+ while (job_cnt) {
+ job_t j;
+
+ j = jobs[0];
+ pj_array_erase(jobs, sizeof(jobs[0]), job_cnt, 0);
+ --job_cnt;
+
+ run_job(&j);
+ }
+ }
+
+ flush_events(100);
+
+ /*
+ * Hangup
+ */
+ TRACE_((THIS_FILE, " Disconnecting call"));
+ status = pjsip_inv_end_session(inv_test.uas, PJSIP_SC_DECLINE, 0, &tdata);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjsip_inv_send_msg(inv_test.uas, tdata);
+ pj_assert(status == PJ_SUCCESS);
+
+ flush_events(500);
+
+ return 0;
+}
+
+
+static pj_bool_t log_on_rx_msg(pjsip_rx_data *rdata)
+{
+ pjsip_msg *msg = rdata->msg_info.msg;
+ char info[80];
+
+ if (msg->type == PJSIP_REQUEST_MSG)
+ pj_ansi_snprintf(info, sizeof(info), "%.*s",
+ (int)msg->line.req.method.name.slen,
+ msg->line.req.method.name.ptr);
+ else
+ pj_ansi_snprintf(info, sizeof(info), "%d/%.*s",
+ msg->line.status.code,
+ (int)rdata->msg_info.cseq->method.name.slen,
+ rdata->msg_info.cseq->method.name.ptr);
+
+ TRACE_((THIS_FILE, " Received %s %s sdp", info,
+ (msg->body ? "with" : "without")));
+
+ return PJ_FALSE;
+}
+
+
+/* Message logger module. */
+static pjsip_module mod_msg_logger =
+{
+ NULL, NULL, /* prev and next */
+ { "mod-msg-loggee", 14}, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &log_on_rx_msg, /* on_rx_request() */
+ &log_on_rx_msg, /* on_rx_response() */
+ NULL, /* on_tx_request() */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+static inv_test_param_t test_params[] =
+{
+/* Normal scenario:
+
+ UAC UAS
+ INVITE (offer) -->
+ 200/INVITE (answer) <--
+ ACK -->
+ */
+#if 0
+ {
+ "Standard INVITE with offer",
+ 0,
+ PJ_TRUE,
+ 1,
+ { OFFERER_UAC }
+ },
+
+ {
+ "Standard INVITE with offer, with 100rel",
+ PJSIP_INV_REQUIRE_100REL,
+ PJ_TRUE,
+ 1,
+ { OFFERER_UAC }
+ },
+#endif
+
+/* Delayed offer:
+ UAC UAS
+ INVITE (no SDP) -->
+ 200/INVITE (offer) <--
+ ACK (answer) -->
+ */
+#if 1
+ {
+ "INVITE with no offer",
+ 0,
+ PJ_TRUE,
+ 1,
+ { OFFERER_UAS }
+ },
+
+ {
+ "INVITE with no offer, with 100rel",
+ PJSIP_INV_REQUIRE_100REL,
+ PJ_TRUE,
+ 1,
+ { OFFERER_UAS }
+ },
+#endif
+
+/* Subsequent UAC offer with UPDATE:
+
+ UAC UAS
+ INVITE (offer) -->
+ 180/rel (answer) <--
+ UPDATE (offer) --> inv_update() on_rx_offer()
+ set_sdp_answer()
+ 200/UPDATE (answer) <--
+ 200/INVITE <--
+ ACK -->
+*/
+#if 1
+ {
+ "INVITE and UPDATE by UAC",
+ 0,
+ PJ_TRUE,
+ 2,
+ { OFFERER_UAC, OFFERER_UAC }
+ },
+ {
+ "INVITE and UPDATE by UAC, with 100rel",
+ PJSIP_INV_REQUIRE_100REL,
+ PJ_TRUE,
+ 2,
+ { OFFERER_UAC, OFFERER_UAC }
+ },
+#endif
+
+/* Subsequent UAS offer with UPDATE:
+
+ INVITE (offer -->
+ 180/rel (answer) <--
+ UPDATE (offer) <-- inv_update()
+ on_rx_offer()
+ set_sdp_answer()
+ 200/UPDATE (answer) -->
+ UPDATE (offer) --> on_rx_offer()
+ set_sdp_answer()
+ 200/UPDATE (answer) <--
+ 200/INVITE <--
+ ACK -->
+
+ */
+ {
+ "INVITE and many UPDATE by UAC and UAS",
+ 0,
+ PJ_TRUE,
+ 4,
+ { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS }
+ },
+
+};
+
+
+static pjsip_dialog* on_dlg_forked(pjsip_dialog *first_set, pjsip_rx_data *res)
+{
+ PJ_UNUSED_ARG(first_set);
+ PJ_UNUSED_ARG(res);
+
+ return NULL;
+}
+
+
+static void on_new_session(pjsip_inv_session *inv, pjsip_event *e)
+{
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(e);
+}
+
+
+int inv_offer_answer_test(void)
+{
+ unsigned i;
+ int rc = 0;
+
+ /* Init UA layer */
+ if (pjsip_ua_instance()->id == -1) {
+ pjsip_ua_init_param ua_param;
+ pj_bzero(&ua_param, sizeof(ua_param));
+ ua_param.on_dlg_forked = &on_dlg_forked;
+ pjsip_ua_init_module(endpt, &ua_param);
+ }
+
+ /* Init inv-usage */
+ if (pjsip_inv_usage_instance()->id == -1) {
+ pjsip_inv_callback inv_cb;
+ pj_bzero(&inv_cb, sizeof(inv_cb));
+ inv_cb.on_media_update = &on_media_update;
+ inv_cb.on_rx_offer = &on_rx_offer;
+ inv_cb.on_create_offer = &on_create_offer;
+ inv_cb.on_state_changed = &on_state_changed;
+ inv_cb.on_new_session = &on_new_session;
+ pjsip_inv_usage_init(endpt, &inv_cb);
+ }
+
+ /* 100rel module */
+ pjsip_100rel_init_module(endpt);
+
+ /* Our module */
+ pjsip_endpt_register_module(endpt, &mod_inv_oa_test);
+ pjsip_endpt_register_module(endpt, &mod_msg_logger);
+
+ /* Create SIP UDP transport */
+ {
+ pj_sockaddr_in addr;
+ pjsip_transport *tp;
+ pj_status_t status;
+
+ pj_sockaddr_in_init(&addr, NULL, PORT);
+ status = pjsip_udp_transport_start(endpt, &addr, NULL, 1, &tp);
+ pj_assert(status == PJ_SUCCESS);
+ }
+
+ /* Do tests */
+ for (i=0; i<PJ_ARRAY_SIZE(test_params); ++i) {
+ rc = perform_test(&test_params[i]);
+ if (rc != 0)
+ goto on_return;
+ }
+
+
+on_return:
+ return rc;
+}
+
diff --git a/pjsip/src/test/main.c b/pjsip/src/test/main.c
new file mode 100644
index 0000000..423e8cb
--- /dev/null
+++ b/pjsip/src/test/main.c
@@ -0,0 +1,92 @@
+/* $Id: main.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+extern const char *system_name;
+
+static void usage()
+{
+ puts("Usage: test-pjsip");
+ puts("Options:");
+ puts(" -i,--interractive Key input at the end.");
+ puts(" -h,--help Show this screen");
+ puts(" -l,--log-level N Set log level (0-6)");
+}
+
+int main(int argc, char *argv[])
+{
+ int interractive = 0;
+ int retval;
+ char **opt_arg;
+
+ PJ_UNUSED_ARG(argc);
+
+ /* Parse arguments. */
+ opt_arg = argv+1;
+ while (*opt_arg) {
+ if (strcmp(*opt_arg, "-i") == 0 ||
+ strcmp(*opt_arg, "--interractive") == 0)
+ {
+ interractive = 1;
+ } else if (strcmp(*opt_arg, "-h") == 0 ||
+ strcmp(*opt_arg, "--help") == 0)
+ {
+ usage();
+ return 1;
+ } else if (strcmp(*opt_arg, "-l") == 0 ||
+ strcmp(*opt_arg, "--log-level") == 0)
+ {
+ ++opt_arg;
+ if (!opt_arg) {
+ usage();
+ return 1;
+ }
+ log_level = atoi(*opt_arg);
+ } else if (strcmp(*opt_arg, "-s") == 0 ||
+ strcmp(*opt_arg, "--system") == 0)
+ {
+ ++opt_arg;
+ if (!opt_arg) {
+ usage();
+ return 1;
+ }
+ system_name = *opt_arg;
+ } else {
+ usage();
+ return 1;
+ }
+
+ ++opt_arg;
+ }
+
+ retval = test_main();
+
+ if (interractive) {
+ char s[10];
+ printf("<Press ENTER to quit>\n"); fflush(stdout);
+ if (fgets(s, sizeof(s), stdin) == NULL)
+ return retval;
+ }
+
+ return retval;
+}
diff --git a/pjsip/src/test/main_rtems.c b/pjsip/src/test/main_rtems.c
new file mode 100644
index 0000000..a4d14e5
--- /dev/null
+++ b/pjsip/src/test/main_rtems.c
@@ -0,0 +1,12 @@
+
+/*
+ * !! OIY OIY !!
+ *
+ * The purpose of this file is only to get pjsip-test linked. I haven't
+ * actually tried to run pjsip-test on RTEMS!!
+ *
+ */
+
+#include "../../pjlib/src/pjlib-test/main_rtems.c"
+
+
diff --git a/pjsip/src/test/main_win32.c b/pjsip/src/test/main_win32.c
new file mode 100644
index 0000000..3043a39
--- /dev/null
+++ b/pjsip/src/test/main_win32.c
@@ -0,0 +1 @@
+#include "../../pjlib/src/pjlib-test/main_win32.c"
diff --git a/pjsip/src/test/msg_err_test.c b/pjsip/src/test/msg_err_test.c
new file mode 100644
index 0000000..d3c5636
--- /dev/null
+++ b/pjsip/src/test/msg_err_test.c
@@ -0,0 +1,104 @@
+/* $Id: msg_err_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "msg_err_test.c"
+
+
+static pj_bool_t verify_success(pjsip_msg *msg,
+ pjsip_parser_err_report *err_list)
+{
+ PJ_UNUSED_ARG(msg);
+ PJ_UNUSED_ARG(err_list);
+
+ return PJ_TRUE;
+}
+
+static struct test_entry
+{
+ char msg[1024];
+ pj_bool_t (*verify)(pjsip_msg *msg,
+ pjsip_parser_err_report *err_list);
+
+} test_entries[] =
+{
+ /* Syntax error in status line */
+ {
+ "SIP/2.0 200\r\n"
+ "H-Name: H-Value\r\n"
+ "\r\n",
+ &verify_success
+ },
+
+ /* Syntax error in header */
+ {
+ "SIP/2.0 200 OK\r\n"
+ "Via: SIP/2.0\r\n"
+ "H-Name: H-Value\r\n"
+ "\r\n",
+ &verify_success
+ },
+
+ /* Multiple syntax errors in headers */
+ {
+ "SIP/2.0 200 OK\r\n"
+ "Via: SIP/2.0\r\n"
+ "H-Name: H-Value\r\n"
+ "Via: SIP/2.0\r\n"
+ "\r\n",
+ &verify_success
+ }
+};
+
+
+int msg_err_test(void)
+{
+ pj_pool_t *pool;
+ unsigned i;
+
+ PJ_LOG(3,(THIS_FILE, "Testing parsing error"));
+
+ pool = pjsip_endpt_create_pool(endpt, "msgerrtest", 4000, 4000);
+
+ for (i=0; i<PJ_ARRAY_SIZE(test_entries); ++i) {
+ pjsip_parser_err_report err_list, *e;
+ pjsip_msg *msg;
+
+ PJ_LOG(3,(THIS_FILE, " Parsing msg %d", i));
+ pj_list_init(&err_list);
+ msg = pjsip_parse_msg(pool, test_entries[i].msg,
+ strlen(test_entries[i].msg), &err_list);
+
+ e = err_list.next;
+ while (e != &err_list) {
+ PJ_LOG(3,(THIS_FILE,
+ " reported syntax error at line %d col %d for %.*s",
+ e->line, e->col,
+ (int)e->hname.slen,
+ e->hname.ptr));
+ e = e->next;
+ }
+ }
+
+ pj_pool_release(pool);
+ return 0;
+}
diff --git a/pjsip/src/test/msg_logger.c b/pjsip/src/test/msg_logger.c
new file mode 100644
index 0000000..aaeda7d
--- /dev/null
+++ b/pjsip/src/test/msg_logger.c
@@ -0,0 +1,104 @@
+/* $Id: msg_logger.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "msg_logger.c"
+
+static pj_bool_t msg_log_enabled;
+
+static pj_bool_t on_rx_msg(pjsip_rx_data *rdata)
+{
+ if (msg_log_enabled) {
+ PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ rdata->msg_info.len,
+ pjsip_rx_data_get_info(rdata),
+ rdata->tp_info.transport->type_name,
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ rdata->msg_info.len,
+ rdata->msg_info.msg_buf));
+ }
+
+ return PJ_FALSE;
+}
+
+static pj_status_t on_tx_msg(pjsip_tx_data *tdata)
+{
+ if (msg_log_enabled) {
+ PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%s:%d:\n"
+ "%.*s\n"
+ "--end msg--",
+ (tdata->buf.cur - tdata->buf.start),
+ pjsip_tx_data_get_info(tdata),
+ tdata->tp_info.transport->type_name,
+ tdata->tp_info.dst_name,
+ tdata->tp_info.dst_port,
+ (tdata->buf.cur - tdata->buf.start),
+ tdata->buf.start));
+ }
+ return PJ_SUCCESS;
+}
+
+
+/* Message logger module. */
+static pjsip_module mod_msg_logger =
+{
+ NULL, NULL, /* prev and next */
+ { "mod-msg-logger", 14}, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &on_rx_msg, /* on_rx_request() */
+ &on_rx_msg, /* on_rx_response() */
+ &on_tx_msg, /* on_tx_request() */
+ &on_tx_msg, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+int init_msg_logger(void)
+{
+ pj_status_t status;
+
+ if (mod_msg_logger.id != -1)
+ return 0;
+
+ status = pjsip_endpt_register_module(endpt, &mod_msg_logger);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error registering module", status);
+ return -10;
+ }
+
+ return 0;
+}
+
+int msg_logger_set_enabled(pj_bool_t enabled)
+{
+ int val = msg_log_enabled;
+ msg_log_enabled = enabled;
+ return val;
+}
+
diff --git a/pjsip/src/test/msg_test.c b/pjsip/src/test/msg_test.c
new file mode 100644
index 0000000..f64906c
--- /dev/null
+++ b/pjsip/src/test/msg_test.c
@@ -0,0 +1,2084 @@
+/* $Id: msg_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define POOL_SIZE 8000
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+# define LOOP 10000
+#else
+# define LOOP 100000
+#endif
+#define AVERAGE_MSG_LEN 800
+#define THIS_FILE "msg_test.c"
+
+static pjsip_msg *create_msg0(pj_pool_t *pool);
+static pjsip_msg *create_msg1(pj_pool_t *pool);
+
+#define STATUS_PARTIAL 1
+#define STATUS_SYNTAX_ERROR 2
+
+#define FLAG_DETECT_ONLY 1
+#define FLAG_PARSE_ONLY 4
+#define FLAG_PRINT_ONLY 8
+
+struct test_msg
+{
+ char msg[1024];
+ pjsip_msg *(*creator)(pj_pool_t *pool);
+ pj_size_t len;
+ int expected_status;
+} test_array[] =
+{
+{
+ /* 'Normal' message with all headers. */
+ "INVITE sip:user@foo SIP/2.0\n"
+ "from: Hi I'm Joe <sip:joe.user@bar.otherdomain.com>;tag=123457890123456\r"
+ "To: Fellow User <sip:user@foo.bar.domain.com>\r\n"
+ "Call-ID: 12345678901234567890@bar\r\n"
+ "Content-Length: 0\r\n"
+ "CSeq: 123456 INVITE\n"
+ "Contact: <sip:joe@bar> ; q=0.5;expires=3600,sip:user@host;q=0.500\r"
+ " ,sip:user2@host2\n"
+ "Content-Type: text/html ; charset=ISO-8859-4\r"
+ "Route: <sip:bigbox3.site3.atlanta.com;lr>,\r\n"
+ " <sip:server10.biloxi.com;lr>\r"
+ "Record-Route: <sip:server10.biloxi.com>,\r\n" /* multiple routes+folding*/
+ " <sip:bigbox3.site3.atlanta.com;lr>\n"
+ "v: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c230\n"
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n" /* folding. */
+ " ;received=192.0.2.1\r\n"
+ "Via: SIP/2.0/UDP 10.2.1.1, SIP/2.0/TCP 192.168.1.1\n"
+ "Organization: \r"
+ "Max-Forwards: 70\n"
+ "X-Header: \r\n" /* empty header */
+ "P-Associated-URI:\r\n" /* empty header without space */
+ "\r\n",
+ &create_msg0,
+ 0,
+ PJ_SUCCESS
+},
+{
+ /* Typical response message. */
+ "SIP/2.0 200 OK\r\n"
+ "Via: SIP/2.0/SCTP server10.biloxi.com;branch=z9hG4bKnashds8;rport;received=192.0.2.1\r\n"
+ "Via: SIP/2.0/UDP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2\r\n"
+ "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds ;received=192.0.2.3\r\n"
+ "Route: <sip:proxy.sipprovider.com>\r\n"
+ "Route: <sip:proxy.supersip.com:5060>\r\n"
+ "Max-Forwards: 70\r\n"
+ "To: Bob <sip:bob@biloxi.com>;tag=a6c85cf\r\n"
+ "From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n"
+ "Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n"
+ "CSeq: 314159 INVITE\r\n"
+ "Contact: <sips:bob@192.0.2.4>\r\n"
+ "Content-Type: application/sdp\r\n"
+ "Content-Length: 150\r\n"
+ "\r\n"
+ "v=0\r\n"
+ "o=alice 53655765 2353687637 IN IP4 pc33.atlanta.com\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "c=IN IP4 pc33.atlanta.com\r\n"
+ "m=audio 3456 RTP/AVP 0 1 3 99\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n",
+ &create_msg1,
+ 0,
+ PJ_SUCCESS
+},
+{
+ /* Torture message from RFC 4475
+ * 3.1.1.1 A short tortuous INVITE
+ */
+ "INVITE sip:vivekg@chair-dnrc.example.com;unknownparam SIP/2.0\n"
+ "TO :\n"
+ " sip:vivekg@chair-dnrc.example.com ; tag = 1918181833n\n"
+ "from : \"J Rosenberg \\\\\\\"\" <sip:jdrosen@example.com>\n"
+ " ;\n"
+ " tag = 98asjd8\n"
+ "MaX-fOrWaRdS: 0068\n"
+ "Call-ID: wsinv.ndaksdj@192.0.2.1\n"
+ "Content-Length : 150\n"
+ "cseq: 0009\n"
+ " INVITE\n"
+ "Via : SIP / 2.0\n"
+ " /UDP\n"
+ " 192.0.2.2;rport;branch=390skdjuw\n"
+ "s :\n"
+ "NewFangledHeader: newfangled value\n"
+ " continued newfangled value\n"
+ "UnknownHeaderWithUnusualValue: ;;,,;;,;\n"
+ "Content-Type: application/sdp\n"
+ "Route:\n"
+ " <sip:services.example.com;lr;unknownwith=value;unknown-no-value>\n"
+ "v: SIP / 2.0 / TCP spindle.example.com ;\n"
+ " branch = z9hG4bK9ikj8 ,\n"
+ " SIP / 2.0 / UDP 192.168.255.111 ; branch=\n"
+ " z9hG4bK30239\n"
+ "m:\"Quoted string \\\"\\\"\" <sip:jdrosen@example.com> ; newparam =\n"
+ " newvalue ;\n"
+ " secondparam ; q = 0.33\r\n"
+ "\r\n"
+ "v=0\r\n"
+ "o=mhandley 29739 7272939 IN IP4 192.0.2.3\r\n"
+ "s=-\r\n"
+ "c=IN IP4 192.0.2.4\r\n"
+ "t=0 0\r\n"
+ "m=audio 49217 RTP/AVP 0 12\r\n"
+ "m=video 3227 RTP/AVP 31\r\n"
+ "a=rtpmap:31 LPC\r\n",
+ NULL,
+ 0,
+ PJ_SUCCESS
+},
+{
+ /* Torture message from RFC 4475
+ * 3.1.1.2 Wide Range of Valid Characters
+ */
+ "!interesting-Method0123456789_*+`.%indeed'~ sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*:&it+has=1,weird!*pas$wo~d_too.(doesn't-it)@example.com SIP/2.0\n"
+ "Via: SIP/2.0/UDP host1.example.com;rport;branch=z9hG4bK-.!%66*_+`'~\n"
+ "To: \"BEL:\\\x07 NUL:\\\x00 DEL:\\\x7F\" <sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*@example.com>\n"
+ "From: token1~` token2'+_ token3*%!.- <sip:mundane@example.com> ;fromParam''~+*_!.-%=\"\xD1\x80\xD0\xB0\xD0\xB1\xD0\xBE\xD1\x82\xD0\xB0\xD1\x8E\xD1\x89\xD0\xB8\xD0\xB9\";tag=_token~1'+`*%!-.\n"
+ "Call-ID: intmeth.word%ZK-!.*_+'@word`~)(><:\\/\"][?}{\n"
+ "CSeq: 139122385 !interesting-Method0123456789_*+`.%indeed'~\n"
+ "Max-Forwards: 255\n"
+ "extensionHeader-!.%*+_`'~: \xEF\xBB\xBF\xE5\xA4\xA7\xE5\x81\x9C\xE9\x9B\xBB\n"
+ "Content-Length: 0\r\n\r\n",
+ NULL,
+ 641,
+ PJ_SUCCESS
+},
+{
+ /* Torture message from RFC 4475
+ * 3.1.1.3 Valid Use of the % Escaping Mechanism
+ */
+ "INVITE sip:sips%3Auser%40example.com@example.net SIP/2.0\n"
+ "To: sip:%75se%72@example.com\n"
+ "From: <sip:I%20have%20spaces@example.net>;tag=1234\n"
+ "Max-Forwards: 87\n"
+ "i: esc01.239409asdfakjkn23onasd0-3234\n"
+ "CSeq: 234234 INVITE\n"
+ "Via: SIP/2.0/UDP host5.example.net;rport;branch=z9hG4bKkdjuw\n"
+ "C: application/sdp\n"
+ "Contact:\n"
+ " <sip:cal%6Cer@192.168.0.2:5060;%6C%72;n%61me=v%61lue%25%34%31>\n"
+ "Content-Length: 150\r\n"
+ "\r\n"
+ "v=0\r\n"
+ "o=mhandley 29739 7272939 IN IP4 192.0.2.1\r\n"
+ "s=-\r\n"
+ "c=IN IP4 192.0.2.1\r\n"
+ "t=0 0\r\n"
+ "m=audio 49217 RTP/AVP 0 12\r\n"
+ "m=video 3227 RTP/AVP 31\r\n"
+ "a=rtpmap:31 LPC\r\n",
+ NULL,
+ 0,
+ PJ_SUCCESS
+},
+{
+ /* Torture message from RFC 4475
+ * 3.1.1.4 Escaped Nulls in URIs
+ */
+ "REGISTER sip:example.com SIP/2.0\r\n"
+ "To: sip:null-%00-null@example.com\r\n"
+ "From: sip:null-%00-null@example.com;tag=839923423\r\n"
+ "Max-Forwards: 70\r\n"
+ "Call-ID: escnull.39203ndfvkjdasfkq3w4otrq0adsfdfnavd\r\n"
+ "CSeq: 14398234 REGISTER\r\n"
+ "Via: SIP/2.0/UDP host5.example.com;rport;branch=z9hG4bKkdjuw\r\n"
+ "Contact: <sip:%00@host5.example.com>\r\n"
+ "Contact: <sip:%00%00@host5.example.com>\r\n"
+ "L:0\r\n"
+ "\r\n",
+ NULL,
+ 0,
+ PJ_SUCCESS
+},
+{
+ /* Torture message from RFC 4475
+ * 3.1.1.5 Use of % When It Is Not an Escape
+ */
+ "RE%47IST%45R sip:registrar.example.com SIP/2.0\r\n"
+ "To: \"%Z%45\" <sip:resource@example.com>\r\n"
+ "From: \"%Z%45\" <sip:resource@example.com>;tag=f232jadfj23\r\n"
+ "Call-ID: esc02.asdfnqwo34rq23i34jrjasdcnl23nrlknsdf\r\n"
+ "Via: SIP/2.0/TCP host.example.com;rport;branch=z9hG4bK209%fzsnel234\r\n"
+ "CSeq: 29344 RE%47IST%45R\r\n"
+ "Max-Forwards: 70\r\n"
+ "Contact: <sip:alias1@host1.example.com>\r\n"
+ "C%6Fntact: <sip:alias2@host2.example.com>\r\n"
+ "Contact: <sip:alias3@host3.example.com>\r\n"
+ "l: 0\r\n"
+ "\r\n",
+ NULL,
+ 0,
+ PJ_SUCCESS
+}
+};
+
+static struct
+{
+ int flag;
+ pj_highprec_t detect_len, parse_len, print_len;
+ pj_timestamp detect_time, parse_time, print_time;
+} var;
+
+static pj_status_t test_entry( pj_pool_t *pool, struct test_msg *entry )
+{
+ pjsip_msg *parsed_msg, *ref_msg = NULL;
+ static pjsip_msg *print_msg;
+ pj_status_t status = PJ_SUCCESS;
+ int len;
+ pj_str_t str1, str2;
+ pjsip_hdr *hdr1, *hdr2;
+ pj_timestamp t1, t2;
+ pjsip_parser_err_report err_list;
+ pj_size_t msg_size;
+ char msgbuf1[PJSIP_MAX_PKT_LEN];
+ char msgbuf2[PJSIP_MAX_PKT_LEN];
+ enum { BUFLEN = 512 };
+
+ if (entry->len==0)
+ entry->len = pj_ansi_strlen(entry->msg);
+
+ if (var.flag & FLAG_PARSE_ONLY)
+ goto parse_msg;
+
+ if (var.flag & FLAG_PRINT_ONLY) {
+ if (print_msg == NULL)
+ print_msg = entry->creator(pool);
+ goto print_msg;
+ }
+
+ /* Detect message. */
+ var.detect_len = var.detect_len + entry->len;
+ pj_get_timestamp(&t1);
+ status = pjsip_find_msg(entry->msg, entry->len, PJ_FALSE, &msg_size);
+ if (status != PJ_SUCCESS) {
+ if (status!=PJSIP_EPARTIALMSG ||
+ entry->expected_status!=STATUS_PARTIAL)
+ {
+ app_perror(" error: unable to detect message", status);
+ return -5;
+ }
+ }
+ if (msg_size != entry->len) {
+ PJ_LOG(3,(THIS_FILE, " error: size mismatch"));
+ return -6;
+ }
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&var.detect_time, &t2);
+
+ if (var.flag & FLAG_DETECT_ONLY)
+ return PJ_SUCCESS;
+
+ /* Parse message. */
+parse_msg:
+ var.parse_len = var.parse_len + entry->len;
+ pj_get_timestamp(&t1);
+ pj_list_init(&err_list);
+ parsed_msg = pjsip_parse_msg(pool, entry->msg, entry->len, &err_list);
+ if (parsed_msg == NULL) {
+ if (entry->expected_status != STATUS_SYNTAX_ERROR) {
+ status = -10;
+ if (err_list.next != &err_list) {
+ PJ_LOG(3,(THIS_FILE, " Syntax error in line %d col %d",
+ err_list.next->line, err_list.next->col));
+ }
+ goto on_return;
+ }
+ }
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&var.parse_time, &t2);
+
+ if ((var.flag & FLAG_PARSE_ONLY) || entry->creator==NULL)
+ return PJ_SUCCESS;
+
+ /* Create reference message. */
+ ref_msg = entry->creator(pool);
+
+ /* Create buffer for comparison. */
+ str1.ptr = (char*)pj_pool_alloc(pool, BUFLEN);
+ str2.ptr = (char*)pj_pool_alloc(pool, BUFLEN);
+
+ /* Compare message type. */
+ if (parsed_msg->type != ref_msg->type) {
+ status = -20;
+ goto on_return;
+ }
+
+ /* Compare request or status line. */
+ if (parsed_msg->type == PJSIP_REQUEST_MSG) {
+ pjsip_method *m1 = &parsed_msg->line.req.method;
+ pjsip_method *m2 = &ref_msg->line.req.method;
+
+ if (pjsip_method_cmp(m1, m2) != 0) {
+ status = -30;
+ goto on_return;
+ }
+ status = pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI,
+ parsed_msg->line.req.uri,
+ ref_msg->line.req.uri);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: request URI mismatch", status);
+ status = -31;
+ goto on_return;
+ }
+ } else {
+ if (parsed_msg->line.status.code != ref_msg->line.status.code) {
+ PJ_LOG(3,(THIS_FILE, " error: status code mismatch"));
+ status = -32;
+ goto on_return;
+ }
+ if (pj_strcmp(&parsed_msg->line.status.reason,
+ &ref_msg->line.status.reason) != 0)
+ {
+ PJ_LOG(3,(THIS_FILE, " error: status text mismatch"));
+ status = -33;
+ goto on_return;
+ }
+ }
+
+ /* Compare headers. */
+ hdr1 = parsed_msg->hdr.next;
+ hdr2 = ref_msg->hdr.next;
+
+ while (hdr1 != &parsed_msg->hdr && hdr2 != &ref_msg->hdr) {
+ len = pjsip_hdr_print_on(hdr1, str1.ptr, BUFLEN);
+ if (len < 0) {
+ status = -40;
+ goto on_return;
+ }
+ str1.ptr[len] = '\0';
+ str1.slen = len;
+
+ len = pjsip_hdr_print_on(hdr2, str2.ptr, BUFLEN);
+ if (len < 0) {
+ status = -50;
+ goto on_return;
+ }
+ str2.ptr[len] = '\0';
+ str2.slen = len;
+
+ if (pj_strcmp(&str1, &str2) != 0) {
+ status = -60;
+ PJ_LOG(3,(THIS_FILE, " error: header string mismatch:\n"
+ " h1='%s'\n"
+ " h2='%s'\n",
+ str1.ptr, str2.ptr));
+ goto on_return;
+ }
+
+ hdr1 = hdr1->next;
+ hdr2 = hdr2->next;
+ }
+
+ if (hdr1 != &parsed_msg->hdr || hdr2 != &ref_msg->hdr) {
+ status = -70;
+ goto on_return;
+ }
+
+ /* Compare body? */
+ if (parsed_msg->body==NULL && ref_msg->body==NULL)
+ goto print_msg;
+
+ /* Compare msg body length. */
+ if (parsed_msg->body->len != ref_msg->body->len) {
+ status = -80;
+ goto on_return;
+ }
+
+ /* Compare msg body content type. */
+ if (pj_strcmp(&parsed_msg->body->content_type.type,
+ &ref_msg->body->content_type.type) != 0) {
+ status = -90;
+ goto on_return;
+ }
+ if (pj_strcmp(&parsed_msg->body->content_type.subtype,
+ &ref_msg->body->content_type.subtype) != 0) {
+ status = -100;
+ goto on_return;
+ }
+
+ /* Compare body content. */
+ str1.slen = parsed_msg->body->print_body(parsed_msg->body,
+ msgbuf1, sizeof(msgbuf1));
+ if (str1.slen < 1) {
+ status = -110;
+ goto on_return;
+ }
+ str1.ptr = msgbuf1;
+
+ str2.slen = ref_msg->body->print_body(ref_msg->body,
+ msgbuf2, sizeof(msgbuf2));
+ if (str2.slen < 1) {
+ status = -120;
+ goto on_return;
+ }
+ str2.ptr = msgbuf2;
+
+ if (pj_strcmp(&str1, &str2) != 0) {
+ status = -140;
+ goto on_return;
+ }
+
+ /* Print message. */
+print_msg:
+ var.print_len = var.print_len + entry->len;
+ pj_get_timestamp(&t1);
+ if (var.flag && FLAG_PRINT_ONLY)
+ ref_msg = print_msg;
+ len = pjsip_msg_print(ref_msg, msgbuf1, PJSIP_MAX_PKT_LEN);
+ if (len < 1) {
+ status = -150;
+ goto on_return;
+ }
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&var.print_time, &t2);
+
+
+ status = PJ_SUCCESS;
+
+on_return:
+ return status;
+}
+
+
+static pjsip_msg *create_msg0(pj_pool_t *pool)
+{
+
+ pjsip_msg *msg;
+ pjsip_name_addr *name_addr;
+ pjsip_sip_uri *url;
+ pjsip_fromto_hdr *fromto;
+ pjsip_cid_hdr *cid;
+ pjsip_clen_hdr *clen;
+ pjsip_cseq_hdr *cseq;
+ pjsip_contact_hdr *contact;
+ pjsip_ctype_hdr *ctype;
+ pjsip_routing_hdr *routing;
+ pjsip_via_hdr *via;
+ pjsip_generic_string_hdr *generic;
+ pjsip_param *prm;
+ pj_str_t str;
+
+ msg = pjsip_msg_create(pool, PJSIP_REQUEST_MSG);
+
+ /* "INVITE sip:user@foo SIP/2.0\n" */
+ pjsip_method_set(&msg->line.req.method, PJSIP_INVITE_METHOD);
+ url = pjsip_sip_uri_create(pool, 0);
+ msg->line.req.uri = (pjsip_uri*)url;
+ pj_strdup2(pool, &url->user, "user");
+ pj_strdup2(pool, &url->host, "foo");
+
+ /* "From: Hi I'm Joe <sip:joe.user@bar.otherdomain.com>;tag=123457890123456\r" */
+ fromto = pjsip_from_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto);
+ pj_strdup2(pool, &fromto->tag, "123457890123456");
+ name_addr = pjsip_name_addr_create(pool);
+ fromto->uri = (pjsip_uri*)name_addr;
+ pj_strdup2(pool, &name_addr->display, "Hi I'm Joe");
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*)url;
+ pj_strdup2(pool, &url->user, "joe.user");
+ pj_strdup2(pool, &url->host, "bar.otherdomain.com");
+
+ /* "To: Fellow User <sip:user@foo.bar.domain.com>\r\n" */
+ fromto = pjsip_to_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto);
+ name_addr = pjsip_name_addr_create(pool);
+ fromto->uri = (pjsip_uri*)name_addr;
+ pj_strdup2(pool, &name_addr->display, "Fellow User");
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*)url;
+ pj_strdup2(pool, &url->user, "user");
+ pj_strdup2(pool, &url->host, "foo.bar.domain.com");
+
+ /* "Call-ID: 12345678901234567890@bar\r\n" */
+ cid = pjsip_cid_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)cid);
+ pj_strdup2(pool, &cid->id, "12345678901234567890@bar");
+
+ /* "Content-Length: 0\r\n" */
+ clen = pjsip_clen_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)clen);
+ clen->len = 0;
+
+ /* "CSeq: 123456 INVITE\n" */
+ cseq = pjsip_cseq_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)cseq);
+ cseq->cseq = 123456;
+ pjsip_method_set(&cseq->method, PJSIP_INVITE_METHOD);
+
+ /* "Contact: <sip:joe@bar>;q=0.5;expires=3600*/
+ contact = pjsip_contact_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
+ contact->q1000 = 500;
+ contact->expires = 3600;
+ name_addr = pjsip_name_addr_create(pool);
+ contact->uri = (pjsip_uri*)name_addr;
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*)url;
+ pj_strdup2(pool, &url->user, "joe");
+ pj_strdup2(pool, &url->host, "bar");
+
+ /*, sip:user@host;q=0.500\r" */
+ contact = pjsip_contact_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
+ contact->q1000 = 500;
+ name_addr = pjsip_name_addr_create(pool);
+ contact->uri = (pjsip_uri*)name_addr;
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*)url;
+ pj_strdup2(pool, &url->user, "user");
+ pj_strdup2(pool, &url->host, "host");
+
+ /* " ,sip:user2@host2\n" */
+ contact = pjsip_contact_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
+ name_addr = pjsip_name_addr_create(pool);
+ contact->uri = (pjsip_uri*)name_addr;
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*)url;
+ pj_strdup2(pool, &url->user, "user2");
+ pj_strdup2(pool, &url->host, "host2");
+
+ /* "Content-Type: text/html; charset=ISO-8859-4\r" */
+ ctype = pjsip_ctype_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)ctype);
+ pj_strdup2(pool, &ctype->media.type, "text");
+ pj_strdup2(pool, &ctype->media.subtype, "html");
+ prm = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ prm->name = pj_str("charset");
+ prm->value = pj_str("ISO-8859-4");
+ pj_list_push_back(&ctype->media.param, prm);
+
+ /* "Route: <sip:bigbox3.site3.atlanta.com;lr>,\r\n" */
+ routing = pjsip_route_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
+ url = pjsip_sip_uri_create(pool, 0);
+ routing->name_addr.uri = (pjsip_uri*)url;
+ pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com");
+ url->lr_param = 1;
+
+ /* " <sip:server10.biloxi.com;lr>\r" */
+ routing = pjsip_route_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
+ url = pjsip_sip_uri_create(pool, 0);
+ routing->name_addr.uri = (pjsip_uri*)url;
+ pj_strdup2(pool, &url->host, "server10.biloxi.com");
+ url->lr_param = 1;
+
+ /* "Record-Route: <sip:server10.biloxi.com>,\r\n" */
+ routing = pjsip_rr_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
+ url = pjsip_sip_uri_create(pool, 0);
+ routing->name_addr.uri = (pjsip_uri*)url;
+ pj_strdup2(pool, &url->host, "server10.biloxi.com");
+ url->lr_param = 0;
+
+ /* " <sip:bigbox3.site3.atlanta.com;lr>\n" */
+ routing = pjsip_rr_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
+ url = pjsip_sip_uri_create(pool, 0);
+ routing->name_addr.uri = (pjsip_uri*)url;
+ pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com");
+ url->lr_param = 1;
+
+ /* "Via: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c230\n" */
+ via = pjsip_via_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+ pj_strdup2(pool, &via->transport, "SCTP");
+ pj_strdup2(pool, &via->sent_by.host, "bigbox3.site3.atlanta.com");
+ pj_strdup2(pool, &via->branch_param, "z9hG4bK77ef4c230");
+
+ /* "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n"
+ " ;received=192.0.2.1\r\n" */
+ via = pjsip_via_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+ pj_strdup2(pool, &via->transport, "UDP");
+ pj_strdup2(pool, &via->sent_by.host, "pc33.atlanta.com");
+ pj_strdup2(pool, &via->branch_param, "z9hG4bKnashds8");
+ pj_strdup2(pool, &via->recvd_param, "192.0.2.1");
+
+
+ /* "Via: SIP/2.0/UDP 10.2.1.1, */
+ via = pjsip_via_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+ pj_strdup2(pool, &via->transport, "UDP");
+ pj_strdup2(pool, &via->sent_by.host, "10.2.1.1");
+
+
+ /*SIP/2.0/TCP 192.168.1.1\n" */
+ via = pjsip_via_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+ pj_strdup2(pool, &via->transport, "TCP");
+ pj_strdup2(pool, &via->sent_by.host, "192.168.1.1");
+
+ /* "Organization: \r" */
+ str.ptr = "Organization";
+ str.slen = 12;
+ generic = pjsip_generic_string_hdr_create(pool, &str, NULL);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
+ generic->hvalue.ptr = NULL;
+ generic->hvalue.slen = 0;
+
+ /* "Max-Forwards: 70\n" */
+ str.ptr = "Max-Forwards";
+ str.slen = 12;
+ generic = pjsip_generic_string_hdr_create(pool, &str, NULL);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
+ str.ptr = "70";
+ str.slen = 2;
+ generic->hvalue = str;
+
+ /* "X-Header: \r\n" */
+ str.ptr = "X-Header";
+ str.slen = 8;
+ generic = pjsip_generic_string_hdr_create(pool, &str, NULL);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
+ str.ptr = NULL;
+ str.slen = 0;
+ generic->hvalue = str;
+
+ /* P-Associated-URI:\r\n */
+ str.ptr = "P-Associated-URI";
+ str.slen = 16;
+ generic = pjsip_generic_string_hdr_create(pool, &str, NULL);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
+ str.ptr = NULL;
+ str.slen = 0;
+ generic->hvalue = str;
+
+ return msg;
+}
+
+static pjsip_msg *create_msg1(pj_pool_t *pool)
+{
+ pjsip_via_hdr *via;
+ pjsip_route_hdr *route;
+ pjsip_name_addr *name_addr;
+ pjsip_sip_uri *url;
+ pjsip_max_fwd_hdr *max_fwd;
+ pjsip_to_hdr *to;
+ pjsip_from_hdr *from;
+ pjsip_contact_hdr *contact;
+ pjsip_ctype_hdr *ctype;
+ pjsip_cid_hdr *cid;
+ pjsip_clen_hdr *clen;
+ pjsip_cseq_hdr *cseq;
+ pjsip_msg *msg = pjsip_msg_create(pool, PJSIP_RESPONSE_MSG);
+ pjsip_msg_body *body;
+
+ //"SIP/2.0 200 OK\r\n"
+ msg->line.status.code = 200;
+ msg->line.status.reason = pj_str("OK");
+
+ //"Via: SIP/2.0/SCTP server10.biloxi.com;branch=z9hG4bKnashds8;rport;received=192.0.2.1\r\n"
+ via = pjsip_via_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+ via->transport = pj_str("SCTP");
+ via->sent_by.host = pj_str("server10.biloxi.com");
+ via->branch_param = pj_str("z9hG4bKnashds8");
+ via->rport_param = 0;
+ via->recvd_param = pj_str("192.0.2.1");
+
+ //"Via: SIP/2.0/UDP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2\r\n"
+ via = pjsip_via_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+ via->transport = pj_str("UDP");
+ via->sent_by.host = pj_str("bigbox3.site3.atlanta.com");
+ via->branch_param = pj_str("z9hG4bK77ef4c2312983.1");
+ via->recvd_param = pj_str("192.0.2.2");
+
+ //"Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds ;received=192.0.2.3\r\n"
+ via = pjsip_via_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+ via->transport = pj_str("UDP");
+ via->sent_by.host = pj_str("pc33.atlanta.com");
+ via->branch_param = pj_str("z9hG4bK776asdhds");
+ via->recvd_param = pj_str("192.0.2.3");
+
+ //"Route: <sip:proxy.sipprovider.com>\r\n"
+ route = pjsip_route_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)route);
+ url = pjsip_sip_uri_create(pool, PJ_FALSE);
+ route->name_addr.uri = (pjsip_uri*)url;
+ url->host = pj_str("proxy.sipprovider.com");
+
+ //"Route: <sip:proxy.supersip.com:5060>\r\n"
+ route = pjsip_route_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)route);
+ url = pjsip_sip_uri_create(pool, PJ_FALSE);
+ route->name_addr.uri = (pjsip_uri*)url;
+ url->host = pj_str("proxy.supersip.com");
+ url->port = 5060;
+
+ //"Max-Forwards: 70\r\n"
+ max_fwd = pjsip_max_fwd_hdr_create(pool, 70);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)max_fwd);
+
+ //"To: Bob <sip:bob@biloxi.com>;tag=a6c85cf\r\n"
+ to = pjsip_to_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)to);
+ name_addr = pjsip_name_addr_create(pool);
+ name_addr->display = pj_str("Bob");
+ to->uri = (pjsip_uri*)name_addr;
+ url = pjsip_sip_uri_create(pool, PJ_FALSE);
+ name_addr->uri = (pjsip_uri*)url;
+ url->user = pj_str("bob");
+ url->host = pj_str("biloxi.com");
+ to->tag = pj_str("a6c85cf");
+
+ //"From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n"
+ from = pjsip_from_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)from);
+ name_addr = pjsip_name_addr_create(pool);
+ name_addr->display = pj_str("Alice");
+ from->uri = (pjsip_uri*)name_addr;
+ url = pjsip_sip_uri_create(pool, PJ_FALSE);
+ name_addr->uri = (pjsip_uri*)url;
+ url->user = pj_str("alice");
+ url->host = pj_str("atlanta.com");
+ from->tag = pj_str("1928301774");
+
+ //"Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n"
+ cid = pjsip_cid_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)cid);
+ cid->id = pj_str("a84b4c76e66710@pc33.atlanta.com");
+
+ //"CSeq: 314159 INVITE\r\n"
+ cseq = pjsip_cseq_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)cseq);
+ cseq->cseq = 314159;
+ pjsip_method_set(&cseq->method, PJSIP_INVITE_METHOD);
+
+ //"Contact: <sips:bob@192.0.2.4>\r\n"
+ contact = pjsip_contact_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
+ name_addr = pjsip_name_addr_create(pool);
+ contact->uri = (pjsip_uri*)name_addr;
+ url = pjsip_sip_uri_create(pool, PJ_TRUE);
+ name_addr->uri = (pjsip_uri*)url;
+ url->user = pj_str("bob");
+ url->host = pj_str("192.0.2.4");
+
+ //"Content-Type: application/sdp\r\n"
+ ctype = pjsip_ctype_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)ctype);
+ ctype->media.type = pj_str("application");
+ ctype->media.subtype = pj_str("sdp");
+
+ //"Content-Length: 150\r\n"
+ clen = pjsip_clen_hdr_create(pool);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)clen);
+ clen->len = 150;
+
+ // Body
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ msg->body = body;
+ body->content_type.type = pj_str("application");
+ body->content_type.subtype = pj_str("sdp");
+ body->data = (void*)
+ "v=0\r\n"
+ "o=alice 53655765 2353687637 IN IP4 pc33.atlanta.com\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "c=IN IP4 pc33.atlanta.com\r\n"
+ "m=audio 3456 RTP/AVP 0 1 3 99\r\n"
+ "a=rtpmap:0 PCMU/8000\r\n";
+ body->len = pj_ansi_strlen((const char*) body->data);
+ body->print_body = &pjsip_print_text_body;
+
+ return msg;
+}
+
+/*****************************************************************************/
+
+static pj_status_t simple_test(void)
+{
+ char stbuf[] = "SIP/2.0 180 Ringing like it never rings before";
+ unsigned i;
+ pjsip_status_line st_line;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " simple test.."));
+
+ status = pjsip_parse_status_line(stbuf, pj_ansi_strlen(stbuf), &st_line);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ for (i=0; i<PJ_ARRAY_SIZE(test_array); ++i) {
+ pj_pool_t *pool;
+ pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE);
+ status = test_entry( pool, &test_array[i] );
+ pjsip_endpt_release_pool(endpt, pool);
+
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+#if INCLUDE_BENCHMARKS
+static int msg_benchmark(unsigned *p_detect, unsigned *p_parse,
+ unsigned *p_print)
+{
+ pj_pool_t *pool;
+ int i, loop;
+ pj_timestamp zero;
+ pj_time_val elapsed;
+ pj_highprec_t avg_detect, avg_parse, avg_print, kbytes;
+ pj_status_t status = PJ_SUCCESS;
+
+ pj_bzero(&var, sizeof(var));
+ zero.u64 = 0;
+
+ for (loop=0; loop<LOOP; ++loop) {
+ for (i=0; i<(int)PJ_ARRAY_SIZE(test_array); ++i) {
+ pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE);
+ status = test_entry( pool, &test_array[i] );
+ pjsip_endpt_release_pool(endpt, pool);
+
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+ }
+
+ kbytes = var.detect_len;
+ pj_highprec_mod(kbytes, 1000000);
+ pj_highprec_div(kbytes, 100000);
+ elapsed = pj_elapsed_time(&zero, &var.detect_time);
+ avg_detect = pj_elapsed_usec(&zero, &var.detect_time);
+ pj_highprec_mul(avg_detect, AVERAGE_MSG_LEN);
+ pj_highprec_div(avg_detect, var.detect_len);
+ avg_detect = 1000000 / avg_detect;
+
+ PJ_LOG(3,(THIS_FILE,
+ " %u.%u MB detected in %d.%03ds (avg=%d msg detection/sec)",
+ (unsigned)(var.detect_len/1000000), (unsigned)kbytes,
+ elapsed.sec, elapsed.msec,
+ (unsigned)avg_detect));
+ *p_detect = (unsigned)avg_detect;
+
+ kbytes = var.parse_len;
+ pj_highprec_mod(kbytes, 1000000);
+ pj_highprec_div(kbytes, 100000);
+ elapsed = pj_elapsed_time(&zero, &var.parse_time);
+ avg_parse = pj_elapsed_usec(&zero, &var.parse_time);
+ pj_highprec_mul(avg_parse, AVERAGE_MSG_LEN);
+ pj_highprec_div(avg_parse, var.parse_len);
+ avg_parse = 1000000 / avg_parse;
+
+ PJ_LOG(3,(THIS_FILE,
+ " %u.%u MB parsed in %d.%03ds (avg=%d msg parsing/sec)",
+ (unsigned)(var.parse_len/1000000), (unsigned)kbytes,
+ elapsed.sec, elapsed.msec,
+ (unsigned)avg_parse));
+ *p_parse = (unsigned)avg_parse;
+
+ kbytes = var.print_len;
+ pj_highprec_mod(kbytes, 1000000);
+ pj_highprec_div(kbytes, 100000);
+ elapsed = pj_elapsed_time(&zero, &var.print_time);
+ avg_print = pj_elapsed_usec(&zero, &var.print_time);
+ pj_highprec_mul(avg_print, AVERAGE_MSG_LEN);
+ pj_highprec_div(avg_print, var.print_len);
+ avg_print = 1000000 / avg_print;
+
+ PJ_LOG(3,(THIS_FILE,
+ " %u.%u MB printed in %d.%03ds (avg=%d msg print/sec)",
+ (unsigned)(var.print_len/1000000), (unsigned)kbytes,
+ elapsed.sec, elapsed.msec,
+ (unsigned)avg_print));
+
+ *p_print = (unsigned)avg_print;
+ return status;
+}
+#endif /* INCLUDE_BENCHMARKS */
+
+/*****************************************************************************/
+/* Test various header parsing and production */
+static int hdr_test_success(pjsip_hdr *h);
+static int hdr_test_accept0(pjsip_hdr *h);
+static int hdr_test_accept1(pjsip_hdr *h);
+static int hdr_test_accept2(pjsip_hdr *h);
+static int hdr_test_allow0(pjsip_hdr *h);
+static int hdr_test_authorization(pjsip_hdr *h);
+static int hdr_test_cid(pjsip_hdr *h);
+static int hdr_test_contact0(pjsip_hdr *h);
+static int hdr_test_contact1(pjsip_hdr *h);
+static int hdr_test_contact_q0(pjsip_hdr *h);
+static int hdr_test_contact_q1(pjsip_hdr *h);
+static int hdr_test_contact_q2(pjsip_hdr *h);
+static int hdr_test_contact_q3(pjsip_hdr *h);
+static int hdr_test_contact_q4(pjsip_hdr *h);
+static int hdr_test_content_length(pjsip_hdr *h);
+static int hdr_test_content_type(pjsip_hdr *h);
+static int hdr_test_from(pjsip_hdr *h);
+static int hdr_test_proxy_authenticate(pjsip_hdr *h);
+static int hdr_test_record_route(pjsip_hdr *h);
+static int hdr_test_supported(pjsip_hdr *h);
+static int hdr_test_to(pjsip_hdr *h);
+static int hdr_test_via(pjsip_hdr *h);
+static int hdr_test_via_ipv6_1(pjsip_hdr *h);
+static int hdr_test_via_ipv6_2(pjsip_hdr *h);
+static int hdr_test_via_ipv6_3(pjsip_hdr *h);
+static int hdr_test_retry_after1(pjsip_hdr *h);
+static int hdr_test_subject_utf(pjsip_hdr *h);
+
+
+#define GENERIC_PARAM "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
+#define GENERIC_PARAM_PARSED "p0=a;p1=\"ab:;cd\";p2=ab:cd;p3"
+#define PARAM_CHAR "][/:&+$"
+#define SIMPLE_ADDR_SPEC "sip:host"
+#define ADDR_SPEC SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\""
+#define NAME_ADDR "<" ADDR_SPEC ">"
+
+#define HDR_FLAG_PARSE_FAIL 1
+#define HDR_FLAG_DONT_PRINT 2
+
+struct hdr_test_t
+{
+ char *hname;
+ char *hshort_name;
+ char *hcontent;
+ int (*test)(pjsip_hdr*);
+ unsigned flags;
+} hdr_test_data[] =
+{
+ {
+ /* Empty Accept */
+ "Accept", NULL,
+ "",
+ &hdr_test_accept0
+ },
+
+ {
+ /* Overflowing generic string header */
+ "Accept", NULL,
+ "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
+ "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
+ "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
+ "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
+ "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
+ "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
+ "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
+ "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a",
+ &hdr_test_success,
+ HDR_FLAG_PARSE_FAIL
+ },
+
+ {
+ /* Normal Accept */
+ "Accept", NULL,
+ "application/*, text/plain",
+ &hdr_test_accept1
+ },
+
+ {
+ /* Accept with params */
+ "Accept", NULL,
+ "application/*;p1=v1, text/plain",
+ &hdr_test_accept2
+ },
+
+ {
+ /* Empty Allow */
+ "Allow", NULL,
+ "",
+ &hdr_test_allow0,
+ },
+
+ {
+ /* Authorization, testing which params should be quoted */
+ "Authorization", NULL,
+ "Digest username=\"username\", realm=\"realm\", nonce=\"nonce\", " \
+ "uri=\"sip:domain\", response=\"RESPONSE\", algorithm=MD5, " \
+ "cnonce=\"CNONCE\", opaque=\"OPAQUE\", qop=auth, nc=00000001",
+ &hdr_test_authorization
+ },
+
+ {
+ /* Call ID */
+ "Call-ID", "i",
+ "-.!%*_+`'~()<>:\\\"/[]?{}",
+ &hdr_test_cid,
+ },
+
+ {
+ /* Parameter belong to hparam */
+ "Contact", "m",
+ SIMPLE_ADDR_SPEC ";p1=v1",
+ &hdr_test_contact0,
+ HDR_FLAG_DONT_PRINT
+ },
+
+ {
+ /* generic-param in Contact header */
+ "Contact", "m",
+ NAME_ADDR ";" GENERIC_PARAM,
+ &hdr_test_contact1
+ },
+
+ {
+ /* q=0 parameter in Contact header */
+ "Contact", "m",
+ NAME_ADDR ";q=0",
+ &hdr_test_contact_q0,
+ HDR_FLAG_DONT_PRINT
+ },
+
+ {
+ /* q=0.5 parameter in Contact header */
+ "Contact", "m",
+ NAME_ADDR ";q=0.5",
+ &hdr_test_contact_q1
+ },
+
+ {
+ /* q=1 parameter in Contact header */
+ "Contact", "m",
+ NAME_ADDR ";q=1",
+ &hdr_test_contact_q2
+ },
+
+ {
+ /* q=1.0 parameter in Contact header */
+ "Contact", "m",
+ NAME_ADDR ";q=1.0",
+ &hdr_test_contact_q3,
+ HDR_FLAG_DONT_PRINT
+ },
+
+ {
+ /* q=1.1 parameter in Contact header */
+ "Contact", "m",
+ NAME_ADDR ";q=1.15",
+ &hdr_test_contact_q4
+ },
+
+ {
+ /* Content-Length */
+ "Content-Length", "l",
+ "10",
+ &hdr_test_content_length
+ },
+
+ {
+ /* Content-Type, with generic-param */
+ "Content-Type", "c",
+ "application/sdp" ";" GENERIC_PARAM,
+ &hdr_test_content_type,
+ HDR_FLAG_DONT_PRINT
+ },
+
+ {
+ /* From, testing parameters and generic-param */
+ "From", "f",
+ NAME_ADDR ";" GENERIC_PARAM,
+ &hdr_test_from
+ },
+
+ {
+ /* Proxy-Authenticate, testing which params should be quoted */
+ "Proxy-Authenticate", NULL,
+ "Digest realm=\"realm\",domain=\"sip:domain\",nonce=\"nonce\"," \
+ "opaque=\"opaque\",stale=true,algorithm=MD5,qop=\"auth\"",
+ &hdr_test_proxy_authenticate
+ },
+
+ {
+ /* Record-Route, param belong to header */
+ "Record-Route", NULL,
+ NAME_ADDR ";" GENERIC_PARAM,
+ &hdr_test_record_route
+ },
+
+ {
+ /* Empty Supported */
+ "Supported", "k",
+ "",
+ &hdr_test_supported,
+ },
+
+ {
+ /* To */
+ "To", "t",
+ NAME_ADDR ";" GENERIC_PARAM,
+ &hdr_test_to
+ },
+
+ {
+ /* Via */
+ "Via", "v",
+ "SIP/2.0/XYZ host" ";" GENERIC_PARAM,
+ &hdr_test_via
+ },
+
+ {
+ /* Via with IPv6 */
+ "Via", "v",
+ "SIP/2.0/UDP [::1]",
+ &hdr_test_via_ipv6_1
+ },
+
+ {
+ /* Via with IPv6 */
+ "Via", "v",
+ "SIP/2.0/UDP [::1]:5061",
+ &hdr_test_via_ipv6_2
+ },
+
+ {
+ /* Via with IPv6 */
+ "Via", "v",
+ "SIP/2.0/UDP [::1];rport=5061;received=::2",
+ &hdr_test_via_ipv6_3
+ },
+
+ {
+ /* Retry-After header with comment */
+ "Retry-After", NULL,
+ "10(Already Pending Register)",
+ &hdr_test_retry_after1
+ },
+
+ {
+ /* Non-ASCII UTF-8 characters in Subject */
+ "Subject", NULL,
+ "\xC0\x81",
+ &hdr_test_subject_utf
+ }
+};
+
+static int hdr_test_success(pjsip_hdr *h)
+{
+ PJ_UNUSED_ARG(h);
+ return 0;
+}
+
+/* "" */
+static int hdr_test_accept0(pjsip_hdr *h)
+{
+ pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h;
+
+ if (h->type != PJSIP_H_ACCEPT)
+ return -1010;
+
+ if (hdr->count != 0)
+ return -1020;
+
+ return 0;
+}
+
+/* "application/ *, text/plain\r\n" */
+static int hdr_test_accept1(pjsip_hdr *h)
+{
+ pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h;
+
+ if (h->type != PJSIP_H_ACCEPT)
+ return -1110;
+
+ if (hdr->count != 2)
+ return -1120;
+
+ if (pj_strcmp2(&hdr->values[0], "application/*"))
+ return -1130;
+
+ if (pj_strcmp2(&hdr->values[1], "text/plain"))
+ return -1140;
+
+ return 0;
+}
+
+/* "application/ *;p1=v1, text/plain\r\n" */
+static int hdr_test_accept2(pjsip_hdr *h)
+{
+ pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h;
+
+ if (h->type != PJSIP_H_ACCEPT)
+ return -1210;
+
+ if (hdr->count != 2)
+ return -1220;
+
+ if (pj_strcmp2(&hdr->values[0], "application/*;p1=v1"))
+ return -1230;
+
+ if (pj_strcmp2(&hdr->values[1], "text/plain"))
+ return -1240;
+
+ return 0;
+}
+
+/* "" */
+static int hdr_test_allow0(pjsip_hdr *h)
+{
+ pjsip_allow_hdr *hdr = (pjsip_allow_hdr*)h;
+
+ if (h->type != PJSIP_H_ALLOW)
+ return -1310;
+
+ if (hdr->count != 0)
+ return -1320;
+
+ return 0;
+
+}
+
+
+/*
+ "Digest username=\"username\", realm=\"realm\", nonce=\"nonce\", " \
+ "uri=\"sip:domain\", response=\"RESPONSE\", algorithm=MD5, " \
+ "cnonce=\"CNONCE\", opaque=\"OPAQUE\", qop=auth, nc=00000001",
+ */
+static int hdr_test_authorization(pjsip_hdr *h)
+{
+ pjsip_authorization_hdr *hdr = (pjsip_authorization_hdr*)h;
+
+ if (h->type != PJSIP_H_AUTHORIZATION)
+ return -1410;
+
+ if (pj_strcmp2(&hdr->scheme, "Digest"))
+ return -1420;
+
+ if (pj_strcmp2(&hdr->credential.digest.username, "username"))
+ return -1421;
+
+ if (pj_strcmp2(&hdr->credential.digest.realm, "realm"))
+ return -1422;
+
+ if (pj_strcmp2(&hdr->credential.digest.nonce, "nonce"))
+ return -1423;
+
+ if (pj_strcmp2(&hdr->credential.digest.uri, "sip:domain"))
+ return -1424;
+
+ if (pj_strcmp2(&hdr->credential.digest.response, "RESPONSE"))
+ return -1425;
+
+ if (pj_strcmp2(&hdr->credential.digest.algorithm, "MD5"))
+ return -1426;
+
+ if (pj_strcmp2(&hdr->credential.digest.cnonce, "CNONCE"))
+ return -1427;
+
+ if (pj_strcmp2(&hdr->credential.digest.opaque, "OPAQUE"))
+ return -1428;
+
+ if (pj_strcmp2(&hdr->credential.digest.qop, "auth"))
+ return -1429;
+
+ if (pj_strcmp2(&hdr->credential.digest.nc, "00000001"))
+ return -1430;
+
+ return 0;
+}
+
+
+/*
+ "-.!%*_+`'~()<>:\\\"/[]?{}\r\n"
+ */
+static int hdr_test_cid(pjsip_hdr *h)
+{
+ pjsip_cid_hdr *hdr = (pjsip_cid_hdr*)h;
+
+ if (h->type != PJSIP_H_CALL_ID)
+ return -1510;
+
+ if (pj_strcmp2(&hdr->id, "-.!%*_+`'~()<>:\\\"/[]?{}"))
+ return -1520;
+
+ return 0;
+}
+
+/*
+ #define SIMPLE_ADDR_SPEC "sip:host"
+ */
+static int test_simple_addr_spec(pjsip_uri *uri)
+{
+ pjsip_sip_uri *sip_uri = (pjsip_sip_uri *)pjsip_uri_get_uri(uri);
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri))
+ return -900;
+
+ if (pj_strcmp2(&sip_uri->host, "host"))
+ return -910;
+
+ if (sip_uri->port != 0)
+ return -920;
+
+ return 0;
+}
+
+/*
+#define PARAM_CHAR "][/:&+$"
+#define SIMPLE_ADDR_SPEC "sip:host"
+#define ADDR_SPEC SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\""
+#define NAME_ADDR "<" ADDR_SPEC ">"
+ */
+static int nameaddr_test(void *uri)
+{
+ pjsip_sip_uri *sip_uri=(pjsip_sip_uri *)pjsip_uri_get_uri((pjsip_uri*)uri);
+ pjsip_param *param;
+ int rc;
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(uri))
+ return -930;
+
+ rc = test_simple_addr_spec((pjsip_uri*)sip_uri);
+ if (rc != 0)
+ return rc;
+
+ if (pj_list_size(&sip_uri->other_param) != 2)
+ return -940;
+
+ param = sip_uri->other_param.next;
+
+ if (pj_strcmp2(&param->name, PARAM_CHAR))
+ return -942;
+
+ if (pj_strcmp2(&param->value, PARAM_CHAR))
+ return -943;
+
+ param = param->next;
+ if (pj_strcmp2(&param->name, "p1"))
+ return -942;
+ if (pj_strcmp2(&param->value, "\";\""))
+ return -943;
+
+ return 0;
+}
+
+/*
+#define GENERIC_PARAM "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
+ */
+static int generic_param_test(pjsip_param *param_head)
+{
+ pjsip_param *param;
+
+ if (pj_list_size(param_head) != 4)
+ return -950;
+
+ param = param_head->next;
+
+ if (pj_strcmp2(&param->name, "p0"))
+ return -952;
+ if (pj_strcmp2(&param->value, "a"))
+ return -953;
+
+ param = param->next;
+ if (pj_strcmp2(&param->name, "p1"))
+ return -954;
+ if (pj_strcmp2(&param->value, "\"ab:;cd\""))
+ return -955;
+
+ param = param->next;
+ if (pj_strcmp2(&param->name, "p2"))
+ return -956;
+ if (pj_strcmp2(&param->value, "ab:cd"))
+ return -957;
+
+ param = param->next;
+ if (pj_strcmp2(&param->name, "p3"))
+ return -958;
+ if (pj_strcmp2(&param->value, ""))
+ return -959;
+
+ return 0;
+}
+
+
+
+/*
+ SIMPLE_ADDR_SPEC ";p1=v1\r\n"
+ */
+static int hdr_test_contact0(pjsip_hdr *h)
+{
+ pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
+ pjsip_param *param;
+ int rc;
+
+ if (h->type != PJSIP_H_CONTACT)
+ return -1610;
+
+ rc = test_simple_addr_spec(hdr->uri);
+ if (rc != 0)
+ return rc;
+
+ if (pj_list_size(&hdr->other_param) != 1)
+ return -1620;
+
+ param = hdr->other_param.next;
+
+ if (pj_strcmp2(&param->name, "p1"))
+ return -1630;
+
+ if (pj_strcmp2(&param->value, "v1"))
+ return -1640;
+
+ return 0;
+}
+
+/*
+ NAME_ADDR GENERIC_PARAM "\r\n",
+ */
+static int hdr_test_contact1(pjsip_hdr *h)
+{
+ pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
+ int rc;
+
+ if (h->type != PJSIP_H_CONTACT)
+ return -1710;
+
+ rc = nameaddr_test(hdr->uri);
+ if (rc != 0)
+ return rc;
+
+ rc = generic_param_test(&hdr->other_param);
+ if (rc != 0)
+ return rc;
+
+ return 0;
+}
+
+/*
+ NAME_ADDR ";q=0"
+ */
+static int hdr_test_contact_q0(pjsip_hdr *h)
+{
+ pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
+ int rc;
+
+ if (h->type != PJSIP_H_CONTACT)
+ return -1710;
+
+ rc = nameaddr_test(hdr->uri);
+ if (rc != 0)
+ return rc;
+
+ if (hdr->q1000 != 0)
+ return -1711;
+
+ return 0;
+}
+
+/*
+ NAME_ADDR ";q=0.5"
+ */
+static int hdr_test_contact_q1(pjsip_hdr *h)
+{
+ pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
+ int rc;
+
+ if (h->type != PJSIP_H_CONTACT)
+ return -1710;
+
+ rc = nameaddr_test(hdr->uri);
+ if (rc != 0)
+ return rc;
+
+ if (hdr->q1000 != 500)
+ return -1712;
+
+ return 0;
+}
+
+/*
+ NAME_ADDR ";q=1"
+ */
+static int hdr_test_contact_q2(pjsip_hdr *h)
+{
+ pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
+ int rc;
+
+ if (h->type != PJSIP_H_CONTACT)
+ return -1710;
+
+ rc = nameaddr_test(hdr->uri);
+ if (rc != 0)
+ return rc;
+
+ if (hdr->q1000 != 1000)
+ return -1713;
+
+ return 0;
+}
+
+/*
+ NAME_ADDR ";q=1.0"
+ */
+static int hdr_test_contact_q3(pjsip_hdr *h)
+{
+ pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
+ int rc;
+
+ if (h->type != PJSIP_H_CONTACT)
+ return -1710;
+
+ rc = nameaddr_test(hdr->uri);
+ if (rc != 0)
+ return rc;
+
+ if (hdr->q1000 != 1000)
+ return -1714;
+
+ return 0;
+}
+
+/*
+ NAME_ADDR ";q=1.15"
+ */
+static int hdr_test_contact_q4(pjsip_hdr *h)
+{
+ pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
+ int rc;
+
+ if (h->type != PJSIP_H_CONTACT)
+ return -1710;
+
+ rc = nameaddr_test(hdr->uri);
+ if (rc != 0)
+ return rc;
+
+ if (hdr->q1000 != 1150)
+ return -1715;
+
+ return 0;
+}
+
+/*
+ "10"
+ */
+static int hdr_test_content_length(pjsip_hdr *h)
+{
+ pjsip_clen_hdr *hdr = (pjsip_clen_hdr*)h;
+
+ if (h->type != PJSIP_H_CONTENT_LENGTH)
+ return -1810;
+
+ if (hdr->len != 10)
+ return -1820;
+
+ return 0;
+}
+
+/*
+ "application/sdp" GENERIC_PARAM,
+ */
+static int hdr_test_content_type(pjsip_hdr *h)
+{
+ pjsip_ctype_hdr *hdr = (pjsip_ctype_hdr*)h;
+ const pjsip_param *prm;
+
+ if (h->type != PJSIP_H_CONTENT_TYPE)
+ return -1910;
+
+ if (pj_strcmp2(&hdr->media.type, "application"))
+ return -1920;
+
+ if (pj_strcmp2(&hdr->media.subtype, "sdp"))
+ return -1930;
+
+ /* Currently, if the media parameter contains escaped characters,
+ * pjsip will print the parameter unescaped.
+ */
+ prm = hdr->media.param.next;
+ if (prm == &hdr->media.param) return -1940;
+ if (pj_strcmp2(&prm->name, "p0")) return -1941;
+ if (pj_strcmp2(&prm->value, "a")) return -1942;
+
+ prm = prm->next;
+ if (prm == &hdr->media.param) return -1950;
+ if (pj_strcmp2(&prm->name, "p1")) { PJ_LOG(3,("", "%.*s", (int)prm->name.slen, prm->name.ptr)); return -1951; }
+ if (pj_strcmp2(&prm->value, "\"ab:;cd\"")) { PJ_LOG(3,("", "%.*s", (int)prm->value.slen, prm->value.ptr)); return -1952; }
+
+ prm = prm->next;
+ if (prm == &hdr->media.param) return -1960;
+ if (pj_strcmp2(&prm->name, "p2")) return -1961;
+ if (pj_strcmp2(&prm->value, "ab:cd")) return -1962;
+
+ prm = prm->next;
+ if (prm == &hdr->media.param) return -1970;
+ if (pj_strcmp2(&prm->name, "p3")) return -1971;
+ if (pj_strcmp2(&prm->value, "")) return -1972;
+
+ return 0;
+}
+
+/*
+ NAME_ADDR GENERIC_PARAM,
+ */
+static int hdr_test_from(pjsip_hdr *h)
+{
+ pjsip_from_hdr *hdr = (pjsip_from_hdr*)h;
+ int rc;
+
+ if (h->type != PJSIP_H_FROM)
+ return -2010;
+
+ rc = nameaddr_test(hdr->uri);
+ if (rc != 0)
+ return rc;
+
+ rc = generic_param_test(&hdr->other_param);
+ if (rc != 0)
+ return rc;
+
+ return 0;
+}
+
+/*
+ "Digest realm=\"realm\", domain=\"sip:domain\", nonce=\"nonce\", " \
+ "opaque=\"opaque\", stale=true, algorithm=MD5, qop=\"auth\"",
+ */
+static int hdr_test_proxy_authenticate(pjsip_hdr *h)
+{
+ pjsip_proxy_authenticate_hdr *hdr = (pjsip_proxy_authenticate_hdr*)h;
+
+ if (h->type != PJSIP_H_PROXY_AUTHENTICATE)
+ return -2110;
+
+ if (pj_strcmp2(&hdr->scheme, "Digest"))
+ return -2120;
+
+ if (pj_strcmp2(&hdr->challenge.digest.realm, "realm"))
+ return -2130;
+
+ if (pj_strcmp2(&hdr->challenge.digest.domain, "sip:domain"))
+ return -2140;
+
+ if (pj_strcmp2(&hdr->challenge.digest.nonce, "nonce"))
+ return -2150;
+
+ if (pj_strcmp2(&hdr->challenge.digest.opaque, "opaque"))
+ return -2160;
+
+ if (hdr->challenge.digest.stale != 1)
+ return -2170;
+
+ if (pj_strcmp2(&hdr->challenge.digest.algorithm, "MD5"))
+ return -2180;
+
+ if (pj_strcmp2(&hdr->challenge.digest.qop, "auth"))
+ return -2190;
+
+ return 0;
+}
+
+/*
+ NAME_ADDR GENERIC_PARAM,
+ */
+static int hdr_test_record_route(pjsip_hdr *h)
+{
+ pjsip_rr_hdr *hdr = (pjsip_rr_hdr*)h;
+ int rc;
+
+ if (h->type != PJSIP_H_RECORD_ROUTE)
+ return -2210;
+
+ rc = nameaddr_test(&hdr->name_addr);
+ if (rc != 0)
+ return rc;
+
+ rc = generic_param_test(&hdr->other_param);
+ if (rc != 0)
+ return rc;
+
+ return 0;
+
+}
+
+/*
+ " \r\n"
+ */
+static int hdr_test_supported(pjsip_hdr *h)
+{
+ pjsip_supported_hdr *hdr = (pjsip_supported_hdr*)h;
+
+ if (h->type != PJSIP_H_SUPPORTED)
+ return -2310;
+
+ if (hdr->count != 0)
+ return -2320;
+
+ return 0;
+}
+
+/*
+ NAME_ADDR GENERIC_PARAM,
+ */
+static int hdr_test_to(pjsip_hdr *h)
+{
+ pjsip_to_hdr *hdr = (pjsip_to_hdr*)h;
+ int rc;
+
+ if (h->type != PJSIP_H_TO)
+ return -2410;
+
+ rc = nameaddr_test(hdr->uri);
+ if (rc != 0)
+ return rc;
+
+ rc = generic_param_test(&hdr->other_param);
+ if (rc != 0)
+ return rc;
+
+ return 0;
+}
+
+/*
+ "SIP/2.0 host" GENERIC_PARAM
+ */
+static int hdr_test_via(pjsip_hdr *h)
+{
+ pjsip_via_hdr *hdr = (pjsip_via_hdr*)h;
+ int rc;
+
+ if (h->type != PJSIP_H_VIA)
+ return -2510;
+
+ if (pj_strcmp2(&hdr->transport, "XYZ"))
+ return -2515;
+
+ if (pj_strcmp2(&hdr->sent_by.host, "host"))
+ return -2520;
+
+ if (hdr->sent_by.port != 0)
+ return -2530;
+
+ rc = generic_param_test(&hdr->other_param);
+ if (rc != 0)
+ return rc;
+
+ return 0;
+}
+
+
+/*
+ "SIP/2.0/UDP [::1]"
+ */
+static int hdr_test_via_ipv6_1(pjsip_hdr *h)
+{
+ pjsip_via_hdr *hdr = (pjsip_via_hdr*)h;
+
+ if (h->type != PJSIP_H_VIA)
+ return -2610;
+
+ if (pj_strcmp2(&hdr->transport, "UDP"))
+ return -2615;
+
+ if (pj_strcmp2(&hdr->sent_by.host, "::1"))
+ return -2620;
+
+ if (hdr->sent_by.port != 0)
+ return -2630;
+
+ return 0;
+}
+
+/* "SIP/2.0/UDP [::1]:5061" */
+static int hdr_test_via_ipv6_2(pjsip_hdr *h)
+{
+ pjsip_via_hdr *hdr = (pjsip_via_hdr*)h;
+
+ if (h->type != PJSIP_H_VIA)
+ return -2710;
+
+ if (pj_strcmp2(&hdr->transport, "UDP"))
+ return -2715;
+
+ if (pj_strcmp2(&hdr->sent_by.host, "::1"))
+ return -2720;
+
+ if (hdr->sent_by.port != 5061)
+ return -2730;
+
+ return 0;
+}
+
+/* "SIP/2.0/UDP [::1];rport=5061;received=::2" */
+static int hdr_test_via_ipv6_3(pjsip_hdr *h)
+{
+ pjsip_via_hdr *hdr = (pjsip_via_hdr*)h;
+
+ if (h->type != PJSIP_H_VIA)
+ return -2810;
+
+ if (pj_strcmp2(&hdr->transport, "UDP"))
+ return -2815;
+
+ if (pj_strcmp2(&hdr->sent_by.host, "::1"))
+ return -2820;
+
+ if (hdr->sent_by.port != 0)
+ return -2830;
+
+ if (pj_strcmp2(&hdr->recvd_param, "::2"))
+ return -2840;
+
+ if (hdr->rport_param != 5061)
+ return -2850;
+
+ return 0;
+}
+
+/* "10(Already Pending Register)" */
+static int hdr_test_retry_after1(pjsip_hdr *h)
+{
+ pjsip_retry_after_hdr *hdr = (pjsip_retry_after_hdr*)h;
+
+ if (h->type != PJSIP_H_RETRY_AFTER)
+ return -2910;
+
+ if (hdr->ivalue != 10)
+ return -2920;
+
+ if (pj_strcmp2(&hdr->comment, "Already Pending Register"))
+ return -2930;
+
+ return 0;
+}
+
+/* Subject: \xC0\x81 */
+static int hdr_test_subject_utf(pjsip_hdr *h)
+{
+ pjsip_subject_hdr *hdr = (pjsip_subject_hdr*)h;
+
+ if (pj_strcmp2(&h->name, "Subject"))
+ return -2950;
+
+ if (pj_strcmp2(&hdr->hvalue, "\xC0\x81"))
+ return -2960;
+
+ return 0;
+}
+
+static int hdr_test(void)
+{
+ unsigned i;
+
+ PJ_LOG(3,(THIS_FILE, " testing header parsing.."));
+
+ for (i=0; i<PJ_ARRAY_SIZE(hdr_test_data); ++i) {
+ struct hdr_test_t *test = &hdr_test_data[i];
+ pj_str_t hname;
+ int len, parsed_len;
+ pj_pool_t *pool;
+ pjsip_hdr *parsed_hdr1=NULL, *parsed_hdr2=NULL;
+ char *input, *output;
+#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
+ static char hcontent[1024];
+#else
+ char *hcontent;
+#endif
+ int rc;
+
+ pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE);
+
+ /* Parse the header */
+ hname = pj_str(test->hname);
+ len = strlen(test->hcontent);
+#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
+ PJ_ASSERT_RETURN(len < sizeof(hcontent), PJSIP_EMSGTOOLONG);
+ strcpy(hcontent, test->hcontent);
+#else
+ hcontent = test->hcontent;
+#endif
+
+ parsed_hdr1 = (pjsip_hdr*) pjsip_parse_hdr(pool, &hname,
+ hcontent, len,
+ &parsed_len);
+ if (parsed_hdr1 == NULL) {
+ if (test->flags & HDR_FLAG_PARSE_FAIL) {
+ pj_pool_release(pool);
+ continue;
+ }
+ PJ_LOG(3,(THIS_FILE, " error parsing header %s: %s", test->hname, test->hcontent));
+ return -500;
+ }
+
+ /* Test the parsing result */
+ if (test->test && (rc=test->test(parsed_hdr1)) != 0) {
+ PJ_LOG(3,(THIS_FILE, " validation failed for header %s: %s", test->hname, test->hcontent));
+ PJ_LOG(3,(THIS_FILE, " error code is %d", rc));
+ return -502;
+ }
+
+#if 1
+ /* Parse with hshortname, if present */
+ if (test->hshort_name) {
+ hname = pj_str(test->hshort_name);
+ len = strlen(test->hcontent);
+#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
+ PJ_ASSERT_RETURN(len < sizeof(hcontent), PJSIP_EMSGTOOLONG);
+ strcpy(hcontent, test->hcontent);
+#else
+ hcontent = test->hcontent;
+#endif
+
+ parsed_hdr2 = (pjsip_hdr*) pjsip_parse_hdr(pool, &hname, hcontent, len, &parsed_len);
+ if (parsed_hdr2 == NULL) {
+ PJ_LOG(3,(THIS_FILE, " error parsing header %s: %s", test->hshort_name, test->hcontent));
+ return -510;
+ }
+ }
+#endif
+
+ if (test->flags & HDR_FLAG_DONT_PRINT) {
+ pj_pool_release(pool);
+ continue;
+ }
+
+ /* Print the original header */
+ input = (char*) pj_pool_alloc(pool, 1024);
+ len = pj_ansi_snprintf(input, 1024, "%s: %s", test->hname, test->hcontent);
+ if (len < 1 || len >= 1024)
+ return -520;
+
+ /* Print the parsed header*/
+ output = (char*) pj_pool_alloc(pool, 1024);
+ len = pjsip_hdr_print_on(parsed_hdr1, output, 1024);
+ if (len < 0 || len >= 1024) {
+ PJ_LOG(3,(THIS_FILE, " header too long: %s: %s", test->hname, test->hcontent));
+ return -530;
+ }
+ output[len] = 0;
+
+ if (strcmp(input, output) != 0) {
+ PJ_LOG(3,(THIS_FILE, " header character by character comparison failed."));
+ PJ_LOG(3,(THIS_FILE, " original header=|%s|", input));
+ PJ_LOG(3,(THIS_FILE, " parsed header =|%s|", output));
+ return -540;
+ }
+
+ pj_pool_release(pool);
+ }
+
+ return 0;
+}
+
+
+/*****************************************************************************/
+
+int msg_test(void)
+{
+ enum { COUNT = 1, DETECT=0, PARSE=1, PRINT=2 };
+ struct {
+ unsigned detect;
+ unsigned parse;
+ unsigned print;
+ } run[COUNT];
+ unsigned i, max, avg_len;
+ char desc[250];
+ pj_status_t status;
+
+ status = hdr_test();
+ if (status != 0)
+ return status;
+
+ status = simple_test();
+ if (status != PJ_SUCCESS)
+ return status;
+
+#if INCLUDE_BENCHMARKS
+ for (i=0; i<COUNT; ++i) {
+ PJ_LOG(3,(THIS_FILE, " benchmarking (%d of %d)..", i+1, COUNT));
+ status = msg_benchmark(&run[i].detect, &run[i].parse, &run[i].print);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Calculate average message length */
+ for (i=0, avg_len=0; i<PJ_ARRAY_SIZE(test_array); ++i) {
+ avg_len += test_array[i].len;
+ }
+ avg_len /= PJ_ARRAY_SIZE(test_array);
+
+
+ /* Print maximum detect/sec */
+ for (i=0, max=0; i<COUNT; ++i)
+ if (run[i].detect > max) max = run[i].detect;
+
+ PJ_LOG(3,("", " Maximum message detection/sec=%u", max));
+
+ pj_ansi_sprintf(desc, "Number of SIP messages "
+ "can be pre-parse by <tt>pjsip_find_msg()</tt> "
+ "per second (tested with %d message sets with "
+ "average message length of "
+ "%d bytes)", (int)PJ_ARRAY_SIZE(test_array), avg_len);
+ report_ival("msg-detect-per-sec", max, "msg/sec", desc);
+
+ /* Print maximum parse/sec */
+ for (i=0, max=0; i<COUNT; ++i)
+ if (run[i].parse > max) max = run[i].parse;
+
+ PJ_LOG(3,("", " Maximum message parsing/sec=%u", max));
+
+ pj_ansi_sprintf(desc, "Number of SIP messages "
+ "can be <b>parsed</b> by <tt>pjsip_parse_msg()</tt> "
+ "per second (tested with %d message sets with "
+ "average message length of "
+ "%d bytes)", (int)PJ_ARRAY_SIZE(test_array), avg_len);
+ report_ival("msg-parse-per-sec", max, "msg/sec", desc);
+
+ /* Msg parsing bandwidth */
+ report_ival("msg-parse-bandwidth-mb", avg_len*max/1000000, "MB/sec",
+ "Message parsing bandwidth in megabytes (number of megabytes"
+ " worth of SIP messages that can be parsed per second). "
+ "The value is derived from msg-parse-per-sec above.");
+
+
+ /* Print maximum print/sec */
+ for (i=0, max=0; i<COUNT; ++i)
+ if (run[i].print > max) max = run[i].print;
+
+ PJ_LOG(3,("", " Maximum message print/sec=%u", max));
+
+ pj_ansi_sprintf(desc, "Number of SIP messages "
+ "can be <b>printed</b> by <tt>pjsip_msg_print()</tt>"
+ " per second (tested with %d message sets with "
+ "average message length of "
+ "%d bytes)", (int)PJ_ARRAY_SIZE(test_array), avg_len);
+
+ report_ival("msg-print-per-sec", max, "msg/sec", desc);
+
+ /* Msg print bandwidth */
+ report_ival("msg-printed-bandwidth-mb", avg_len*max/1000000, "MB/sec",
+ "Message print bandwidth in megabytes (total size of "
+ "SIP messages printed per second). "
+ "The value is derived from msg-print-per-sec above.");
+
+#endif /* INCLUDE_BENCHMARKS */
+
+ return PJ_SUCCESS;
+}
+
+
+
+
diff --git a/pjsip/src/test/multipart_test.c b/pjsip/src/test/multipart_test.c
new file mode 100644
index 0000000..30d6126
--- /dev/null
+++ b/pjsip/src/test/multipart_test.c
@@ -0,0 +1,266 @@
+/* $Id: multipart_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE ""
+
+/*
+ * multipart tests
+ */
+typedef pj_status_t (*verify_ptr)(pj_pool_t*,pjsip_msg_body*);
+
+static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body);
+
+static struct test_t
+{
+ char *ctype;
+ char *csubtype;
+ char *boundary;
+ const char *msg;
+ verify_ptr verify;
+} p_tests[] =
+{
+ {
+ /* Content-type */
+ "multipart", "mixed", "12345",
+
+ /* Body: */
+ "This is the prolog, which should be ignored.\r\n"
+ "--12345\r\n"
+ "Content-Type: my/text\r\n"
+ "\r\n"
+ "Header and body\r\n"
+ "--12345 \t\r\n"
+ "Content-Type: hello/world\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ "--12345\r\n"
+ "\r\n"
+ "Body only\r\n"
+ "--12345\r\n"
+ "Content-Type: multipart/mixed;boundary=6789\r\n"
+ "\r\n"
+ "Prolog of the subbody, should be ignored\r\n"
+ "--6789\r\n"
+ "\r\n"
+ "Subbody\r\n"
+ "--6789--\r\n"
+ "Epilogue of the subbody, should be ignored\r\n"
+ "--12345--\r\n"
+ "This is epilogue, which should be ignored too",
+
+ &verify1
+ }
+};
+
+static void init_media_type(pjsip_media_type *mt,
+ char *type, char *subtype, char *boundary)
+{
+ static pjsip_param prm;
+
+ pjsip_media_type_init(mt, NULL, NULL);
+ if (type) mt->type = pj_str(type);
+ if (subtype) mt->subtype = pj_str(subtype);
+ if (boundary) {
+ pj_list_init(&prm);
+ prm.name = pj_str("boundary");
+ prm.value = pj_str(boundary);
+ pj_list_push_back(&mt->param, &prm);
+ }
+}
+
+static int verify_part(pjsip_multipart_part *part,
+ char *h_content_type,
+ char *h_content_subtype,
+ char *boundary,
+ int h_content_length,
+ const char *body)
+{
+ pjsip_ctype_hdr *ctype_hdr = NULL;
+ pjsip_clen_hdr *clen_hdr = NULL;
+ pjsip_hdr *hdr;
+ pj_str_t the_body;
+
+ hdr = part->hdr.next;
+ while (hdr != &part->hdr) {
+ if (hdr->type == PJSIP_H_CONTENT_TYPE)
+ ctype_hdr = (pjsip_ctype_hdr*)hdr;
+ else if (hdr->type == PJSIP_H_CONTENT_LENGTH)
+ clen_hdr = (pjsip_clen_hdr*)hdr;
+ hdr = hdr->next;
+ }
+
+ if (h_content_type) {
+ pjsip_media_type mt;
+
+ if (ctype_hdr == NULL)
+ return -10;
+
+ init_media_type(&mt, h_content_type, h_content_subtype, boundary);
+
+ if (pjsip_media_type_cmp(&ctype_hdr->media, &mt, 2) != 0)
+ return -20;
+
+ } else {
+ if (ctype_hdr)
+ return -30;
+ }
+
+ if (h_content_length >= 0) {
+ if (clen_hdr == NULL)
+ return -50;
+ if (clen_hdr->len != h_content_length)
+ return -60;
+ } else {
+ if (clen_hdr)
+ return -70;
+ }
+
+ the_body.ptr = (char*)part->body->data;
+ the_body.slen = part->body->len;
+
+ if (pj_strcmp2(&the_body, body) != 0)
+ return -90;
+
+ return 0;
+}
+
+static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body)
+{
+ pjsip_media_type mt;
+ pjsip_multipart_part *part;
+ int rc;
+
+ PJ_UNUSED_ARG(pool);
+
+ /* Check content-type: "multipart/mixed;boundary=12345" */
+ init_media_type(&mt, "multipart", "mixed", "12345");
+ if (pjsip_media_type_cmp(&body->content_type, &mt, 2) != 0)
+ return -200;
+
+ /* First part:
+ "Content-Type: my/text\r\n"
+ "\r\n"
+ "Header and body\r\n"
+ */
+ part = pjsip_multipart_get_first_part(body);
+ if (!part)
+ return -210;
+ if (verify_part(part, "my", "text", NULL, -1, "Header and body"))
+ return -220;
+
+ /* Next part:
+ "Content-Type: hello/world\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ */
+ part = pjsip_multipart_get_next_part(body, part);
+ if (!part)
+ return -230;
+ if ((rc=verify_part(part, "hello", "world", NULL, 0, ""))!=0) {
+ PJ_LOG(3,(THIS_FILE, " err: verify_part rc=%d", rc));
+ return -240;
+ }
+
+ /* Next part:
+ "\r\n"
+ "Body only\r\n"
+ */
+ part = pjsip_multipart_get_next_part(body, part);
+ if (!part)
+ return -260;
+ if (verify_part(part, NULL, NULL, NULL, -1, "Body only"))
+ return -270;
+
+ /* Next part:
+ "Content-Type: multipart/mixed;boundary=6789\r\n"
+ "\r\n"
+ "Prolog of the subbody, should be ignored\r\n"
+ "--6789\r\n"
+ "\r\n"
+ "Subbody\r\n"
+ "--6789--\r\n"
+ "Epilogue of the subbody, should be ignored\r\n"
+
+ */
+ part = pjsip_multipart_get_next_part(body, part);
+ if (!part)
+ return -280;
+ if ((rc=verify_part(part, "multipart", "mixed", "6789", -1,
+ "Prolog of the subbody, should be ignored\r\n"
+ "--6789\r\n"
+ "\r\n"
+ "Subbody\r\n"
+ "--6789--\r\n"
+ "Epilogue of the subbody, should be ignored"))!=0) {
+ PJ_LOG(3,(THIS_FILE, " err: verify_part rc=%d", rc));
+ return -290;
+ }
+
+ return 0;
+}
+
+static int parse_test(void)
+{
+ unsigned i;
+
+ for (i=0; i<PJ_ARRAY_SIZE(p_tests); ++i) {
+ pj_pool_t *pool;
+ pjsip_media_type ctype;
+ pjsip_msg_body *body;
+ pj_str_t str;
+ int rc;
+
+ pool = pjsip_endpt_create_pool(endpt, NULL, 512, 512);
+
+ init_media_type(&ctype, p_tests[i].ctype, p_tests[i].csubtype,
+ p_tests[i].boundary);
+
+ pj_strdup2_with_null(pool, &str, p_tests[i].msg);
+ body = pjsip_multipart_parse(pool, str.ptr, str.slen, &ctype, 0);
+ if (!body)
+ return -100;
+
+ if (p_tests[i].verify) {
+ rc = p_tests[i].verify(pool, body);
+ } else {
+ rc = 0;
+ }
+
+ pj_pool_release(pool);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+int multipart_test(void)
+{
+ int rc;
+
+ rc = parse_test();
+ if (rc)
+ return rc;
+
+ return rc;
+}
+
diff --git a/pjsip/src/test/regc_test.c b/pjsip/src/test/regc_test.c
new file mode 100644
index 0000000..45912a3
--- /dev/null
+++ b/pjsip/src/test/regc_test.c
@@ -0,0 +1,1162 @@
+/* $Id: regc_test.c 4094 2012-04-26 09:31:00Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <pjsip_ua.h>
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "regc_test.c"
+
+
+/************************************************************************/
+/* A module to inject error into outgoing sending operation */
+static pj_status_t mod_send_on_tx_request(pjsip_tx_data *tdata);
+
+static struct
+{
+ pjsip_module mod;
+ unsigned count;
+ unsigned count_before_reject;
+} send_mod =
+{
+ {
+ NULL, NULL, /* prev, next. */
+ { "mod-send", 8 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ NULL, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ &mod_send_on_tx_request, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+ },
+ 0,
+ 0xFFFF
+};
+
+
+static pj_status_t mod_send_on_tx_request(pjsip_tx_data *tdata)
+{
+ PJ_UNUSED_ARG(tdata);
+
+ if (++send_mod.count > send_mod.count_before_reject)
+ return PJ_ECANCELLED;
+ else
+ return PJ_SUCCESS;
+};
+
+
+/************************************************************************/
+/* Registrar for testing */
+static pj_bool_t regs_rx_request(pjsip_rx_data *rdata);
+
+enum contact_op
+{
+ NONE, /* don't put Contact header */
+ EXACT, /* return exact contact */
+ MODIFIED, /* return modified Contact header */
+};
+
+struct registrar_cfg
+{
+ pj_bool_t respond; /* should it respond at all */
+ unsigned status_code; /* final response status code */
+ pj_bool_t authenticate; /* should we authenticate? */
+ enum contact_op contact_op; /* What should we do with Contact */
+ unsigned expires_param; /* non-zero to put in expires param */
+ unsigned expires; /* non-zero to put in Expires header*/
+
+ pj_str_t more_contacts; /* Additional Contact headers to put*/
+};
+
+static struct registrar
+{
+ pjsip_module mod;
+ struct registrar_cfg cfg;
+ unsigned response_cnt;
+} registrar =
+{
+ {
+ NULL, NULL, /* prev, next. */
+ { "registrar", 9 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &regs_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+ }
+};
+
+static pj_bool_t regs_rx_request(pjsip_rx_data *rdata)
+{
+ pjsip_msg *msg = rdata->msg_info.msg;
+ pjsip_hdr hdr_list;
+ int code;
+ pj_status_t status;
+
+ if (msg->line.req.method.id != PJSIP_REGISTER_METHOD)
+ return PJ_FALSE;
+
+ if (!registrar.cfg.respond)
+ return PJ_TRUE;
+
+ pj_list_init(&hdr_list);
+
+ if (registrar.cfg.authenticate &&
+ pjsip_msg_find_hdr(msg, PJSIP_H_AUTHORIZATION, NULL)==NULL)
+ {
+ pjsip_generic_string_hdr *hwww;
+ const pj_str_t hname = pj_str("WWW-Authenticate");
+ const pj_str_t hvalue = pj_str("Digest realm=\"test\"");
+
+ hwww = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &hname,
+ &hvalue);
+ pj_list_push_back(&hdr_list, hwww);
+
+ code = 401;
+
+ } else {
+ if (registrar.cfg.contact_op == EXACT ||
+ registrar.cfg.contact_op == MODIFIED)
+ {
+ pjsip_hdr *hsrc;
+
+ for (hsrc=msg->hdr.next; hsrc!=&msg->hdr; hsrc=hsrc->next) {
+ pjsip_contact_hdr *hdst;
+
+ if (hsrc->type != PJSIP_H_CONTACT)
+ continue;
+
+ hdst = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(rdata->tp_info.pool, hsrc);
+
+ if (hdst->expires==0)
+ continue;
+
+ if (registrar.cfg.contact_op == MODIFIED) {
+ if (PJSIP_URI_SCHEME_IS_SIP(hdst->uri) ||
+ PJSIP_URI_SCHEME_IS_SIPS(hdst->uri))
+ {
+ pjsip_sip_uri *sip_uri = (pjsip_sip_uri*)
+ pjsip_uri_get_uri(hdst->uri);
+ sip_uri->host = pj_str("x-modified-host");
+ sip_uri->port = 1;
+ }
+ }
+
+ if (registrar.cfg.expires_param)
+ hdst->expires = registrar.cfg.expires_param;
+
+ pj_list_push_back(&hdr_list, hdst);
+ }
+ }
+
+ if (registrar.cfg.more_contacts.slen) {
+ pjsip_generic_string_hdr *hcontact;
+ const pj_str_t hname = pj_str("Contact");
+
+ hcontact = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &hname,
+ &registrar.cfg.more_contacts);
+ pj_list_push_back(&hdr_list, hcontact);
+ }
+
+ if (registrar.cfg.expires) {
+ pjsip_expires_hdr *hexp;
+
+ hexp = pjsip_expires_hdr_create(rdata->tp_info.pool,
+ registrar.cfg.expires);
+ pj_list_push_back(&hdr_list, hexp);
+ }
+
+ registrar.response_cnt++;
+
+ code = registrar.cfg.status_code;
+ }
+
+ status = pjsip_endpt_respond(endpt, NULL, rdata, code, NULL,
+ &hdr_list, NULL, NULL);
+ pj_assert(status == PJ_SUCCESS);
+
+ return PJ_TRUE;
+}
+
+
+/************************************************************************/
+/* Client registration test session */
+struct client
+{
+ /* Result/expected result */
+ int error;
+ int code;
+ pj_bool_t have_reg;
+ int expiration;
+ unsigned contact_cnt;
+ pj_bool_t auth;
+
+ /* Commands */
+ pj_bool_t destroy_on_cb;
+
+ /* Status */
+ pj_bool_t done;
+
+ /* Additional results */
+ int interval;
+ int next_reg;
+};
+
+/* regc callback */
+static void client_cb(struct pjsip_regc_cbparam *param)
+{
+ struct client *client = (struct client*) param->token;
+ pjsip_regc_info info;
+ pj_status_t status;
+
+ client->done = PJ_TRUE;
+
+ status = pjsip_regc_get_info(param->regc, &info);
+ pj_assert(status == PJ_SUCCESS);
+
+ client->error = (param->status != PJ_SUCCESS);
+ client->code = param->code;
+
+ if (client->error)
+ return;
+
+ client->have_reg = info.auto_reg && info.interval>0 &&
+ param->expiration>0;
+ client->expiration = param->expiration;
+ client->contact_cnt = param->contact_cnt;
+ client->interval = info.interval;
+ client->next_reg = info.next_reg;
+
+ if (client->destroy_on_cb)
+ pjsip_regc_destroy(param->regc);
+}
+
+
+/* Generic client test session */
+static struct client client_result;
+static int do_test(const char *title,
+ const struct registrar_cfg *srv_cfg,
+ const struct client *client_cfg,
+ const pj_str_t *registrar_uri,
+ unsigned contact_cnt,
+ const pj_str_t contacts[],
+ unsigned expires,
+ pj_bool_t leave_session,
+ pjsip_regc **p_regc)
+{
+ pjsip_regc *regc;
+ unsigned i;
+ const pj_str_t aor = pj_str("<sip:regc-test@pjsip.org>");
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " %s", title));
+
+ /* Modify registrar settings */
+ pj_memcpy(&registrar.cfg, srv_cfg, sizeof(*srv_cfg));
+
+ pj_bzero(&client_result, sizeof(client_result));
+ client_result.destroy_on_cb = client_cfg->destroy_on_cb;
+
+ status = pjsip_regc_create(endpt, &client_result, &client_cb, &regc);
+ if (status != PJ_SUCCESS)
+ return -100;
+
+ status = pjsip_regc_init(regc, registrar_uri, &aor, &aor, contact_cnt,
+ contacts, expires ? expires : 60);
+ if (status != PJ_SUCCESS) {
+ pjsip_regc_destroy(regc);
+ return -110;
+ }
+
+ if (client_cfg->auth) {
+ pjsip_cred_info cred;
+
+ pj_bzero(&cred, sizeof(cred));
+ cred.realm = pj_str("*");
+ cred.scheme = pj_str("digest");
+ cred.username = pj_str("user");
+ cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+ cred.data = pj_str("password");
+
+ status = pjsip_regc_set_credentials(regc, 1, &cred);
+ if (status != PJ_SUCCESS) {
+ pjsip_regc_destroy(regc);
+ return -115;
+ }
+ }
+
+ /* Register */
+ status = pjsip_regc_register(regc, PJ_TRUE, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_regc_destroy(regc);
+ return -120;
+ }
+ status = pjsip_regc_send(regc, tdata);
+
+ /* That's it, wait until the callback is sent */
+ for (i=0; i<600 && !client_result.done; ++i) {
+ flush_events(100);
+ }
+
+ if (!client_result.done) {
+ PJ_LOG(3,(THIS_FILE, " error: test has timed out"));
+ pjsip_regc_destroy(regc);
+ return -200;
+ }
+
+ /* Destroy the regc, we're done with the test, unless we're
+ * instructed to leave the session open.
+ */
+ if (!leave_session && !client_cfg->destroy_on_cb)
+ pjsip_regc_destroy(regc);
+
+ /* Compare results with expected results */
+
+ if (client_result.error != client_cfg->error) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting err=%d, got err=%d",
+ client_cfg->error, client_result.error));
+ return -210;
+ }
+ if (client_result.code != client_cfg->code) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting code=%d, got code=%d",
+ client_cfg->code, client_result.code));
+ return -220;
+ }
+ if (client_result.expiration != client_cfg->expiration) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting expiration=%d, got expiration=%d",
+ client_cfg->expiration, client_result.expiration));
+ return -240;
+ }
+ if (client_result.contact_cnt != client_cfg->contact_cnt) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting contact_cnt=%d, got contact_cnt=%d",
+ client_cfg->contact_cnt, client_result.contact_cnt));
+ return -250;
+ }
+ if (client_result.have_reg != client_cfg->have_reg) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting have_reg=%d, got have_reg=%d",
+ client_cfg->have_reg, client_result.have_reg));
+ return -260;
+ }
+ if (client_result.interval != client_result.expiration) {
+ PJ_LOG(3,(THIS_FILE, " error: interval (%d) is different than expiration (%d)",
+ client_result.interval, client_result.expiration));
+ return -270;
+ }
+ if (client_result.expiration > 0 && client_result.next_reg < 1) {
+ PJ_LOG(3,(THIS_FILE, " error: next_reg=%d, expecting positive number because expiration is %d",
+ client_result.next_reg, client_result.expiration));
+ return -280;
+ }
+
+ /* Looks like everything is okay. */
+ if (leave_session) {
+ *p_regc = regc;
+ }
+
+ return 0;
+}
+
+
+/************************************************************************/
+/* Customized tests */
+
+/* Check that client is sending register refresh */
+static int keep_alive_test(const pj_str_t *registrar_uri)
+{
+ enum { TIMEOUT = 40 };
+ struct registrar_cfg server_cfg =
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_FALSE, EXACT, TIMEOUT, 0, {NULL, 0}};
+ struct client client_cfg =
+ /* error code have_reg expiration contact_cnt auth? destroy*/
+ { PJ_FALSE, 200, PJ_TRUE, TIMEOUT, 1, PJ_FALSE,PJ_FALSE};
+ pj_str_t contact = pj_str("<sip:c@C>");
+
+
+ pjsip_regc *regc;
+ unsigned i;
+ int ret;
+
+ ret = do_test("register refresh (takes ~40 secs)", &server_cfg, &client_cfg, registrar_uri,
+ 1, &contact, TIMEOUT, PJ_TRUE, &regc);
+ if (ret != 0)
+ return ret;
+
+ /* Reset server response_cnt */
+ registrar.response_cnt = 0;
+
+ /* Wait until keep-alive/refresh is done */
+ for (i=0; i<(TIMEOUT-1)*10 && registrar.response_cnt==0; ++i) {
+ flush_events(100);
+ }
+
+ if (registrar.response_cnt==0) {
+ PJ_LOG(3,(THIS_FILE, " error: no refresh is received"));
+ return -400;
+ }
+
+ if (client_result.error) {
+ PJ_LOG(3,(THIS_FILE, " error: got error"));
+ return -410;
+ }
+ if (client_result.code != 200) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting code=%d, got code=%d",
+ 200, client_result.code));
+ return -420;
+ }
+ if (client_result.expiration != TIMEOUT) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting expiration=%d, got expiration=%d",
+ TIMEOUT, client_result.expiration));
+ return -440;
+ }
+ if (client_result.contact_cnt != 1) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting contact_cnt=%d, got contact_cnt=%d",
+ TIMEOUT, client_result.contact_cnt));
+ return -450;
+ }
+ if (client_result.have_reg == 0) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting have_reg=%d, got have_reg=%d",
+ 1, client_result.have_reg));
+ return -460;
+ }
+ if (client_result.interval != TIMEOUT) {
+ PJ_LOG(3,(THIS_FILE, " error: interval (%d) is different than expiration (%d)",
+ client_result.interval, TIMEOUT));
+ return -470;
+ }
+ if (client_result.expiration > 0 && client_result.next_reg < 1) {
+ PJ_LOG(3,(THIS_FILE, " error: next_reg=%d, expecting positive number because expiration is %d",
+ client_result.next_reg, client_result.expiration));
+ return -480;
+ }
+
+ /* Success */
+ pjsip_regc_destroy(regc);
+ return 0;
+}
+
+
+/* Send error on refresh */
+static int refresh_error(const pj_str_t *registrar_uri,
+ pj_bool_t destroy_on_cb)
+{
+ enum { TIMEOUT = 40 };
+ struct registrar_cfg server_cfg =
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_FALSE, EXACT, TIMEOUT, 0, {NULL, 0}};
+ struct client client_cfg =
+ /* error code have_reg expiration contact_cnt auth? destroy*/
+ { PJ_FALSE, 200, PJ_TRUE, TIMEOUT, 1, PJ_FALSE,PJ_FALSE};
+ pj_str_t contact = pj_str("<sip:c@C>");
+
+ pjsip_regc *regc;
+ unsigned i;
+ int ret;
+
+ ret = do_test("refresh error (takes ~40 secs)", &server_cfg, &client_cfg, registrar_uri,
+ 1, &contact, TIMEOUT, PJ_TRUE, &regc);
+ if (ret != 0)
+ return ret;
+
+ /* Reset server response_cnt */
+ registrar.response_cnt = 0;
+
+ /* inject error for transmission */
+ send_mod.count = 0;
+ send_mod.count_before_reject = 0;
+
+ /* reconfigure client */
+ client_result.done = PJ_FALSE;
+ client_result.destroy_on_cb = destroy_on_cb;
+
+ /* Wait until keep-alive/refresh is done */
+ for (i=0; i<TIMEOUT*10 && !client_result.done; ++i) {
+ flush_events(100);
+ }
+
+ send_mod.count_before_reject = 0xFFFF;
+
+ if (!destroy_on_cb)
+ pjsip_regc_destroy(regc);
+
+ if (!client_result.done) {
+ PJ_LOG(3,(THIS_FILE, " error: test has timed out"));
+ return -500;
+ }
+
+ /* Expecting error */
+ if (client_result.error==PJ_FALSE && client_result.code/100==2) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting error got successfull result"));
+ return -510;
+ }
+
+ return PJ_SUCCESS;
+};
+
+
+/* Send error on refresh */
+static int update_test(const pj_str_t *registrar_uri)
+{
+ enum { TIMEOUT = 40 };
+ struct registrar_cfg server_cfg =
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_FALSE, EXACT, TIMEOUT, 0, {NULL, 0}};
+ struct client client_cfg =
+ /* error code have_reg expiration contact_cnt auth? destroy*/
+ { PJ_FALSE, 200, PJ_TRUE, TIMEOUT, 1, PJ_FALSE,PJ_FALSE};
+ pj_str_t contacts[] = {
+ { "<sip:a>", 7 },
+ { "<sip:b>", 7 },
+ { "<sip:c>", 7 }
+ };
+
+ pjsip_regc *regc;
+ pjsip_contact_hdr *h1, *h2;
+ pjsip_sip_uri *u1, *u2;
+ unsigned i;
+ pj_status_t status;
+ pjsip_tx_data *tdata = NULL;
+ int ret = 0;
+
+ /* initially only has 1 contact */
+ ret = do_test("update test", &server_cfg, &client_cfg, registrar_uri,
+ 1, &contacts[0], TIMEOUT, PJ_TRUE, &regc);
+ if (ret != 0) {
+ return -600;
+ }
+
+ /*****
+ * replace the contact with new one
+ */
+ PJ_LOG(3,(THIS_FILE, " replacing contact"));
+ status = pjsip_regc_update_contact(regc, 1, &contacts[1]);
+ if (status != PJ_SUCCESS) {
+ ret = -610;
+ goto on_return;
+ }
+
+ status = pjsip_regc_register(regc, PJ_TRUE, &tdata);
+ if (status != PJ_SUCCESS) {
+ ret = -620;
+ goto on_return;
+ }
+
+ /* Check that the REGISTER contains two Contacts:
+ * - <sip:a>;expires=0,
+ * - <sip:b>
+ */
+ h1 = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
+ if (!h1) {
+ ret = -630;
+ goto on_return;
+ }
+ if ((void*)h1->next == (void*)&tdata->msg->hdr)
+ h2 = NULL;
+ else
+ h2 = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h1->next);
+ if (!h2) {
+ ret = -640;
+ goto on_return;
+ }
+ /* must not have other Contact header */
+ if ((void*)h2->next != (void*)&tdata->msg->hdr &&
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h2->next) != NULL)
+ {
+ ret = -645;
+ goto on_return;
+ }
+
+ u1 = (pjsip_sip_uri*) pjsip_uri_get_uri(h1->uri);
+ u2 = (pjsip_sip_uri*) pjsip_uri_get_uri(h2->uri);
+
+ if (*u1->host.ptr == 'a') {
+ if (h1->expires != 0) {
+ ret = -650;
+ goto on_return;
+ }
+ if (h2->expires == 0) {
+ ret = -660;
+ goto on_return;
+ }
+
+ } else {
+ pj_assert(*u1->host.ptr == 'b');
+ if (h1->expires == 0) {
+ ret = -670;
+ goto on_return;
+ }
+ if (h2->expires != 0) {
+ ret = -680;
+ goto on_return;
+ }
+ }
+
+ /* Destroy tdata */
+ pjsip_tx_data_dec_ref(tdata);
+ tdata = NULL;
+
+
+
+ /**
+ * First loop, it will update with more contacts. Second loop
+ * should do nothing.
+ */
+ for (i=0; i<2; ++i) {
+ if (i==0)
+ PJ_LOG(3,(THIS_FILE, " replacing with more contacts"));
+ else
+ PJ_LOG(3,(THIS_FILE, " updating contacts with same contacts"));
+
+ status = pjsip_regc_update_contact(regc, 2, &contacts[1]);
+ if (status != PJ_SUCCESS) {
+ ret = -710;
+ goto on_return;
+ }
+
+ status = pjsip_regc_register(regc, PJ_TRUE, &tdata);
+ if (status != PJ_SUCCESS) {
+ ret = -720;
+ goto on_return;
+ }
+
+ /* Check that the REGISTER contains two Contacts:
+ * - <sip:b>
+ * - <sip:c>
+ */
+ h1 = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
+ if (!h1) {
+ ret = -730;
+ goto on_return;
+ }
+ if ((void*)h1->next == (void*)&tdata->msg->hdr)
+ h2 = NULL;
+ else
+ h2 = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h1->next);
+ if (!h2) {
+ ret = -740;
+ goto on_return;
+ }
+ /* must not have other Contact header */
+ if ((void*)h2->next != (void*)&tdata->msg->hdr &&
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h2->next) != NULL)
+ {
+ ret = -745;
+ goto on_return;
+ }
+
+ /* both contacts must not have expires=0 parameter */
+ if (h1->expires == 0) {
+ ret = -750;
+ goto on_return;
+ }
+ if (h2->expires == 0) {
+ ret = -760;
+ goto on_return;
+ }
+
+ /* Destroy tdata */
+ pjsip_tx_data_dec_ref(tdata);
+ tdata = NULL;
+ }
+
+on_return:
+ if (tdata) pjsip_tx_data_dec_ref(tdata);
+ pjsip_regc_destroy(regc);
+ return ret;
+};
+
+
+/* send error on authentication */
+static int auth_send_error(const pj_str_t *registrar_uri,
+ pj_bool_t destroy_on_cb)
+{
+ enum { TIMEOUT = 40 };
+ struct registrar_cfg server_cfg =
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_TRUE, EXACT, 75, 0, {NULL, 0}};
+ struct client client_cfg =
+ /* error code have_reg expiration contact_cnt auth? destroy*/
+ { PJ_TRUE, 401, PJ_FALSE, -1, 0, PJ_TRUE, PJ_TRUE};
+ pj_str_t contact = pj_str("<sip:c@C>");
+
+ pjsip_regc *regc;
+ int ret;
+
+ client_cfg.destroy_on_cb = destroy_on_cb;
+
+ /* inject error for second request retry */
+ send_mod.count = 0;
+ send_mod.count_before_reject = 1;
+
+ ret = do_test("auth send error", &server_cfg, &client_cfg, registrar_uri,
+ 1, &contact, TIMEOUT, PJ_TRUE, &regc);
+
+ send_mod.count_before_reject = 0xFFFF;
+
+ return ret;
+};
+
+
+
+
+/************************************************************************/
+enum
+{
+ OFF = 1,
+ ON = 2,
+ ON_OFF = 3,
+};
+
+int regc_test(void)
+{
+ struct test_rec {
+ unsigned check_contact;
+ unsigned add_xuid_param;
+
+ const char *title;
+ char *alt_registrar;
+ unsigned contact_cnt;
+ char *contacts[4];
+ unsigned expires;
+ struct registrar_cfg server_cfg;
+ struct client client_cfg;
+ } test_rec[] =
+ {
+ /* immediate error */
+ {
+ OFF, /* check_contact */
+ OFF, /* add_xuid_param */
+ "immediate error", /* title */
+ "sip:unresolved-host-xyy", /* alt_registrar */
+ 1, /* contact cnt */
+ { "sip:user@127.0.0.1:5060" }, /* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_FALSE, 200, PJ_FALSE, NONE, 0, 0, {NULL, 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth?*/
+ { PJ_FALSE, 502, PJ_FALSE, -1, 0, PJ_FALSE}
+ },
+
+ /* timeout test */
+ {
+ OFF, /* check_contact */
+ OFF, /* add_xuid_param */
+ "timeout test (takes ~32 secs)",/* title */
+ NULL, /* alt_registrar */
+ 1, /* contact cnt */
+ { "sip:user@127.0.0.1:5060" }, /* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_FALSE, 200, PJ_FALSE, NONE, 0, 0, {NULL, 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth? */
+ { PJ_FALSE, 408, PJ_FALSE, -1, 0, PJ_FALSE}
+ },
+
+ /* Basic successful registration scenario:
+ * a good registrar returns the Contact header as is and
+ * add expires parameter. In this test no additional bindings
+ * are returned.
+ */
+ {
+ ON_OFF, /* check_contact */
+ ON_OFF, /* add_xuid_param */
+ "basic", /* title */
+ NULL, /* alt_registrar */
+ 1, /* contact cnt */
+ { "<sip:user@127.0.0.1:5060;transport=udp;x-param=1234>" }, /* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_FALSE, EXACT, 75, 65, {NULL, 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth?*/
+ { PJ_FALSE, 200, PJ_TRUE, 75, 1, PJ_FALSE}
+ },
+
+ /* Basic successful registration scenario with authentication
+ */
+ {
+ ON_OFF, /* check_contact */
+ ON_OFF, /* add_xuid_param */
+ "authentication", /* title */
+ NULL, /* alt_registrar */
+ 1, /* contact cnt */
+ { "<sip:user@127.0.0.1:5060;transport=udp;x-param=1234>" }, /* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_TRUE, EXACT, 75, 65, {NULL, 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth?*/
+ { PJ_FALSE, 200, PJ_TRUE, 75, 1, PJ_TRUE}
+ },
+
+ /* a good registrar returns the Contact header as is and
+ * add expires parameter. Also it adds bindings from other
+ * clients in this test.
+ */
+ {
+ ON_OFF, /* check_contact */
+ ON, /* add_xuid_param */
+ "more bindings in response", /* title */
+ NULL, /* alt_registrar */
+ 1, /* contact cnt */
+ { "<sip:user@127.0.0.1:5060;transport=udp>" }, /* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_FALSE, EXACT, 75, 65, {"<sip:a@a>;expires=70", 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth?*/
+ { PJ_FALSE, 200, PJ_TRUE, 75, 2, PJ_FALSE}
+ },
+
+
+ /* a bad registrar returns modified Contact header, but it
+ * still returns all parameters intact. In this case
+ * the expiration is taken from the expires param because
+ * of matching xuid param or because the number of
+ * Contact header matches.
+ */
+ {
+ ON_OFF, /* check_contact */
+ ON_OFF, /* add_xuid_param */
+ "registrar modifies Contact header", /* title */
+ NULL, /* alt_registrar */
+ 1, /* contact cnt */
+ { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_FALSE, MODIFIED, 75, 65, {NULL, 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth?*/
+ { PJ_FALSE, 200, PJ_TRUE, 75, 1, PJ_FALSE}
+ },
+
+
+ /* a bad registrar returns modified Contact header, but it
+ * still returns all parameters intact. In addition it returns
+ * bindings from other clients.
+ *
+ * In this case the expiration is taken from the expires param
+ * because add_xuid_param is enabled.
+ */
+ {
+ ON_OFF, /* check_contact */
+ ON, /* add_xuid_param */
+ "registrar modifies Contact header and add bindings", /* title */
+ NULL, /* alt_registrar */
+ 1, /* contact cnt */
+ { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_FALSE, MODIFIED, 75, 65, {"<sip:a@a>;expires=70", 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth?*/
+ { PJ_FALSE, 200, PJ_TRUE, 75, 2, PJ_FALSE}
+ },
+
+
+ /* a bad registrar returns completely different Contact and
+ * all parameters are gone. In this case the expiration is
+ * also taken from the expires param since the number of
+ * header matches.
+ */
+ {
+ ON_OFF, /* check_contact */
+ ON_OFF, /* add_xuid_param */
+ "registrar replaces Contact header", /* title */
+ NULL, /* alt_registrar */
+ 1, /* contact cnt */
+ { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 202, PJ_FALSE, NONE, 0, 65, {"<sip:a@A>;expires=75", 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth?*/
+ { PJ_FALSE, 202, PJ_TRUE, 75, 1, PJ_FALSE}
+ },
+
+
+ /* a bad registrar returns completely different Contact (and
+ * all parameters are gone) and it also includes bindings from
+ * other clients.
+ * In this case the expiration is taken from the Expires header.
+ */
+ {
+ ON_OFF, /* check_contact */
+ ON_OFF, /* add_xuid_param */
+ " as above with additional bindings", /* title */
+ NULL, /* alt_registrar */
+ 1, /* contact cnt */
+ { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_FALSE, NONE, 0, 65, {"<sip:a@A>;expires=75, <sip:b@B;expires=70>", 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth?*/
+ { PJ_FALSE, 200, PJ_TRUE, 65, 2, PJ_FALSE}
+ },
+
+ /* the registrar doesn't return any bindings, but for some
+ * reason it includes an Expires header.
+ * In this case the expiration is taken from the Expires header.
+ */
+ {
+ ON_OFF, /* check_contact */
+ ON_OFF, /* add_xuid_param */
+ "no Contact but with Expires", /* title */
+ NULL, /* alt_registrar */
+ 1, /* contact cnt */
+ { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_FALSE, NONE, 0, 65, {NULL, 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth?*/
+ { PJ_FALSE, 200, PJ_TRUE, 65, 0, PJ_FALSE}
+ },
+
+ /* Neither Contact header nor Expires header are present.
+ * In this case the expiration is taken from the request.
+ */
+ {
+ ON_OFF, /* check_contact */
+ ON_OFF, /* add_xuid_param */
+ "no Contact and no Expires", /* title */
+ NULL, /* alt_registrar */
+ 1, /* contact cnt */
+ { "<sip:user@127.0.0.1:5060>" },/* contacts[] */
+ 600, /* expires */
+
+ /* registrar config: */
+ /* respond code auth contact exp_prm expires more_contacts */
+ { PJ_TRUE, 200, PJ_FALSE, NONE, 0, 0, {NULL, 0}},
+
+ /* client expected results: */
+ /* error code have_reg expiration contact_cnt auth?*/
+ { PJ_FALSE, 200, PJ_TRUE, 600, 0, PJ_FALSE}
+ },
+ };
+
+ unsigned i;
+ pj_sockaddr_in addr;
+ pjsip_transport *udp = NULL;
+ pj_uint16_t port;
+ char registrar_uri_buf[80];
+ pj_str_t registrar_uri;
+ int rc = 0;
+
+ pj_sockaddr_in_init(&addr, 0, 0);
+
+ /* Acquire existing transport, if any */
+ rc = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP, &addr, sizeof(addr), NULL, &udp);
+ if (rc == PJ_SUCCESS) {
+ port = pj_sockaddr_get_port(&udp->local_addr);
+ pjsip_transport_dec_ref(udp);
+ udp = NULL;
+ } else {
+ rc = pjsip_udp_transport_start(endpt, NULL, NULL, 1, &udp);
+ if (rc != PJ_SUCCESS) {
+ app_perror(" error creating UDP transport", rc);
+ rc = -2;
+ goto on_return;
+ }
+
+ port = pj_sockaddr_get_port(&udp->local_addr);
+ }
+
+ /* Register registrar module */
+ rc = pjsip_endpt_register_module(endpt, &registrar.mod);
+ if (rc != PJ_SUCCESS) {
+ app_perror(" error registering module", rc);
+ rc = -3;
+ goto on_return;
+ }
+
+ /* Register send module */
+ rc = pjsip_endpt_register_module(endpt, &send_mod.mod);
+ if (rc != PJ_SUCCESS) {
+ app_perror(" error registering module", rc);
+ rc = -3;
+ goto on_return;
+ }
+
+ pj_ansi_snprintf(registrar_uri_buf, sizeof(registrar_uri_buf),
+ "sip:127.0.0.1:%d", (int)port);
+ registrar_uri = pj_str(registrar_uri_buf);
+
+ for (i=0; i<PJ_ARRAY_SIZE(test_rec); ++i) {
+ struct test_rec *t = &test_rec[i];
+ unsigned j, x;
+ pj_str_t reg_uri;
+ pj_str_t contacts[8];
+
+ /* Fill in the registrar address if it's not specified */
+ if (t->alt_registrar == NULL) {
+ reg_uri = registrar_uri;
+ } else {
+ reg_uri = pj_str(t->alt_registrar);
+ }
+
+ /* Build contact pj_str_t's */
+ for (j=0; j<t->contact_cnt; ++j) {
+ contacts[j] = pj_str(t->contacts[j]);
+ }
+
+ /* Normalize more_contacts field */
+ if (t->server_cfg.more_contacts.ptr)
+ t->server_cfg.more_contacts.slen = strlen(t->server_cfg.more_contacts.ptr);
+
+ /* Do tests with three combinations:
+ * - check_contact on/off
+ * - add_xuid_param on/off
+ * - destroy_on_callback on/off
+ */
+ for (x=1; x<=2; ++x) {
+ unsigned y;
+
+ if ((t->check_contact & x) == 0)
+ continue;
+
+ pjsip_cfg()->regc.check_contact = (x-1);
+
+ for (y=1; y<=2; ++y) {
+ unsigned z;
+
+ if ((t->add_xuid_param & y) == 0)
+ continue;
+
+ pjsip_cfg()->regc.add_xuid_param = (y-1);
+
+ for (z=0; z<=1; ++z) {
+ char new_title[200];
+
+ t->client_cfg.destroy_on_cb = z;
+
+ sprintf(new_title, "%s [check=%d, xuid=%d, destroy=%d]",
+ t->title, pjsip_cfg()->regc.check_contact,
+ pjsip_cfg()->regc.add_xuid_param, z);
+ rc = do_test(new_title, &t->server_cfg, &t->client_cfg,
+ &reg_uri, t->contact_cnt, contacts,
+ t->expires, PJ_FALSE, NULL);
+ if (rc != 0)
+ goto on_return;
+ }
+
+ }
+ }
+
+ /* Sleep between test groups to avoid using up too many
+ * active transactions.
+ */
+ pj_thread_sleep(1000);
+ }
+
+ /* keep-alive test */
+ rc = keep_alive_test(&registrar_uri);
+ if (rc != 0)
+ goto on_return;
+
+ /* Send error on refresh without destroy on callback */
+ rc = refresh_error(&registrar_uri, PJ_FALSE);
+ if (rc != 0)
+ goto on_return;
+
+ /* Send error on refresh, destroy on callback */
+ rc = refresh_error(&registrar_uri, PJ_TRUE);
+ if (rc != 0)
+ goto on_return;
+
+ /* Updating contact */
+ rc = update_test(&registrar_uri);
+ if (rc != 0)
+ goto on_return;
+
+ /* Send error during auth, don't destroy on callback */
+ rc = auth_send_error(&registrar_uri, PJ_FALSE);
+ if (rc != 0)
+ goto on_return;
+
+ /* Send error during auth, destroy on callback */
+ rc = auth_send_error(&registrar_uri, PJ_FALSE);
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ if (registrar.mod.id != -1) {
+ pjsip_endpt_unregister_module(endpt, &registrar.mod);
+ }
+ if (send_mod.mod.id != -1) {
+ pjsip_endpt_unregister_module(endpt, &send_mod.mod);
+ }
+ if (udp) {
+ pjsip_transport_dec_ref(udp);
+ }
+ return rc;
+}
+
+
diff --git a/pjsip/src/test/test.c b/pjsip/src/test/test.c
new file mode 100644
index 0000000..4d17939
--- /dev/null
+++ b/pjsip/src/test/test.c
@@ -0,0 +1,398 @@
+/* $Id: test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include "test.h"
+#include <pjlib.h>
+#include <pjlib-util.h>
+#include <pjsip.h>
+
+#define THIS_FILE "test.c"
+
+#define DO_TEST(test) do { \
+ PJ_LOG(3, (THIS_FILE, "Running %s...", #test)); \
+ rc = test; \
+ PJ_LOG(3, (THIS_FILE, \
+ "%s(%d)", \
+ (rc ? "..ERROR" : "..success"), rc)); \
+ if (rc!=0) goto on_return; \
+ } while (0)
+
+#define DO_TSX_TEST(test, param) \
+ do { \
+ PJ_LOG(3, (THIS_FILE, "Running %s(%s)...", #test, (param)->tp_type)); \
+ rc = test(param); \
+ PJ_LOG(3, (THIS_FILE, \
+ "%s(%d)", \
+ (rc ? "..ERROR" : "..success"), rc)); \
+ if (rc!=0) goto on_return; \
+ } while (0)
+
+
+pjsip_endpoint *endpt;
+int log_level = 3;
+int param_log_decor = PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_TIME |
+ PJ_LOG_HAS_MICRO_SEC;
+
+static pj_oshandle_t fd_report;
+const char *system_name = "Unknown";
+static char buf[1024];
+
+void app_perror(const char *msg, pj_status_t rc)
+{
+ char errbuf[256];
+
+ PJ_CHECK_STACK();
+
+ pj_strerror(rc, errbuf, sizeof(errbuf));
+ PJ_LOG(3,(THIS_FILE, "%s: [pj_status_t=%d] %s", msg, rc, errbuf));
+
+}
+
+void flush_events(unsigned duration)
+{
+ pj_time_val stop_time;
+
+ pj_gettimeofday(&stop_time);
+ stop_time.msec += duration;
+ pj_time_val_normalize(&stop_time);
+
+ /* Process all events for the specified duration. */
+ for (;;) {
+ pj_time_val timeout = {0, 1}, now;
+
+ pjsip_endpt_handle_events(endpt, &timeout);
+
+ pj_gettimeofday(&now);
+ if (PJ_TIME_VAL_GTE(now, stop_time))
+ break;
+ }
+}
+
+pj_status_t register_static_modules(pj_size_t *count, pjsip_module **modules)
+{
+ PJ_UNUSED_ARG(modules);
+
+ *count = 0;
+ return PJ_SUCCESS;
+}
+
+static pj_status_t init_report(void)
+{
+ char tmp[80];
+ pj_time_val timestamp;
+ pj_parsed_time date_time;
+ pj_ssize_t len;
+ pj_status_t status;
+
+ pj_ansi_sprintf(tmp, "pjsip-static-bench-%s-%s.htm", PJ_OS_NAME, PJ_CC_NAME);
+
+ status = pj_file_open(NULL, tmp, PJ_O_WRONLY, &fd_report);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Title */
+ len = pj_ansi_sprintf(buf, "<HTML>\n"
+ " <HEAD>\n"
+ " <TITLE>PJSIP %s (%s) - Static Benchmark</TITLE>\n"
+ " </HEAD>\n"
+ "<BODY>\n"
+ "\n",
+ PJ_VERSION,
+ (PJ_DEBUG ? "Debug" : "Release"));
+ pj_file_write(fd_report, buf, &len);
+
+
+ /* Title */
+ len = pj_ansi_sprintf(buf, "<H1>PJSIP %s (%s) - Static Benchmark</H1>\n",
+ PJ_VERSION,
+ (PJ_DEBUG ? "Debug" : "Release"));
+ pj_file_write(fd_report, buf, &len);
+
+ len = pj_ansi_sprintf(buf, "<P>Below is the benchmark result generated "
+ "by <b>test-pjsip</b> program. The program "
+ "is single-threaded only.</P>\n");
+ pj_file_write(fd_report, buf, &len);
+
+
+ /* Write table heading */
+ len = pj_ansi_sprintf(buf, "<TABLE border=\"1\" cellpadding=\"4\">\n"
+ " <TR><TD bgColor=\"aqua\" align=\"center\">Variable</TD>\n"
+ " <TD bgColor=\"aqua\" align=\"center\">Value</TD>\n"
+ " <TD bgColor=\"aqua\" align=\"center\">Description</TD>\n"
+ " </TR>\n");
+ pj_file_write(fd_report, buf, &len);
+
+
+ /* Write version */
+ report_sval("version", PJ_VERSION, "", "PJLIB/PJSIP version");
+
+
+ /* Debug or release */
+ report_sval("build-type", (PJ_DEBUG ? "Debug" : "Release"), "", "Build type");
+
+
+ /* Write timestamp */
+ pj_gettimeofday(&timestamp);
+ report_ival("timestamp", timestamp.sec, "", "System timestamp of the test");
+
+
+ /* Write time of day */
+ pj_time_decode(&timestamp, &date_time);
+ len = pj_ansi_sprintf(tmp, "%04d-%02d-%02d %02d:%02d:%02d",
+ date_time.year, date_time.mon+1, date_time.day,
+ date_time.hour, date_time.min, date_time.sec);
+ report_sval("date-time", tmp, "", "Date/time of the test");
+
+
+ /* Write System */
+ report_sval("system", system_name, "", "System description");
+
+
+ /* Write OS type */
+ report_sval("os-family", PJ_OS_NAME, "", "Operating system family");
+
+
+ /* Write CC name */
+ len = pj_ansi_sprintf(tmp, "%s-%d.%d.%d", PJ_CC_NAME,
+ PJ_CC_VER_1, PJ_CC_VER_2, PJ_CC_VER_2);
+ report_sval("cc-name", tmp, "", "Compiler name and version");
+
+
+ return PJ_SUCCESS;
+}
+
+void report_sval(const char *name, const char* value, const char *valname,
+ const char *desc)
+{
+ pj_ssize_t len;
+
+ len = pj_ansi_sprintf(buf, " <TR><TD><TT>%s</TT></TD>\n"
+ " <TD align=\"right\"><B>%s %s</B></TD>\n"
+ " <TD>%s</TD>\n"
+ " </TR>\n",
+ name, value, valname, desc);
+ pj_file_write(fd_report, buf, &len);
+}
+
+
+void report_ival(const char *name, int value, const char *valname,
+ const char *desc)
+{
+ pj_ssize_t len;
+
+ len = pj_ansi_sprintf(buf, " <TR><TD><TT>%s</TT></TD>\n"
+ " <TD align=\"right\"><B>%d %s</B></TD>\n"
+ " <TD>%s</TD>\n"
+ " </TR>\n",
+ name, value, valname, desc);
+ pj_file_write(fd_report, buf, &len);
+
+}
+
+static void close_report(void)
+{
+ pj_ssize_t len;
+
+ if (fd_report) {
+ len = pj_ansi_sprintf(buf, "</TABLE>\n</BODY>\n</HTML>\n");
+ pj_file_write(fd_report, buf, &len);
+
+ pj_file_close(fd_report);
+ }
+}
+
+
+int test_main(void)
+{
+ pj_status_t rc;
+ pj_caching_pool caching_pool;
+ const char *filename;
+ unsigned tsx_test_cnt=0;
+ struct tsx_test_param tsx_test[10];
+ pj_status_t status;
+#if INCLUDE_TSX_TEST
+ unsigned i;
+ pjsip_transport *tp;
+#if PJ_HAS_TCP
+ pjsip_tpfactory *tpfactory;
+#endif /* PJ_HAS_TCP */
+#endif /* INCLUDE_TSX_TEST */
+ int line;
+
+ pj_log_set_level(log_level);
+ pj_log_set_decor(param_log_decor);
+
+ if ((rc=pj_init()) != PJ_SUCCESS) {
+ app_perror("pj_init", rc);
+ return rc;
+ }
+
+ if ((rc=pjlib_util_init()) != PJ_SUCCESS) {
+ app_perror("pj_init", rc);
+ return rc;
+ }
+
+ status = init_report();
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_dump_config();
+
+ pj_caching_pool_init( &caching_pool, &pj_pool_factory_default_policy,
+ PJSIP_TEST_MEM_SIZE );
+
+ rc = pjsip_endpt_create(&caching_pool.factory, "endpt", &endpt);
+ if (rc != PJ_SUCCESS) {
+ app_perror("pjsip_endpt_create", rc);
+ pj_caching_pool_destroy(&caching_pool);
+ return rc;
+ }
+
+ PJ_LOG(3,(THIS_FILE,""));
+
+ /* Init logger module. */
+ init_msg_logger();
+ msg_logger_set_enabled(1);
+
+ /* Start transaction layer module. */
+ rc = pjsip_tsx_layer_init_module(endpt);
+ if (rc != PJ_SUCCESS) {
+ app_perror(" Error initializing transaction module", rc);
+ goto on_return;
+ }
+
+ /* Create loop transport. */
+ rc = pjsip_loop_start(endpt, NULL);
+ if (rc != PJ_SUCCESS) {
+ app_perror(" error: unable to create datagram loop transport",
+ rc);
+ goto on_return;
+ }
+ tsx_test[tsx_test_cnt].port = 5060;
+ tsx_test[tsx_test_cnt].tp_type = "loop-dgram";
+ tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_LOOP_DGRAM;
+ ++tsx_test_cnt;
+
+
+#if INCLUDE_URI_TEST
+ DO_TEST(uri_test());
+#endif
+
+#if INCLUDE_MSG_TEST
+ DO_TEST(msg_test());
+ DO_TEST(msg_err_test());
+#endif
+
+#if INCLUDE_MULTIPART_TEST
+ DO_TEST(multipart_test());
+#endif
+
+#if INCLUDE_TXDATA_TEST
+ DO_TEST(txdata_test());
+#endif
+
+#if INCLUDE_TSX_BENCH
+ DO_TEST(tsx_bench());
+#endif
+
+#if INCLUDE_UDP_TEST
+ DO_TEST(transport_udp_test());
+#endif
+
+#if INCLUDE_LOOP_TEST
+ DO_TEST(transport_loop_test());
+#endif
+
+#if INCLUDE_TCP_TEST
+ DO_TEST(transport_tcp_test());
+#endif
+
+#if INCLUDE_RESOLVE_TEST
+ DO_TEST(resolve_test());
+#endif
+
+
+#if INCLUDE_TSX_TEST
+ status = pjsip_udp_transport_start(endpt, NULL, NULL, 1, &tp);
+ if (status == PJ_SUCCESS) {
+ tsx_test[tsx_test_cnt].port = tp->local_name.port;
+ tsx_test[tsx_test_cnt].tp_type = "udp";
+ tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_UDP;
+ ++tsx_test_cnt;
+ }
+
+#if PJ_HAS_TCP
+ status = pjsip_tcp_transport_start(endpt, NULL, 1, &tpfactory);
+ if (status == PJ_SUCCESS) {
+ tsx_test[tsx_test_cnt].port = tpfactory->addr_name.port;
+ tsx_test[tsx_test_cnt].tp_type = "tcp";
+ tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_TCP;
+ ++tsx_test_cnt;
+ } else {
+ app_perror("Unable to create TCP", status);
+ rc = -4;
+ goto on_return;
+ }
+#endif
+
+
+ for (i=0; i<tsx_test_cnt; ++i) {
+ DO_TSX_TEST(tsx_basic_test, &tsx_test[i]);
+ DO_TSX_TEST(tsx_uac_test, &tsx_test[i]);
+ DO_TSX_TEST(tsx_uas_test, &tsx_test[i]);
+ }
+#endif
+
+#if INCLUDE_INV_OA_TEST
+ DO_TEST(inv_offer_answer_test());
+#endif
+
+#if INCLUDE_REGC_TEST
+ DO_TEST(regc_test());
+#endif
+
+
+on_return:
+ flush_events(500);
+
+ /* Dumping memory pool usage */
+ PJ_LOG(3,(THIS_FILE, "Peak memory size=%u MB",
+ caching_pool.peak_used_size / 1000000));
+
+ pjsip_endpt_destroy(endpt);
+ pj_caching_pool_destroy(&caching_pool);
+
+ PJ_LOG(3,(THIS_FILE, ""));
+
+ pj_thread_get_stack_info(pj_thread_this(), &filename, &line);
+ PJ_LOG(3,(THIS_FILE, "Stack max usage: %u, deepest: %s:%u",
+ pj_thread_get_stack_max_usage(pj_thread_this()),
+ filename, line));
+ if (rc == 0)
+ PJ_LOG(3,(THIS_FILE, "Looks like everything is okay!.."));
+ else
+ PJ_LOG(3,(THIS_FILE, "Test completed with error(s)"));
+
+ report_ival("test-status", rc, "", "Overall test status/result (0==success)");
+ close_report();
+ return rc;
+}
+
diff --git a/pjsip/src/test/test.h b/pjsip/src/test/test.h
new file mode 100644
index 0000000..d046682
--- /dev/null
+++ b/pjsip/src/test/test.h
@@ -0,0 +1,127 @@
+/* $Id: test.h 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __TEST_H__
+#define __TEST_H__
+
+#include <pjsip/sip_types.h>
+
+extern pjsip_endpoint *endpt;
+
+#define TEST_UDP_PORT 15060
+#define TEST_UDP_PORT_STR "15060"
+
+/**
+ * Memory size to use in caching pool.
+ * Default: 2MB
+ */
+#ifndef PJSIP_TEST_MEM_SIZE
+# define PJSIP_TEST_MEM_SIZE (2*1024*1024)
+#endif
+
+
+
+#define INCLUDE_MESSAGING_GROUP 1
+#define INCLUDE_TRANSPORT_GROUP 1
+#define INCLUDE_TSX_GROUP 1
+#define INCLUDE_INV_GROUP 1
+#define INCLUDE_REGC_GROUP 1
+
+#define INCLUDE_BENCHMARKS 1
+
+/*
+ * Include tests that normally would fail under certain gcc
+ * optimization levels.
+ */
+#ifndef INCLUDE_GCC_TEST
+# define INCLUDE_GCC_TEST 0
+#endif
+
+
+#define INCLUDE_URI_TEST INCLUDE_MESSAGING_GROUP
+#define INCLUDE_MSG_TEST INCLUDE_MESSAGING_GROUP
+#define INCLUDE_MULTIPART_TEST INCLUDE_MESSAGING_GROUP
+#define INCLUDE_TXDATA_TEST INCLUDE_MESSAGING_GROUP
+#define INCLUDE_TSX_BENCH INCLUDE_MESSAGING_GROUP
+#define INCLUDE_UDP_TEST INCLUDE_TRANSPORT_GROUP
+#define INCLUDE_LOOP_TEST INCLUDE_TRANSPORT_GROUP
+#define INCLUDE_TCP_TEST INCLUDE_TRANSPORT_GROUP
+#define INCLUDE_RESOLVE_TEST INCLUDE_TRANSPORT_GROUP
+#define INCLUDE_TSX_TEST INCLUDE_TSX_GROUP
+#define INCLUDE_INV_OA_TEST INCLUDE_INV_GROUP
+#define INCLUDE_REGC_TEST INCLUDE_REGC_GROUP
+
+
+/* The tests */
+int uri_test(void);
+int msg_test(void);
+int msg_err_test(void);
+int multipart_test(void);
+int txdata_test(void);
+int tsx_bench(void);
+int transport_udp_test(void);
+int transport_loop_test(void);
+int transport_tcp_test(void);
+int resolve_test(void);
+int regc_test(void);
+
+struct tsx_test_param
+{
+ int type;
+ int port;
+ char *tp_type;
+};
+
+int tsx_basic_test(struct tsx_test_param *param);
+int tsx_uac_test(struct tsx_test_param *param);
+int tsx_uas_test(struct tsx_test_param *param);
+
+/* Transport test helpers (transport_test.c). */
+int generic_transport_test(pjsip_transport *tp);
+int transport_send_recv_test( pjsip_transport_type_e tp_type,
+ pjsip_transport *ref_tp,
+ char *target_url,
+ int *p_usec_rtt);
+int transport_rt_test( pjsip_transport_type_e tp_type,
+ pjsip_transport *ref_tp,
+ char *target_url,
+ int *pkt_lost);
+int transport_load_test(char *target_url);
+
+/* Invite session */
+int inv_offer_answer_test(void);
+
+/* Test main entry */
+int test_main(void);
+
+/* Test utilities. */
+void app_perror(const char *msg, pj_status_t status);
+int init_msg_logger(void);
+int msg_logger_set_enabled(pj_bool_t enabled);
+void flush_events(unsigned duration);
+
+
+void report_ival(const char *name, int value, const char *valname, const char *desc);
+void report_sval(const char *name, const char* value, const char *valname, const char *desc);
+
+
+/* Settings. */
+extern int log_level;
+
+#endif /* __TEST_H__ */
diff --git a/pjsip/src/test/transport_loop_test.c b/pjsip/src/test/transport_loop_test.c
new file mode 100644
index 0000000..6c60036
--- /dev/null
+++ b/pjsip/src/test/transport_loop_test.c
@@ -0,0 +1,127 @@
+/* $Id: transport_loop_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "transport_loop_test.c"
+
+static int datagram_loop_test()
+{
+ enum { LOOP = 8 };
+ pjsip_transport *loop;
+ int i, pkt_lost;
+ pj_sockaddr_in addr;
+ pj_status_t status;
+ long ref_cnt;
+ int rtt[LOOP], min_rtt;
+
+ PJ_LOG(3,(THIS_FILE, "testing datagram loop transport"));
+
+ /* Test acquire transport. */
+ status = pjsip_endpt_acquire_transport( endpt, PJSIP_TRANSPORT_LOOP_DGRAM,
+ &addr, sizeof(addr), NULL, &loop);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: loop transport is not configured", status);
+ return -20;
+ }
+
+ /* Get initial reference counter */
+ ref_cnt = pj_atomic_get(loop->ref_cnt);
+
+ /* Test basic transport attributes */
+ status = generic_transport_test(loop);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Basic transport's send/receive loopback test. */
+ for (i=0; i<LOOP; ++i) {
+ status = transport_send_recv_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop,
+ "sip:bob@130.0.0.1;transport=loop-dgram",
+ &rtt[i]);
+ if (status != 0)
+ return status;
+ }
+
+ min_rtt = 0xFFFFFFF;
+ for (i=0; i<LOOP; ++i)
+ if (rtt[i] < min_rtt) min_rtt = rtt[i];
+
+ report_ival("loop-rtt-usec", min_rtt, "usec",
+ "Best Loopback transport round trip time, in microseconds "
+ "(time from sending request until response is received. "
+ "Tests were performed on local machine only)");
+
+
+ /* Multi-threaded round-trip test. */
+ status = transport_rt_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop,
+ "sip:bob@130.0.0.1;transport=loop-dgram",
+ &pkt_lost);
+ if (status != 0)
+ return status;
+
+ if (pkt_lost != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: %d packet(s) was lost", pkt_lost));
+ return -40;
+ }
+
+ /* Put delay. */
+ PJ_LOG(3,(THIS_FILE," setting network delay to 10 ms"));
+ pjsip_loop_set_delay(loop, 10);
+
+ /* Multi-threaded round-trip test. */
+ status = transport_rt_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop,
+ "sip:bob@130.0.0.1;transport=loop-dgram",
+ &pkt_lost);
+ if (status != 0)
+ return status;
+
+ if (pkt_lost != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: %d packet(s) was lost", pkt_lost));
+ return -50;
+ }
+
+ /* Restore delay. */
+ pjsip_loop_set_delay(loop, 0);
+
+ /* Check reference counter. */
+ if (pj_atomic_get(loop->ref_cnt) != ref_cnt) {
+ PJ_LOG(3,(THIS_FILE, " error: ref counter is not %d (%d)",
+ ref_cnt, pj_atomic_get(loop->ref_cnt)));
+ return -51;
+ }
+
+ /* Decrement reference. */
+ pjsip_transport_dec_ref(loop);
+
+ return 0;
+}
+
+int transport_loop_test(void)
+{
+ int status;
+
+ status = datagram_loop_test();
+ if (status != 0)
+ return status;
+
+ return 0;
+}
diff --git a/pjsip/src/test/transport_tcp_test.c b/pjsip/src/test/transport_tcp_test.c
new file mode 100644
index 0000000..4bc8c1c
--- /dev/null
+++ b/pjsip/src/test/transport_tcp_test.c
@@ -0,0 +1,155 @@
+/* $Id: transport_tcp_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "transport_tcp_test.c"
+
+
+/*
+ * TCP transport test.
+ */
+#if PJ_HAS_TCP
+int transport_tcp_test(void)
+{
+ enum { SEND_RECV_LOOP = 8 };
+ pjsip_tpfactory *tpfactory;
+ pjsip_transport *tcp;
+ pj_sockaddr_in rem_addr;
+ pj_status_t status;
+ char url[PJSIP_MAX_URL_SIZE];
+ int rtt[SEND_RECV_LOOP], min_rtt;
+ int i, pkt_lost;
+
+ /* Start TCP listener on arbitrary port. */
+ status = pjsip_tcp_transport_start(endpt, NULL, 1, &tpfactory);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to start TCP transport", status);
+ return -10;
+ }
+
+
+ /* Get the listener address */
+ status = pj_sockaddr_in_init(&rem_addr, &tpfactory->addr_name.host,
+ (pj_uint16_t)tpfactory->addr_name.port);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: possibly invalid TCP address name", status);
+ return -14;
+ }
+
+ pj_ansi_sprintf(url, "sip:alice@%s:%d;transport=tcp",
+ pj_inet_ntoa(rem_addr.sin_addr),
+ pj_ntohs(rem_addr.sin_port));
+
+
+ /* Acquire one TCP transport. */
+ status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_TCP,
+ &rem_addr, sizeof(rem_addr),
+ NULL, &tcp);
+ if (status != PJ_SUCCESS || tcp == NULL) {
+ app_perror(" Error: unable to acquire TCP transport", status);
+ return -17;
+ }
+
+ /* After pjsip_endpt_acquire_transport, TCP transport must have
+ * reference counter 1.
+ */
+ if (pj_atomic_get(tcp->ref_cnt) != 1)
+ return -20;
+
+ /* Test basic transport attributes */
+ status = generic_transport_test(tcp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Check again that reference counter is 1. */
+ if (pj_atomic_get(tcp->ref_cnt) != 1)
+ return -40;
+
+ /* Load test */
+ if (transport_load_test(url) != 0)
+ return -60;
+
+ /* Basic transport's send/receive loopback test. */
+ for (i=0; i<SEND_RECV_LOOP; ++i) {
+ status = transport_send_recv_test(PJSIP_TRANSPORT_TCP, tcp, url, &rtt[i]);
+
+ if (status != 0) {
+ pjsip_transport_dec_ref(tcp);
+ flush_events(500);
+ return -72;
+ }
+ }
+
+ min_rtt = 0xFFFFFFF;
+ for (i=0; i<SEND_RECV_LOOP; ++i)
+ if (rtt[i] < min_rtt) min_rtt = rtt[i];
+
+ report_ival("tcp-rtt-usec", min_rtt, "usec",
+ "Best TCP transport round trip time, in microseconds "
+ "(time from sending request until response is received. "
+ "Tests were performed on local machine only, and after "
+ "TCP socket has been established by previous test)");
+
+
+ /* Multi-threaded round-trip test. */
+ status = transport_rt_test(PJSIP_TRANSPORT_TCP, tcp, url, &pkt_lost);
+ if (status != 0) {
+ pjsip_transport_dec_ref(tcp);
+ return status;
+ }
+
+ if (pkt_lost != 0)
+ PJ_LOG(3,(THIS_FILE, " note: %d packet(s) was lost", pkt_lost));
+
+ /* Check again that reference counter is still 1. */
+ if (pj_atomic_get(tcp->ref_cnt) != 1)
+ return -80;
+
+ /* Destroy this transport. */
+ pjsip_transport_dec_ref(tcp);
+
+ /* Force destroy this transport. */
+ status = pjsip_transport_destroy(tcp);
+ if (status != PJ_SUCCESS)
+ return -90;
+
+ /* Unregister factory */
+ status = pjsip_tpmgr_unregister_tpfactory(pjsip_endpt_get_tpmgr(endpt),
+ tpfactory);
+ if (status != PJ_SUCCESS)
+ return -95;
+
+ /* Flush events. */
+ PJ_LOG(3,(THIS_FILE, " Flushing events, 1 second..."));
+ flush_events(1000);
+
+ /* Done */
+ return 0;
+}
+#else /* PJ_HAS_TCP */
+int transport_tcp_test(void)
+{
+ return 0;
+}
+#endif /* PJ_HAS_TCP */
diff --git a/pjsip/src/test/transport_test.c b/pjsip/src/test/transport_test.c
new file mode 100644
index 0000000..409ec5a
--- /dev/null
+++ b/pjsip/src/test/transport_test.c
@@ -0,0 +1,771 @@
+/* $Id: transport_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "transport_test.c"
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Generic testing for transport, to make sure that basic
+ * attributes have been initialized properly.
+ */
+int generic_transport_test(pjsip_transport *tp)
+{
+ PJ_LOG(3,(THIS_FILE, " structure test..."));
+
+ /* Check that local address name is valid. */
+ {
+ struct pj_in_addr addr;
+
+ /* Note: inet_aton() returns non-zero if addr is valid! */
+ if (pj_inet_aton(&tp->local_name.host, &addr) != 0) {
+ if (addr.s_addr==PJ_INADDR_ANY || addr.s_addr==PJ_INADDR_NONE) {
+ PJ_LOG(3,(THIS_FILE, " Error: invalid address name"));
+ return -420;
+ }
+ } else {
+ /* It's okay. local_name.host may be a hostname instead of
+ * IP address.
+ */
+ }
+ }
+
+ /* Check that port is valid. */
+ if (tp->local_name.port <= 0) {
+ return -430;
+ }
+
+ /* Check length of address (for now we only check against sockaddr_in). */
+ if (tp->addr_len != sizeof(pj_sockaddr_in))
+ return -440;
+
+ /* Check type. */
+ if (tp->key.type == PJSIP_TRANSPORT_UNSPECIFIED)
+ return -450;
+
+ /* That's it. */
+ return PJ_SUCCESS;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Send/receive test.
+ *
+ * This test sends a request to loopback address; as soon as request is
+ * received, response will be sent, and time is recorded.
+ *
+ * The main purpose is to test that the basic transport functionalities works,
+ * before we continue with more complicated tests.
+ */
+#define FROM_HDR "Bob <sip:bob@example.com>"
+#define CONTACT_HDR "Bob <sip:bob@127.0.0.1>"
+#define CALL_ID_HDR "SendRecv-Test"
+#define CSEQ_VALUE 100
+#define BODY "Hello World!"
+
+static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata);
+static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata);
+
+/* Flag to indicate message has been received
+ * (or failed to send)
+ */
+#define NO_STATUS -2
+static int send_status = NO_STATUS;
+static int recv_status = NO_STATUS;
+static pj_timestamp my_send_time, my_recv_time;
+
+/* Module to receive messages for this test. */
+static pjsip_module my_module =
+{
+ NULL, NULL, /* prev and next */
+ { "Transport-Test", 14}, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &my_on_rx_request, /* on_rx_request() */
+ &my_on_rx_response, /* on_rx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+
+static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata)
+{
+ /* Check that this is our request. */
+ if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) {
+ /* It is! */
+ /* Send response. */
+ pjsip_tx_data *tdata;
+ pjsip_response_addr res_addr;
+ pj_status_t status;
+
+ status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ recv_status = status;
+ return PJ_TRUE;
+ }
+ status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr);
+ if (status != PJ_SUCCESS) {
+ recv_status = status;
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_TRUE;
+ }
+ status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ recv_status = status;
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_TRUE;
+ }
+ return PJ_TRUE;
+ }
+
+ /* Not ours. */
+ return PJ_FALSE;
+}
+
+static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata)
+{
+ if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) {
+ pj_get_timestamp(&my_recv_time);
+ recv_status = PJ_SUCCESS;
+ return PJ_TRUE;
+ }
+ return PJ_FALSE;
+}
+
+/* Transport callback. */
+static void send_msg_callback(pjsip_send_state *stateless_data,
+ pj_ssize_t sent, pj_bool_t *cont)
+{
+ PJ_UNUSED_ARG(stateless_data);
+
+ if (sent < 1) {
+ /* Obtain the error code. */
+ send_status = -sent;
+ } else {
+ send_status = PJ_SUCCESS;
+ }
+
+ /* Don't want to continue. */
+ *cont = PJ_FALSE;
+}
+
+
+/* Test that we receive loopback message. */
+int transport_send_recv_test( pjsip_transport_type_e tp_type,
+ pjsip_transport *ref_tp,
+ char *target_url,
+ int *p_usec_rtt)
+{
+ pj_bool_t msg_log_enabled;
+ pj_status_t status;
+ pj_str_t target, from, to, contact, call_id, body;
+ pjsip_method method;
+ pjsip_tx_data *tdata;
+ pj_time_val timeout;
+
+ PJ_UNUSED_ARG(tp_type);
+ PJ_UNUSED_ARG(ref_tp);
+
+ PJ_LOG(3,(THIS_FILE, " single message round-trip test..."));
+
+ /* Register out test module to receive the message (if necessary). */
+ if (my_module.id == -1) {
+ status = pjsip_endpt_register_module( endpt, &my_module );
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to register module", status);
+ return -500;
+ }
+ }
+
+ /* Disable message logging. */
+ msg_log_enabled = msg_logger_set_enabled(0);
+
+ /* Create a request message. */
+ target = pj_str(target_url);
+ from = pj_str(FROM_HDR);
+ to = pj_str(target_url);
+ contact = pj_str(CONTACT_HDR);
+ call_id = pj_str(CALL_ID_HDR);
+ body = pj_str(BODY);
+
+ pjsip_method_set(&method, PJSIP_OPTIONS_METHOD);
+ status = pjsip_endpt_create_request( endpt, &method, &target, &from, &to,
+ &contact, &call_id, CSEQ_VALUE,
+ &body, &tdata );
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create request", status);
+ return -510;
+ }
+
+ /* Reset statuses */
+ send_status = recv_status = NO_STATUS;
+
+ /* Start time. */
+ pj_get_timestamp(&my_send_time);
+
+ /* Send the message (statelessly). */
+ PJ_LOG(5,(THIS_FILE, "Sending request to %.*s",
+ (int)target.slen, target.ptr));
+ status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL,
+ &send_msg_callback);
+ if (status != PJ_SUCCESS) {
+ /* Immediate error! */
+ pjsip_tx_data_dec_ref(tdata);
+ send_status = status;
+ }
+
+ /* Set the timeout (2 seconds from now) */
+ pj_gettimeofday(&timeout);
+ timeout.sec += 2;
+
+ /* Loop handling events until we get status */
+ do {
+ pj_time_val now;
+ pj_time_val poll_interval = { 0, 10 };
+
+ pj_gettimeofday(&now);
+ if (PJ_TIME_VAL_GTE(now, timeout)) {
+ PJ_LOG(3,(THIS_FILE, " error: timeout in send/recv test"));
+ status = -540;
+ goto on_return;
+ }
+
+ if (send_status!=NO_STATUS && send_status!=PJ_SUCCESS) {
+ app_perror(" error sending message", send_status);
+ status = -550;
+ goto on_return;
+ }
+
+ if (recv_status!=NO_STATUS && recv_status!=PJ_SUCCESS) {
+ app_perror(" error receiving message", recv_status);
+ status = -560;
+ goto on_return;
+ }
+
+ if (send_status!=NO_STATUS && recv_status!=NO_STATUS) {
+ /* Success! */
+ break;
+ }
+
+ pjsip_endpt_handle_events(endpt, &poll_interval);
+
+ } while (1);
+
+ if (status == PJ_SUCCESS) {
+ unsigned usec_rt;
+ usec_rt = pj_elapsed_usec(&my_send_time, &my_recv_time);
+
+ PJ_LOG(3,(THIS_FILE, " round-trip = %d usec", usec_rt));
+
+ *p_usec_rtt = usec_rt;
+ }
+
+ /* Restore message logging. */
+ msg_logger_set_enabled(msg_log_enabled);
+
+ status = PJ_SUCCESS;
+
+on_return:
+ return status;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Multithreaded round-trip test
+ *
+ * This test will spawn multiple threads, each of them send a request. As soon
+ * as request is received, response will be sent, and time is recorded.
+ *
+ * The main purpose of this test is to ensure there's no crash when multiple
+ * threads are sending/receiving messages.
+ *
+ */
+static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata);
+static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata);
+
+static pjsip_module rt_module =
+{
+ NULL, NULL, /* prev and next */
+ { "Transport-RT-Test", 17}, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &rt_on_rx_request, /* on_rx_request() */
+ &rt_on_rx_response, /* on_rx_response() */
+ NULL, /* tsx_handler() */
+};
+
+static struct
+{
+ pj_thread_t *thread;
+ pj_timestamp send_time;
+ pj_timestamp total_rt_time;
+ int sent_request_count, recv_response_count;
+ pj_str_t call_id;
+ pj_timer_entry timeout_timer;
+ pj_timer_entry tx_timer;
+ pj_mutex_t *mutex;
+} rt_test_data[16];
+
+static char rt_target_uri[64];
+static pj_bool_t rt_stop;
+static pj_str_t rt_call_id;
+
+static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata)
+{
+ if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) {
+ pjsip_tx_data *tdata;
+ pjsip_response_addr res_addr;
+ pj_status_t status;
+
+ status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error creating response", status);
+ return PJ_TRUE;
+ }
+ status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error in get response address", status);
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_TRUE;
+ }
+ status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error sending response", status);
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_TRUE;
+ }
+ return PJ_TRUE;
+
+ }
+ return PJ_FALSE;
+}
+
+static pj_status_t rt_send_request(int thread_id)
+{
+ pj_status_t status;
+ pj_str_t target, from, to, contact, call_id;
+ pjsip_tx_data *tdata;
+ pj_time_val timeout_delay;
+
+ pj_mutex_lock(rt_test_data[thread_id].mutex);
+
+ /* Create a request message. */
+ target = pj_str(rt_target_uri);
+ from = pj_str(FROM_HDR);
+ to = pj_str(rt_target_uri);
+ contact = pj_str(CONTACT_HDR);
+ call_id = rt_test_data[thread_id].call_id;
+
+ status = pjsip_endpt_create_request( endpt, &pjsip_options_method,
+ &target, &from, &to,
+ &contact, &call_id, -1,
+ NULL, &tdata );
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create request", status);
+ pj_mutex_unlock(rt_test_data[thread_id].mutex);
+ return -610;
+ }
+
+ /* Start time. */
+ pj_get_timestamp(&rt_test_data[thread_id].send_time);
+
+ /* Send the message (statelessly). */
+ status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ /* Immediate error! */
+ app_perror(" error: send request", status);
+ pjsip_tx_data_dec_ref(tdata);
+ pj_mutex_unlock(rt_test_data[thread_id].mutex);
+ return -620;
+ }
+
+ /* Update counter. */
+ rt_test_data[thread_id].sent_request_count++;
+
+ /* Set timeout timer. */
+ if (rt_test_data[thread_id].timeout_timer.user_data != NULL) {
+ pjsip_endpt_cancel_timer(endpt, &rt_test_data[thread_id].timeout_timer);
+ }
+ timeout_delay.sec = 100; timeout_delay.msec = 0;
+ rt_test_data[thread_id].timeout_timer.user_data = (void*)1;
+ pjsip_endpt_schedule_timer(endpt, &rt_test_data[thread_id].timeout_timer,
+ &timeout_delay);
+
+ pj_mutex_unlock(rt_test_data[thread_id].mutex);
+ return PJ_SUCCESS;
+}
+
+static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata)
+{
+ if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) {
+ char *pos = pj_strchr(&rdata->msg_info.cid->id, '/')+1;
+ int thread_id = (*pos - '0');
+ pj_timestamp recv_time;
+
+ pj_mutex_lock(rt_test_data[thread_id].mutex);
+
+ /* Stop timer. */
+ pjsip_endpt_cancel_timer(endpt, &rt_test_data[thread_id].timeout_timer);
+
+ /* Update counter and end-time. */
+ rt_test_data[thread_id].recv_response_count++;
+ pj_get_timestamp(&recv_time);
+
+ pj_sub_timestamp(&recv_time, &rt_test_data[thread_id].send_time);
+ pj_add_timestamp(&rt_test_data[thread_id].total_rt_time, &recv_time);
+
+ if (!rt_stop) {
+ pj_time_val tx_delay = { 0, 0 };
+ pj_assert(rt_test_data[thread_id].tx_timer.user_data == NULL);
+ rt_test_data[thread_id].tx_timer.user_data = (void*)1;
+ pjsip_endpt_schedule_timer(endpt, &rt_test_data[thread_id].tx_timer,
+ &tx_delay);
+ }
+
+ pj_mutex_unlock(rt_test_data[thread_id].mutex);
+
+ return PJ_TRUE;
+ }
+ return PJ_FALSE;
+}
+
+static void rt_timeout_timer( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry )
+{
+ pj_mutex_lock(rt_test_data[entry->id].mutex);
+
+ PJ_UNUSED_ARG(timer_heap);
+ PJ_LOG(3,(THIS_FILE, " timeout waiting for response"));
+ rt_test_data[entry->id].timeout_timer.user_data = NULL;
+
+ if (rt_test_data[entry->id].tx_timer.user_data == NULL) {
+ pj_time_val delay = { 0, 0 };
+ rt_test_data[entry->id].tx_timer.user_data = (void*)1;
+ pjsip_endpt_schedule_timer(endpt, &rt_test_data[entry->id].tx_timer,
+ &delay);
+ }
+
+ pj_mutex_unlock(rt_test_data[entry->id].mutex);
+}
+
+static void rt_tx_timer( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry )
+{
+ pj_mutex_lock(rt_test_data[entry->id].mutex);
+
+ PJ_UNUSED_ARG(timer_heap);
+ pj_assert(rt_test_data[entry->id].tx_timer.user_data != NULL);
+ rt_test_data[entry->id].tx_timer.user_data = NULL;
+ rt_send_request(entry->id);
+
+ pj_mutex_unlock(rt_test_data[entry->id].mutex);
+}
+
+
+static int rt_worker_thread(void *arg)
+{
+ int i;
+ pj_time_val poll_delay = { 0, 10 };
+
+ PJ_UNUSED_ARG(arg);
+
+ /* Sleep to allow main threads to run. */
+ pj_thread_sleep(10);
+
+ while (!rt_stop) {
+ pjsip_endpt_handle_events(endpt, &poll_delay);
+ }
+
+ /* Exhaust responses. */
+ for (i=0; i<100; ++i)
+ pjsip_endpt_handle_events(endpt, &poll_delay);
+
+ return 0;
+}
+
+int transport_rt_test( pjsip_transport_type_e tp_type,
+ pjsip_transport *ref_tp,
+ char *target_url,
+ int *lost)
+{
+ enum { THREADS = 4, INTERVAL = 10 };
+ int i;
+ pj_status_t status;
+ pj_pool_t *pool;
+ pj_bool_t logger_enabled;
+
+ pj_timestamp zero_time, total_time;
+ unsigned usec_rt;
+ unsigned total_sent;
+ unsigned total_recv;
+
+ PJ_UNUSED_ARG(tp_type);
+ PJ_UNUSED_ARG(ref_tp);
+
+ PJ_LOG(3,(THIS_FILE, " multithreaded round-trip test (%d threads)...",
+ THREADS));
+ PJ_LOG(3,(THIS_FILE, " this will take approx %d seconds, please wait..",
+ INTERVAL));
+
+ /* Make sure msg logger is disabled. */
+ logger_enabled = msg_logger_set_enabled(0);
+
+ /* Register module (if not yet registered) */
+ if (rt_module.id == -1) {
+ status = pjsip_endpt_register_module( endpt, &rt_module );
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to register module", status);
+ return -600;
+ }
+ }
+
+ /* Create pool for this test. */
+ pool = pjsip_endpt_create_pool(endpt, NULL, 4000, 4000);
+ if (!pool)
+ return -610;
+
+ /* Initialize static test data. */
+ pj_ansi_strcpy(rt_target_uri, target_url);
+ rt_call_id = pj_str("RT-Call-Id/");
+ rt_stop = PJ_FALSE;
+
+ /* Initialize thread data. */
+ for (i=0; i<THREADS; ++i) {
+ char buf[1];
+ pj_str_t str_id;
+
+ pj_strset(&str_id, buf, 1);
+ pj_bzero(&rt_test_data[i], sizeof(rt_test_data[i]));
+
+ /* Init timer entry */
+ rt_test_data[i].tx_timer.id = i;
+ rt_test_data[i].tx_timer.cb = &rt_tx_timer;
+ rt_test_data[i].timeout_timer.id = i;
+ rt_test_data[i].timeout_timer.cb = &rt_timeout_timer;
+
+ /* Generate Call-ID for each thread. */
+ rt_test_data[i].call_id.ptr = (char*) pj_pool_alloc(pool, rt_call_id.slen+1);
+ pj_strcpy(&rt_test_data[i].call_id, &rt_call_id);
+ buf[0] = '0' + (char)i;
+ pj_strcat(&rt_test_data[i].call_id, &str_id);
+
+ /* Init mutex. */
+ status = pj_mutex_create_recursive(pool, "rt", &rt_test_data[i].mutex);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create mutex", status);
+ return -615;
+ }
+
+ /* Create thread, suspended. */
+ status = pj_thread_create(pool, "rttest%p", &rt_worker_thread, (void*)(long)i, 0,
+ PJ_THREAD_SUSPENDED, &rt_test_data[i].thread);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create thread", status);
+ return -620;
+ }
+ }
+
+ /* Start threads! */
+ for (i=0; i<THREADS; ++i) {
+ pj_time_val delay = {0,0};
+ pj_thread_resume(rt_test_data[i].thread);
+
+ /* Schedule first message transmissions. */
+ rt_test_data[i].tx_timer.user_data = (void*)1;
+ pjsip_endpt_schedule_timer(endpt, &rt_test_data[i].tx_timer, &delay);
+ }
+
+ /* Sleep for some time. */
+ pj_thread_sleep(INTERVAL * 1000);
+
+ /* Signal thread to stop. */
+ rt_stop = PJ_TRUE;
+
+ /* Wait threads to complete. */
+ for (i=0; i<THREADS; ++i) {
+ pj_thread_join(rt_test_data[i].thread);
+ pj_thread_destroy(rt_test_data[i].thread);
+ }
+
+ /* Destroy rt_test_data */
+ for (i=0; i<THREADS; ++i) {
+ pj_mutex_destroy(rt_test_data[i].mutex);
+ pjsip_endpt_cancel_timer(endpt, &rt_test_data[i].timeout_timer);
+ }
+
+ /* Gather statistics. */
+ pj_bzero(&total_time, sizeof(total_time));
+ pj_bzero(&zero_time, sizeof(zero_time));
+ usec_rt = total_sent = total_recv = 0;
+ for (i=0; i<THREADS; ++i) {
+ total_sent += rt_test_data[i].sent_request_count;
+ total_recv += rt_test_data[i].recv_response_count;
+ pj_add_timestamp(&total_time, &rt_test_data[i].total_rt_time);
+ }
+
+ /* Display statistics. */
+ if (total_recv)
+ total_time.u64 = total_time.u64/total_recv;
+ else
+ total_time.u64 = 0;
+ usec_rt = pj_elapsed_usec(&zero_time, &total_time);
+ PJ_LOG(3,(THIS_FILE, " done."));
+ PJ_LOG(3,(THIS_FILE, " total %d messages sent", total_sent));
+ PJ_LOG(3,(THIS_FILE, " average round-trip=%d usec", usec_rt));
+
+ pjsip_endpt_release_pool(endpt, pool);
+
+ *lost = total_sent-total_recv;
+
+ /* Flush events. */
+ flush_events(500);
+
+ /* Restore msg logger. */
+ msg_logger_set_enabled(logger_enabled);
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Transport load testing
+ */
+static pj_bool_t load_on_rx_request(pjsip_rx_data *rdata);
+
+static struct mod_load_test
+{
+ pjsip_module mod;
+ pj_int32_t next_seq;
+ pj_bool_t err;
+} mod_load =
+{
+ {
+ NULL, NULL, /* prev and next */
+ { "mod-load-test", 13}, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &load_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* tsx_handler() */
+ }
+};
+
+
+static pj_bool_t load_on_rx_request(pjsip_rx_data *rdata)
+{
+ if (rdata->msg_info.cseq->cseq != mod_load.next_seq) {
+ PJ_LOG(1,("THIS_FILE", " err: expecting cseq %u, got %u",
+ mod_load.next_seq, rdata->msg_info.cseq->cseq));
+ mod_load.err = PJ_TRUE;
+ mod_load.next_seq = rdata->msg_info.cseq->cseq + 1;
+ } else
+ mod_load.next_seq++;
+ return PJ_TRUE;
+}
+
+int transport_load_test(char *target_url)
+{
+ enum { COUNT = 2000 };
+ unsigned i;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* exhaust packets */
+ do {
+ pj_time_val delay = {1, 0};
+ i = 0;
+ pjsip_endpt_handle_events2(endpt, &delay, &i);
+ } while (i != 0);
+
+ PJ_LOG(3,(THIS_FILE, " transport load test..."));
+
+ if (mod_load.mod.id == -1) {
+ status = pjsip_endpt_register_module( endpt, &mod_load.mod);
+ if (status != PJ_SUCCESS) {
+ app_perror("error registering module", status);
+ return -1;
+ }
+ }
+ mod_load.err = PJ_FALSE;
+ mod_load.next_seq = 0;
+
+ for (i=0; i<COUNT && !mod_load.err; ++i) {
+ pj_str_t target, from, call_id;
+ pjsip_tx_data *tdata;
+
+ target = pj_str(target_url);
+ from = pj_str("<sip:user@host>");
+ call_id = pj_str("thecallid");
+ status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
+ &target, &from,
+ &target, &from, &call_id,
+ i, NULL, &tdata );
+ if (status != PJ_SUCCESS) {
+ app_perror("error creating request", status);
+ goto on_return;
+ }
+
+ status = pjsip_endpt_send_request_stateless(endpt, tdata, NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror("error sending request", status);
+ goto on_return;
+ }
+ }
+
+ do {
+ pj_time_val delay = {1, 0};
+ i = 0;
+ pjsip_endpt_handle_events2(endpt, &delay, &i);
+ } while (i != 0);
+
+ if (mod_load.next_seq != COUNT) {
+ PJ_LOG(1,("THIS_FILE", " err: expecting %u msg, got only %u",
+ COUNT, mod_load.next_seq));
+ status = -2;
+ goto on_return;
+ }
+
+on_return:
+ if (mod_load.mod.id != -1) {
+ pjsip_endpt_unregister_module( endpt, &mod_load.mod);
+ mod_load.mod.id = -1;
+ }
+ if (status != PJ_SUCCESS || mod_load.err) {
+ return -2;
+ }
+ PJ_LOG(3,(THIS_FILE, " success"));
+ return 0;
+}
+
+
diff --git a/pjsip/src/test/transport_udp_test.c b/pjsip/src/test/transport_udp_test.c
new file mode 100644
index 0000000..3c92632
--- /dev/null
+++ b/pjsip/src/test/transport_udp_test.c
@@ -0,0 +1,128 @@
+/* $Id: transport_udp_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "transport_udp_test.c"
+
+
+/*
+ * UDP transport test.
+ */
+int transport_udp_test(void)
+{
+ enum { SEND_RECV_LOOP = 8 };
+ pjsip_transport *udp_tp, *tp;
+ pj_sockaddr_in addr, rem_addr;
+ pj_str_t s;
+ pj_status_t status;
+ int rtt[SEND_RECV_LOOP], min_rtt;
+ int i, pkt_lost;
+
+ pj_sockaddr_in_init(&addr, NULL, TEST_UDP_PORT);
+
+ /* Start UDP transport. */
+ status = pjsip_udp_transport_start( endpt, &addr, NULL, 1, &udp_tp);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to start UDP transport", status);
+ return -10;
+ }
+
+ /* UDP transport must have initial reference counter set to 1. */
+ if (pj_atomic_get(udp_tp->ref_cnt) != 1)
+ return -20;
+
+ /* Test basic transport attributes */
+ status = generic_transport_test(udp_tp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Test that transport manager is returning the correct
+ * transport.
+ */
+ pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "1.1.1.1"), 80);
+ status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP,
+ &rem_addr, sizeof(rem_addr),
+ NULL, &tp);
+ if (status != PJ_SUCCESS)
+ return -50;
+ if (tp != udp_tp)
+ return -60;
+
+ /* pjsip_endpt_acquire_transport() adds reference, so we need
+ * to decrement it.
+ */
+ pjsip_transport_dec_ref(tp);
+
+ /* Check again that reference counter is 1. */
+ if (pj_atomic_get(udp_tp->ref_cnt) != 1)
+ return -70;
+
+ /* Basic transport's send/receive loopback test. */
+ pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "127.0.0.1"), TEST_UDP_PORT);
+ for (i=0; i<SEND_RECV_LOOP; ++i) {
+ status = transport_send_recv_test(PJSIP_TRANSPORT_UDP, tp,
+ "sip:alice@127.0.0.1:"TEST_UDP_PORT_STR,
+ &rtt[i]);
+ if (status != 0)
+ return status;
+ }
+
+ min_rtt = 0xFFFFFFF;
+ for (i=0; i<SEND_RECV_LOOP; ++i)
+ if (rtt[i] < min_rtt) min_rtt = rtt[i];
+
+ report_ival("udp-rtt-usec", min_rtt, "usec",
+ "Best UDP transport round trip time, in microseconds "
+ "(time from sending request until response is received. "
+ "Tests were performed on local machine only)");
+
+
+ /* Multi-threaded round-trip test. */
+ status = transport_rt_test(PJSIP_TRANSPORT_UDP, tp,
+ "sip:alice@127.0.0.1:"TEST_UDP_PORT_STR,
+ &pkt_lost);
+ if (status != 0)
+ return status;
+
+ if (pkt_lost != 0)
+ PJ_LOG(3,(THIS_FILE, " note: %d packet(s) was lost", pkt_lost));
+
+ /* Check again that reference counter is 1. */
+ if (pj_atomic_get(udp_tp->ref_cnt) != 1)
+ return -80;
+
+ /* Destroy this transport. */
+ pjsip_transport_dec_ref(udp_tp);
+
+ /* Force destroy this transport. */
+ status = pjsip_transport_destroy(udp_tp);
+ if (status != PJ_SUCCESS)
+ return -90;
+
+ /* Flush events. */
+ PJ_LOG(3,(THIS_FILE, " Flushing events, 1 second..."));
+ flush_events(1000);
+
+ /* Done */
+ return 0;
+}
diff --git a/pjsip/src/test/tsx_basic_test.c b/pjsip/src/test/tsx_basic_test.c
new file mode 100644
index 0000000..20b93a0
--- /dev/null
+++ b/pjsip/src/test/tsx_basic_test.c
@@ -0,0 +1,157 @@
+/* $Id: tsx_basic_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "tsx_basic_test.c"
+
+static char TARGET_URI[PJSIP_MAX_URL_SIZE];
+static char FROM_URI[PJSIP_MAX_URL_SIZE];
+
+
+/* Test transaction layer. */
+static int tsx_layer_test(void)
+{
+ pj_str_t target, from, tsx_key;
+ pjsip_tx_data *tdata;
+ pjsip_transaction *tsx, *found;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " transaction layer test"));
+
+ target = pj_str(TARGET_URI);
+ from = pj_str(FROM_URI);
+
+ status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target,
+ &from, &target, NULL, NULL, -1, NULL,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create request", status);
+ return -110;
+ }
+
+ status = pjsip_tsx_create_uac(NULL, tdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create transaction", status);
+ return -120;
+ }
+
+ pj_strdup(tdata->pool, &tsx_key, &tsx->transaction_key);
+
+ found = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE);
+ if (found != tsx) {
+ return -130;
+ }
+
+ pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
+ flush_events(500);
+
+ if (pjsip_tx_data_dec_ref(tdata) != PJSIP_EBUFDESTROYED) {
+ return -140;
+ }
+
+ return 0;
+}
+
+/* Double terminate test. */
+static int double_terminate(void)
+{
+ pj_str_t target, from, tsx_key;
+ pjsip_tx_data *tdata;
+ pjsip_transaction *tsx;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " double terminate test"));
+
+ target = pj_str(TARGET_URI);
+ from = pj_str(FROM_URI);
+
+ /* Create request. */
+ status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target,
+ &from, &target, NULL, NULL, -1, NULL,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create request", status);
+ return -10;
+ }
+
+ /* Create transaction. */
+ status = pjsip_tsx_create_uac(NULL, tdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create transaction", status);
+ return -20;
+ }
+
+ /* Save transaction key for later. */
+ pj_strdup_with_null(tdata->pool, &tsx_key, &tsx->transaction_key);
+
+ /* Add reference to transmit buffer (tsx_send_msg() will dec txdata). */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Send message to start timeout timer. */
+ status = pjsip_tsx_send_msg(tsx, NULL);
+
+ /* Terminate transaction. */
+ status = pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to terminate transaction", status);
+ return -30;
+ }
+
+ tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE);
+ if (tsx) {
+ /* Terminate transaction again. */
+ pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to terminate transaction", status);
+ return -40;
+ }
+ pj_mutex_unlock(tsx->mutex);
+ }
+
+ flush_events(500);
+ if (pjsip_tx_data_dec_ref(tdata) != PJSIP_EBUFDESTROYED) {
+ return -50;
+ }
+
+ return PJ_SUCCESS;
+}
+
+int tsx_basic_test(struct tsx_test_param *param)
+{
+ int status;
+
+ pj_ansi_sprintf(TARGET_URI, "sip:bob@127.0.0.1:%d;transport=%s",
+ param->port, param->tp_type);
+ pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s",
+ param->port, param->tp_type);
+
+ status = tsx_layer_test();
+ if (status != 0)
+ return status;
+
+ status = double_terminate();
+ if (status != 0)
+ return status;
+
+ return 0;
+}
diff --git a/pjsip/src/test/tsx_bench.c b/pjsip/src/test/tsx_bench.c
new file mode 100644
index 0000000..834f444
--- /dev/null
+++ b/pjsip/src/test/tsx_bench.c
@@ -0,0 +1,280 @@
+/* $Id: tsx_bench.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "tsx_uas_test.c"
+
+
+static pjsip_module mod_tsx_user;
+
+static int uac_tsx_bench(unsigned working_set, pj_timestamp *p_elapsed)
+{
+ unsigned i;
+ pjsip_tx_data *request;
+ pjsip_transaction **tsx;
+ pj_timestamp t1, t2, elapsed;
+ pjsip_via_hdr *via;
+ pj_status_t status;
+
+ /* Create the request first. */
+ pj_str_t str_target = pj_str("sip:someuser@someprovider.com");
+ pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>");
+ pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>");
+ pj_str_t str_contact = str_from;
+
+ status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
+ &str_target, &str_from, &str_to,
+ &str_contact, NULL, -1, NULL,
+ &request);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create request", status);
+ return status;
+ }
+
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_VIA,
+ NULL);
+
+ /* Create transaction array */
+ tsx = (pjsip_transaction**) pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*));
+
+ pj_bzero(&mod_tsx_user, sizeof(mod_tsx_user));
+ mod_tsx_user.id = -1;
+
+ /* Benchmark */
+ elapsed.u64 = 0;
+ pj_get_timestamp(&t1);
+ for (i=0; i<working_set; ++i) {
+ status = pjsip_tsx_create_uac(&mod_tsx_user, request, &tsx[i]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ /* Reset branch param */
+ via->branch_param.slen = 0;
+ }
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&elapsed, &t2);
+
+ p_elapsed->u64 = elapsed.u64;
+ status = PJ_SUCCESS;
+
+on_error:
+ for (i=0; i<working_set; ++i) {
+ if (tsx[i]) {
+ pjsip_tsx_terminate(tsx[i], 601);
+ tsx[i] = NULL;
+ }
+ }
+ pjsip_tx_data_dec_ref(request);
+ flush_events(2000);
+ return status;
+}
+
+
+
+static int uas_tsx_bench(unsigned working_set, pj_timestamp *p_elapsed)
+{
+ unsigned i;
+ pjsip_tx_data *request;
+ pjsip_via_hdr *via;
+ pjsip_rx_data rdata;
+ pj_sockaddr_in remote;
+ pjsip_transaction **tsx;
+ pj_timestamp t1, t2, elapsed;
+ char branch_buf[80] = PJSIP_RFC3261_BRANCH_ID "0000000000";
+ pj_status_t status;
+
+ /* Create the request first. */
+ pj_str_t str_target = pj_str("sip:someuser@someprovider.com");
+ pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>");
+ pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>");
+ pj_str_t str_contact = str_from;
+
+ status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
+ &str_target, &str_from, &str_to,
+ &str_contact, NULL, -1, NULL,
+ &request);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create request", status);
+ return status;
+ }
+
+ /* Create Via */
+ via = pjsip_via_hdr_create(request->pool);
+ via->sent_by.host = pj_str("192.168.0.7");
+ via->sent_by.port = 5061;
+ via->transport = pj_str("udp");
+ via->rport_param = 1;
+ via->recvd_param = pj_str("192.168.0.7");
+ pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*)via);
+
+
+ /* Create "dummy" rdata from the tdata */
+ pj_bzero(&rdata, sizeof(pjsip_rx_data));
+ rdata.tp_info.pool = request->pool;
+ rdata.msg_info.msg = request->msg;
+ rdata.msg_info.from = (pjsip_from_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL);
+ rdata.msg_info.to = (pjsip_to_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL);
+ rdata.msg_info.cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL);
+ rdata.msg_info.cid = (pjsip_cid_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL);
+ rdata.msg_info.via = via;
+
+ pj_sockaddr_in_init(&remote, 0, 0);
+ status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM,
+ &remote, sizeof(pj_sockaddr_in),
+ NULL, &rdata.tp_info.transport);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to get loop transport", status);
+ return status;
+ }
+
+
+ /* Create transaction array */
+ tsx = (pjsip_transaction**) pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*));
+
+ pj_bzero(&mod_tsx_user, sizeof(mod_tsx_user));
+ mod_tsx_user.id = -1;
+
+
+ /* Benchmark */
+ elapsed.u64 = 0;
+ pj_get_timestamp(&t1);
+ for (i=0; i<working_set; ++i) {
+ via->branch_param.ptr = branch_buf;
+ via->branch_param.slen = PJSIP_RFC3261_BRANCH_LEN +
+ pj_ansi_sprintf(branch_buf+PJSIP_RFC3261_BRANCH_LEN,
+ "-%d", i);
+ status = pjsip_tsx_create_uas(&mod_tsx_user, &rdata, &tsx[i]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ }
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&elapsed, &t2);
+
+ p_elapsed->u64 = elapsed.u64;
+ status = PJ_SUCCESS;
+
+on_error:
+ for (i=0; i<working_set; ++i) {
+ if (tsx[i]) {
+ pjsip_tsx_terminate(tsx[i], 601);
+ tsx[i] = NULL;
+ }
+ }
+ pjsip_tx_data_dec_ref(request);
+ flush_events(2000);
+ return status;
+}
+
+
+
+int tsx_bench(void)
+{
+ enum { WORKING_SET=10000, REPEAT = 4 };
+ unsigned i, speed;
+ pj_timestamp usec[REPEAT], min, freq;
+ char desc[250];
+ int status;
+
+ status = pj_get_timestamp_freq(&freq);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /*
+ * Benchmark UAC
+ */
+ PJ_LOG(3,(THIS_FILE, " benchmarking UAC transaction creation:"));
+ for (i=0; i<REPEAT; ++i) {
+ PJ_LOG(3,(THIS_FILE, " test %d of %d..",
+ i+1, REPEAT));
+ status = uac_tsx_bench(WORKING_SET, &usec[i]);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF);
+ for (i=0; i<REPEAT; ++i) {
+ if (usec[i].u64 < min.u64) min.u64 = usec[i].u64;
+ }
+
+
+ /* Report time */
+ pj_ansi_sprintf(desc, "Time to create %d UAC transactions, in miliseconds",
+ WORKING_SET);
+ report_ival("create-uac-time", (unsigned)(min.u64 * 1000 / freq.u64), "msec", desc);
+
+
+ /* Write speed */
+ speed = (unsigned)(freq.u64 * WORKING_SET / min.u64);
+ PJ_LOG(3,(THIS_FILE, " UAC created at %d tsx/sec", speed));
+
+ pj_ansi_sprintf(desc, "Number of UAC transactions that potentially can be created per second "
+ "with <tt>pjsip_tsx_create_uac()</tt>, based on the time "
+ "to create %d simultaneous transactions above.",
+ WORKING_SET);
+
+ report_ival("create-uac-tsx-per-sec",
+ speed, "tsx/sec", desc);
+
+
+
+ /*
+ * Benchmark UAS
+ */
+ PJ_LOG(3,(THIS_FILE, " benchmarking UAS transaction creation:"));
+ for (i=0; i<REPEAT; ++i) {
+ PJ_LOG(3,(THIS_FILE, " test %d of %d..",
+ i+1, REPEAT));
+ status = uas_tsx_bench(WORKING_SET, &usec[i]);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF);
+ for (i=0; i<REPEAT; ++i) {
+ if (usec[i].u64 < min.u64) min.u64 = usec[i].u64;
+ }
+
+
+ /* Report time */
+ pj_ansi_sprintf(desc, "Time to create %d UAS transactions, in miliseconds",
+ WORKING_SET);
+ report_ival("create-uas-time", (unsigned)(min.u64 * 1000 / freq.u64), "msec", desc);
+
+
+ /* Write speed */
+ speed = (unsigned)(freq.u64 * WORKING_SET / min.u64);
+ PJ_LOG(3,(THIS_FILE, " UAS created at %d tsx/sec", speed));
+
+ pj_ansi_sprintf(desc, "Number of UAS transactions that potentially can be created per second "
+ "with <tt>pjsip_tsx_create_uas()</tt>, based on the time "
+ "to create %d simultaneous transactions above.",
+ WORKING_SET);
+
+ report_ival("create-uas-tsx-per-sec",
+ speed, "tsx/sec", desc);
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/test/tsx_uac_test.c b/pjsip/src/test/tsx_uac_test.c
new file mode 100644
index 0000000..c8f3074
--- /dev/null
+++ b/pjsip/src/test/tsx_uac_test.c
@@ -0,0 +1,1456 @@
+/* $Id: tsx_uac_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "tsx_uac_test.c"
+
+
+/*****************************************************************************
+ **
+ ** UAC tests.
+ **
+ ** This file performs various tests for UAC transactions. Each test will have
+ ** a different Via branch param so that message receiver module and
+ ** transaction user module can identify which test is being carried out.
+ **
+ ** TEST1_BRANCH_ID
+ ** Perform basic retransmission and timeout test. Message receiver will
+ ** verify that retransmission is received at correct time.
+ ** This test verifies the following requirements:
+ ** - retransmit timer doubles for INVITE
+ ** - retransmit timer doubles and caps off for non-INVITE
+ ** - retransmit timer timer is precise
+ ** - correct timeout and retransmission count
+ ** Requirements not tested:
+ ** - retransmit timer only starts after resolving has completed.
+ **
+ ** TEST2_BRANCH_ID
+ ** Test scenario where resolver is unable to resolve destination host.
+ **
+ ** TEST3_BRANCH_ID
+ ** Test scenario where transaction is terminated while resolver is still
+ ** running.
+ **
+ ** TEST4_BRANCH_ID
+ ** Test scenario where transport failed after several retransmissions.
+ **
+ ** TEST5_BRANCH_ID
+ ** Test scenario where transaction is terminated by user after several
+ ** retransmissions.
+ **
+ ** TEST6_BRANCH_ID
+ ** Test successfull non-INVITE transaction.
+ ** It tests the following requirements:
+ ** - transaction correctly moves to COMPLETED state.
+ ** - retransmission must cease.
+ ** - tx_data must be maintained until state is terminated.
+ **
+ ** TEST7_BRANCH_ID
+ ** Test successfull non-INVITE transaction, with provisional response.
+ **
+ ** TEST8_BRANCH_ID
+ ** Test failed INVITE transaction (e.g. ACK must be received)
+ **
+ ** TEST9_BRANCH_ID
+ ** Test failed INVITE transaction with provisional response.
+ **
+ **
+ *****************************************************************************
+ */
+
+static char *TEST1_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test1";
+static char *TEST2_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test2";
+static char *TEST3_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test3";
+static char *TEST4_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test4";
+static char *TEST5_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test5";
+static char *TEST6_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test6";
+static char *TEST7_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test7";
+static char *TEST8_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test8";
+static char *TEST9_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test9";
+
+#define TEST1_ALLOWED_DIFF (150)
+#define TEST4_RETRANSMIT_CNT 3
+#define TEST5_RETRANSMIT_CNT 3
+
+static char TARGET_URI[128];
+static char FROM_URI[128];
+static unsigned tp_flag;
+static struct tsx_test_param *test_param;
+
+static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e);
+static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata);
+
+/* UAC transaction user module. */
+static pjsip_module tsx_user =
+{
+ NULL, NULL, /* prev and next */
+ { "Tsx-UAC-User", 12}, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ NULL, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request() */
+ NULL, /* on_tx_response() */
+ &tsx_user_on_tsx_state, /* on_tsx_state() */
+};
+
+/* Module to receive the loop-backed request. */
+static pjsip_module msg_receiver =
+{
+ NULL, NULL, /* prev and next */
+ { "Msg-Receiver", 12}, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &msg_receiver_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request() */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+/* Static vars, which will be reset on each test. */
+static int recv_count;
+static pj_time_val recv_last;
+static pj_bool_t test_complete;
+
+/* Loop transport instance. */
+static pjsip_transport *loop;
+
+/* General timer entry to be used by tests. */
+static struct my_timer
+{
+ pj_timer_entry entry;
+ char key_buf[1024];
+ pj_str_t tsx_key;
+} timer;
+
+/*
+ * This is the handler to receive state changed notification from the
+ * transaction. It is used to verify that the transaction behaves according
+ * to the test scenario.
+ */
+static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e)
+{
+ if (pj_strcmp2(&tsx->branch, TEST1_BRANCH_ID)==0) {
+ /*
+ * Transaction with TEST1_BRANCH_ID should terminate with transaction
+ * timeout status.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ if (test_complete == 0)
+ test_complete = 1;
+
+ /* Test the status code. */
+ if (tsx->status_code != PJSIP_SC_TSX_TIMEOUT) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, PJSIP_SC_TSX_TIMEOUT));
+ test_complete = -710;
+ }
+
+
+ /* If transport is reliable, then there must not be any
+ * retransmissions.
+ */
+ if (tp_flag & PJSIP_TRANSPORT_RELIABLE) {
+ if (recv_count != 1) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: there were %d (re)transmissions",
+ recv_count));
+ test_complete = -715;
+ }
+ } else {
+ /* Check the number of transmissions, which must be
+ * 6 for INVITE and 10 for non-INVITE
+ */
+ if (tsx->method.id==PJSIP_INVITE_METHOD && recv_count != 7) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: there were %d (re)transmissions",
+ recv_count));
+ test_complete = -716;
+ } else
+ if (tsx->method.id==PJSIP_OPTIONS_METHOD && recv_count != 11) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: there were %d (re)transmissions",
+ recv_count));
+ test_complete = -717;
+ } else
+ if (tsx->method.id!=PJSIP_INVITE_METHOD &&
+ tsx->method.id!=PJSIP_OPTIONS_METHOD)
+ {
+ PJ_LOG(3,(THIS_FILE, " error: unexpected method"));
+ test_complete = -718;
+ }
+ }
+ }
+
+ } else if (pj_strcmp2(&tsx->branch, TEST2_BRANCH_ID)==0) {
+ /*
+ * Transaction with TEST2_BRANCH_ID should terminate with transport error.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ /* Test the status code. */
+ if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, PJSIP_SC_TSX_TRANSPORT_ERROR));
+ test_complete = -720;
+ }
+
+ if (test_complete == 0)
+ test_complete = 1;
+ }
+
+ } else if (pj_strcmp2(&tsx->branch, TEST3_BRANCH_ID)==0) {
+ /*
+ * This test terminates the transaction while resolver is still
+ * running.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_CALLING) {
+
+ /* Terminate the transaction. */
+ pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
+
+ } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ /* Check if status code is correct. */
+ if (tsx->status_code != PJSIP_SC_REQUEST_TERMINATED) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, PJSIP_SC_REQUEST_TERMINATED));
+ test_complete = -730;
+ }
+
+ if (test_complete == 0)
+ test_complete = 1;
+
+ }
+
+ } else if (pj_strcmp2(&tsx->branch, TEST4_BRANCH_ID)==0) {
+ /*
+ * This test simulates transport failure after several
+ * retransmissions.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ /* Status code must be transport error. */
+ if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, PJSIP_SC_TSX_TRANSPORT_ERROR));
+ test_complete = -730;
+ }
+
+ /* Must have correct retransmission count. */
+ if (tsx->retransmit_count != TEST4_RETRANSMIT_CNT) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: retransmit cnt is %d instead of %d",
+ tsx->retransmit_count, TEST4_RETRANSMIT_CNT));
+ test_complete = -731;
+ }
+
+ if (test_complete == 0)
+ test_complete = 1;
+ }
+
+
+ } else if (pj_strcmp2(&tsx->branch, TEST5_BRANCH_ID)==0) {
+ /*
+ * This test simulates transport failure after several
+ * retransmissions.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ /* Status code must be PJSIP_SC_REQUEST_TERMINATED. */
+ if (tsx->status_code != PJSIP_SC_REQUEST_TERMINATED) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, PJSIP_SC_REQUEST_TERMINATED));
+ test_complete = -733;
+ }
+
+ /* Must have correct retransmission count. */
+ if (tsx->retransmit_count != TEST5_RETRANSMIT_CNT) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: retransmit cnt is %d instead of %d",
+ tsx->retransmit_count, TEST5_RETRANSMIT_CNT));
+ test_complete = -734;
+ }
+
+ if (test_complete == 0)
+ test_complete = 1;
+ }
+
+
+ } else if (pj_strcmp2(&tsx->branch, TEST6_BRANCH_ID)==0) {
+ /*
+ * Successfull non-INVITE transaction.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+
+ /* Status code must be 202. */
+ if (tsx->status_code != 202) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, 202));
+ test_complete = -736;
+ }
+
+ /* Must have correct retransmission count. */
+ if (tsx->retransmit_count != 0) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: retransmit cnt is %d instead of %d",
+ tsx->retransmit_count, 0));
+ test_complete = -737;
+ }
+
+ /* Must still keep last_tx */
+ if (tsx->last_tx == NULL) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: transaction lost last_tx"));
+ test_complete = -738;
+ }
+
+ if (test_complete == 0) {
+ test_complete = 1;
+ pjsip_tsx_terminate(tsx, 202);
+ }
+
+ } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ /* Previous state must be COMPLETED. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
+ test_complete = -7381;
+ }
+
+ }
+
+ } else if (pj_strcmp2(&tsx->branch, TEST7_BRANCH_ID)==0) {
+ /*
+ * Successfull non-INVITE transaction.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+
+ /* Check prev state. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: prev state is %s instead of %s",
+ pjsip_tsx_state_str((pjsip_tsx_state_e)e->body.tsx_state.prev_state),
+ pjsip_tsx_state_str(PJSIP_TSX_STATE_PROCEEDING)));
+ test_complete = -739;
+ }
+
+ /* Status code must be 202. */
+ if (tsx->status_code != 202) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, 202));
+ test_complete = -740;
+ }
+
+ /* Must have correct retransmission count. */
+ if (tsx->retransmit_count != 0) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: retransmit cnt is %d instead of %d",
+ tsx->retransmit_count, 0));
+ test_complete = -741;
+ }
+
+ /* Must still keep last_tx */
+ if (tsx->last_tx == NULL) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: transaction lost last_tx"));
+ test_complete = -741;
+ }
+
+ if (test_complete == 0) {
+ test_complete = 1;
+ pjsip_tsx_terminate(tsx, 202);
+ }
+
+ } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ /* Previous state must be COMPLETED. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
+ test_complete = -742;
+ }
+
+ }
+
+
+ } else if (pj_strcmp2(&tsx->branch, TEST8_BRANCH_ID)==0) {
+ /*
+ * Failed INVITE transaction.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+
+ /* Status code must be 301. */
+ if (tsx->status_code != 301) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, 301));
+ test_complete = -745;
+ }
+
+ /* Must have correct retransmission count. */
+ if (tsx->retransmit_count != 0) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: retransmit cnt is %d instead of %d",
+ tsx->retransmit_count, 0));
+ test_complete = -746;
+ }
+
+ /* Must still keep last_tx */
+ if (tsx->last_tx == NULL) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: transaction lost last_tx"));
+ test_complete = -747;
+ }
+
+ /* last_tx MUST be the INVITE request
+ * (authorization depends on this behavior)
+ */
+ if (tsx->last_tx && tsx->last_tx->msg->line.req.method.id !=
+ PJSIP_INVITE_METHOD)
+ {
+ PJ_LOG(3,(THIS_FILE,
+ " error: last_tx is not INVITE"));
+ test_complete = -748;
+ }
+ }
+ else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ test_complete = 1;
+
+ /* Previous state must be COMPLETED. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
+ test_complete = -750;
+ }
+
+ /* Status code must be 301. */
+ if (tsx->status_code != 301) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, 301));
+ test_complete = -751;
+ }
+
+ }
+
+
+ } else if (pj_strcmp2(&tsx->branch, TEST9_BRANCH_ID)==0) {
+ /*
+ * Failed INVITE transaction with provisional response.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+
+ /* Previous state must be PJSIP_TSX_STATE_PROCEEDING. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) {
+ test_complete = -760;
+ }
+
+ /* Status code must be 302. */
+ if (tsx->status_code != 302) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, 302));
+ test_complete = -761;
+ }
+
+ /* Must have correct retransmission count. */
+ if (tsx->retransmit_count != 0) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: retransmit cnt is %d instead of %d",
+ tsx->retransmit_count, 0));
+ test_complete = -762;
+ }
+
+ /* Must still keep last_tx */
+ if (tsx->last_tx == NULL) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: transaction lost last_tx"));
+ test_complete = -763;
+ }
+
+ /* last_tx MUST be INVITE.
+ * (authorization depends on this behavior)
+ */
+ if (tsx->last_tx && tsx->last_tx->msg->line.req.method.id !=
+ PJSIP_INVITE_METHOD)
+ {
+ PJ_LOG(3,(THIS_FILE,
+ " error: last_tx is not INVITE"));
+ test_complete = -764;
+ }
+
+ }
+ else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ test_complete = 1;
+
+ /* Previous state must be COMPLETED. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
+ test_complete = -767;
+ }
+
+ /* Status code must be 302. */
+ if (tsx->status_code != 302) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: status code is %d instead of %d",
+ tsx->status_code, 302));
+ test_complete = -768;
+ }
+
+ }
+
+ }
+}
+
+/*
+ * This timer callback is called to send delayed response.
+ */
+struct response
+{
+ pjsip_response_addr res_addr;
+ pjsip_tx_data *tdata;
+};
+
+static void send_response_callback( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ struct response *r = (struct response*) entry->user_data;
+ pjsip_transport *tp = r->res_addr.transport;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ pjsip_endpt_send_response(endpt, &r->res_addr, r->tdata, NULL, NULL);
+ if (tp)
+ pjsip_transport_dec_ref(tp);
+}
+
+/* Timer callback to terminate a transaction. */
+static void terminate_tsx_callback( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ struct my_timer *m = (struct my_timer *)entry;
+ pjsip_transaction *tsx = pjsip_tsx_layer_find_tsx(&m->tsx_key, PJ_FALSE);
+ int status_code = entry->id;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ if (tsx) {
+ pjsip_tsx_terminate(tsx, status_code);
+ }
+}
+
+
+#define DIFF(a,b) ((a<b) ? (b-a) : (a-b))
+
+/*
+ * This is the handler to receive message for this test. It is used to
+ * control and verify the behavior of the message transmitted by the
+ * transaction.
+ */
+static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata)
+{
+ if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST1_BRANCH_ID) == 0) {
+ /*
+ * The TEST1_BRANCH_ID test performs the verifications for transaction
+ * retransmission mechanism. It will not answer the incoming request
+ * with any response.
+ */
+ pjsip_msg *msg = rdata->msg_info.msg;
+
+ PJ_LOG(4,(THIS_FILE, " received request"));
+
+ /* Only wants to take INVITE or OPTIONS method. */
+ if (msg->line.req.method.id != PJSIP_INVITE_METHOD &&
+ msg->line.req.method.id != PJSIP_OPTIONS_METHOD)
+ {
+ PJ_LOG(3,(THIS_FILE, " error: received unexpected method %.*s",
+ msg->line.req.method.name.slen,
+ msg->line.req.method.name.ptr));
+ test_complete = -600;
+ return PJ_TRUE;
+ }
+
+ if (recv_count == 0) {
+ recv_count++;
+ //pj_gettimeofday(&recv_last);
+ recv_last = rdata->pkt_info.timestamp;
+ } else {
+ pj_time_val now;
+ unsigned msec_expected, msec_elapsed;
+ int max_received;
+
+ //pj_gettimeofday(&now);
+ now = rdata->pkt_info.timestamp;
+ PJ_TIME_VAL_SUB(now, recv_last);
+ msec_elapsed = now.sec*1000 + now.msec;
+
+ ++recv_count;
+ msec_expected = (1<<(recv_count-2))*pjsip_cfg()->tsx.t1;
+
+ if (msg->line.req.method.id != PJSIP_INVITE_METHOD) {
+ if (msec_expected > pjsip_cfg()->tsx.t2)
+ msec_expected = pjsip_cfg()->tsx.t2;
+ max_received = 11;
+ } else {
+ max_received = 7;
+ }
+
+ if (DIFF(msec_expected, msec_elapsed) > TEST1_ALLOWED_DIFF) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: expecting retransmission no. %d in %d "
+ "ms, received in %d ms",
+ recv_count-1, msec_expected, msec_elapsed));
+ test_complete = -610;
+ }
+
+
+ if (recv_count > max_received) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: too many messages (%d) received",
+ recv_count));
+ test_complete = -620;
+ }
+
+ //pj_gettimeofday(&recv_last);
+ recv_last = rdata->pkt_info.timestamp;
+ }
+ return PJ_TRUE;
+
+ } else
+ if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST4_BRANCH_ID) == 0) {
+ /*
+ * The TEST4_BRANCH_ID test simulates transport failure after several
+ * retransmissions.
+ */
+ recv_count++;
+
+ if (recv_count == TEST4_RETRANSMIT_CNT) {
+ /* Simulate transport failure. */
+ pjsip_loop_set_failure(loop, 2, NULL);
+
+ } else if (recv_count > TEST4_RETRANSMIT_CNT) {
+ PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!",
+ recv_count));
+ test_complete = -631;
+ }
+
+ return PJ_TRUE;
+
+
+ } else
+ if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST5_BRANCH_ID) == 0) {
+ /*
+ * The TEST5_BRANCH_ID test simulates user terminating the transaction
+ * after several retransmissions.
+ */
+ recv_count++;
+
+ if (recv_count == TEST5_RETRANSMIT_CNT+1) {
+ pj_str_t key;
+ pjsip_transaction *tsx;
+
+ pjsip_tsx_create_key( rdata->tp_info.pool, &key, PJSIP_ROLE_UAC,
+ &rdata->msg_info.msg->line.req.method, rdata);
+ tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE);
+ if (tsx) {
+ pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
+ pj_mutex_unlock(tsx->mutex);
+ } else {
+ PJ_LOG(3,(THIS_FILE, " error: uac transaction not found!"));
+ test_complete = -633;
+ }
+
+ } else if (recv_count > TEST5_RETRANSMIT_CNT+1) {
+ PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!",
+ recv_count));
+ test_complete = -634;
+ }
+
+ return PJ_TRUE;
+
+ } else
+ if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST6_BRANCH_ID) == 0) {
+ /*
+ * The TEST6_BRANCH_ID test successfull non-INVITE transaction.
+ */
+ pj_status_t status;
+
+ recv_count++;
+
+ if (recv_count > 1) {
+ PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!",
+ recv_count));
+ test_complete = -635;
+ }
+
+ status = pjsip_endpt_respond_stateless(endpt, rdata, 202, NULL,
+ NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to send response", status);
+ test_complete = -636;
+ }
+
+ return PJ_TRUE;
+
+
+ } else
+ if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST7_BRANCH_ID) == 0) {
+ /*
+ * The TEST7_BRANCH_ID test successfull non-INVITE transaction
+ * with provisional response.
+ */
+ pj_status_t status;
+ pjsip_response_addr res_addr;
+ struct response *r;
+ pjsip_tx_data *tdata;
+ pj_time_val delay = { 2, 0 };
+
+ recv_count++;
+
+ if (recv_count > 1) {
+ PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!",
+ recv_count));
+ test_complete = -640;
+ return PJ_TRUE;
+ }
+
+ /* Respond with provisional response */
+ status = pjsip_endpt_create_response(endpt, rdata, 100, NULL, &tdata);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjsip_endpt_send_response(endpt, &res_addr, tdata,
+ NULL, NULL);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Create the final response. */
+ status = pjsip_endpt_create_response(endpt, rdata, 202, NULL, &tdata);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Schedule sending final response in couple of of secs. */
+ r = PJ_POOL_ALLOC_T(tdata->pool, struct response);
+ r->res_addr = res_addr;
+ r->tdata = tdata;
+ if (r->res_addr.transport)
+ pjsip_transport_add_ref(r->res_addr.transport);
+
+ timer.entry.cb = &send_response_callback;
+ timer.entry.user_data = r;
+ pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay);
+
+ return PJ_TRUE;
+
+
+ } else
+ if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST8_BRANCH_ID) == 0) {
+ /*
+ * The TEST8_BRANCH_ID test failed INVITE transaction.
+ */
+ pjsip_method *method;
+ pj_status_t status;
+
+ method = &rdata->msg_info.msg->line.req.method;
+
+ recv_count++;
+
+ if (method->id == PJSIP_INVITE_METHOD) {
+
+ if (recv_count > 1) {
+ PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!",
+ recv_count));
+ test_complete = -635;
+ }
+
+ status = pjsip_endpt_respond_stateless(endpt, rdata, 301, NULL,
+ NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to send response", status);
+ test_complete = -636;
+ }
+
+ } else if (method->id == PJSIP_ACK_METHOD) {
+
+ if (recv_count == 2) {
+ pj_str_t key;
+ pj_time_val delay = { 5, 0 };
+
+ /* Schedule timer to destroy transaction after 5 seconds.
+ * This is to make sure that transaction does not
+ * retransmit ACK.
+ */
+ pjsip_tsx_create_key(rdata->tp_info.pool, &key,
+ PJSIP_ROLE_UAC, &pjsip_invite_method,
+ rdata);
+
+ pj_strcpy(&timer.tsx_key, &key);
+ timer.entry.id = 301;
+ timer.entry.cb = &terminate_tsx_callback;
+
+ pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay);
+ }
+
+ if (recv_count > 2) {
+ PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!",
+ recv_count));
+ test_complete = -638;
+ }
+
+
+ } else {
+ PJ_LOG(3,(THIS_FILE," error: not expecting %s",
+ pjsip_rx_data_get_info(rdata)));
+ test_complete = -639;
+
+ }
+
+
+ } else
+ if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST9_BRANCH_ID) == 0) {
+ /*
+ * The TEST9_BRANCH_ID test failed INVITE transaction with
+ * provisional response.
+ */
+ pjsip_method *method;
+ pj_status_t status;
+
+ method = &rdata->msg_info.msg->line.req.method;
+
+ recv_count++;
+
+ if (method->id == PJSIP_INVITE_METHOD) {
+
+ pjsip_response_addr res_addr;
+ struct response *r;
+ pjsip_tx_data *tdata;
+ pj_time_val delay = { 2, 0 };
+
+ if (recv_count > 1) {
+ PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!",
+ recv_count));
+ test_complete = -650;
+ return PJ_TRUE;
+ }
+
+ /* Respond with provisional response */
+ status = pjsip_endpt_create_response(endpt, rdata, 100, NULL,
+ &tdata);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pjsip_endpt_send_response(endpt, &res_addr, tdata,
+ NULL, NULL);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Create the final response. */
+ status = pjsip_endpt_create_response(endpt, rdata, 302, NULL,
+ &tdata);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Schedule sending final response in couple of of secs. */
+ r = PJ_POOL_ALLOC_T(tdata->pool, struct response);
+ r->res_addr = res_addr;
+ r->tdata = tdata;
+ if (r->res_addr.transport)
+ pjsip_transport_add_ref(r->res_addr.transport);
+
+ timer.entry.cb = &send_response_callback;
+ timer.entry.user_data = r;
+ pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay);
+
+ } else if (method->id == PJSIP_ACK_METHOD) {
+
+ if (recv_count == 2) {
+ pj_str_t key;
+ pj_time_val delay = { 5, 0 };
+
+ /* Schedule timer to destroy transaction after 5 seconds.
+ * This is to make sure that transaction does not
+ * retransmit ACK.
+ */
+ pjsip_tsx_create_key(rdata->tp_info.pool, &key,
+ PJSIP_ROLE_UAC, &pjsip_invite_method,
+ rdata);
+
+ pj_strcpy(&timer.tsx_key, &key);
+ timer.entry.id = 302;
+ timer.entry.cb = &terminate_tsx_callback;
+
+ pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay);
+ }
+
+ if (recv_count > 2) {
+ PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!",
+ recv_count));
+ test_complete = -638;
+ }
+
+
+ } else {
+ PJ_LOG(3,(THIS_FILE," error: not expecting %s",
+ pjsip_rx_data_get_info(rdata)));
+ test_complete = -639;
+
+ }
+
+ return PJ_TRUE;
+
+ }
+
+ return PJ_FALSE;
+}
+
+/*
+ * The generic test framework, used by most of the tests.
+ */
+static int perform_tsx_test(int dummy, char *target_uri, char *from_uri,
+ char *branch_param, int test_time,
+ const pjsip_method *method)
+{
+ pjsip_tx_data *tdata;
+ pjsip_transaction *tsx;
+ pj_str_t target, from, tsx_key;
+ pjsip_via_hdr *via;
+ pj_time_val timeout;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(dummy);
+
+ PJ_LOG(3,(THIS_FILE,
+ " please standby, this will take at most %d seconds..",
+ test_time));
+
+ /* Reset test. */
+ recv_count = 0;
+ test_complete = 0;
+
+ /* Init headers. */
+ target = pj_str(target_uri);
+ from = pj_str(from_uri);
+
+ /* Create request. */
+ status = pjsip_endpt_create_request( endpt, method, &target,
+ &from, &target, NULL, NULL, -1,
+ NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to create request", status);
+ return -100;
+ }
+
+ /* Set the branch param for test 1. */
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ via->branch_param = pj_str(branch_param);
+
+ /* Add additional reference to tdata to prevent transaction from
+ * deleting it.
+ */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Create transaction. */
+ status = pjsip_tsx_create_uac( &tsx_user, tdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to create UAC transaction", status);
+ pjsip_tx_data_dec_ref(tdata);
+ return -110;
+ }
+
+ /* Get transaction key. */
+ pj_strdup(tdata->pool, &tsx_key, &tsx->transaction_key);
+
+ /* Send the message. */
+ status = pjsip_tsx_send_msg(tsx, NULL);
+ // Ignore send result. Some tests do deliberately triggers error
+ // when sending message.
+ if (status != PJ_SUCCESS) {
+ // app_perror(" Error: unable to send request", status);
+ pjsip_tx_data_dec_ref(tdata);
+ // return -120;
+ }
+
+
+ /* Set test completion time. */
+ pj_gettimeofday(&timeout);
+ timeout.sec += test_time;
+
+ /* Wait until test complete. */
+ while (!test_complete) {
+ pj_time_val now, poll_delay = {0, 10};
+
+ pjsip_endpt_handle_events(endpt, &poll_delay);
+
+ pj_gettimeofday(&now);
+ if (now.sec > timeout.sec) {
+ PJ_LOG(3,(THIS_FILE, " Error: test has timed out"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -130;
+ }
+ }
+
+ if (test_complete < 0) {
+ tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE);
+ if (tsx) {
+ pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
+ pj_mutex_unlock(tsx->mutex);
+ flush_events(1000);
+ }
+ pjsip_tx_data_dec_ref(tdata);
+ return test_complete;
+
+ } else {
+ pj_time_val now;
+
+ /* Allow transaction to destroy itself */
+ flush_events(500);
+
+ /* Wait until test completes */
+ pj_gettimeofday(&now);
+
+ if (PJ_TIME_VAL_LT(now, timeout)) {
+ pj_time_val interval;
+ interval = timeout;
+ PJ_TIME_VAL_SUB(interval, now);
+ flush_events(PJ_TIME_VAL_MSEC(interval));
+ }
+ }
+
+ /* Make sure transaction has been destroyed. */
+ if (pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE) != NULL) {
+ PJ_LOG(3,(THIS_FILE, " Error: transaction has not been destroyed"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -140;
+ }
+
+ /* Check tdata reference counter. */
+ if (pj_atomic_get(tdata->ref_cnt) != 1) {
+ PJ_LOG(3,(THIS_FILE, " Error: tdata reference counter is %d",
+ pj_atomic_get(tdata->ref_cnt)));
+ pjsip_tx_data_dec_ref(tdata);
+ return -150;
+ }
+
+ /* Destroy txdata */
+ pjsip_tx_data_dec_ref(tdata);
+
+ return PJ_SUCCESS;
+}
+
+/*****************************************************************************
+ **
+ ** TEST1_BRANCH_ID: UAC basic retransmission and timeout test.
+ **
+ ** This will test the retransmission of the UAC transaction. Remote will not
+ ** answer the transaction, so the transaction should fail. The Via branch prm
+ ** TEST1_BRANCH_ID will be used for this test.
+ **
+ *****************************************************************************
+ */
+static int tsx_uac_retransmit_test(void)
+{
+ int status = 0, enabled;
+ int i;
+ struct {
+ const pjsip_method *method;
+ unsigned delay;
+ } sub_test[] =
+ {
+ { &pjsip_invite_method, 0},
+ { &pjsip_invite_method, TEST1_ALLOWED_DIFF*2},
+ { &pjsip_options_method, 0},
+ { &pjsip_options_method, TEST1_ALLOWED_DIFF*2}
+ };
+
+ PJ_LOG(3,(THIS_FILE, " test1: basic uac retransmit and timeout test"));
+
+
+ /* For this test. message printing shound be disabled because it makes
+ * incorrect timing.
+ */
+ enabled = msg_logger_set_enabled(0);
+
+ for (i=0; i<(int)PJ_ARRAY_SIZE(sub_test); ++i) {
+
+ PJ_LOG(3,(THIS_FILE,
+ " variant %c: %s with %d ms network delay",
+ ('a' + i),
+ sub_test[i].method->name.ptr,
+ sub_test[i].delay));
+
+ /* Configure transport */
+ pjsip_loop_set_failure(loop, 0, NULL);
+ pjsip_loop_set_recv_delay(loop, sub_test[i].delay, NULL);
+
+ /* Do the test. */
+ status = perform_tsx_test(-500, TARGET_URI, FROM_URI,
+ TEST1_BRANCH_ID,
+ 35, sub_test[i].method);
+ if (status != 0)
+ break;
+ }
+
+ /* Restore transport. */
+ pjsip_loop_set_recv_delay(loop, 0, NULL);
+
+ /* Restore msg logger. */
+ msg_logger_set_enabled(enabled);
+
+ /* Done. */
+ return status;
+}
+
+/*****************************************************************************
+ **
+ ** TEST2_BRANCH_ID: UAC resolve error test.
+ **
+ ** Test the scenario where destination host is unresolvable. There are
+ ** two variants:
+ ** (a) resolver returns immediate error
+ ** (b) resolver returns error via the callback.
+ **
+ *****************************************************************************
+ */
+static int tsx_resolve_error_test(void)
+{
+ int status = 0;
+
+ PJ_LOG(3,(THIS_FILE, " test2: resolve error test"));
+
+ /*
+ * Variant (a): immediate resolve error.
+ */
+ PJ_LOG(3,(THIS_FILE, " variant a: immediate resolving error"));
+
+ status = perform_tsx_test(-800,
+ "sip:bob@unresolved-host",
+ FROM_URI, TEST2_BRANCH_ID, 20,
+ &pjsip_options_method);
+ if (status != 0)
+ return status;
+
+ /*
+ * Variant (b): error via callback.
+ */
+ PJ_LOG(3,(THIS_FILE, " variant b: error via callback"));
+
+ /* This only applies to "loop-dgram" transport */
+ if (test_param->type == PJSIP_TRANSPORT_LOOP_DGRAM) {
+ /* Set loop transport to return delayed error. */
+ pjsip_loop_set_failure(loop, 2, NULL);
+ pjsip_loop_set_send_callback_delay(loop, 10, NULL);
+
+ status = perform_tsx_test(-800, TARGET_URI, FROM_URI,
+ TEST2_BRANCH_ID, 2,
+ &pjsip_options_method);
+ if (status != 0)
+ return status;
+
+ /* Restore loop transport settings. */
+ pjsip_loop_set_failure(loop, 0, NULL);
+ pjsip_loop_set_send_callback_delay(loop, 0, NULL);
+ }
+
+ return status;
+}
+
+
+/*****************************************************************************
+ **
+ ** TEST3_BRANCH_ID: UAC terminate while resolving test.
+ **
+ ** Terminate the transaction while resolver is still running.
+ **
+ *****************************************************************************
+ */
+static int tsx_terminate_resolving_test(void)
+{
+ unsigned prev_delay;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " test3: terminate while resolving test"));
+
+ /* Configure transport delay. */
+ pjsip_loop_set_send_callback_delay(loop, 100, &prev_delay);
+
+ /* Start the test. */
+ status = perform_tsx_test(-900, TARGET_URI, FROM_URI,
+ TEST3_BRANCH_ID, 2, &pjsip_options_method);
+
+ /* Restore delay. */
+ pjsip_loop_set_send_callback_delay(loop, prev_delay, NULL);
+
+ return status;
+}
+
+
+/*****************************************************************************
+ **
+ ** TEST4_BRANCH_ID: Transport failed after several retransmissions
+ **
+ ** There are two variants of this test: (a) failure occurs immediately when
+ ** transaction calls pjsip_transport_send() or (b) failure is reported via
+ ** transport callback.
+ **
+ *****************************************************************************
+ */
+static int tsx_retransmit_fail_test(void)
+{
+ int i;
+ unsigned delay[] = {0, 10};
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_LOG(3,(THIS_FILE,
+ " test4: transport fails after several retransmissions test"));
+
+
+ for (i=0; i<(int)PJ_ARRAY_SIZE(delay); ++i) {
+
+ PJ_LOG(3,(THIS_FILE,
+ " variant %c: transport delay %d ms", ('a'+i), delay[i]));
+
+ /* Configure transport delay. */
+ pjsip_loop_set_send_callback_delay(loop, delay[i], NULL);
+
+ /* Restore transport failure mode. */
+ pjsip_loop_set_failure(loop, 0, 0);
+
+ /* Start the test. */
+ status = perform_tsx_test(-1000, TARGET_URI, FROM_URI,
+ TEST4_BRANCH_ID, 6, &pjsip_options_method);
+
+ if (status != 0)
+ break;
+
+ }
+
+ /* Restore delay. */
+ pjsip_loop_set_send_callback_delay(loop, 0, NULL);
+
+ /* Restore transport failure mode. */
+ pjsip_loop_set_failure(loop, 0, 0);
+
+ return status;
+}
+
+
+/*****************************************************************************
+ **
+ ** TEST5_BRANCH_ID: Terminate transaction after several retransmissions
+ **
+ *****************************************************************************
+ */
+static int tsx_terminate_after_retransmit_test(void)
+{
+ int status;
+
+ PJ_LOG(3,(THIS_FILE, " test5: terminate after retransmissions"));
+
+ /* Do the test. */
+ status = perform_tsx_test(-1100, TARGET_URI, FROM_URI,
+ TEST5_BRANCH_ID,
+ 6, &pjsip_options_method);
+
+ /* Done. */
+ return status;
+}
+
+
+/*****************************************************************************
+ **
+ ** TEST6_BRANCH_ID: Successfull non-invite transaction
+ ** TEST7_BRANCH_ID: Successfull non-invite transaction with provisional
+ ** TEST8_BRANCH_ID: Failed invite transaction
+ ** TEST9_BRANCH_ID: Failed invite transaction with provisional
+ **
+ *****************************************************************************
+ */
+static int perform_generic_test( const char *title,
+ char *branch_id,
+ const pjsip_method *method)
+{
+ int i, status = 0;
+ unsigned delay[] = { 1, 200 };
+
+ PJ_LOG(3,(THIS_FILE, " %s", title));
+
+ /* Do the test. */
+ for (i=0; i<(int)PJ_ARRAY_SIZE(delay); ++i) {
+
+ if (test_param->type == PJSIP_TRANSPORT_LOOP_DGRAM) {
+ PJ_LOG(3,(THIS_FILE, " variant %c: with %d ms transport delay",
+ ('a'+i), delay[i]));
+
+ pjsip_loop_set_delay(loop, delay[i]);
+ }
+
+ status = perform_tsx_test(-1200, TARGET_URI, FROM_URI,
+ branch_id, 10, method);
+ if (status != 0)
+ return status;
+
+ if (test_param->type != PJSIP_TRANSPORT_LOOP_DGRAM)
+ break;
+ }
+
+ pjsip_loop_set_delay(loop, 0);
+
+ /* Done. */
+ return status;
+}
+
+
+/*****************************************************************************
+ **
+ ** UAC Transaction Test.
+ **
+ *****************************************************************************
+ */
+int tsx_uac_test(struct tsx_test_param *param)
+{
+ pj_sockaddr_in addr;
+ pj_status_t status;
+
+ timer.tsx_key.ptr = timer.key_buf;
+
+ test_param = param;
+
+ /* Get transport flag */
+ tp_flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)test_param->type);
+
+ pj_ansi_sprintf(TARGET_URI, "sip:bob@127.0.0.1:%d;transport=%s",
+ param->port, param->tp_type);
+ pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s",
+ param->port, param->tp_type);
+
+ /* Check if loop transport is configured. */
+ status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM,
+ &addr, sizeof(addr), NULL, &loop);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(THIS_FILE, " Error: loop transport is not configured!"));
+ return -10;
+ }
+
+ /* Register modules. */
+ status = pjsip_endpt_register_module(endpt, &tsx_user);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to register module", status);
+ return -30;
+ }
+ status = pjsip_endpt_register_module(endpt, &msg_receiver);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to register module", status);
+ return -40;
+ }
+
+ /* TEST1_BRANCH_ID: Basic retransmit and timeout test. */
+ status = tsx_uac_retransmit_test();
+ if (status != 0)
+ return status;
+
+ /* TEST2_BRANCH_ID: Resolve error test. */
+ status = tsx_resolve_error_test();
+ if (status != 0)
+ return status;
+
+ /* TEST3_BRANCH_ID: UAC terminate while resolving test. */
+ status = tsx_terminate_resolving_test();
+ if (status != 0)
+ return status;
+
+ /* TEST4_BRANCH_ID: Transport failed after several retransmissions.
+ * Only applies to loop transport.
+ */
+ if (test_param->type == PJSIP_TRANSPORT_LOOP_DGRAM) {
+ status = tsx_retransmit_fail_test();
+ if (status != 0)
+ return status;
+ }
+
+ /* TEST5_BRANCH_ID: Terminate transaction after several retransmissions
+ * Only applicable to non-reliable transports.
+ */
+ if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) {
+ status = tsx_terminate_after_retransmit_test();
+ if (status != 0)
+ return status;
+ }
+
+ /* TEST6_BRANCH_ID: Successfull non-invite transaction */
+ status = perform_generic_test("test6: successfull non-invite transaction",
+ TEST6_BRANCH_ID, &pjsip_options_method);
+ if (status != 0)
+ return status;
+
+ /* TEST7_BRANCH_ID: Successfull non-invite transaction */
+ status = perform_generic_test("test7: successfull non-invite transaction "
+ "with provisional response",
+ TEST7_BRANCH_ID, &pjsip_options_method);
+ if (status != 0)
+ return status;
+
+ /* TEST8_BRANCH_ID: Failed invite transaction */
+ status = perform_generic_test("test8: failed invite transaction",
+ TEST8_BRANCH_ID, &pjsip_invite_method);
+ if (status != 0)
+ return status;
+
+ /* TEST9_BRANCH_ID: Failed invite transaction with provisional response */
+ status = perform_generic_test("test9: failed invite transaction with "
+ "provisional response",
+ TEST9_BRANCH_ID, &pjsip_invite_method);
+ if (status != 0)
+ return status;
+
+ pjsip_transport_dec_ref(loop);
+ flush_events(500);
+
+ /* Unregister modules. */
+ status = pjsip_endpt_unregister_module(endpt, &tsx_user);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to unregister module", status);
+ return -31;
+ }
+ status = pjsip_endpt_unregister_module(endpt, &msg_receiver);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to unregister module", status);
+ return -41;
+ }
+
+ return 0;
+}
+
diff --git a/pjsip/src/test/tsx_uas_test.c b/pjsip/src/test/tsx_uas_test.c
new file mode 100644
index 0000000..045e958
--- /dev/null
+++ b/pjsip/src/test/tsx_uas_test.c
@@ -0,0 +1,1658 @@
+/* $Id: tsx_uas_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "tsx_uas_test.c"
+
+
+/*****************************************************************************
+ **
+ ** UAS tests.
+ **
+ ** This file performs various tests for UAC transactions. Each test will have
+ ** a different Via branch param so that message receiver module and
+ ** transaction user module can identify which test is being carried out.
+ **
+ ** TEST1_BRANCH_ID
+ ** Test that non-INVITE transaction returns 2xx response to the correct
+ ** transport and correctly terminates the transaction.
+ ** This also checks that transaction is destroyed immediately after
+ ** it sends final response when reliable transport is used.
+ **
+ ** TEST2_BRANCH_ID
+ ** As above, for non-2xx final response.
+ **
+ ** TEST3_BRANCH_ID
+ ** Transaction correctly progressing to PROCEEDING state when provisional
+ ** response is sent.
+ **
+ ** TEST4_BRANCH_ID
+ ** Transaction retransmits last response (if any) without notifying
+ ** transaction user upon receiving request retransmissions on TRYING
+ ** state
+ **
+ ** TEST5_BRANCH_ID
+ ** As above, in PROCEEDING state.
+ **
+ ** TEST6_BRANCH_ID
+ ** As above, in COMPLETED state, with first sending provisional response.
+ ** (Only applicable for non-reliable transports).
+ **
+ ** TEST7_BRANCH_ID
+ ** INVITE transaction MUST retransmit non-2xx final response.
+ **
+ ** TEST8_BRANCH_ID
+ ** As above, for INVITE's 2xx final response (this is PJSIP specific).
+ **
+ ** TEST9_BRANCH_ID
+ ** INVITE transaction MUST cease retransmission of final response when
+ ** ACK is received. (Note: PJSIP also retransmit 2xx final response
+ ** until it's terminated by user).
+ ** Transaction also MUST terminate in T4 seconds.
+ ** (Only applicable for non-reliable transports).
+ **
+ ** TEST11_BRANCH_ID
+ ** Test scenario where transport fails before response is sent (i.e.
+ ** in TRYING state).
+ **
+ ** TEST12_BRANCH_ID
+ ** As above, after provisional response is sent but before final
+ ** response is sent (i.e. in PROCEEDING state).
+ **
+ ** TEST13_BRANCH_ID
+ ** As above, for INVITE, after final response has been sent but before
+ ** ACK is received (i.e. in CONNECTED state).
+ **
+ ** TEST14_BRANCH_ID
+ ** When UAS failed to deliver the response with the selected transport,
+ ** it should try contacting the client with other transport or begin
+ ** RFC 3263 server resolution procedure.
+ ** This should be tested on:
+ ** a. TRYING state (when delivering first response).
+ ** b. PROCEEDING state (when failed to retransmit last response
+ ** upon receiving request retransmission).
+ ** c. COMPLETED state.
+ **
+ **/
+
+#define TEST1_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test1")
+#define TEST2_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test2")
+#define TEST3_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test3")
+#define TEST4_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test4")
+#define TEST5_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test5")
+#define TEST6_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test6")
+#define TEST7_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test7")
+#define TEST8_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test8")
+#define TEST9_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test9")
+#define TEST10_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test10")
+#define TEST11_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test11")
+#define TEST12_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test12")
+//#define TEST13_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test13")
+
+#define TEST1_STATUS_CODE 200
+#define TEST2_STATUS_CODE 301
+#define TEST3_PROVISIONAL_CODE PJSIP_SC_QUEUED
+#define TEST3_STATUS_CODE 202
+#define TEST4_STATUS_CODE 200
+#define TEST4_REQUEST_COUNT 2
+#define TEST5_PROVISIONAL_CODE 100
+#define TEST5_STATUS_CODE 200
+#define TEST5_REQUEST_COUNT 2
+#define TEST5_RESPONSE_COUNT 2
+#define TEST6_PROVISIONAL_CODE 100
+#define TEST6_STATUS_CODE 200 /* Must be final */
+#define TEST6_REQUEST_COUNT 2
+#define TEST6_RESPONSE_COUNT 3
+#define TEST7_STATUS_CODE 301
+#define TEST8_STATUS_CODE 302
+#define TEST9_STATUS_CODE 301
+
+
+#define TEST4_TITLE "test4: absorbing request retransmission"
+#define TEST5_TITLE "test5: retransmit last response in PROCEEDING state"
+#define TEST6_TITLE "test6: retransmit last response in COMPLETED state"
+
+
+static char TARGET_URI[128];
+static char FROM_URI[128];
+static struct tsx_test_param *test_param;
+static unsigned tp_flag;
+
+
+#define TEST_TIMEOUT_ERROR -30
+#define MAX_ALLOWED_DIFF 150
+
+static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e);
+static pj_bool_t on_rx_message(pjsip_rx_data *rdata);
+
+/* UAC transaction user module. */
+static pjsip_module tsx_user =
+{
+ NULL, NULL, /* prev and next */
+ { "Tsx-UAS-User", 12}, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ NULL, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request() */
+ NULL, /* on_tx_response() */
+ &tsx_user_on_tsx_state, /* on_tsx_state() */
+};
+
+/* Module to send request. */
+static pjsip_module msg_sender =
+{
+ NULL, NULL, /* prev and next */
+ { "Msg-Sender", 10}, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &on_rx_message, /* on_rx_request() */
+ &on_rx_message, /* on_rx_response() */
+ NULL, /* on_tx_request() */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+/* Static vars, which will be reset on each test. */
+static int recv_count;
+static pj_time_val recv_last;
+static pj_bool_t test_complete;
+
+/* Loop transport instance. */
+static pjsip_transport *loop;
+
+/* UAS transaction key. */
+static char key_buf[64];
+static pj_str_t tsx_key = { key_buf, 0 };
+
+
+/* General timer entry to be used by tests. */
+//static pj_timer_entry timer;
+
+/* Timer to send response via transaction. */
+struct response
+{
+ pj_str_t tsx_key;
+ pjsip_tx_data *tdata;
+};
+
+/* Timer callback to send response. */
+static void send_response_timer( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pjsip_transaction *tsx;
+ struct response *r = (struct response*) entry->user_data;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ tsx = pjsip_tsx_layer_find_tsx(&r->tsx_key, PJ_TRUE);
+ if (!tsx) {
+ PJ_LOG(3,(THIS_FILE," error: timer unable to find transaction"));
+ pjsip_tx_data_dec_ref(r->tdata);
+ return;
+ }
+
+ status = pjsip_tsx_send_msg(tsx, r->tdata);
+ if (status != PJ_SUCCESS) {
+ // Some tests do expect failure!
+ //PJ_LOG(3,(THIS_FILE," error: timer unable to send response"));
+ pj_mutex_unlock(tsx->mutex);
+ pjsip_tx_data_dec_ref(r->tdata);
+ return;
+ }
+
+ pj_mutex_unlock(tsx->mutex);
+}
+
+/* Utility to send response. */
+static void send_response( pjsip_rx_data *rdata,
+ pjsip_transaction *tsx,
+ int status_code )
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ status = pjsip_endpt_create_response( endpt, rdata, status_code, NULL,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create response", status);
+ test_complete = -196;
+ return;
+ }
+
+ status = pjsip_tsx_send_msg(tsx, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ // Some tests do expect failure!
+ //app_perror(" error: unable to send response", status);
+ //test_complete = -197;
+ return;
+ }
+}
+
+/* Schedule timer to send response for the specified UAS transaction */
+static void schedule_send_response( pjsip_rx_data *rdata,
+ const pj_str_t *tsx_key,
+ int status_code,
+ int msec_delay )
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+ pj_timer_entry *t;
+ struct response *r;
+ pj_time_val delay;
+
+ status = pjsip_endpt_create_response( endpt, rdata, status_code, NULL,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create response", status);
+ test_complete = -198;
+ return;
+ }
+
+ r = PJ_POOL_ALLOC_T(tdata->pool, struct response);
+ pj_strdup(tdata->pool, &r->tsx_key, tsx_key);
+ r->tdata = tdata;
+
+ delay.sec = 0;
+ delay.msec = msec_delay;
+ pj_time_val_normalize(&delay);
+
+ t = PJ_POOL_ZALLOC_T(tdata->pool, pj_timer_entry);
+ t->user_data = r;
+ t->cb = &send_response_timer;
+
+ status = pjsip_endpt_schedule_timer(endpt, t, &delay);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ app_perror(" error: unable to schedule timer", status);
+ test_complete = -199;
+ return;
+ }
+}
+
+
+/* Find and terminate tsx with the specified key. */
+static void terminate_our_tsx(int status_code)
+{
+ pjsip_transaction *tsx;
+
+ tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE);
+ if (!tsx) {
+ PJ_LOG(3,(THIS_FILE," error: timer unable to find transaction"));
+ return;
+ }
+
+ pjsip_tsx_terminate(tsx, status_code);
+ pj_mutex_unlock(tsx->mutex);
+}
+
+#if 0 /* Unused for now */
+/* Timer callback to terminate transaction. */
+static void terminate_tsx_timer( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ terminate_our_tsx(entry->id);
+}
+
+
+/* Schedule timer to terminate transaction. */
+static void schedule_terminate_tsx( pjsip_transaction *tsx,
+ int status_code,
+ int msec_delay )
+{
+ pj_time_val delay;
+
+ delay.sec = 0;
+ delay.msec = msec_delay;
+ pj_time_val_normalize(&delay);
+
+ pj_assert(pj_strcmp(&tsx->transaction_key, &tsx_key)==0);
+ timer.user_data = NULL;
+ timer.id = status_code;
+ timer.cb = &terminate_tsx_timer;
+ pjsip_endpt_schedule_timer(endpt, &timer, &delay);
+}
+#endif
+
+
+/*
+ * This is the handler to receive state changed notification from the
+ * transaction. It is used to verify that the transaction behaves according
+ * to the test scenario.
+ */
+static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e)
+{
+ if (pj_strcmp2(&tsx->branch, TEST1_BRANCH_ID)==0 ||
+ pj_strcmp2(&tsx->branch, TEST2_BRANCH_ID)==0)
+ {
+ /*
+ * TEST1_BRANCH_ID tests that non-INVITE transaction transmits final
+ * response using correct transport and terminates transaction after
+ * T4 (PJSIP_T4_TIMEOUT, 5 seconds).
+ *
+ * TEST2_BRANCH_ID does similar test for non-2xx final response.
+ */
+ int status_code = (pj_strcmp2(&tsx->branch, TEST1_BRANCH_ID)==0) ?
+ TEST1_STATUS_CODE : TEST2_STATUS_CODE;
+
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ test_complete = 1;
+
+ /* Check that status code is status_code. */
+ if (tsx->status_code != status_code) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -100;
+ }
+
+ /* Previous state must be completed. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -101;
+ }
+
+ } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+
+ /* Previous state must be TRYING. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -102;
+ }
+ }
+
+ }
+ else
+ if (pj_strcmp2(&tsx->branch, TEST3_BRANCH_ID)==0) {
+ /*
+ * TEST3_BRANCH_ID tests sending provisional response.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ test_complete = 1;
+
+ /* Check that status code is status_code. */
+ if (tsx->status_code != TEST3_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -110;
+ }
+
+ /* Previous state must be completed. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -111;
+ }
+
+ } else if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) {
+
+ /* Previous state must be TRYING. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -112;
+ }
+
+ /* Check that status code is status_code. */
+ if (tsx->status_code != TEST3_PROVISIONAL_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -113;
+ }
+
+ /* Check that event must be TX_MSG */
+ if (e->body.tsx_state.type != PJSIP_EVENT_TX_MSG) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect event"));
+ test_complete = -114;
+ }
+
+ } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+
+ /* Previous state must be PROCEEDING. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -115;
+ }
+
+ /* Check that status code is status_code. */
+ if (tsx->status_code != TEST3_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -116;
+ }
+
+ /* Check that event must be TX_MSG */
+ if (e->body.tsx_state.type != PJSIP_EVENT_TX_MSG) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect event"));
+ test_complete = -117;
+ }
+
+ }
+
+ } else
+ if (pj_strcmp2(&tsx->branch, TEST4_BRANCH_ID)==0) {
+ /*
+ * TEST4_BRANCH_ID tests receiving retransmissions in TRYING state.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TRYING) {
+ /* Request is received. */
+ } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ /* Check that status code is status_code. */
+ if (tsx->status_code != TEST4_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: incorrect status code %d "
+ "(expecting %d)", tsx->status_code,
+ TEST4_STATUS_CODE));
+ test_complete = -120;
+ }
+
+ /* Previous state. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -121;
+ }
+
+ } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED)
+ {
+ PJ_LOG(3,(THIS_FILE, " error: unexpected state %s (122)",
+ pjsip_tsx_state_str(tsx->state)));
+ test_complete = -122;
+
+ }
+
+
+ } else
+ if (pj_strcmp2(&tsx->branch, TEST5_BRANCH_ID)==0) {
+ /*
+ * TEST5_BRANCH_ID tests receiving retransmissions in PROCEEDING state
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TRYING) {
+ /* Request is received. */
+
+ } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ /* Check that status code is status_code. */
+ if (tsx->status_code != TEST5_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -130;
+ }
+
+ /* Previous state. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -131;
+ }
+
+ } else if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) {
+
+ /* Check status code. */
+ if (tsx->status_code != TEST5_PROVISIONAL_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -132;
+ }
+
+ } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) {
+ PJ_LOG(3,(THIS_FILE, " error: unexpected state %s (133)",
+ pjsip_tsx_state_str(tsx->state)));
+ test_complete = -133;
+
+ }
+
+ } else
+ if (pj_strcmp2(&tsx->branch, TEST6_BRANCH_ID)==0) {
+ /*
+ * TEST6_BRANCH_ID tests receiving retransmissions in COMPLETED state
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TRYING) {
+ /* Request is received. */
+
+ } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ /* Check that status code is status_code. */
+ if (tsx->status_code != TEST6_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code %d "
+ "(expecting %d)", tsx->status_code,
+ TEST6_STATUS_CODE));
+ test_complete = -140;
+ }
+
+ /* Previous state. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -141;
+ }
+
+ } else if (tsx->state != PJSIP_TSX_STATE_PROCEEDING &&
+ tsx->state != PJSIP_TSX_STATE_COMPLETED &&
+ tsx->state != PJSIP_TSX_STATE_DESTROYED)
+ {
+ PJ_LOG(3,(THIS_FILE, " error: unexpected state %s (142)",
+ pjsip_tsx_state_str(tsx->state)));
+ test_complete = -142;
+
+ }
+
+
+ } else
+ if (pj_strcmp2(&tsx->branch, TEST7_BRANCH_ID)==0 ||
+ pj_strcmp2(&tsx->branch, TEST8_BRANCH_ID)==0)
+ {
+ /*
+ * TEST7_BRANCH_ID and TEST8_BRANCH_ID test retransmission of
+ * INVITE final response
+ */
+ int code;
+
+ if (pj_strcmp2(&tsx->branch, TEST7_BRANCH_ID) == 0)
+ code = TEST7_STATUS_CODE;
+ else
+ code = TEST8_STATUS_CODE;
+
+ if (tsx->state == PJSIP_TSX_STATE_TRYING) {
+ /* Request is received. */
+
+ } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ if (test_complete == 0)
+ test_complete = 1;
+
+ /* Check status code. */
+ if (tsx->status_code != PJSIP_SC_TSX_TIMEOUT) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -150;
+ }
+
+ /* Previous state. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -151;
+ }
+
+ /* Check the number of retransmissions */
+ if (tp_flag & PJSIP_TRANSPORT_RELIABLE) {
+
+ if (tsx->retransmit_count != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: should not retransmit"));
+ test_complete = -1510;
+ }
+
+ } else {
+
+ if (tsx->retransmit_count != 10) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: incorrect retransmit count %d "
+ "(expecting 10)",
+ tsx->retransmit_count));
+ test_complete = -1510;
+ }
+
+ }
+
+ } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+
+ /* Check that status code is status_code. */
+ if (tsx->status_code != code) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -152;
+ }
+
+ /* Previous state. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -153;
+ }
+
+ } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) {
+
+ PJ_LOG(3,(THIS_FILE, " error: unexpected state (154)"));
+ test_complete = -154;
+
+ }
+
+
+ } else
+ if (pj_strcmp2(&tsx->branch, TEST9_BRANCH_ID)==0) {
+ /*
+ * TEST9_BRANCH_ID tests that retransmission of INVITE final response
+ * must cease when ACK is received.
+ */
+
+ if (tsx->state == PJSIP_TSX_STATE_TRYING) {
+ /* Request is received. */
+
+ } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ if (test_complete == 0)
+ test_complete = 1;
+
+ /* Check status code. */
+ if (tsx->status_code != TEST9_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -160;
+ }
+
+ /* Previous state. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_CONFIRMED) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -161;
+ }
+
+ } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+
+ /* Check that status code is status_code. */
+ if (tsx->status_code != TEST9_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -162;
+ }
+
+ /* Previous state. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -163;
+ }
+
+
+ } else if (tsx->state == PJSIP_TSX_STATE_CONFIRMED) {
+
+ /* Check that status code is status_code. */
+ if (tsx->status_code != TEST9_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -164;
+ }
+
+ /* Previous state. */
+ if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state"));
+ test_complete = -165;
+ }
+
+ } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) {
+
+ PJ_LOG(3,(THIS_FILE, " error: unexpected state (166)"));
+ test_complete = -166;
+
+ }
+
+
+ } else
+ if (pj_strcmp2(&tsx->branch, TEST10_BRANCH_ID)==0 ||
+ pj_strcmp2(&tsx->branch, TEST11_BRANCH_ID)==0 ||
+ pj_strcmp2(&tsx->branch, TEST12_BRANCH_ID)==0)
+ {
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+ if (!test_complete)
+ test_complete = 1;
+
+ if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) {
+ PJ_LOG(3,(THIS_FILE," error: incorrect status code"));
+ test_complete = -170;
+ }
+ }
+ }
+
+}
+
+/* Save transaction key to global variables. */
+static void save_key(pjsip_transaction *tsx)
+{
+ pj_str_t key;
+
+ pj_strdup(tsx->pool, &key, &tsx->transaction_key);
+ pj_strcpy(&tsx_key, &key);
+}
+
+#define DIFF(a,b) ((a<b) ? (b-a) : (a-b))
+
+/*
+ * Message receiver handler.
+ */
+static pj_bool_t on_rx_message(pjsip_rx_data *rdata)
+{
+ pjsip_msg *msg = rdata->msg_info.msg;
+ pj_str_t branch_param = rdata->msg_info.via->branch_param;
+ pj_status_t status;
+
+ if (pj_strcmp2(&branch_param, TEST1_BRANCH_ID) == 0 ||
+ pj_strcmp2(&branch_param, TEST2_BRANCH_ID) == 0)
+ {
+ /*
+ * TEST1_BRANCH_ID tests that non-INVITE transaction transmits 2xx
+ * final response using correct transport and terminates transaction
+ * after 32 seconds.
+ *
+ * TEST2_BRANCH_ID performs similar test for non-2xx final response.
+ */
+ int status_code = (pj_strcmp2(&branch_param, TEST1_BRANCH_ID) == 0) ?
+ TEST1_STATUS_CODE : TEST2_STATUS_CODE;
+
+ if (msg->type == PJSIP_REQUEST_MSG) {
+ /* On received request, create UAS and respond with final
+ * response.
+ */
+ pjsip_transaction *tsx;
+
+ status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create transaction", status);
+ test_complete = -110;
+ return PJ_TRUE;
+ }
+ pjsip_tsx_recv_msg(tsx, rdata);
+
+ save_key(tsx);
+ send_response(rdata, tsx, status_code);
+
+ } else {
+ /* Verify the response received. */
+
+ ++recv_count;
+
+ /* Verify status code. */
+ if (msg->line.status.code != status_code) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -113;
+ }
+
+ /* Verify that no retransmissions is received. */
+ if (recv_count > 1) {
+ PJ_LOG(3,(THIS_FILE, " error: retransmission received"));
+ test_complete = -114;
+ }
+
+ }
+ return PJ_TRUE;
+
+ } else if (pj_strcmp2(&branch_param, TEST3_BRANCH_ID) == 0) {
+
+ /* TEST3_BRANCH_ID tests provisional response. */
+
+ if (msg->type == PJSIP_REQUEST_MSG) {
+ /* On received request, create UAS and respond with provisional
+ * response, then schedule timer to send final response.
+ */
+ pjsip_transaction *tsx;
+
+ status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create transaction", status);
+ test_complete = -116;
+ return PJ_TRUE;
+ }
+ pjsip_tsx_recv_msg(tsx, rdata);
+
+ save_key(tsx);
+
+ send_response(rdata, tsx, TEST3_PROVISIONAL_CODE);
+ schedule_send_response(rdata, &tsx->transaction_key,
+ TEST3_STATUS_CODE, 2000);
+
+ } else {
+ /* Verify the response received. */
+
+ ++recv_count;
+
+ if (recv_count == 1) {
+ /* Verify status code. */
+ if (msg->line.status.code != TEST3_PROVISIONAL_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -123;
+ }
+ } else if (recv_count == 2) {
+ /* Verify status code. */
+ if (msg->line.status.code != TEST3_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code"));
+ test_complete = -124;
+ }
+ } else {
+ PJ_LOG(3,(THIS_FILE, " error: retransmission received"));
+ test_complete = -125;
+ }
+
+ }
+ return PJ_TRUE;
+
+ } else if (pj_strcmp2(&branch_param, TEST4_BRANCH_ID) == 0 ||
+ pj_strcmp2(&branch_param, TEST5_BRANCH_ID) == 0 ||
+ pj_strcmp2(&branch_param, TEST6_BRANCH_ID) == 0)
+ {
+
+ /* TEST4_BRANCH_ID: absorbs retransmissions in TRYING state. */
+ /* TEST5_BRANCH_ID: retransmit last response in PROCEEDING state. */
+ /* TEST6_BRANCH_ID: retransmit last response in COMPLETED state. */
+
+ if (msg->type == PJSIP_REQUEST_MSG) {
+ /* On received request, create UAS. */
+ pjsip_transaction *tsx;
+
+ PJ_LOG(4,(THIS_FILE, " received request (probably retransmission)"));
+
+ status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create transaction", status);
+ test_complete = -130;
+ return PJ_TRUE;
+ }
+
+ pjsip_tsx_recv_msg(tsx, rdata);
+ save_key(tsx);
+
+ if (pj_strcmp2(&branch_param, TEST4_BRANCH_ID) == 0) {
+
+ } else if (pj_strcmp2(&branch_param, TEST5_BRANCH_ID) == 0) {
+ send_response(rdata, tsx, TEST5_PROVISIONAL_CODE);
+
+ } else if (pj_strcmp2(&branch_param, TEST6_BRANCH_ID) == 0) {
+ PJ_LOG(4,(THIS_FILE, " sending provisional response"));
+ send_response(rdata, tsx, TEST6_PROVISIONAL_CODE);
+ PJ_LOG(4,(THIS_FILE, " sending final response"));
+ send_response(rdata, tsx, TEST6_STATUS_CODE);
+ }
+
+ } else {
+ /* Verify the response received. */
+
+ PJ_LOG(4,(THIS_FILE, " received response number %d", recv_count));
+
+ ++recv_count;
+
+ if (pj_strcmp2(&branch_param, TEST4_BRANCH_ID) == 0) {
+ PJ_LOG(3,(THIS_FILE, " error: not expecting response!"));
+ test_complete = -132;
+
+ } else if (pj_strcmp2(&branch_param, TEST5_BRANCH_ID) == 0) {
+
+ if (rdata->msg_info.msg->line.status.code!=TEST5_PROVISIONAL_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: incorrect status code!"));
+ test_complete = -133;
+
+ }
+ if (recv_count > TEST5_RESPONSE_COUNT) {
+ PJ_LOG(3,(THIS_FILE, " error: not expecting response!"));
+ test_complete = -134;
+ }
+
+ } else if (pj_strcmp2(&branch_param, TEST6_BRANCH_ID) == 0) {
+
+ int code = rdata->msg_info.msg->line.status.code;
+
+ switch (recv_count) {
+ case 1:
+ if (code != TEST6_PROVISIONAL_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: invalid code!"));
+ test_complete = -135;
+ }
+ break;
+ case 2:
+ case 3:
+ if (code != TEST6_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE, " error: invalid code %d "
+ "(expecting %d)", code, TEST6_STATUS_CODE));
+ test_complete = -136;
+ }
+ break;
+ default:
+ PJ_LOG(3,(THIS_FILE, " error: not expecting response"));
+ test_complete = -137;
+ break;
+ }
+ }
+ }
+ return PJ_TRUE;
+
+
+ } else if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0 ||
+ pj_strcmp2(&branch_param, TEST8_BRANCH_ID) == 0)
+ {
+
+ /*
+ * TEST7_BRANCH_ID and TEST8_BRANCH_ID test the retransmission
+ * of INVITE final response
+ */
+ if (msg->type == PJSIP_REQUEST_MSG) {
+
+ /* On received request, create UAS. */
+ pjsip_transaction *tsx;
+
+ status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create transaction", status);
+ test_complete = -140;
+ return PJ_TRUE;
+ }
+
+ pjsip_tsx_recv_msg(tsx, rdata);
+ save_key(tsx);
+
+ if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0) {
+
+ send_response(rdata, tsx, TEST7_STATUS_CODE);
+
+ } else {
+
+ send_response(rdata, tsx, TEST8_STATUS_CODE);
+
+ }
+
+ } else {
+ int code;
+
+ ++recv_count;
+
+ if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0)
+ code = TEST7_STATUS_CODE;
+ else
+ code = TEST8_STATUS_CODE;
+
+ if (recv_count==1) {
+
+ if (rdata->msg_info.msg->line.status.code != code) {
+ PJ_LOG(3,(THIS_FILE," error: invalid status code"));
+ test_complete = -141;
+ }
+
+ recv_last = rdata->pkt_info.timestamp;
+
+ } else {
+
+ pj_time_val now;
+ unsigned msec, msec_expected;
+
+ now = rdata->pkt_info.timestamp;
+
+ PJ_TIME_VAL_SUB(now, recv_last);
+
+ msec = now.sec*1000 + now.msec;
+ msec_expected = (1 << (recv_count-2)) * pjsip_cfg()->tsx.t1;
+ if (msec_expected > pjsip_cfg()->tsx.t2)
+ msec_expected = pjsip_cfg()->tsx.t2;
+
+ if (DIFF(msec, msec_expected) > MAX_ALLOWED_DIFF) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: incorrect retransmission "
+ "time (%d ms expected, %d ms received",
+ msec_expected, msec));
+ test_complete = -142;
+ }
+
+ if (recv_count > 11) {
+ PJ_LOG(3,(THIS_FILE," error: too many responses (%d)",
+ recv_count));
+ test_complete = -143;
+ }
+
+ recv_last = rdata->pkt_info.timestamp;
+ }
+
+ }
+ return PJ_TRUE;
+
+ } else if (pj_strcmp2(&branch_param, TEST9_BRANCH_ID) == 0) {
+
+ /*
+ * TEST9_BRANCH_ID tests that the retransmission of INVITE final
+ * response should cease when ACK is received. Transaction also MUST
+ * terminate in T4 seconds.
+ */
+ if (msg->type == PJSIP_REQUEST_MSG) {
+
+ /* On received request, create UAS. */
+ pjsip_transaction *tsx;
+
+ status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create transaction", status);
+ test_complete = -150;
+ return PJ_TRUE;
+ }
+
+ pjsip_tsx_recv_msg(tsx, rdata);
+ save_key(tsx);
+ send_response(rdata, tsx, TEST9_STATUS_CODE);
+
+
+ } else {
+
+ ++recv_count;
+
+ if (rdata->msg_info.msg->line.status.code != TEST9_STATUS_CODE) {
+ PJ_LOG(3,(THIS_FILE," error: invalid status code"));
+ test_complete = -151;
+ }
+
+ if (recv_count==1) {
+
+ recv_last = rdata->pkt_info.timestamp;
+
+ } else if (recv_count < 5) {
+
+ /* Let UAS retransmit some messages before we send ACK. */
+ pj_time_val now;
+ unsigned msec, msec_expected;
+
+ now = rdata->pkt_info.timestamp;
+
+ PJ_TIME_VAL_SUB(now, recv_last);
+
+ msec = now.sec*1000 + now.msec;
+ msec_expected = (1 << (recv_count-2)) * pjsip_cfg()->tsx.t1;
+ if (msec_expected > pjsip_cfg()->tsx.t2)
+ msec_expected = pjsip_cfg()->tsx.t2;
+
+ if (DIFF(msec, msec_expected) > MAX_ALLOWED_DIFF) {
+ PJ_LOG(3,(THIS_FILE,
+ " error: incorrect retransmission "
+ "time (%d ms expected, %d ms received",
+ msec_expected, msec));
+ test_complete = -152;
+ }
+
+ recv_last = rdata->pkt_info.timestamp;
+
+ } else if (recv_count == 5) {
+ pjsip_tx_data *tdata;
+ pjsip_sip_uri *uri;
+ pjsip_via_hdr *via;
+
+ status = pjsip_endpt_create_request_from_hdr(
+ endpt, &pjsip_ack_method,
+ rdata->msg_info.to->uri,
+ rdata->msg_info.from,
+ rdata->msg_info.to,
+ NULL,
+ rdata->msg_info.cid,
+ rdata->msg_info.cseq->cseq,
+ NULL,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create ACK", status);
+ test_complete = -153;
+ return PJ_TRUE;
+ }
+
+ uri=(pjsip_sip_uri*)pjsip_uri_get_uri(tdata->msg->line.req.uri);
+ uri->transport_param = pj_str("loop-dgram");
+
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ via->branch_param = pj_str(TEST9_BRANCH_ID);
+
+ status = pjsip_endpt_send_request_stateless(endpt, tdata,
+ NULL, NULL);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to send ACK", status);
+ test_complete = -154;
+ }
+
+ } else {
+ PJ_LOG(3,(THIS_FILE," error: too many responses (%d)",
+ recv_count));
+ test_complete = -155;
+ }
+
+ }
+ return PJ_TRUE;
+
+ } else if (pj_strcmp2(&branch_param, TEST10_BRANCH_ID) == 0 ||
+ pj_strcmp2(&branch_param, TEST11_BRANCH_ID) == 0 ||
+ pj_strcmp2(&branch_param, TEST12_BRANCH_ID) == 0)
+ {
+ int test_num, code1, code2;
+
+ if (pj_strcmp2(&branch_param, TEST10_BRANCH_ID) == 0)
+ test_num=10, code1 = 100, code2 = 0;
+ else if (pj_strcmp2(&branch_param, TEST11_BRANCH_ID) == 0)
+ test_num=11, code1 = 100, code2 = 200;
+ else
+ test_num=12, code1 = 200, code2 = 0;
+
+ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) {
+
+ /* On received response, create UAS. */
+ pjsip_transaction *tsx;
+
+ status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create transaction", status);
+ test_complete = -150;
+ return PJ_TRUE;
+ }
+
+ pjsip_tsx_recv_msg(tsx, rdata);
+ save_key(tsx);
+
+ schedule_send_response(rdata, &tsx_key, code1, 1000);
+
+ if (code2)
+ schedule_send_response(rdata, &tsx_key, code2, 2000);
+
+ } else {
+
+ }
+
+ return PJ_TRUE;
+ }
+
+ return PJ_FALSE;
+}
+
+/*
+ * The generic test framework, used by most of the tests.
+ */
+static int perform_test( char *target_uri, char *from_uri,
+ char *branch_param, int test_time,
+ const pjsip_method *method,
+ int request_cnt, int request_interval_msec,
+ int expecting_timeout)
+{
+ pjsip_tx_data *tdata;
+ pj_str_t target, from;
+ pjsip_via_hdr *via;
+ pj_time_val timeout, next_send;
+ int sent_cnt;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE,
+ " please standby, this will take at most %d seconds..",
+ test_time));
+
+ /* Reset test. */
+ recv_count = 0;
+ test_complete = 0;
+ tsx_key.slen = 0;
+
+ /* Init headers. */
+ target = pj_str(target_uri);
+ from = pj_str(from_uri);
+
+ /* Create request. */
+ status = pjsip_endpt_create_request( endpt, method, &target,
+ &from, &target, NULL, NULL, -1,
+ NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to create request", status);
+ return -10;
+ }
+
+ /* Set the branch param for test 1. */
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ via->branch_param = pj_str(branch_param);
+
+ /* Schedule first send. */
+ sent_cnt = 0;
+ pj_gettimeofday(&next_send);
+ pj_time_val_normalize(&next_send);
+
+ /* Set test completion time. */
+ pj_gettimeofday(&timeout);
+ timeout.sec += test_time;
+
+ /* Wait until test complete. */
+ while (!test_complete) {
+ pj_time_val now, poll_delay = {0, 10};
+
+ pjsip_endpt_handle_events(endpt, &poll_delay);
+
+ pj_gettimeofday(&now);
+
+ if (sent_cnt < request_cnt && PJ_TIME_VAL_GTE(now, next_send)) {
+ /* Add additional reference to tdata to prevent transaction from
+ * deleting it.
+ */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* (Re)Send the request. */
+ PJ_LOG(4,(THIS_FILE, " (re)sending request %d", sent_cnt));
+
+ status = pjsip_endpt_send_request_stateless(endpt, tdata, 0, 0);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to send request", status);
+ pjsip_tx_data_dec_ref(tdata);
+ return -20;
+ }
+
+ /* Schedule next send, if any. */
+ sent_cnt++;
+ if (sent_cnt < request_cnt) {
+ pj_gettimeofday(&next_send);
+ next_send.msec += request_interval_msec;
+ pj_time_val_normalize(&next_send);
+ }
+ }
+
+ if (now.sec > timeout.sec) {
+ if (!expecting_timeout)
+ PJ_LOG(3,(THIS_FILE, " Error: test has timed out"));
+ pjsip_tx_data_dec_ref(tdata);
+ return TEST_TIMEOUT_ERROR;
+ }
+ }
+
+ if (test_complete < 0) {
+ pjsip_transaction *tsx;
+
+ tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE);
+ if (tsx) {
+ pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
+ pj_mutex_unlock(tsx->mutex);
+ flush_events(1000);
+ }
+ pjsip_tx_data_dec_ref(tdata);
+ return test_complete;
+ }
+
+ /* Allow transaction to destroy itself */
+ flush_events(500);
+
+ /* Make sure transaction has been destroyed. */
+ if (pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE) != NULL) {
+ PJ_LOG(3,(THIS_FILE, " Error: transaction has not been destroyed"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -40;
+ }
+
+ /* Check tdata reference counter. */
+ if (pj_atomic_get(tdata->ref_cnt) != 1) {
+ PJ_LOG(3,(THIS_FILE, " Error: tdata reference counter is %d",
+ pj_atomic_get(tdata->ref_cnt)));
+ pjsip_tx_data_dec_ref(tdata);
+ return -50;
+ }
+
+ /* Destroy txdata */
+ pjsip_tx_data_dec_ref(tdata);
+
+ return PJ_SUCCESS;
+
+}
+
+
+/*****************************************************************************
+ **
+ ** TEST1_BRANCH_ID: Basic 2xx final response
+ ** TEST2_BRANCH_ID: Basic non-2xx final response
+ **
+ *****************************************************************************
+ */
+static int tsx_basic_final_response_test(void)
+{
+ unsigned duration;
+ int status;
+
+ PJ_LOG(3,(THIS_FILE," test1: basic sending 2xx final response"));
+
+ /* Test duration must be greater than 32 secs if unreliable transport
+ * is used.
+ */
+ duration = (tp_flag & PJSIP_TRANSPORT_RELIABLE) ? 1 : 33;
+
+ status = perform_test(TARGET_URI, FROM_URI, TEST1_BRANCH_ID,
+ duration, &pjsip_options_method, 1, 0, 0);
+ if (status != 0)
+ return status;
+
+ PJ_LOG(3,(THIS_FILE," test2: basic sending non-2xx final response"));
+
+ status = perform_test(TARGET_URI, FROM_URI, TEST2_BRANCH_ID,
+ duration, &pjsip_options_method, 1, 0, 0);
+ if (status != 0)
+ return status;
+
+ return 0;
+}
+
+
+/*****************************************************************************
+ **
+ ** TEST3_BRANCH_ID: Sending provisional response
+ **
+ *****************************************************************************
+ */
+static int tsx_basic_provisional_response_test(void)
+{
+ unsigned duration;
+ int status;
+
+ PJ_LOG(3,(THIS_FILE," test3: basic sending 2xx final response"));
+
+ duration = (tp_flag & PJSIP_TRANSPORT_RELIABLE) ? 1 : 33;
+ duration += 2;
+
+ status = perform_test(TARGET_URI, FROM_URI, TEST3_BRANCH_ID, duration,
+ &pjsip_options_method, 1, 0, 0);
+
+ return status;
+}
+
+
+/*****************************************************************************
+ **
+ ** TEST4_BRANCH_ID: Absorbs retransmissions in TRYING state
+ ** TEST5_BRANCH_ID: Absorbs retransmissions in PROCEEDING state
+ ** TEST6_BRANCH_ID: Absorbs retransmissions in COMPLETED state
+ **
+ *****************************************************************************
+ */
+static int tsx_retransmit_last_response_test(const char *title,
+ char *branch_id,
+ int request_cnt,
+ int status_code)
+{
+ int status;
+
+ PJ_LOG(3,(THIS_FILE," %s", title));
+
+ status = perform_test(TARGET_URI, FROM_URI, branch_id, 5,
+ &pjsip_options_method,
+ request_cnt, 1000, 1);
+ if (status && status != TEST_TIMEOUT_ERROR)
+ return status;
+ if (!status) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting timeout"));
+ return -31;
+ }
+
+ terminate_our_tsx(status_code);
+ flush_events(100);
+
+ if (test_complete != 1)
+ return test_complete;
+
+ flush_events(100);
+ return 0;
+}
+
+/*****************************************************************************
+ **
+ ** TEST7_BRANCH_ID: INVITE non-2xx final response retransmission test
+ ** TEST8_BRANCH_ID: INVITE 2xx final response retransmission test
+ **
+ *****************************************************************************
+ */
+static int tsx_final_response_retransmission_test(void)
+{
+ int status;
+
+ PJ_LOG(3,(THIS_FILE,
+ " test7: INVITE non-2xx final response retransmission"));
+
+ status = perform_test(TARGET_URI, FROM_URI, TEST7_BRANCH_ID,
+ 33, /* Test duration must be greater than 32 secs */
+ &pjsip_invite_method, 1, 0, 0);
+ if (status != 0)
+ return status;
+
+ PJ_LOG(3,(THIS_FILE,
+ " test8: INVITE 2xx final response retransmission"));
+
+ status = perform_test(TARGET_URI, FROM_URI, TEST8_BRANCH_ID,
+ 33, /* Test duration must be greater than 32 secs */
+ &pjsip_invite_method, 1, 0, 0);
+ if (status != 0)
+ return status;
+
+ return 0;
+}
+
+
+/*****************************************************************************
+ **
+ ** TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must
+ ** cease when ACK is received
+ **
+ *****************************************************************************
+ */
+static int tsx_ack_test(void)
+{
+ int status;
+
+ PJ_LOG(3,(THIS_FILE,
+ " test9: receiving ACK for non-2xx final response"));
+
+ status = perform_test(TARGET_URI, FROM_URI, TEST9_BRANCH_ID,
+ 20, /* allow 5 retransmissions */
+ &pjsip_invite_method, 1, 0, 0);
+ if (status != 0)
+ return status;
+
+
+ return 0;
+}
+
+
+
+/*****************************************************************************
+ **
+ ** TEST10_BRANCH_ID: test transport failure in TRYING state.
+ ** TEST11_BRANCH_ID: test transport failure in PROCEEDING state.
+ ** TEST12_BRANCH_ID: test transport failure in CONNECTED state.
+ ** TEST13_BRANCH_ID: test transport failure in CONFIRMED state.
+ **
+ *****************************************************************************
+ */
+static int tsx_transport_failure_test(void)
+{
+ struct test_desc
+ {
+ int transport_delay;
+ int fail_delay;
+ char *branch_id;
+ char *title;
+ } tests[] =
+ {
+ { 0, 10, TEST10_BRANCH_ID, "test10: failed transport in TRYING state (no delay)" },
+ { 50, 10, TEST10_BRANCH_ID, "test10: failed transport in TRYING state (50 ms delay)" },
+ { 0, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (no delay)" },
+ { 50, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (50 ms delay)" },
+ { 0, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (no delay)" },
+ { 50, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (50 ms delay)" },
+ };
+ int i, status;
+
+ for (i=0; i<(int)PJ_ARRAY_SIZE(tests); ++i) {
+ pj_time_val fail_time, end_test, now;
+
+ PJ_LOG(3,(THIS_FILE, " %s", tests[i].title));
+ pjsip_loop_set_failure(loop, 0, NULL);
+ pjsip_loop_set_delay(loop, tests[i].transport_delay);
+
+ status = perform_test(TARGET_URI, FROM_URI, tests[i].branch_id,
+ 0, &pjsip_invite_method, 1, 0, 1);
+ if (status && status != TEST_TIMEOUT_ERROR)
+ return status;
+ if (!status) {
+ PJ_LOG(3,(THIS_FILE, " error: expecting timeout"));
+ return -40;
+ }
+
+ pj_gettimeofday(&fail_time);
+ fail_time.msec += tests[i].fail_delay;
+ pj_time_val_normalize(&fail_time);
+
+ do {
+ pj_time_val interval = { 0, 1 };
+ pj_gettimeofday(&now);
+ pjsip_endpt_handle_events(endpt, &interval);
+ } while (PJ_TIME_VAL_LT(now, fail_time));
+
+ pjsip_loop_set_failure(loop, 1, NULL);
+
+ end_test = now;
+ end_test.sec += 5;
+
+ do {
+ pj_time_val interval = { 0, 1 };
+ pj_gettimeofday(&now);
+ pjsip_endpt_handle_events(endpt, &interval);
+ } while (!test_complete && PJ_TIME_VAL_LT(now, end_test));
+
+ if (test_complete == 0) {
+ PJ_LOG(3,(THIS_FILE, " error: test has timed out"));
+ return -41;
+ }
+
+ if (test_complete != 1)
+ return test_complete;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ **
+ ** UAS Transaction Test.
+ **
+ *****************************************************************************
+ */
+int tsx_uas_test(struct tsx_test_param *param)
+{
+ pj_sockaddr_in addr;
+ pj_status_t status;
+
+ test_param = param;
+ tp_flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)param->type);
+
+ pj_ansi_sprintf(TARGET_URI, "sip:bob@127.0.0.1:%d;transport=%s",
+ param->port, param->tp_type);
+ pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s",
+ param->port, param->tp_type);
+
+ /* Check if loop transport is configured. */
+ status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM,
+ &addr, sizeof(addr), NULL, &loop);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(THIS_FILE, " Error: loop transport is not configured!"));
+ return -10;
+ }
+ /* Register modules. */
+ status = pjsip_endpt_register_module(endpt, &tsx_user);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to register module", status);
+ return -3;
+ }
+ status = pjsip_endpt_register_module(endpt, &msg_sender);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to register module", status);
+ return -4;
+ }
+
+ /* TEST1_BRANCH_ID: Basic 2xx final response.
+ * TEST2_BRANCH_ID: Basic non-2xx final response.
+ */
+ status = tsx_basic_final_response_test();
+ if (status != 0)
+ return status;
+
+ /* TEST3_BRANCH_ID: with provisional response
+ */
+ status = tsx_basic_provisional_response_test();
+ if (status != 0)
+ return status;
+
+ /* TEST4_BRANCH_ID: absorbs retransmissions in TRYING state
+ */
+ status = tsx_retransmit_last_response_test(TEST4_TITLE,
+ TEST4_BRANCH_ID,
+ TEST4_REQUEST_COUNT,
+ TEST4_STATUS_CODE);
+ if (status != 0)
+ return status;
+
+ /* TEST5_BRANCH_ID: retransmit last response in PROCEEDING state
+ */
+ status = tsx_retransmit_last_response_test(TEST5_TITLE,
+ TEST5_BRANCH_ID,
+ TEST5_REQUEST_COUNT,
+ TEST5_STATUS_CODE);
+ if (status != 0)
+ return status;
+
+ /* TEST6_BRANCH_ID: retransmit last response in COMPLETED state
+ * This only applies to non-reliable transports,
+ * since UAS transaction is destroyed as soon
+ * as final response is sent for reliable transports.
+ */
+ if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) {
+ status = tsx_retransmit_last_response_test(TEST6_TITLE,
+ TEST6_BRANCH_ID,
+ TEST6_REQUEST_COUNT,
+ TEST6_STATUS_CODE);
+ if (status != 0)
+ return status;
+ }
+
+ /* TEST7_BRANCH_ID: INVITE non-2xx final response retransmission test
+ * TEST8_BRANCH_ID: INVITE 2xx final response retransmission test
+ */
+ status = tsx_final_response_retransmission_test();
+ if (status != 0)
+ return status;
+
+ /* TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must
+ * cease when ACK is received
+ * Only applicable for non-reliable transports.
+ */
+ if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) {
+ status = tsx_ack_test();
+ if (status != 0)
+ return status;
+ }
+
+
+ /* TEST10_BRANCH_ID: test transport failure in TRYING state.
+ * TEST11_BRANCH_ID: test transport failure in PROCEEDING state.
+ * TEST12_BRANCH_ID: test transport failure in CONNECTED state.
+ * TEST13_BRANCH_ID: test transport failure in CONFIRMED state.
+ */
+ /* Only valid for loop-dgram */
+ if (param->type == PJSIP_TRANSPORT_LOOP_DGRAM) {
+ status = tsx_transport_failure_test();
+ if (status != 0)
+ return status;
+ }
+
+
+ /* Register modules. */
+ status = pjsip_endpt_unregister_module(endpt, &tsx_user);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to unregister module", status);
+ return -8;
+ }
+ status = pjsip_endpt_unregister_module(endpt, &msg_sender);
+ if (status != PJ_SUCCESS) {
+ app_perror(" Error: unable to unregister module", status);
+ return -9;
+ }
+
+
+ if (loop)
+ pjsip_transport_dec_ref(loop);
+
+ return 0;
+}
+
diff --git a/pjsip/src/test/txdata_test.c b/pjsip/src/test/txdata_test.c
new file mode 100644
index 0000000..fd111e2
--- /dev/null
+++ b/pjsip/src/test/txdata_test.c
@@ -0,0 +1,856 @@
+/* $Id: txdata_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+
+#define THIS_FILE "txdata_test.c"
+
+
+#define HFIND(msg,h,H) ((pjsip_##h##_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_##H, NULL))
+
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+# define LOOP 10000
+#else
+# define LOOP 100000
+#endif
+
+
+/*
+ * This tests various core message creation functions.
+ */
+static int core_txdata_test(void)
+{
+ pj_status_t status;
+ pj_str_t target, from, to, contact, body;
+ pjsip_rx_data dummy_rdata;
+ pjsip_tx_data *invite, *invite2, *cancel, *response, *ack;
+
+ PJ_LOG(3,(THIS_FILE, " core transmit data test"));
+
+ /* Create INVITE request. */
+ target = pj_str("tel:+1");
+ from = pj_str("tel:+0");
+ to = pj_str("tel:+1");
+ contact = pj_str("Bob <sip:+0@example.com;user=phone>");
+ body = pj_str("Hello world!");
+
+ status = pjsip_endpt_create_request( endpt, &pjsip_invite_method, &target,
+ &from, &to, &contact, NULL, 10, &body,
+ &invite);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create request", status);
+ return -10;
+ }
+
+ /* Buffer must be invalid. */
+ if (pjsip_tx_data_is_valid(invite) != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid"));
+ return -14;
+ }
+ /* Reference counter must be set to 1. */
+ if (pj_atomic_get(invite->ref_cnt) != 1) {
+ PJ_LOG(3,(THIS_FILE, " error: invalid reference counter"));
+ return -15;
+ }
+ /* Check message type. */
+ if (invite->msg->type != PJSIP_REQUEST_MSG)
+ return -16;
+ /* Check method. */
+ if (invite->msg->line.req.method.id != PJSIP_INVITE_METHOD)
+ return -17;
+
+ /* Check that mandatory headers are present. */
+ if (HFIND(invite->msg, from, FROM) == 0)
+ return -20;
+ if (HFIND(invite->msg, to, TO) == 0)
+ return -21;
+ if (HFIND(invite->msg, contact, CONTACT) == 0)
+ return -22;
+ if (HFIND(invite->msg, cid, CALL_ID) == 0)
+ return -23;
+ if (HFIND(invite->msg, cseq, CSEQ) == 0)
+ return -24;
+ do {
+ pjsip_via_hdr *via = HFIND(invite->msg, via, VIA);
+ if (via == NULL)
+ return -25;
+ /* Branch param must be empty. */
+ if (via->branch_param.slen != 0)
+ return -26;
+ } while (0);
+ if (invite->msg->body == NULL)
+ return -28;
+
+ /* Create another INVITE request from first request. */
+ status = pjsip_endpt_create_request_from_hdr( endpt, &pjsip_invite_method,
+ invite->msg->line.req.uri,
+ HFIND(invite->msg,from,FROM),
+ HFIND(invite->msg,to,TO),
+ HFIND(invite->msg,contact,CONTACT),
+ HFIND(invite->msg,cid,CALL_ID),
+ 10, &body, &invite2);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: create second request failed", status);
+ return -30;
+ }
+
+ /* Buffer must be invalid. */
+ if (pjsip_tx_data_is_valid(invite2) != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid"));
+ return -34;
+ }
+ /* Reference counter must be set to 1. */
+ if (pj_atomic_get(invite2->ref_cnt) != 1) {
+ PJ_LOG(3,(THIS_FILE, " error: invalid reference counter"));
+ return -35;
+ }
+ /* Check message type. */
+ if (invite2->msg->type != PJSIP_REQUEST_MSG)
+ return -36;
+ /* Check method. */
+ if (invite2->msg->line.req.method.id != PJSIP_INVITE_METHOD)
+ return -37;
+
+ /* Check that mandatory headers are again present. */
+ if (HFIND(invite2->msg, from, FROM) == 0)
+ return -40;
+ if (HFIND(invite2->msg, to, TO) == 0)
+ return -41;
+ if (HFIND(invite2->msg, contact, CONTACT) == 0)
+ return -42;
+ if (HFIND(invite2->msg, cid, CALL_ID) == 0)
+ return -43;
+ if (HFIND(invite2->msg, cseq, CSEQ) == 0)
+ return -44;
+ if (HFIND(invite2->msg, via, VIA) == 0)
+ return -45;
+ /*
+ if (HFIND(invite2->msg, ctype, CONTENT_TYPE) == 0)
+ return -46;
+ if (HFIND(invite2->msg, clen, CONTENT_LENGTH) == 0)
+ return -47;
+ */
+ if (invite2->msg->body == NULL)
+ return -48;
+
+ /* Done checking invite2. We can delete this. */
+ if (pjsip_tx_data_dec_ref(invite2) != PJSIP_EBUFDESTROYED) {
+ PJ_LOG(3,(THIS_FILE, " error: request buffer not destroyed!"));
+ return -49;
+ }
+
+ /* Initialize dummy rdata (to simulate receiving a request)
+ * We should never do this in real application, as there are many
+ * many more fields need to be initialized!!
+ */
+ dummy_rdata.msg_info.cid = HFIND(invite->msg, cid, CALL_ID);
+ dummy_rdata.msg_info.clen = NULL;
+ dummy_rdata.msg_info.cseq = HFIND(invite->msg, cseq, CSEQ);
+ dummy_rdata.msg_info.ctype = NULL;
+ dummy_rdata.msg_info.from = HFIND(invite->msg, from, FROM);
+ dummy_rdata.msg_info.max_fwd = NULL;
+ dummy_rdata.msg_info.msg = invite->msg;
+ dummy_rdata.msg_info.record_route = NULL;
+ dummy_rdata.msg_info.require = NULL;
+ dummy_rdata.msg_info.route = NULL;
+ dummy_rdata.msg_info.to = HFIND(invite->msg, to, TO);
+ dummy_rdata.msg_info.via = HFIND(invite->msg, via, VIA);
+
+ /* Create a response message for the request. */
+ status = pjsip_endpt_create_response( endpt, &dummy_rdata, 301, NULL,
+ &response);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create response", status);
+ return -50;
+ }
+
+ /* Buffer must be invalid. */
+ if (pjsip_tx_data_is_valid(response) != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid"));
+ return -54;
+ }
+ /* Check reference counter. */
+ if (pj_atomic_get(response->ref_cnt) != 1) {
+ PJ_LOG(3,(THIS_FILE, " error: invalid ref count in response"));
+ return -55;
+ }
+ /* Check message type. */
+ if (response->msg->type != PJSIP_RESPONSE_MSG)
+ return -56;
+ /* Check correct status is set. */
+ if (response->msg->line.status.code != 301)
+ return -57;
+
+ /* Check that mandatory headers are again present. */
+ if (HFIND(response->msg, from, FROM) == 0)
+ return -60;
+ if (HFIND(response->msg, to, TO) == 0)
+ return -61;
+ /*
+ if (HFIND(response->msg, contact, CONTACT) == 0)
+ return -62;
+ */
+ if (HFIND(response->msg, cid, CALL_ID) == 0)
+ return -63;
+ if (HFIND(response->msg, cseq, CSEQ) == 0)
+ return -64;
+ if (HFIND(response->msg, via, VIA) == 0)
+ return -65;
+
+ /* This response message will be used later when creating ACK */
+
+ /* Create CANCEL request for the original request. */
+ status = pjsip_endpt_create_cancel( endpt, invite, &cancel);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create CANCEL request", status);
+ return -80;
+ }
+
+ /* Buffer must be invalid. */
+ if (pjsip_tx_data_is_valid(cancel) != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid"));
+ return -84;
+ }
+ /* Check reference counter. */
+ if (pj_atomic_get(cancel->ref_cnt) != 1) {
+ PJ_LOG(3,(THIS_FILE, " error: invalid ref count in CANCEL request"));
+ return -85;
+ }
+ /* Check message type. */
+ if (cancel->msg->type != PJSIP_REQUEST_MSG)
+ return -86;
+ /* Check method. */
+ if (cancel->msg->line.req.method.id != PJSIP_CANCEL_METHOD)
+ return -87;
+
+ /* Check that mandatory headers are again present. */
+ if (HFIND(cancel->msg, from, FROM) == 0)
+ return -90;
+ if (HFIND(cancel->msg, to, TO) == 0)
+ return -91;
+ /*
+ if (HFIND(cancel->msg, contact, CONTACT) == 0)
+ return -92;
+ */
+ if (HFIND(cancel->msg, cid, CALL_ID) == 0)
+ return -93;
+ if (HFIND(cancel->msg, cseq, CSEQ) == 0)
+ return -94;
+ if (HFIND(cancel->msg, via, VIA) == 0)
+ return -95;
+
+ /* Done checking CANCEL request. */
+ if (pjsip_tx_data_dec_ref(cancel) != PJSIP_EBUFDESTROYED) {
+ PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!"));
+ return -99;
+ }
+
+ /* Modify dummy_rdata to simulate receiving response. */
+ pj_bzero(&dummy_rdata, sizeof(dummy_rdata));
+ dummy_rdata.msg_info.msg = response->msg;
+ dummy_rdata.msg_info.to = HFIND(response->msg, to, TO);
+
+ /* Create ACK request */
+ status = pjsip_endpt_create_ack( endpt, invite, &dummy_rdata, &ack );
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(THIS_FILE, " error: unable to create ACK"));
+ return -100;
+ }
+ /* Buffer must be invalid. */
+ if (pjsip_tx_data_is_valid(ack) != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid"));
+ return -104;
+ }
+ /* Check reference counter. */
+ if (pj_atomic_get(ack->ref_cnt) != 1) {
+ PJ_LOG(3,(THIS_FILE, " error: invalid ref count in ACK request"));
+ return -105;
+ }
+ /* Check message type. */
+ if (ack->msg->type != PJSIP_REQUEST_MSG)
+ return -106;
+ /* Check method. */
+ if (ack->msg->line.req.method.id != PJSIP_ACK_METHOD)
+ return -107;
+ /* Check Request-URI is present. */
+ if (ack->msg->line.req.uri == NULL)
+ return -108;
+
+ /* Check that mandatory headers are again present. */
+ if (HFIND(ack->msg, from, FROM) == 0)
+ return -110;
+ if (HFIND(ack->msg, to, TO) == 0)
+ return -111;
+ if (HFIND(ack->msg, cid, CALL_ID) == 0)
+ return -112;
+ if (HFIND(ack->msg, cseq, CSEQ) == 0)
+ return -113;
+ if (HFIND(ack->msg, via, VIA) == 0)
+ return -114;
+ if (ack->msg->body != NULL)
+ return -115;
+
+ /* Done checking invite message. */
+ if (pjsip_tx_data_dec_ref(invite) != PJSIP_EBUFDESTROYED) {
+ PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!"));
+ return -120;
+ }
+
+ /* Done checking response message. */
+ if (pjsip_tx_data_dec_ref(response) != PJSIP_EBUFDESTROYED) {
+ PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!"));
+ return -130;
+ }
+
+ /* Done checking ack message. */
+ if (pjsip_tx_data_dec_ref(ack) != PJSIP_EBUFDESTROYED) {
+ PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!"));
+ return -140;
+ }
+
+ /* Done. */
+ return 0;
+}
+
+
+
+/*
+ * This test demonstrate the bug as reported in:
+ * http://bugzilla.pjproject.net/show_bug.cgi?id=49
+ */
+#if INCLUDE_GCC_TEST
+static int gcc_test()
+{
+ char msgbuf[512];
+ pj_str_t target = pj_str("sip:alice@wonderland:5061;x-param=param%201"
+ "?X-Hdr-1=Header%201"
+ "&X-Empty-Hdr=");
+ pjsip_tx_data *tdata;
+ pjsip_parser_err_report err_list;
+ pjsip_msg *msg;
+ int len;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " header param in URI to create request"));
+
+ /* Create request with header param in target URI. */
+ status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target,
+ &target, &target, &target, NULL, -1,
+ NULL, &tdata);
+ if (status != 0) {
+ app_perror(" error: Unable to create request", status);
+ return -200;
+ }
+
+ /* Print and parse the request.
+ * We'll check that header params are not present in
+ */
+ len = pjsip_msg_print(tdata->msg, msgbuf, sizeof(msgbuf));
+ if (len < 1) {
+ PJ_LOG(3,(THIS_FILE, " error: printing message"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -250;
+ }
+ msgbuf[len] = '\0';
+
+ PJ_LOG(5,(THIS_FILE, "%d bytes request created:--begin-msg--\n"
+ "%s\n"
+ "--end-msg--", len, msgbuf));
+
+ /* Now parse the message. */
+ pj_list_init(&err_list);
+ msg = pjsip_parse_msg( tdata->pool, msgbuf, len, &err_list);
+ if (msg == NULL) {
+ pjsip_parser_err_report *e;
+
+ PJ_LOG(3,(THIS_FILE, " error: parsing message message"));
+
+ e = err_list.next;
+ while (e != &err_list) {
+ PJ_LOG(3,(THIS_FILE, " %s in line %d col %d hname=%.*s",
+ pj_exception_id_name(e->except_code),
+ e->line, e->col+1,
+ (int)e->hname.slen,
+ e->hname.ptr));
+ e = e->next;
+ }
+
+ pjsip_tx_data_dec_ref(tdata);
+ return -255;
+ }
+
+ pjsip_tx_data_dec_ref(tdata);
+ return 0;
+}
+#endif
+
+
+/* This tests the request creating functions against the following
+ * requirements:
+ * - header params in URI creates header in the request.
+ * - method and headers params are correctly shown or hidden in
+ * request URI, From, To, and Contact header.
+ */
+static int txdata_test_uri_params(void)
+{
+ char msgbuf[512];
+ pj_str_t target = pj_str("sip:alice@wonderland:5061;x-param=param%201"
+ "?X-Hdr-1=Header%201"
+ "&X-Empty-Hdr=");
+ pj_str_t contact;
+ pj_str_t pname = pj_str("x-param");
+ pj_str_t hname = pj_str("X-Hdr-1");
+ pj_str_t hemptyname = pj_str("X-Empty-Hdr");
+ pjsip_from_hdr *from_hdr;
+ pjsip_to_hdr *to_hdr;
+ pjsip_contact_hdr *contact_hdr;
+ pjsip_generic_string_hdr *hdr;
+ pjsip_tx_data *tdata;
+ pjsip_sip_uri *uri;
+ pjsip_param *param;
+ pjsip_via_hdr *via;
+ pjsip_parser_err_report err_list;
+ pjsip_msg *msg;
+ int len;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " header param in URI to create request"));
+
+ /* Due to #930, contact argument is now parsed as Contact header, so
+ * must enclose it with <> to make it be parsed as URI.
+ */
+ pj_ansi_snprintf(msgbuf, sizeof(msgbuf), "<%.*s>",
+ (int)target.slen, target.ptr);
+ contact.ptr = msgbuf;
+ contact.slen = strlen(msgbuf);
+
+ /* Create request with header param in target URI. */
+ status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target,
+ &target, &target, &contact, NULL, -1,
+ NULL, &tdata);
+ if (status != 0) {
+ app_perror(" error: Unable to create request", status);
+ return -200;
+ }
+
+ /* Fill up the Via header to prevent syntax error on parsing */
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ via->transport = pj_str("TCP");
+ via->sent_by.host = pj_str("127.0.0.1");
+
+ /* Print and parse the request.
+ * We'll check that header params are not present in
+ */
+ len = pjsip_msg_print(tdata->msg, msgbuf, sizeof(msgbuf));
+ if (len < 1) {
+ PJ_LOG(3,(THIS_FILE, " error: printing message"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -250;
+ }
+ msgbuf[len] = '\0';
+
+ PJ_LOG(5,(THIS_FILE, "%d bytes request created:--begin-msg--\n"
+ "%s\n"
+ "--end-msg--", len, msgbuf));
+
+ /* Now parse the message. */
+ pj_list_init(&err_list);
+ msg = pjsip_parse_msg( tdata->pool, msgbuf, len, &err_list);
+ if (msg == NULL) {
+ pjsip_parser_err_report *e;
+
+ PJ_LOG(3,(THIS_FILE, " error: parsing message message"));
+
+ e = err_list.next;
+ while (e != &err_list) {
+ PJ_LOG(3,(THIS_FILE, " %s in line %d col %d hname=%.*s",
+ pj_exception_id_name(e->except_code),
+ e->line, e->col+1,
+ (int)e->hname.slen,
+ e->hname.ptr));
+ e = e->next;
+ }
+
+ pjsip_tx_data_dec_ref(tdata);
+ return -256;
+ }
+
+ /* Check the existence of port, other_param, and header param.
+ * Port is now allowed in To and From header.
+ */
+ /* Port in request URI. */
+ uri = (pjsip_sip_uri*) pjsip_uri_get_uri(msg->line.req.uri);
+ if (uri->port != 5061) {
+ PJ_LOG(3,(THIS_FILE, " error: port not present in request URI"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -260;
+ }
+ /* other_param in request_uri */
+ param = pjsip_param_find(&uri->other_param, &pname);
+ if (param == NULL || pj_strcmp2(&param->value, "param 1") != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: x-param not present in request URI"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -261;
+ }
+ /* header param in request uri. */
+ if (!pj_list_empty(&uri->header_param)) {
+ PJ_LOG(3,(THIS_FILE, " error: hparam in request URI"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -262;
+ }
+
+ /* Port in From header. */
+ from_hdr = (pjsip_from_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_FROM, NULL);
+ uri = (pjsip_sip_uri*) pjsip_uri_get_uri(from_hdr->uri);
+ if (uri->port != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: port most not exist in From header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -270;
+ }
+ /* other_param in From header */
+ param = pjsip_param_find(&uri->other_param, &pname);
+ if (param == NULL || pj_strcmp2(&param->value, "param 1") != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: x-param not present in From header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -271;
+ }
+ /* header param in From header. */
+ if (!pj_list_empty(&uri->header_param)) {
+ PJ_LOG(3,(THIS_FILE, " error: hparam in From header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -272;
+ }
+
+
+ /* Port in To header. */
+ to_hdr = (pjsip_to_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_TO, NULL);
+ uri = (pjsip_sip_uri*) pjsip_uri_get_uri(to_hdr->uri);
+ if (uri->port != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: port most not exist in To header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -280;
+ }
+ /* other_param in To header */
+ param = pjsip_param_find(&uri->other_param, &pname);
+ if (param == NULL || pj_strcmp2(&param->value, "param 1") != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: x-param not present in To header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -281;
+ }
+ /* header param in From header. */
+ if (!pj_list_empty(&uri->header_param)) {
+ PJ_LOG(3,(THIS_FILE, " error: hparam in To header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -282;
+ }
+
+
+
+ /* Port in Contact header. */
+ contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL);
+ uri = (pjsip_sip_uri*) pjsip_uri_get_uri(contact_hdr->uri);
+ if (uri->port != 5061) {
+ PJ_LOG(3,(THIS_FILE, " error: port not present in Contact header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -290;
+ }
+ /* other_param in Contact header */
+ param = pjsip_param_find(&uri->other_param, &pname);
+ if (param == NULL || pj_strcmp2(&param->value, "param 1") != 0) {
+ PJ_LOG(3,(THIS_FILE, " error: x-param not present in Contact header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -291;
+ }
+ /* header param in Contact header. */
+ if (pj_list_empty(&uri->header_param)) {
+ PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -292;
+ }
+ /* Check for X-Hdr-1 */
+ param = pjsip_param_find(&uri->header_param, &hname);
+ if (param == NULL || pj_strcmp2(&param->value, "Header 1")!=0) {
+ PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -293;
+ }
+ /* Check for X-Empty-Hdr */
+ param = pjsip_param_find(&uri->header_param, &hemptyname);
+ if (param == NULL || pj_strcmp2(&param->value, "")!=0) {
+ PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -294;
+ }
+
+
+ /* Check that headers are present in the request. */
+ hdr = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(msg, &hname, NULL);
+ if (hdr == NULL || pj_strcmp2(&hdr->hvalue, "Header 1")!=0) {
+ PJ_LOG(3,(THIS_FILE, " error: header X-Hdr-1 not created"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -300;
+ }
+
+ hdr = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(msg, &hemptyname, NULL);
+ if (hdr == NULL || pj_strcmp2(&param->value, "")!=0) {
+ PJ_LOG(3,(THIS_FILE, " error: header X-Empty-Hdr not created"));
+ pjsip_tx_data_dec_ref(tdata);
+ return -330;
+ }
+
+ pjsip_tx_data_dec_ref(tdata);
+ return 0;
+}
+
+
+/*
+ * create request benchmark
+ */
+static int create_request_bench(pj_timestamp *p_elapsed)
+{
+ enum { COUNT = 100 };
+ unsigned i, j;
+ pjsip_tx_data *tdata[COUNT];
+ pj_timestamp t1, t2, elapsed;
+ pj_status_t status;
+
+ pj_str_t str_target = pj_str("sip:someuser@someprovider.com");
+ pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>");
+ pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>");
+ pj_str_t str_contact = str_from;
+
+ elapsed.u64 = 0;
+
+ for (i=0; i<LOOP; i+=COUNT) {
+ pj_bzero(tdata, sizeof(tdata));
+
+ pj_get_timestamp(&t1);
+
+ for (j=0; j<COUNT; ++j) {
+ status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
+ &str_target, &str_from, &str_to,
+ &str_contact, NULL, -1, NULL,
+ &tdata[j]);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create request", status);
+ goto on_error;
+ }
+ }
+
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&elapsed, &t2);
+
+ for (j=0; j<COUNT; ++j)
+ pjsip_tx_data_dec_ref(tdata[j]);
+ }
+
+ p_elapsed->u64 = elapsed.u64;
+ return PJ_SUCCESS;
+
+on_error:
+ for (i=0; i<COUNT; ++i) {
+ if (tdata[i])
+ pjsip_tx_data_dec_ref(tdata[i]);
+ }
+ return -400;
+}
+
+
+
+/*
+ * create response benchmark
+ */
+static int create_response_bench(pj_timestamp *p_elapsed)
+{
+ enum { COUNT = 100 };
+ unsigned i, j;
+ pjsip_via_hdr *via;
+ pjsip_rx_data rdata;
+ pjsip_tx_data *request;
+ pjsip_tx_data *tdata[COUNT];
+ pj_timestamp t1, t2, elapsed;
+ pj_status_t status;
+
+ /* Create the request first. */
+ pj_str_t str_target = pj_str("sip:someuser@someprovider.com");
+ pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>");
+ pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>");
+ pj_str_t str_contact = str_from;
+
+ status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
+ &str_target, &str_from, &str_to,
+ &str_contact, NULL, -1, NULL,
+ &request);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create request", status);
+ return status;
+ }
+
+ /* Create several Via headers */
+ via = pjsip_via_hdr_create(request->pool);
+ via->sent_by.host = pj_str("192.168.0.7");
+ via->sent_by.port = 5061;
+ via->transport = pj_str("udp");
+ via->rport_param = 0;
+ via->branch_param = pj_str("012345678901234567890123456789");
+ via->recvd_param = pj_str("192.168.0.7");
+ pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*) pjsip_hdr_clone(request->pool, via));
+ pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*) pjsip_hdr_clone(request->pool, via));
+ pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*)via);
+
+
+ /* Create "dummy" rdata from the tdata */
+ pj_bzero(&rdata, sizeof(pjsip_rx_data));
+ rdata.tp_info.pool = request->pool;
+ rdata.msg_info.msg = request->msg;
+ rdata.msg_info.from = (pjsip_from_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL);
+ rdata.msg_info.to = (pjsip_to_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL);
+ rdata.msg_info.cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL);
+ rdata.msg_info.cid = (pjsip_cid_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL);
+ rdata.msg_info.via = via;
+
+ /*
+ * Now benchmark create_response
+ */
+ elapsed.u64 = 0;
+
+ for (i=0; i<LOOP; i+=COUNT) {
+ pj_bzero(tdata, sizeof(tdata));
+
+ pj_get_timestamp(&t1);
+
+ for (j=0; j<COUNT; ++j) {
+ status = pjsip_endpt_create_response(endpt, &rdata, 200, NULL, &tdata[j]);
+ if (status != PJ_SUCCESS) {
+ app_perror(" error: unable to create request", status);
+ goto on_error;
+ }
+ }
+
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&elapsed, &t2);
+
+ for (j=0; j<COUNT; ++j)
+ pjsip_tx_data_dec_ref(tdata[j]);
+ }
+
+ p_elapsed->u64 = elapsed.u64;
+ pjsip_tx_data_dec_ref(request);
+ return PJ_SUCCESS;
+
+on_error:
+ for (i=0; i<COUNT; ++i) {
+ if (tdata[i])
+ pjsip_tx_data_dec_ref(tdata[i]);
+ }
+ return -400;
+}
+
+
+int txdata_test(void)
+{
+ enum { REPEAT = 4 };
+ unsigned i, msgs;
+ pj_timestamp usec[REPEAT], min, freq;
+ int status;
+
+ status = pj_get_timestamp_freq(&freq);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = core_txdata_test();
+ if (status != 0)
+ return status;
+
+#if INCLUDE_GCC_TEST
+ status = gcc_test();
+ if (status != 0)
+ return status;
+#endif
+
+ status = txdata_test_uri_params();
+ if (status != 0)
+ return status;
+
+
+ /*
+ * Benchmark create_request()
+ */
+ PJ_LOG(3,(THIS_FILE, " benchmarking request creation:"));
+ for (i=0; i<REPEAT; ++i) {
+ PJ_LOG(3,(THIS_FILE, " test %d of %d..",
+ i+1, REPEAT));
+ status = create_request_bench(&usec[i]);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF);
+ for (i=0; i<REPEAT; ++i) {
+ if (usec[i].u64 < min.u64) min.u64 = usec[i].u64;
+ }
+
+ msgs = (unsigned)(freq.u64 * LOOP / min.u64);
+
+ PJ_LOG(3,(THIS_FILE, " Requests created at %d requests/sec", msgs));
+
+ report_ival("create-request-per-sec",
+ msgs, "msg/sec",
+ "Number of typical request messages that can be created "
+ "per second with <tt>pjsip_endpt_create_request()</tt>");
+
+
+ /*
+ * Benchmark create_response()
+ */
+ PJ_LOG(3,(THIS_FILE, " benchmarking response creation:"));
+ for (i=0; i<REPEAT; ++i) {
+ PJ_LOG(3,(THIS_FILE, " test %d of %d..",
+ i+1, REPEAT));
+ status = create_response_bench(&usec[i]);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF);
+ for (i=0; i<REPEAT; ++i) {
+ if (usec[i].u64 < min.u64) min.u64 = usec[i].u64;
+ }
+
+ msgs = (unsigned)(freq.u64 * LOOP / min.u64);
+
+ PJ_LOG(3,(THIS_FILE, " Responses created at %d responses/sec", msgs));
+
+ report_ival("create-response-per-sec",
+ msgs, "msg/sec",
+ "Number of typical response messages that can be created "
+ "per second with <tt>pjsip_endpt_create_response()</tt>");
+
+
+ return 0;
+}
+
diff --git a/pjsip/src/test/uri_test.c b/pjsip/src/test/uri_test.c
new file mode 100644
index 0000000..8527faa
--- /dev/null
+++ b/pjsip/src/test/uri_test.c
@@ -0,0 +1,1097 @@
+/* $Id: uri_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE "uri_test.c"
+
+
+#define ALPHANUM "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "0123456789"
+#define MARK "-_.!~*'()"
+#define USER_CHAR ALPHANUM MARK "&=+$,;?/"
+#define PASS_CHAR ALPHANUM MARK "&=+$,"
+#define PARAM_CHAR ALPHANUM MARK "[]/:&+$"
+
+#define POOL_SIZE 8000
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+# define LOOP_COUNT 10000
+#else
+# define LOOP_COUNT 40000
+#endif
+#define AVERAGE_URL_LEN 80
+#define THREAD_COUNT 4
+
+static struct
+{
+ pj_highprec_t parse_len, print_len, cmp_len;
+ pj_timestamp parse_time, print_time, cmp_time;
+} var;
+
+
+/* URI creator functions. */
+static pjsip_uri *create_uri0( pj_pool_t *pool );
+static pjsip_uri *create_uri1( pj_pool_t *pool );
+static pjsip_uri *create_uri2( pj_pool_t *pool );
+static pjsip_uri *create_uri3( pj_pool_t *pool );
+static pjsip_uri *create_uri4( pj_pool_t *pool );
+static pjsip_uri *create_uri5( pj_pool_t *pool );
+static pjsip_uri *create_uri6( pj_pool_t *pool );
+static pjsip_uri *create_uri7( pj_pool_t *pool );
+static pjsip_uri *create_uri8( pj_pool_t *pool );
+static pjsip_uri *create_uri9( pj_pool_t *pool );
+static pjsip_uri *create_uri10( pj_pool_t *pool );
+static pjsip_uri *create_uri11( pj_pool_t *pool );
+static pjsip_uri *create_uri12( pj_pool_t *pool );
+static pjsip_uri *create_uri13( pj_pool_t *pool );
+static pjsip_uri *create_uri14( pj_pool_t *pool );
+static pjsip_uri *create_uri15( pj_pool_t *pool );
+static pjsip_uri *create_uri16( pj_pool_t *pool );
+static pjsip_uri *create_uri17( pj_pool_t *pool );
+static pjsip_uri *create_uri25( pj_pool_t *pool );
+static pjsip_uri *create_uri26( pj_pool_t *pool );
+static pjsip_uri *create_uri27( pj_pool_t *pool );
+static pjsip_uri *create_uri28( pj_pool_t *pool );
+static pjsip_uri *create_uri29( pj_pool_t *pool );
+static pjsip_uri *create_uri30( pj_pool_t *pool );
+static pjsip_uri *create_uri31( pj_pool_t *pool );
+static pjsip_uri *create_uri32( pj_pool_t *pool );
+static pjsip_uri *create_uri33( pj_pool_t *pool );
+static pjsip_uri *create_uri34( pj_pool_t *pool );
+static pjsip_uri *create_uri35( pj_pool_t *pool );
+static pjsip_uri *create_uri36( pj_pool_t *pool );
+static pjsip_uri *create_uri37( pj_pool_t *pool );
+static pjsip_uri *create_uri38( pj_pool_t *pool );
+static pjsip_uri *create_dummy( pj_pool_t *pool );
+
+#define ERR_NOT_EQUAL -1001
+#define ERR_SYNTAX_ERR -1002
+
+struct uri_test
+{
+ pj_status_t status;
+ char str[PJSIP_MAX_URL_SIZE];
+ pjsip_uri *(*creator)(pj_pool_t *pool);
+ const char *printed;
+ pj_size_t len;
+} uri_test_array[] =
+{
+ {
+ PJ_SUCCESS,
+ "sip:localhost",
+ &create_uri0
+ },
+ {
+ PJ_SUCCESS,
+ "sip:user@localhost",
+ &create_uri1
+ },
+ {
+ PJ_SUCCESS,
+ "sip:user:password@localhost:5060",
+ &create_uri2, },
+ {
+ /* Port is specified should not match unspecified port. */
+ ERR_NOT_EQUAL,
+ "sip:localhost:5060",
+ &create_uri3
+ },
+ {
+ /* All recognized parameters. */
+ PJ_SUCCESS,
+ "sip:localhost;transport=tcp;user=ip;ttl=255;lr;maddr=127.0.0.1;method=ACK",
+ &create_uri4
+ },
+ {
+ /* Params mixed with other params and header params. */
+ PJ_SUCCESS,
+ "sip:localhost;pickup=hurry;user=phone;message=I%20am%20sorry"
+ "?Subject=Hello%20There&Server=SIP%20Server",
+ &create_uri5
+ },
+ {
+ /* SIPS. */
+ PJ_SUCCESS,
+ "sips:localhost",
+ &create_uri6,
+ },
+ {
+ /* Name address */
+ PJ_SUCCESS,
+ "<sip:localhost>",
+ &create_uri7
+ },
+ {
+ /* Name address with display name and SIPS scheme with some redundant
+ * whitespaced.
+ */
+ PJ_SUCCESS,
+ " Power Administrator <sips:localhost>",
+ &create_uri8
+ },
+ {
+ /* Name address. */
+ PJ_SUCCESS,
+ " \"User\" <sip:user@localhost:5071>",
+ &create_uri9
+ },
+ {
+ /* Escaped sequence in display name (display=Strange User\"\\\"). */
+ PJ_SUCCESS,
+ " \"Strange User\\\"\\\\\\\"\" <sip:localhost>",
+ &create_uri10,
+ },
+ {
+ /* Errorneous escaping in display name. */
+ ERR_SYNTAX_ERR,
+ " \"Rogue User\\\" <sip:localhost>",
+ &create_uri11,
+ },
+ {
+ /* Dangling quote in display name, but that should be OK. */
+ PJ_SUCCESS,
+ "Strange User\" <sip:localhost>",
+ &create_uri12,
+ },
+ {
+ /* Special characters in parameter value must be quoted. */
+ PJ_SUCCESS,
+ "sip:localhost;pvalue=\"hello world\"",
+ &create_uri13,
+ },
+ {
+ /* Excercise strange character sets allowed in display, user, password,
+ * host, and port.
+ */
+ PJ_SUCCESS,
+ "This is -. !% *_+`'~ me <sip:a19A&=+$,;?/%2c:%40a&Zz=+$,@"
+ "my_proxy09.MY-domain.com:9801>",
+ &create_uri14,
+ },
+ {
+ /* Another excercise to the allowed character sets to the hostname. */
+ PJ_SUCCESS,
+ "sip:" ALPHANUM "-_.com",
+ &create_uri15,
+ },
+ {
+ /* Another excercise to the allowed character sets to the username
+ * and password.
+ */
+ PJ_SUCCESS,
+ "sip:" USER_CHAR ":" PASS_CHAR "@host",
+ &create_uri16,
+ },
+ {
+ /* Excercise to the pname and pvalue, and mixup of other-param
+ * between 'recognized' params.
+ */
+ PJ_SUCCESS,
+ "sip:host;user=ip;" PARAM_CHAR "%21=" PARAM_CHAR "%21"
+ ";lr;other=1;transport=sctp;other2",
+ &create_uri17,
+ },
+ {
+ /* 18: This should trigger syntax error. */
+ ERR_SYNTAX_ERR,
+ "sip:",
+ &create_dummy,
+ },
+ {
+ /* 19: Syntax error: whitespace after scheme. */
+ ERR_SYNTAX_ERR,
+ "sip :host",
+ &create_dummy,
+ },
+ {
+ /* 20: Syntax error: whitespace before hostname. */
+ ERR_SYNTAX_ERR,
+ "sip: host",
+ &create_dummy,
+ },
+ {
+ /* 21: Syntax error: invalid port. */
+ ERR_SYNTAX_ERR,
+ "sip:user:password",
+ &create_dummy,
+ },
+ {
+ /* 22: Syntax error: no host. */
+ ERR_SYNTAX_ERR,
+ "sip:user@",
+ &create_dummy,
+ },
+ {
+ /* 23: Syntax error: no user/host. */
+ ERR_SYNTAX_ERR,
+ "sip:@",
+ &create_dummy,
+ },
+ {
+ /* 24: Syntax error: empty string. */
+ ERR_SYNTAX_ERR,
+ "",
+ &create_dummy,
+ },
+ {
+ /* 25: Simple tel: URI with global context */
+ PJ_SUCCESS,
+ "tel:+1-201-555-0123",
+ &create_uri25,
+ "tel:+1-201-555-0123"
+ },
+ {
+ /* 26: Simple tel: URI with local context */
+ PJ_SUCCESS,
+ "tel:7042;phone-context=example.com",
+ &create_uri26,
+ "tel:7042;phone-context=example.com"
+ },
+ {
+ /* 27: Simple tel: URI with local context */
+ PJ_SUCCESS,
+ "tel:863-1234;phone-context=+1-914-555",
+ &create_uri27,
+ "tel:863-1234;phone-context=+1-914-555"
+ },
+ {
+ /* 28: Comparison between local and global number */
+ ERR_NOT_EQUAL,
+ "tel:+1",
+ &create_uri28,
+ "tel:+1"
+ },
+ {
+ /* 29: tel: with some visual chars and spaces */
+ PJ_SUCCESS,
+ "tel:(44).1234-*#+Deaf",
+ &create_uri29,
+ "tel:(44).1234-*#+Deaf"
+ },
+ {
+ /* 30: isub parameters */
+ PJ_SUCCESS,
+ "tel:+1;isub=/:@&$,-_.!~*'()[]/:&$aA1%21+=",
+ &create_uri30,
+ "tel:+1;isub=/:@&$,-_.!~*'()[]/:&$aA1!+%3d"
+ },
+ {
+ /* 31: extension number parsing and encoding */
+ PJ_SUCCESS,
+ "tel:+1;ext=+123",
+ &create_uri31,
+ "tel:+1;ext=%2b123"
+ },
+ {
+ /* 32: context parameter parsing and encoding */
+ PJ_SUCCESS,
+ "tel:911;phone-context=+1-911",
+ &create_uri32,
+ "tel:911;phone-context=+1-911"
+ },
+ {
+ /* 33: case-insensitive comparison */
+ PJ_SUCCESS,
+ "tel:911;phone-context=emergency.example.com",
+ &create_uri33,
+ "tel:911;phone-context=emergency.example.com"
+ },
+ {
+ /* 34: parameter only appears in one URL */
+ ERR_NOT_EQUAL,
+ "tel:911;p1=p1;p2=p2",
+ &create_uri34,
+ "tel:911;p1=p1;p2=p2"
+ },
+ {
+ /* 35: IPv6 in host and maddr parameter */
+ PJ_SUCCESS,
+ "sip:user@[::1];maddr=[::01]",
+ &create_uri35,
+ "sip:user@[::1];maddr=[::01]"
+ },
+ {
+ /* 36: IPv6 in host and maddr, without username */
+ PJ_SUCCESS,
+ "sip:[::1];maddr=[::01]",
+ &create_uri36,
+ "sip:[::1];maddr=[::01]"
+ },
+ {
+ /* 37: Non-ASCII UTF-8 in display name, with quote */
+ PJ_SUCCESS,
+ "\"\xC0\x81\" <sip:localhost>",
+ &create_uri37,
+ "\"\xC0\x81\" <sip:localhost>"
+ },
+ {
+ /* 38: Non-ASCII UTF-8 in display name, without quote */
+ PJ_SUCCESS,
+ "\xC0\x81 <sip:localhost>",
+ &create_uri38,
+ "\"\xC0\x81\" <sip:localhost>"
+ }
+
+};
+
+static pjsip_uri *create_uri0(pj_pool_t *pool)
+{
+ /* "sip:localhost" */
+ pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0);
+
+ pj_strdup2(pool, &url->host, "localhost");
+ return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri1(pj_pool_t *pool)
+{
+ /* "sip:user@localhost" */
+ pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0);
+
+ pj_strdup2( pool, &url->user, "user");
+ pj_strdup2( pool, &url->host, "localhost");
+
+ return (pjsip_uri*) url;
+}
+
+static pjsip_uri *create_uri2(pj_pool_t *pool)
+{
+ /* "sip:user:password@localhost:5060" */
+ pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0);
+
+ pj_strdup2( pool, &url->user, "user");
+ pj_strdup2( pool, &url->passwd, "password");
+ pj_strdup2( pool, &url->host, "localhost");
+ url->port = 5060;
+
+ return (pjsip_uri*) url;
+}
+
+static pjsip_uri *create_uri3(pj_pool_t *pool)
+{
+ /* Like: "sip:localhost:5060", but without the port. */
+ pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0);
+
+ pj_strdup2(pool, &url->host, "localhost");
+ return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri4(pj_pool_t *pool)
+{
+ /* "sip:localhost;transport=tcp;user=ip;ttl=255;lr;maddr=127.0.0.1;method=ACK" */
+ pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0);
+
+ pj_strdup2(pool, &url->host, "localhost");
+ pj_strdup2(pool, &url->transport_param, "tcp");
+ pj_strdup2(pool, &url->user_param, "ip");
+ url->ttl_param = 255;
+ url->lr_param = 1;
+ pj_strdup2(pool, &url->maddr_param, "127.0.0.1");
+ pj_strdup2(pool, &url->method_param, "ACK");
+
+ return (pjsip_uri*)url;
+}
+
+#define param_add(list,pname,pvalue) \
+ do { \
+ pjsip_param *param; \
+ param=PJ_POOL_ALLOC_T(pool, pjsip_param); \
+ param->name = pj_str(pname); \
+ param->value = pj_str(pvalue); \
+ pj_list_insert_before(&list, param); \
+ } while (0)
+
+static pjsip_uri *create_uri5(pj_pool_t *pool)
+{
+ /* "sip:localhost;pickup=hurry;user=phone;message=I%20am%20sorry"
+ "?Subject=Hello%20There&Server=SIP%20Server"
+ */
+ pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0);
+
+ pj_strdup2(pool, &url->host, "localhost");
+ pj_strdup2(pool, &url->user_param, "phone");
+
+ //pj_strdup2(pool, &url->other_param, ";pickup=hurry;message=I%20am%20sorry");
+ param_add(url->other_param, "pickup", "hurry");
+ param_add(url->other_param, "message", "I am sorry");
+
+ //pj_strdup2(pool, &url->header_param, "?Subject=Hello%20There&Server=SIP%20Server");
+ param_add(url->header_param, "Subject", "Hello There");
+ param_add(url->header_param, "Server", "SIP Server");
+ return (pjsip_uri*)url;
+
+}
+
+static pjsip_uri *create_uri6(pj_pool_t *pool)
+{
+ /* "sips:localhost" */
+ pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 1);
+
+ pj_strdup2(pool, &url->host, "localhost");
+ return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri7(pj_pool_t *pool)
+{
+ /* "<sip:localhost>" */
+ pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+ pjsip_sip_uri *url;
+
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*) url;
+
+ pj_strdup2(pool, &url->host, "localhost");
+ return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri8(pj_pool_t *pool)
+{
+ /* " Power Administrator <sips:localhost>" */
+ pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+ pjsip_sip_uri *url;
+
+ url = pjsip_sip_uri_create(pool, 1);
+ name_addr->uri = (pjsip_uri*) url;
+
+ pj_strdup2(pool, &name_addr->display, "Power Administrator");
+ pj_strdup2(pool, &url->host, "localhost");
+ return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri9(pj_pool_t *pool)
+{
+ /* " \"User\" <sip:user@localhost:5071>" */
+ pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+ pjsip_sip_uri *url;
+
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*) url;
+
+ pj_strdup2(pool, &name_addr->display, "User");
+ pj_strdup2(pool, &url->user, "user");
+ pj_strdup2(pool, &url->host, "localhost");
+ url->port = 5071;
+ return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri10(pj_pool_t *pool)
+{
+ /* " \"Strange User\\\"\\\\\\\"\" <sip:localhost>" */
+ pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+ pjsip_sip_uri *url;
+
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*) url;
+
+ pj_strdup2(pool, &name_addr->display, "Strange User\\\"\\\\\\\"");
+ pj_strdup2(pool, &url->host, "localhost");
+ return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri11(pj_pool_t *pool)
+{
+ /* " \"Rogue User\\\" <sip:localhost>" */
+ pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+ pjsip_sip_uri *url;
+
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*) url;
+
+ pj_strdup2(pool, &name_addr->display, "Rogue User\\");
+ pj_strdup2(pool, &url->host, "localhost");
+ return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri12(pj_pool_t *pool)
+{
+ /* "Strange User\" <sip:localhost>" */
+ pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+ pjsip_sip_uri *url;
+
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*) url;
+
+ pj_strdup2(pool, &name_addr->display, "Strange User\"");
+ pj_strdup2(pool, &url->host, "localhost");
+ return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri13(pj_pool_t *pool)
+{
+ /* "sip:localhost;pvalue=\"hello world\"" */
+ pjsip_sip_uri *url;
+ url = pjsip_sip_uri_create(pool, 0);
+ pj_strdup2(pool, &url->host, "localhost");
+ //pj_strdup2(pool, &url->other_param, ";pvalue=\"hello world\"");
+ param_add(url->other_param, "pvalue", "\"hello world\"");
+ return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri14(pj_pool_t *pool)
+{
+ /* "This is -. !% *_+`'~ me <sip:a19A&=+$,;?/%2c:%40a&Zz=+$,@my_proxy09.my-domain.com:9801>" */
+ pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+ pjsip_sip_uri *url;
+
+ url = pjsip_sip_uri_create(pool, 0);
+ name_addr->uri = (pjsip_uri*) url;
+
+ pj_strdup2(pool, &name_addr->display, "This is -. !% *_+`'~ me");
+ pj_strdup2(pool, &url->user, "a19A&=+$,;?/,");
+ pj_strdup2(pool, &url->passwd, "@a&Zz=+$,");
+ pj_strdup2(pool, &url->host, "my_proxy09.MY-domain.com");
+ url->port = 9801;
+ return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri15(pj_pool_t *pool)
+{
+ /* "sip:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.com" */
+ pjsip_sip_uri *url;
+ url = pjsip_sip_uri_create(pool, 0);
+ pj_strdup2(pool, &url->host, ALPHANUM "-_.com");
+ return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri16(pj_pool_t *pool)
+{
+ /* "sip:" USER_CHAR ":" PASS_CHAR "@host" */
+ pjsip_sip_uri *url;
+ url = pjsip_sip_uri_create(pool, 0);
+ pj_strdup2(pool, &url->user, USER_CHAR);
+ pj_strdup2(pool, &url->passwd, PASS_CHAR);
+ pj_strdup2(pool, &url->host, "host");
+ return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri17(pj_pool_t *pool)
+{
+ /* "sip:host;user=ip;" PARAM_CHAR "%21=" PARAM_CHAR "%21;lr;other=1;transport=sctp;other2" */
+ pjsip_sip_uri *url;
+ url = pjsip_sip_uri_create(pool, 0);
+ pj_strdup2(pool, &url->host, "host");
+ pj_strdup2(pool, &url->user_param, "ip");
+ pj_strdup2(pool, &url->transport_param, "sctp");
+ param_add(url->other_param, PARAM_CHAR "!", PARAM_CHAR "!");
+ param_add(url->other_param, "other", "1");
+ param_add(url->other_param, "other2", "");
+ url->lr_param = 1;
+ return (pjsip_uri*)url;
+}
+
+
+static pjsip_uri *create_uri25(pj_pool_t *pool)
+{
+ /* "tel:+1-201-555-0123" */
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ uri->number = pj_str("+1-201-555-0123");
+ return (pjsip_uri*)uri;
+}
+
+static pjsip_uri *create_uri26(pj_pool_t *pool)
+{
+ /* tel:7042;phone-context=example.com */
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ uri->number = pj_str("7042");
+ uri->context = pj_str("example.com");
+ return (pjsip_uri*)uri;
+}
+
+static pjsip_uri *create_uri27(pj_pool_t *pool)
+{
+ /* "tel:863-1234;phone-context=+1-914-555" */
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ uri->number = pj_str("863-1234");
+ uri->context = pj_str("+1-914-555");
+ return (pjsip_uri*)uri;
+}
+
+/* "tel:1" */
+static pjsip_uri *create_uri28(pj_pool_t *pool)
+{
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ uri->number = pj_str("1");
+ return (pjsip_uri*)uri;
+}
+
+/* "tel:(44).1234-*#+Deaf" */
+static pjsip_uri *create_uri29(pj_pool_t *pool)
+{
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ uri->number = pj_str("(44).1234-*#+Deaf");
+ return (pjsip_uri*)uri;
+}
+
+/* "tel:+1;isub=/:@&$,-_.!~*'()[]/:&$aA1%21+=" */
+static pjsip_uri *create_uri30(pj_pool_t *pool)
+{
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ uri->number = pj_str("+1");
+ uri->isub_param = pj_str("/:@&$,-_.!~*'()[]/:&$aA1!+=");
+ return (pjsip_uri*)uri;
+}
+
+/* "tel:+1;ext=+123" */
+static pjsip_uri *create_uri31(pj_pool_t *pool)
+{
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ uri->number = pj_str("+1");
+ uri->ext_param = pj_str("+123");
+ return (pjsip_uri*)uri;
+}
+
+/* "tel:911;phone-context=+1-911" */
+static pjsip_uri *create_uri32(pj_pool_t *pool)
+{
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ uri->number = pj_str("911");
+ uri->context = pj_str("+1-911");
+ return (pjsip_uri*)uri;
+}
+
+/* "tel:911;phone-context=emergency.example.com" */
+static pjsip_uri *create_uri33(pj_pool_t *pool)
+{
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ uri->number = pj_str("911");
+ uri->context = pj_str("EMERGENCY.EXAMPLE.COM");
+ return (pjsip_uri*)uri;
+}
+
+/* "tel:911;p1=p1;p2=p2" */
+static pjsip_uri *create_uri34(pj_pool_t *pool)
+{
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+ pjsip_param *p;
+
+ uri->number = pj_str("911");
+
+ p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = p->value = pj_str("p1");
+ pj_list_insert_before(&uri->other_param, p);
+
+ return (pjsip_uri*)uri;
+}
+
+/* "sip:user@[::1];maddr=[::01]" */
+static pjsip_uri *create_uri35( pj_pool_t *pool )
+{
+ pjsip_sip_uri *url;
+ url = pjsip_sip_uri_create(pool, 0);
+ url->user = pj_str("user");
+ url->host = pj_str("::1");
+ url->maddr_param = pj_str("::01");
+ return (pjsip_uri*)url;
+}
+
+/* "sip:[::1];maddr=[::01]" */
+static pjsip_uri *create_uri36( pj_pool_t *pool )
+{
+ pjsip_sip_uri *url;
+ url = pjsip_sip_uri_create(pool, 0);
+ url->host = pj_str("::1");
+ url->maddr_param = pj_str("::01");
+ return (pjsip_uri*)url;
+
+}
+
+/* "\"\xC0\x81\" <sip:localhost>" */
+static pjsip_uri *create_uri37( pj_pool_t *pool )
+{
+ pjsip_name_addr *name;
+ pjsip_sip_uri *url;
+
+ name = pjsip_name_addr_create(pool);
+ name->display = pj_str("\xC0\x81");
+
+ url = pjsip_sip_uri_create(pool, 0);
+ url->host = pj_str("localhost");
+
+ name->uri = (pjsip_uri*)url;
+
+ return (pjsip_uri*)name;
+
+}
+
+/* "\xC0\x81 <sip:localhost>" */
+static pjsip_uri *create_uri38( pj_pool_t *pool )
+{
+ pjsip_name_addr *name;
+ pjsip_sip_uri *url;
+
+ name = pjsip_name_addr_create(pool);
+ name->display = pj_str("\xC0\x81");
+
+ url = pjsip_sip_uri_create(pool, 0);
+ url->host = pj_str("localhost");
+
+ name->uri = (pjsip_uri*)url;
+
+ return (pjsip_uri*)name;
+
+}
+
+static pjsip_uri *create_dummy(pj_pool_t *pool)
+{
+ PJ_UNUSED_ARG(pool);
+ return NULL;
+}
+
+/*****************************************************************************/
+
+/*
+ * Test one test entry.
+ */
+static pj_status_t do_uri_test(pj_pool_t *pool, struct uri_test *entry)
+{
+ pj_status_t status;
+ int len;
+ char *input;
+ pjsip_uri *parsed_uri, *ref_uri;
+ pj_str_t s1 = {NULL, 0}, s2 = {NULL, 0};
+ pj_timestamp t1, t2;
+
+ if (entry->len == 0)
+ entry->len = pj_ansi_strlen(entry->str);
+
+#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
+ input = pj_pool_alloc(pool, entry->len + 1);
+ pj_memcpy(input, entry->str, entry->len);
+ input[entry->len] = '\0';
+#else
+ input = entry->str;
+#endif
+
+ /* Parse URI text. */
+ pj_get_timestamp(&t1);
+ var.parse_len = var.parse_len + entry->len;
+ parsed_uri = pjsip_parse_uri(pool, input, entry->len, 0);
+ if (!parsed_uri) {
+ /* Parsing failed. If the entry says that this is expected, then
+ * return OK.
+ */
+ status = entry->status==ERR_SYNTAX_ERR ? PJ_SUCCESS : -10;
+ if (status != 0) {
+ PJ_LOG(3,(THIS_FILE, " uri parse error!\n"
+ " uri='%s'\n",
+ input));
+ }
+ goto on_return;
+ }
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&var.parse_time, &t2);
+
+ /* Create the reference URI. */
+ ref_uri = entry->creator(pool);
+
+ /* Print both URI. */
+ s1.ptr = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+ s2.ptr = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+
+ pj_get_timestamp(&t1);
+ len = pjsip_uri_print( PJSIP_URI_IN_OTHER, parsed_uri, s1.ptr, PJSIP_MAX_URL_SIZE);
+ if (len < 1) {
+ status = -20;
+ goto on_return;
+ }
+ s1.ptr[len] = '\0';
+ s1.slen = len;
+
+ var.print_len = var.print_len + len;
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&var.print_time, &t2);
+
+ len = pjsip_uri_print( PJSIP_URI_IN_OTHER, ref_uri, s2.ptr, PJSIP_MAX_URL_SIZE);
+ if (len < 1) {
+ status = -30;
+ goto on_return;
+ }
+ s2.ptr[len] = '\0';
+ s2.slen = len;
+
+ /* Full comparison of parsed URI with reference URI. */
+ pj_get_timestamp(&t1);
+ status = pjsip_uri_cmp(PJSIP_URI_IN_OTHER, parsed_uri, ref_uri);
+ if (status != 0) {
+ /* Not equal. See if this is the expected status. */
+ status = entry->status==ERR_NOT_EQUAL ? PJ_SUCCESS : -40;
+ if (status != 0) {
+ PJ_LOG(3,(THIS_FILE, " uri comparison mismatch, status=%d:\n"
+ " uri1='%s'\n"
+ " uri2='%s'",
+ status, s1.ptr, s2.ptr));
+ }
+ goto on_return;
+
+ } else {
+ /* Equal. See if this is the expected status. */
+ status = entry->status==PJ_SUCCESS ? PJ_SUCCESS : -50;
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+ }
+
+ var.cmp_len = var.cmp_len + len;
+ pj_get_timestamp(&t2);
+ pj_sub_timestamp(&t2, &t1);
+ pj_add_timestamp(&var.cmp_time, &t2);
+
+ /* Compare text. */
+ if (entry->printed) {
+ if (pj_strcmp2(&s1, entry->printed) != 0) {
+ /* Not equal. */
+ PJ_LOG(3,(THIS_FILE, " uri print mismatch:\n"
+ " printed='%s'\n"
+ " expectd='%s'",
+ s1.ptr, entry->printed));
+ status = -60;
+ }
+ } else {
+ if (pj_strcmp(&s1, &s2) != 0) {
+ /* Not equal. */
+ PJ_LOG(3,(THIS_FILE, " uri print mismatch:\n"
+ " uri1='%s'\n"
+ " uri2='%s'",
+ s1.ptr, s2.ptr));
+ status = -70;
+ }
+ }
+
+on_return:
+ return status;
+}
+
+
+static int simple_uri_test(void)
+{
+ unsigned i;
+ pj_pool_t *pool;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " simple test"));
+ for (i=0; i<PJ_ARRAY_SIZE(uri_test_array); ++i) {
+ pool = pjsip_endpt_create_pool(endpt, "", POOL_SIZE, POOL_SIZE);
+ status = do_uri_test(pool, &uri_test_array[i]);
+ pjsip_endpt_release_pool(endpt, pool);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(THIS_FILE, " error %d when testing entry %d",
+ status, i));
+ return status;
+ }
+ }
+
+ return 0;
+}
+
+#if INCLUDE_BENCHMARKS
+static int uri_benchmark(unsigned *p_parse, unsigned *p_print, unsigned *p_cmp)
+{
+ unsigned i, loop;
+ pj_status_t status = PJ_SUCCESS;
+ pj_timestamp zero;
+ pj_time_val elapsed;
+ pj_highprec_t avg_parse, avg_print, avg_cmp, kbytes;
+
+ pj_bzero(&var, sizeof(var));
+
+ zero.u32.hi = zero.u32.lo = 0;
+
+ var.parse_len = var.print_len = var.cmp_len = 0;
+ var.parse_time.u32.hi = var.parse_time.u32.lo = 0;
+ var.print_time.u32.hi = var.print_time.u32.lo = 0;
+ var.cmp_time.u32.hi = var.cmp_time.u32.lo = 0;
+ for (loop=0; loop<LOOP_COUNT; ++loop) {
+ for (i=0; i<PJ_ARRAY_SIZE(uri_test_array); ++i) {
+ pj_pool_t *pool;
+ pool = pjsip_endpt_create_pool(endpt, "", POOL_SIZE, POOL_SIZE);
+ status = do_uri_test(pool, &uri_test_array[i]);
+ pjsip_endpt_release_pool(endpt, pool);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(THIS_FILE, " error %d when testing entry %d",
+ status, i));
+ pjsip_endpt_release_pool(endpt, pool);
+ goto on_return;
+ }
+ }
+ }
+
+ kbytes = var.parse_len;
+ pj_highprec_mod(kbytes, 1000000);
+ pj_highprec_div(kbytes, 100000);
+ elapsed = pj_elapsed_time(&zero, &var.parse_time);
+ avg_parse = pj_elapsed_usec(&zero, &var.parse_time);
+ pj_highprec_mul(avg_parse, AVERAGE_URL_LEN);
+ pj_highprec_div(avg_parse, var.parse_len);
+ if (avg_parse == 0)
+ avg_parse = 1;
+ avg_parse = 1000000 / avg_parse;
+
+ PJ_LOG(3,(THIS_FILE,
+ " %u.%u MB of urls parsed in %d.%03ds (avg=%d urls/sec)",
+ (unsigned)(var.parse_len/1000000), (unsigned)kbytes,
+ elapsed.sec, elapsed.msec,
+ (unsigned)avg_parse));
+
+ *p_parse = (unsigned)avg_parse;
+
+ kbytes = var.print_len;
+ pj_highprec_mod(kbytes, 1000000);
+ pj_highprec_div(kbytes, 100000);
+ elapsed = pj_elapsed_time(&zero, &var.print_time);
+ avg_print = pj_elapsed_usec(&zero, &var.print_time);
+ pj_highprec_mul(avg_print, AVERAGE_URL_LEN);
+ pj_highprec_div(avg_print, var.parse_len);
+ if (avg_print == 0)
+ avg_print = 1;
+ avg_print = 1000000 / avg_print;
+
+ PJ_LOG(3,(THIS_FILE,
+ " %u.%u MB of urls printed in %d.%03ds (avg=%d urls/sec)",
+ (unsigned)(var.print_len/1000000), (unsigned)kbytes,
+ elapsed.sec, elapsed.msec,
+ (unsigned)avg_print));
+
+ *p_print = (unsigned)avg_print;
+
+ kbytes = var.cmp_len;
+ pj_highprec_mod(kbytes, 1000000);
+ pj_highprec_div(kbytes, 100000);
+ elapsed = pj_elapsed_time(&zero, &var.cmp_time);
+ avg_cmp = pj_elapsed_usec(&zero, &var.cmp_time);
+ pj_highprec_mul(avg_cmp, AVERAGE_URL_LEN);
+ pj_highprec_div(avg_cmp, var.cmp_len);
+ if (avg_cmp == 0)
+ avg_cmp = 1;
+ avg_cmp = 1000000 / avg_cmp;
+
+ PJ_LOG(3,(THIS_FILE,
+ " %u.%u MB of urls compared in %d.%03ds (avg=%d urls/sec)",
+ (unsigned)(var.cmp_len/1000000), (unsigned)kbytes,
+ elapsed.sec, elapsed.msec,
+ (unsigned)avg_cmp));
+
+ *p_cmp = (unsigned)avg_cmp;
+
+on_return:
+ return status;
+}
+#endif /* INCLUDE_BENCHMARKS */
+
+/*****************************************************************************/
+
+int uri_test(void)
+{
+ enum { COUNT = 1, DETECT=0, PARSE=1, PRINT=2 };
+ struct {
+ unsigned parse;
+ unsigned print;
+ unsigned cmp;
+ } run[COUNT];
+ unsigned i, max, avg_len;
+ char desc[200];
+ pj_status_t status;
+
+ status = simple_uri_test();
+ if (status != PJ_SUCCESS)
+ return status;
+
+#if INCLUDE_BENCHMARKS
+ for (i=0; i<COUNT; ++i) {
+ PJ_LOG(3,(THIS_FILE, " benchmarking (%d of %d)...", i+1, COUNT));
+ status = uri_benchmark(&run[i].parse, &run[i].print, &run[i].cmp);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Calculate average URI length */
+ for (i=0, avg_len=0; i<PJ_ARRAY_SIZE(uri_test_array); ++i) {
+ avg_len += uri_test_array[i].len;
+ }
+ avg_len /= PJ_ARRAY_SIZE(uri_test_array);
+
+
+ /*
+ * Print maximum parse/sec
+ */
+ for (i=0, max=0; i<COUNT; ++i)
+ if (run[i].parse > max) max = run[i].parse;
+
+ PJ_LOG(3,("", " Maximum URI parse/sec=%u", max));
+
+ pj_ansi_sprintf(desc, "Number of SIP/TEL URIs that can be <B>parsed</B> with "
+ "<tt>pjsip_parse_uri()</tt> per second "
+ "(tested with %d URI set, with average length of "
+ "%d chars)",
+ (int)PJ_ARRAY_SIZE(uri_test_array), avg_len);
+
+ report_ival("uri-parse-per-sec", max, "URI/sec", desc);
+
+ /* URI parsing bandwidth */
+ report_ival("uri-parse-bandwidth-mb", avg_len*max/1000000, "MB/sec",
+ "URI parsing bandwidth in megabytes (number of megabytes "
+ "worth of URI that can be parsed per second)");
+
+
+ /* Print maximum print/sec */
+ for (i=0, max=0; i<COUNT; ++i)
+ if (run[i].print > max) max = run[i].print;
+
+ PJ_LOG(3,("", " Maximum URI print/sec=%u", max));
+
+ pj_ansi_sprintf(desc, "Number of SIP/TEL URIs that can be <B>printed</B> with "
+ "<tt>pjsip_uri_print()</tt> per second "
+ "(tested with %d URI set, with average length of "
+ "%d chars)",
+ (int)PJ_ARRAY_SIZE(uri_test_array), avg_len);
+
+ report_ival("uri-print-per-sec", max, "URI/sec", desc);
+
+ /* Print maximum detect/sec */
+ for (i=0, max=0; i<COUNT; ++i)
+ if (run[i].cmp > max) max = run[i].cmp;
+
+ PJ_LOG(3,("", " Maximum URI comparison/sec=%u", max));
+
+ pj_ansi_sprintf(desc, "Number of SIP/TEL URIs that can be <B>compared</B> with "
+ "<tt>pjsip_uri_cmp()</tt> per second "
+ "(tested with %d URI set, with average length of "
+ "%d chars)",
+ (int)PJ_ARRAY_SIZE(uri_test_array), avg_len);
+
+ report_ival("uri-cmp-per-sec", max, "URI/sec", desc);
+
+#endif /* INCLUDE_BENCHMARKS */
+
+ return PJ_SUCCESS;
+}
+