summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip/sip_dialog.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip/src/pjsip/sip_dialog.c')
-rw-r--r--pjsip/src/pjsip/sip_dialog.c2250
1 files changed, 2250 insertions, 0 deletions
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;
+}