diff options
Diffstat (limited to 'pjsip/src/pjsip-ua/sip_dialog.c')
-rw-r--r-- | pjsip/src/pjsip-ua/sip_dialog.c | 3604 |
1 files changed, 1802 insertions, 1802 deletions
diff --git a/pjsip/src/pjsip-ua/sip_dialog.c b/pjsip/src/pjsip-ua/sip_dialog.c index ac110412..bb4861b8 100644 --- a/pjsip/src/pjsip-ua/sip_dialog.c +++ b/pjsip/src/pjsip-ua/sip_dialog.c @@ -1,1802 +1,1802 @@ -/* $Id$ */
-/*
- * Copyright (C) 2003-2006 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_mod_ua/sip_dialog.h>
-#include <pjsip_mod_ua/sip_ua.h>
-#include <pjsip_mod_ua/sip_ua_private.h>
-#include <pjsip/sip_transport.h>
-#include <pjsip/sip_transaction.h>
-#include <pjsip/sip_types.h>
-#include <pjsip/sip_endpoint.h>
-#include <pjsip/sip_uri.h>
-#include <pjsip/sip_util.h>
-#include <pjsip/sip_module.h>
-#include <pjsip/sip_event.h>
-#include <pjsip/sip_parser.h>
-#include <pj/string.h>
-#include <pj/log.h>
-#include <pj/os.h>
-#include <pj/guid.h>
-#include <pj/except.h>
-#include <pj/pool.h>
-
-/* TLS to keep dialog lock record (initialized by UA) */
-int pjsip_dlg_lock_tls_id;
-
-struct dialog_lock_data
-{
- struct dialog_lock_data *prev;
- pjsip_dlg *dlg;
- int is_alive;
-};
-
-/*
- * Static function prototypes.
- */
-static void dlg_create_request_throw( pjsip_tx_data **p_tdata,
- pjsip_dlg *dlg,
- const pjsip_method *method,
- int cseq );
-static int dlg_on_all_state_pre( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_all_state_post( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_state_null( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_state_incoming( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_state_calling( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_state_proceeding( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_state_proceeding_caller( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_state_proceeding_callee( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_state_connecting( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_state_established( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_state_disconnected( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-static int dlg_on_state_terminated( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event);
-
-/*
- * Dialog state handlers.
- */
-static int (*dlg_state_handlers[])(struct pjsip_dlg *, pjsip_transaction *,
- pjsip_event *) =
-{
- &dlg_on_state_null,
- &dlg_on_state_incoming,
- &dlg_on_state_calling,
- &dlg_on_state_proceeding,
- &dlg_on_state_connecting,
- &dlg_on_state_established,
- &dlg_on_state_disconnected,
- &dlg_on_state_terminated,
-};
-
-/*
- * Dialog state names.
- */
-static const char* dlg_state_names[] =
-{
- "STATE_NULL",
- "STATE_INCOMING",
- "STATE_CALLING",
- "STATE_PROCEEDING",
- "STATE_CONNECTING",
- "STATE_ESTABLISHED",
- "STATE_DISCONNECTED",
- "STATE_TERMINATED",
-};
-
-
-/*
- * Get the dialog string state, normally for logging purpose.
- */
-const char *pjsip_dlg_state_str(pjsip_dlg_state_e state)
-{
- return dlg_state_names[state];
-}
-
-/* Lock dialog mutex. */
-static void lock_dialog(pjsip_dlg *dlg, struct dialog_lock_data *lck)
-{
- struct dialog_lock_data *prev;
-
- pj_mutex_lock(dlg->mutex);
- prev = pj_thread_local_get(pjsip_dlg_lock_tls_id);
- lck->prev = prev;
- lck->dlg = dlg;
- lck->is_alive = 1;
- pj_thread_local_set(pjsip_dlg_lock_tls_id, lck);
-}
-
-/* Carefully unlock dialog mutex, protect against situation when the dialog
- * has already been destroyed.
- */
-static pj_status_t unlock_dialog(pjsip_dlg *dlg, struct dialog_lock_data *lck)
-{
- pj_assert(pj_thread_local_get(pjsip_dlg_lock_tls_id) == lck);
- pj_assert(dlg == lck->dlg);
-
- pj_thread_local_set(pjsip_dlg_lock_tls_id, lck->prev);
- if (lck->is_alive)
- pj_mutex_unlock(dlg->mutex);
-
- return lck->is_alive ? 0 : -1;
-}
-
-/*
- * This is called by dialog's FSM to change dialog's state.
- */
-static void dlg_set_state( pjsip_dlg *dlg, pjsip_dlg_state_e state,
- pjsip_event *event)
-{
- PJ_UNUSED_ARG(event);
-
- PJ_LOG(4, (dlg->obj_name, "State %s-->%s (ev=%s, src=%s, data=%p)",
- pjsip_dlg_state_str(dlg->state), pjsip_dlg_state_str(state),
- event ? pjsip_event_str(event->type) : "",
- event ? pjsip_event_str(event->src_type) : "",
- event ? event->src.data : NULL));
-
- dlg->state = state;
- dlg->handle_tsx_event = dlg_state_handlers[state];
-}
-
-/*
- * Invoke dialog's callback.
- * This function is called by dialog's FSM, and interpret the event and call
- * the corresponding callback registered by application.
- */
-static void dlg_call_dlg_callback( pjsip_dlg *dlg, pjsip_dlg_event_e dlg_event,
- pjsip_event *event )
-{
- pjsip_dlg_callback *cb = dlg->ua->dlg_cb;
- if (!cb) {
- PJ_LOG(4,(dlg->obj_name, "Can not call callback (none registered)."));
- return;
- }
-
- /* Low level event: call the all-events callback. */
- if (cb->on_all_events) {
- (*cb->on_all_events)(dlg, dlg_event, event);
- }
-
- /* Low level event: call the tx/rx callback if this is a tx/rx event. */
- if (event->type == PJSIP_EVENT_BEFORE_TX && cb->on_before_tx)
- {
- (*cb->on_before_tx)(dlg, event->obj.tsx, event->src.tdata, event->data.long_data);
- }
- else if (event->type == PJSIP_EVENT_TX_MSG &&
- event->src_type == PJSIP_EVENT_TX_MSG && cb->on_tx_msg)
- {
- (*cb->on_tx_msg)(dlg, event->obj.tsx, event->src.tdata);
- }
- else if (event->type == PJSIP_EVENT_RX_MSG &&
- event->src_type == PJSIP_EVENT_RX_MSG && cb->on_rx_msg) {
- (*cb->on_rx_msg)(dlg, event->obj.tsx, event->src.rdata);
- }
-
- /* Now trigger high level events.
- * High level event should only occurs when dialog's state has changed,
- * except for on_provisional, which may be called multiple times whenever
- * response message is sent
- */
- if (dlg->state == PJSIP_DIALOG_STATE_PROCEEDING &&
- (event->type== PJSIP_EVENT_TSX_STATE_CHANGED) &&
- event->obj.tsx == dlg->invite_tsx)
- {
- /* Sent/received provisional responses. */
- if (cb->on_provisional)
- (*cb->on_provisional)(dlg, event->obj.tsx, event);
- }
-
- if (dlg_event == PJSIP_DIALOG_EVENT_MID_CALL_REQUEST) {
- if (cb->on_mid_call_events)
- (*cb->on_mid_call_events)(dlg, event);
- return;
- }
-
- if (dlg_event != PJSIP_DIALOG_EVENT_STATE_CHANGED)
- return;
-
- if (dlg->state == PJSIP_DIALOG_STATE_INCOMING) {
-
- /* New incoming dialog. */
- pj_assert (event->src_type == PJSIP_EVENT_RX_MSG);
- (*cb->on_incoming)(dlg, event->obj.tsx, event->src.rdata);
-
- } else if (dlg->state == PJSIP_DIALOG_STATE_CALLING) {
-
- /* Dialog has just sent the first INVITE. */
- if (cb->on_calling) {
- (*cb->on_calling)(dlg, event->obj.tsx, event->src.tdata);
- }
-
- } else if (dlg->state == PJSIP_DIALOG_STATE_DISCONNECTED) {
-
- if (cb->on_disconnected)
- (*cb->on_disconnected)(dlg, event);
-
- } else if (dlg->state == PJSIP_DIALOG_STATE_TERMINATED) {
-
- if (cb->on_terminated)
- (*cb->on_terminated)(dlg);
-
- pjsip_ua_destroy_dialog(dlg);
-
- } else if (dlg->state == PJSIP_DIALOG_STATE_CONNECTING) {
-
- if (cb->on_connecting)
- (*cb->on_connecting)(dlg, event);
-
- } else if (dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) {
-
- if (cb->on_established)
- (*cb->on_established)(dlg, event);
- }
-}
-
-/*
- * This callback receives event from the transaction layer (via User Agent),
- * or from dialog timer (for 200/INVITE or ACK retransmission).
- */
-void pjsip_dlg_on_tsx_event( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- int status = 0;
- struct dialog_lock_data lck;
-
- PJ_LOG(4, (dlg->obj_name, "state=%s (evt=%s, src=%s)",
- pjsip_dlg_state_str(dlg->state),
- pjsip_event_str(event->type),
- pjsip_event_str(event->src_type)));
-
-
- lock_dialog(dlg, &lck);
-
- status = dlg_on_all_state_pre( dlg, tsx, event);
-
- if (status==0) {
- status = (*dlg->handle_tsx_event)(dlg, tsx, event);
- }
- if (status==0) {
- status = dlg_on_all_state_post( dlg, tsx, event);
- }
-
- unlock_dialog(dlg, &lck);
-}
-
-/*
- * This function contains common processing in all states, and it is called
- * before the FSM is invoked.
- */
-static int dlg_on_all_state_pre( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- PJ_UNUSED_ARG(event)
-
- if (event->type != PJSIP_EVENT_TSX_STATE_CHANGED)
- return 0;
-
- if (tsx && (tsx->state==PJSIP_TSX_STATE_CALLING ||
- tsx->state==PJSIP_TSX_STATE_TRYING))
- {
- ++dlg->pending_tsx_count;
-
- } else if (tsx && tsx->state==PJSIP_TSX_STATE_DESTROYED)
- {
- --dlg->pending_tsx_count;
- if (tsx == dlg->invite_tsx)
- dlg->invite_tsx = NULL;
- }
-
- if (tsx->method.id == PJSIP_INVITE_METHOD) {
- tsx->handle_ack = 1;
- }
- return 0;
-}
-
-
-/*
- * This function contains common processing in all states, and it is called
- * after the FSM is invoked.
- */
-static int dlg_on_all_state_post( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- PJ_UNUSED_ARG(event)
-
- if (tsx && tsx->state==PJSIP_TSX_STATE_DESTROYED) {
- if (dlg->pending_tsx_count == 0 &&
- dlg->state != PJSIP_DIALOG_STATE_CONNECTING &&
- dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED &&
- dlg->state != PJSIP_DIALOG_STATE_TERMINATED)
- {
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event);
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
- return -1;
- }
- }
-
- return 0;
-}
-
-
-/*
- * Internal function to initialize dialog, contains common initialization
- * for both UAS and UAC dialog.
- */
-static pj_status_t dlg_init( pjsip_dlg *dlg )
-{
- /* Init mutex. */
- dlg->mutex = pj_mutex_create(dlg->pool, "mdlg%p", 0);
- if (!dlg->mutex) {
- PJ_PERROR((dlg->obj_name, "pj_mutex_create()"));
- return -1;
- }
-
- /* Init route-set (Initially empty) */
- pj_list_init(&dlg->route_set);
-
- /* Init auth credential list. */
- pj_list_init(&dlg->auth_sess);
-
- return PJ_SUCCESS;
-}
-
-/*
- * This one is called just before dialog is destroyed.
- * It is called while mutex is held.
- */
-PJ_DEF(void) pjsip_on_dialog_destroyed( pjsip_dlg *dlg )
-{
- struct dialog_lock_data *lck;
-
- //PJ_TODO(CHECK_THIS);
- pj_assert(dlg->pending_tsx_count == 0);
-
- /* Mark dialog as dead. */
- lck = pj_thread_local_get(pjsip_dlg_lock_tls_id);
- while (lck) {
- if (lck->dlg == dlg)
- lck->is_alive = 0;
- lck = lck->prev;
- }
-}
-
-/*
- * Initialize dialog from the received request.
- * This is an internal function which is called by the User Agent (sip_ua.c),
- * and it will initialize most of dialog's properties with values from the
- * received message.
- */
-pj_status_t pjsip_dlg_init_from_rdata( pjsip_dlg *dlg, pjsip_rx_data *rdata )
-{
- pjsip_msg *msg = rdata->msg;
- pjsip_to_hdr *to;
- pjsip_contact_hdr *contact;
- pjsip_name_addr *name_addr;
- pjsip_url *url;
- unsigned flag;
- pjsip_event event;
-
- pj_assert(dlg && rdata);
-
- PJ_LOG(5, (dlg->obj_name, "init_from_rdata(%p)", rdata));
-
- /* Must be an INVITE request. */
- pj_assert(msg->type == PJSIP_REQUEST_MSG &&
- msg->line.req.method.id == PJSIP_INVITE_METHOD);
-
- /* Init general dialog data. */
- if (dlg_init(dlg) != PJ_SUCCESS) {
- return -1;
- }
-
- /* Get the To header. */
- to = rdata->to;
-
- /* Copy URI in the To header as our local URI. */
- dlg->local.info = pjsip_hdr_clone( dlg->pool, to);
-
- /* Set tag in the local info. */
- dlg->local.info->tag = dlg->local.tag;
-
- /* Create local Contact to be advertised in the response.
- * At the moment, just copy URI from the local URI as our contact.
- */
- dlg->local.contact = pjsip_contact_hdr_create( dlg->pool );
- dlg->local.contact->star = 0;
- name_addr = (pjsip_name_addr *)dlg->local.info->uri;
- dlg->local.contact->uri = (pjsip_uri*) name_addr;
- url = (pjsip_url*) name_addr->uri;
- //url->port = rdata->via->sent_by.port;
- //url->port = pj_sockaddr_get_port( pjsip_transport_get_local_addr(rdata->transport) );
-
- /* Save remote URI. */
- dlg->remote.info = pjsip_hdr_clone( dlg->pool, rdata->from );
- pjsip_fromto_set_to( dlg->remote.info );
- pj_strdup( dlg->pool, &dlg->remote.tag, &rdata->from->tag );
-
- /* Save remote Contact. */
- contact = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL);
- if (contact) {
- dlg->remote.contact = pjsip_hdr_clone( dlg->pool, contact );
- } else {
- PJ_LOG(3,(dlg->obj_name, "No Contact header in INVITE from %s",
- pj_sockaddr_get_str_addr(&rdata->addr)));
- dlg->remote.contact = pjsip_contact_hdr_create( dlg->pool );
- dlg->remote.contact->uri = dlg->remote.info->uri;
- }
-
- /* Save Call-ID. */
- dlg->call_id = pjsip_cid_hdr_create(dlg->pool);
- pj_strdup( dlg->pool, &dlg->call_id->id, &rdata->call_id );
-
- /* Initialize local CSeq and save remote CSeq.*/
- dlg->local.cseq = rdata->timestamp.sec & 0xFFFF;
- dlg->remote.cseq = rdata->cseq->cseq;
-
- /* Secure? */
- flag = pjsip_transport_get_flag(rdata->transport);
- dlg->secure = (flag & PJSIP_TRANSPORT_SECURE) != 0;
-
- /* Initial state is NULL. */
- event.type = event.src_type = PJSIP_EVENT_RX_MSG;
- event.src.rdata = rdata;
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, &event);
-
- PJ_LOG(5, (dlg->obj_name, "init_from_rdata(%p) complete", rdata));
- return PJ_SUCCESS;
-}
-
-/*
- * Set the contact details.
- */
-PJ_DEF(pj_status_t) pjsip_dlg_set_contact( pjsip_dlg *dlg,
- const pj_str_t *contact )
-{
- pjsip_uri *local_uri;
- pj_str_t tmp;
-
- pj_strdup_with_null(dlg->pool, &tmp, contact);
- local_uri = pjsip_parse_uri( dlg->pool, tmp.ptr, tmp.slen,
- PJSIP_PARSE_URI_AS_NAMEADDR);
- if (local_uri == NULL) {
- PJ_LOG(2, (dlg->obj_name, "set_contact: invalid URI"));
- return -1;
- }
-
- dlg->local.contact->star = 0;
- dlg->local.contact->uri = local_uri;
- return 0;
-}
-
-/*
- * Set route set.
- */
-PJ_DEF(pj_status_t) pjsip_dlg_set_route_set( pjsip_dlg *dlg,
- const pjsip_route_hdr *route_set )
-{
- pjsip_route_hdr *hdr;
-
- pj_list_init(&dlg->route_set);
- hdr = route_set->next;
- while (hdr != route_set) {
- pjsip_route_hdr *cloned = pjsip_hdr_clone(dlg->pool, hdr);
- pj_list_insert_before( &dlg->route_set, cloned);
- hdr = hdr->next;
- }
- return 0;
-}
-
-/*
- * Set route set without cloning the header.
- */
-PJ_DEF(pj_status_t) pjsip_dlg_set_route_set_np( pjsip_dlg *dlg,
- pjsip_route_hdr *route_set)
-{
- pjsip_route_hdr *hdr;
-
- pj_list_init(&dlg->route_set);
- hdr = route_set->next;
- while (hdr != route_set) {
- pj_list_insert_before( &dlg->route_set, hdr);
- hdr = hdr->next;
- }
- return 0;
-}
-
-/*
- * Application calls this function when it wants to initiate an outgoing
- * dialog (incoming dialogs are created automatically by UA when it receives
- * INVITE, by calling pjsip_dlg_init_from_rdata()).
- * This function should initialize most of the dialog's properties.
- */
-PJ_DEF(pj_status_t) pjsip_dlg_init( pjsip_dlg *dlg,
- const pj_str_t *c_local_info,
- const pj_str_t *c_remote_info,
- const pj_str_t *c_target)
-{
- pj_time_val tv;
- pjsip_event event;
- pj_str_t buf;
-
- if (!dlg || !c_local_info || !c_remote_info) {
- pj_assert(dlg && c_local_info && c_remote_info);
- return -1;
- }
-
- PJ_LOG(5, (dlg->obj_name, "initializing"));
-
- /* Init general dialog */
- if (dlg_init(dlg) != PJ_SUCCESS) {
- return -1;
- }
-
- /* Duplicate local info. */
- pj_strdup_with_null( dlg->pool, &buf, c_local_info);
-
- /* Build local URI. */
- dlg->local.target = pjsip_parse_uri(dlg->pool, buf.ptr, buf.slen,
- PJSIP_PARSE_URI_AS_NAMEADDR);
- if (dlg->local.target == NULL) {
- PJ_LOG(2, (dlg->obj_name,
- "pjsip_dlg_init: invalid local URI %s", buf.ptr));
- return -1;
- }
-
- /* Set local URI. */
- dlg->local.info = pjsip_from_hdr_create(dlg->pool);
- dlg->local.info->uri = dlg->local.target;
- dlg->local.info->tag = dlg->local.tag;
-
- /* Create local Contact to be advertised in the response. */
- dlg->local.contact = pjsip_contact_hdr_create( dlg->pool );
- dlg->local.contact->star = 0;
- dlg->local.contact->uri = dlg->local.target;
-
- /* Set remote URI. */
- dlg->remote.info = pjsip_to_hdr_create(dlg->pool);
-
- /* Duplicate to buffer. */
- pj_strdup_with_null( dlg->pool, &buf, c_remote_info);
-
- /* Build remote info. */
- dlg->remote.info->uri = pjsip_parse_uri( dlg->pool, buf.ptr, buf.slen,
- PJSIP_PARSE_URI_AS_NAMEADDR);
- if (dlg->remote.info->uri == NULL) {
- PJ_LOG(2, (dlg->obj_name,
- "pjsip_dlg_init: invalid remote URI %s", buf.ptr));
- return -1;
- }
-
- /* Set remote Contact initially equal to the remote URI. */
- dlg->remote.contact = pjsip_contact_hdr_create(dlg->pool);
- dlg->remote.contact->star = 0;
- dlg->remote.contact->uri = dlg->remote.info->uri;
-
- /* Set initial remote target. */
- if (c_target != NULL) {
- pj_strdup_with_null( dlg->pool, &buf, c_target);
- dlg->remote.target = pjsip_parse_uri( dlg->pool, buf.ptr, buf.slen, 0);
- if (dlg->remote.target == NULL) {
- PJ_LOG(2, (dlg->obj_name,
- "pjsip_dlg_init: invalid remote target %s", buf.ptr));
- return -1;
- }
- } else {
- dlg->remote.target = dlg->remote.info->uri;
- }
-
- /* Create globally unique Call-ID */
- dlg->call_id = pjsip_cid_hdr_create(dlg->pool);
- pj_create_unique_string( dlg->pool, &dlg->call_id->id );
-
- /* Local and remote CSeq */
- pj_gettimeofday(&tv);
- dlg->local.cseq = tv.sec & 0xFFFF;
- dlg->remote.cseq = 0;
-
- /* Initial state is NULL. */
- event.type = event.src_type = PJSIP_EVENT_TX_MSG;
- event.src.data = NULL;
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, &event);
-
- /* Done. */
- PJ_LOG(4, (dlg->obj_name, "%s dialog initialized, From: %.*s, To: %.*s",
- pjsip_role_name(dlg->role),
- c_local_info->slen, c_local_info->ptr,
- c_remote_info->slen, c_remote_info->ptr));
-
- return PJ_SUCCESS;
-}
-
-/*
- * Set credentials.
- */
-PJ_DEF(pj_status_t) pjsip_dlg_set_credentials( pjsip_dlg *dlg,
- int count,
- const pjsip_cred_info *cred)
-{
- if (count > 0) {
- dlg->cred_info = pj_pool_alloc(dlg->pool, count * sizeof(pjsip_cred_info));
- pj_memcpy(dlg->cred_info, cred, count * sizeof(pjsip_cred_info));
- }
- dlg->cred_count = count;
- return 0;
-}
-
-/*
- * 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 void dlg_create_request_throw( pjsip_tx_data **p_tdata,
- pjsip_dlg *dlg,
- const pjsip_method *method,
- int cseq )
-{
- pjsip_tx_data *tdata;
- pjsip_contact_hdr *contact;
- pjsip_route_hdr *route, *end_list;
-
- /* Contact Header field.
- * Contact can only be present in requests that establish dialog (in the
- * core SIP spec, only INVITE).
- */
- if (method->id == PJSIP_INVITE_METHOD)
- contact = dlg->local.contact;
- else
- contact = NULL;
-
- tdata = pjsip_endpt_create_request_from_hdr( dlg->ua->endpt,
- method,
- dlg->remote.target,
- dlg->local.info,
- dlg->remote.info,
- contact,
- dlg->call_id,
- cseq,
- NULL);
- if (!tdata) {
- PJ_THROW(1);
- return;
- }
-
- /* 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_hdr_shallow_clone( tdata->pool, route );
- pjsip_routing_hdr_set_route(r);
- pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)r);
- }
-
- /* Copy authorization headers. */
- pjsip_auth_init_req( dlg->pool, tdata, &dlg->auth_sess,
- dlg->cred_count, dlg->cred_info);
-
- *p_tdata = tdata;
-}
-
-
-/*
- * This function is called by application to create new outgoing request
- * message for this dialog. After the request is created, application can
- * modify the message (such adding headers), and eventually send the request.
- */
-PJ_DEF(pjsip_tx_data*) pjsip_dlg_create_request( pjsip_dlg *dlg,
- const pjsip_method *method,
- int cseq)
-{
- PJ_USE_EXCEPTION;
- struct dialog_lock_data lck;
- pjsip_tx_data *tdata = NULL;
-
- pj_assert(dlg != NULL && method != NULL);
- if (!dlg || !method) {
- return NULL;
- }
-
- PJ_LOG(5, (dlg->obj_name, "Creating request"));
-
- /* Lock dialog. */
- lock_dialog(dlg, &lck);
-
- /* Use outgoing CSeq and increment it by one. */
- if (cseq < 0)
- cseq = dlg->local.cseq + 1;
-
- PJ_LOG(5, (dlg->obj_name, "creating request %.*s cseq=%d",
- method->name.slen, method->name.ptr, cseq));
-
- /* Create the request. */
- PJ_TRY {
- dlg_create_request_throw(&tdata, dlg, method, cseq);
- PJ_LOG(5, (dlg->obj_name, "request data %s created", tdata->obj_name));
- }
- PJ_DEFAULT {
- /* Failed! Delete transmit data. */
- if (tdata) {
- pjsip_tx_data_dec_ref( tdata );
- tdata = NULL;
- }
- }
- PJ_END;
-
- /* Unlock dialog. */
- unlock_dialog(dlg, &lck);
-
- return tdata;
-}
-
-/*
- * Sends request.
- * Select the transport for the request message
- */
-static pj_status_t dlg_send_request( pjsip_dlg *dlg, pjsip_tx_data *tdata )
-{
- pjsip_transaction *tsx;
- pj_status_t status = PJ_SUCCESS;
- struct dialog_lock_data lck;
-
- pj_assert(dlg != NULL && tdata != NULL);
- if (!dlg || !tdata) {
- return -1;
- }
-
- PJ_LOG(5, (dlg->obj_name, "sending request %s", tdata->obj_name));
-
- /* Lock dialog. */
- lock_dialog(dlg, &lck);
-
- /* Create a new transaction. */
- tsx = pjsip_endpt_create_tsx( dlg->ua->endpt );
- if (!tsx) {
- unlock_dialog(dlg, &lck);
- return -1;
- }
-
- PJ_LOG(4, (dlg->obj_name, "Created new UAC transaction: %s", tsx->obj_name));
-
- /* Initialize transaction */
- tsx->module_data[dlg->ua->mod_id] = dlg;
- status = pjsip_tsx_init_uac( tsx, tdata );
- if (status != PJ_SUCCESS) {
- unlock_dialog(dlg, &lck);
- pjsip_endpt_destroy_tsx( dlg->ua->endpt, tsx );
- return -1;
- }
- pjsip_endpt_register_tsx( dlg->ua->endpt, tsx );
-
- /* Start the transaction. */
- pjsip_tsx_on_tx_msg(tsx, tdata);
-
- /* Unlock dialog. */
- unlock_dialog(dlg, &lck);
-
- return status;
-}
-
-/*
- * This function can be called by application to send ANY outgoing message
- * to remote party.
- */
-PJ_DEF(pj_status_t) pjsip_dlg_send_msg( pjsip_dlg *dlg, pjsip_tx_data *tdata )
-{
- pj_status_t status;
- int tsx_status;
- struct dialog_lock_data lck;
-
- pj_assert(dlg != NULL && tdata != NULL);
- if (!dlg || !tdata) {
- return -1;
- }
-
- lock_dialog(dlg, &lck);
-
- if (tdata->msg->type == PJSIP_REQUEST_MSG) {
- int request_cseq;
- pjsip_msg *msg = tdata->msg;
- pjsip_cseq_hdr *cseq_hdr;
-
- switch (msg->line.req.method.id) {
- case PJSIP_CANCEL_METHOD:
-
- /* Check the INVITE transaction state. */
- tsx_status = dlg->invite_tsx->status_code;
- if (tsx_status >= 200) {
- /* Already terminated. Can't cancel. */
- status = -1;
- goto on_return;
- }
-
- /* If we've got provisional response, then send CANCEL and wait for
- * the response to INVITE to arrive. Otherwise just send CANCEL and
- * terminate the INVITE.
- */
- if (tsx_status < 100) {
- pjsip_tsx_terminate( dlg->invite_tsx,
- PJSIP_SC_REQUEST_TERMINATED);
- status = 0;
- goto on_return;
- }
-
- status = 0;
- request_cseq = dlg->invite_tsx->cseq;
- break;
-
- case PJSIP_ACK_METHOD:
- /* Sending ACK outside of transaction is not supported at present! */
- pj_assert(0);
- status = 0;
- request_cseq = dlg->local.cseq;
- break;
-
- case PJSIP_INVITE_METHOD:
- /* For an initial INVITE, reset dialog state to NULL so we get
- * 'normal' UAC notifications such as on_provisional(), etc.
- * Initial INVITE is the request that is sent when the dialog has
- * not been established yet. It's not necessarily the first INVITE
- * sent, as when the Authorization fails, subsequent INVITE are also
- * considered as an initial INVITE.
- */
- if (dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) {
- /* Set state to NULL. */
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, NULL);
-
- } else {
- /* This is a re-INVITE */
- }
- status = 0;
- request_cseq = dlg->local.cseq + 1;
- break;
-
- default:
- status = 0;
- request_cseq = dlg->local.cseq + 1;
- break;
- }
-
- if (status != 0)
- goto on_return;
-
- /* Update dialog's local CSeq, if necessary. */
- if (request_cseq != dlg->local.cseq)
- dlg->local.cseq = request_cseq;
-
- /* Update CSeq header in the request. */
- cseq_hdr = (pjsip_cseq_hdr*) pjsip_msg_find_hdr( tdata->msg,
- PJSIP_H_CSEQ, NULL);
- pj_assert(cseq_hdr != NULL);
-
- /* Update the CSeq */
- cseq_hdr->cseq = request_cseq;
-
- /* Force the whole message to be re-printed. */
- pjsip_tx_data_invalidate_msg( tdata );
-
- /* Now send the request. */
- status = dlg_send_request(dlg, tdata);
-
- } else {
- /*
- * This is only valid for sending response to INVITE!
- */
- pjsip_cseq_hdr *cseq_hdr;
-
- if (dlg->invite_tsx == NULL || dlg->invite_tsx->status_code >= 200) {
- status = -1;
- goto on_return;
- }
-
- cseq_hdr = (pjsip_cseq_hdr*) pjsip_msg_find_hdr( tdata->msg,
- PJSIP_H_CSEQ, NULL);
- pj_assert(cseq_hdr);
-
- if (cseq_hdr->method.id != PJSIP_INVITE_METHOD) {
- status = -1;
- goto on_return;
- }
-
- pj_assert(cseq_hdr->cseq == dlg->invite_tsx->cseq);
-
- pjsip_tsx_on_tx_msg(dlg->invite_tsx, tdata);
- status = 0;
- }
-
-on_return:
- /* Unlock dialog. */
- unlock_dialog(dlg, &lck);
-
- /* Whatever happen delete the message. */
- pjsip_tx_data_dec_ref( tdata );
-
- return status;
-}
-
-/*
- * Sends outgoing invitation.
- */
-PJ_DEF(pjsip_tx_data*) pjsip_dlg_invite( pjsip_dlg *dlg )
-{
- pjsip_method method;
- struct dialog_lock_data lck;
- const pjsip_allow_hdr *allow_hdr;
- pjsip_tx_data *tdata;
-
- pj_assert(dlg != NULL);
- if (!dlg) {
- return NULL;
- }
-
- PJ_LOG(4, (dlg->obj_name, "request to send invitation"));
-
- /* Lock dialog. */
- lock_dialog(dlg, &lck);
-
- /* Create request. */
- pjsip_method_set( &method, PJSIP_INVITE_METHOD);
- tdata = pjsip_dlg_create_request( dlg, &method, -1 );
- if (tdata == NULL) {
- unlock_dialog(dlg, &lck);
- return NULL;
- }
-
- /* Invite SHOULD contain "Allow" header. */
- allow_hdr = pjsip_endpt_get_allow_hdr( dlg->ua->endpt );
- if (allow_hdr) {
- pjsip_msg_add_hdr( tdata->msg,
- pjsip_hdr_shallow_clone( tdata->pool, allow_hdr));
- }
-
- /* Unlock dialog. */
- unlock_dialog(dlg, &lck);
-
- return tdata;
-}
-
-/*
- * Cancel pending outgoing dialog invitation.
- */
-PJ_DEF(pjsip_tx_data*) pjsip_dlg_cancel( pjsip_dlg *dlg )
-{
- pjsip_tx_data *tdata = NULL;
- struct dialog_lock_data lck;
-
- pj_assert(dlg != NULL);
- if (!dlg) {
- return NULL;
- }
-
- PJ_LOG(4, (dlg->obj_name, "request to cancel invitation"));
-
- lock_dialog(dlg, &lck);
-
- /* Check the INVITE transaction. */
- if (dlg->invite_tsx == NULL || dlg->role != PJSIP_ROLE_UAC) {
- PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_cancel failed: "
- "no INVITE transaction found"));
- goto on_return;
- }
-
- /* Construct the CANCEL request. */
- tdata = pjsip_endpt_create_cancel( dlg->ua->endpt,
- dlg->invite_tsx->last_tx );
- if (tdata == NULL) {
- PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_cancel failed: "
- "unable to construct request"));
- goto on_return;
- }
-
- /* Add reference counter to tdata. */
- pjsip_tx_data_add_ref(tdata);
-
-on_return:
- unlock_dialog(dlg, &lck);
- return tdata;
-}
-
-
-/*
- * Answer incoming dialog invitation, with either provisional responses
- * or a final response.
- */
-PJ_DEF(pjsip_tx_data*) pjsip_dlg_answer( pjsip_dlg *dlg, int code )
-{
- pjsip_tx_data *tdata = NULL;
- pjsip_msg *msg;
- struct dialog_lock_data lck;
-
- pj_assert(dlg != NULL);
- if (!dlg) {
- return NULL;
- }
-
- PJ_LOG(4, (dlg->obj_name, "pjsip_dlg_answer: code=%d", code));
-
- /* Lock dialog. */
- lock_dialog(dlg, &lck);
-
- /* Must have pending INVITE. */
- if (dlg->invite_tsx == NULL) {
- PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: no INVITE transaction found"));
- goto on_return;
- }
- /* Must be UAS. */
- if (dlg->role != PJSIP_ROLE_UAS) {
- PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: not UAS"));
- goto on_return;
- }
- /* Must have not answered with final response before. */
- if (dlg->invite_tsx->status_code >= 200) {
- PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: transaction already terminated "
- "with status %d", dlg->invite_tsx->status_code));
- goto on_return;
- }
-
- /* Get transmit data and the message.
- * We will rewrite the message with a new status code.
- */
- only if tdata is not pending!!!
- tdata = dlg->invite_tsx->last_tx;
- msg = tdata->msg;
-
- /* Set status code and reason phrase. */
- if (code < 100 || code >= 700) code = 500;
- msg->line.status.code = code;
- msg->line.status.reason = *pjsip_get_status_text(code);
-
- /* For 2xx response, Contact and Record-Route must be added. */
- if (PJSIP_IS_STATUS_IN_CLASS(code,200)) {
- const pjsip_allow_hdr *allow_hdr;
-
- if (pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL) == NULL) {
- pjsip_contact_hdr *contact;
- contact = pjsip_hdr_shallow_clone( tdata->pool, dlg->local.contact);
- pjsip_msg_add_hdr( msg, (pjsip_hdr*)contact );
- }
-
- /* 2xx response MUST contain "Allow" header. */
- allow_hdr = pjsip_endpt_get_allow_hdr( dlg->ua->endpt );
- if (allow_hdr) {
- pjsip_msg_add_hdr( msg, pjsip_hdr_shallow_clone( tdata->pool, allow_hdr));
- }
- }
-
- /* for all but 100 responses, To-tag must be set. */
- if (code != 100) {
- pjsip_to_hdr *to;
- to = pjsip_msg_find_hdr( msg, PJSIP_H_TO, NULL);
- to->tag = dlg->local.tag;
- }
-
- /* Reset packet buffer. */
- pjsip_tx_data_invalidate_msg(tdata);
-
- /* Add reference counter */
- pjsip_tx_data_add_ref(tdata);
-
-on_return:
-
- /* Unlock dialog. */
- unlock_dialog(dlg, &lck);
-
- return tdata;
-}
-
-
-/*
- * Send BYE request to terminate the dialog's session.
- */
-PJ_DEF(pjsip_tx_data*) pjsip_dlg_bye( pjsip_dlg *dlg )
-{
- pjsip_method method;
- struct dialog_lock_data lck;
- pjsip_tx_data *tdata;
-
- if (!dlg) {
- pj_assert(dlg != NULL);
- return NULL;
- }
-
- PJ_LOG(4, (dlg->obj_name, "request to terminate session"));
-
- lock_dialog(dlg, &lck);
-
- pjsip_method_set( &method, PJSIP_BYE_METHOD);
- tdata = pjsip_dlg_create_request( dlg, &method, -1 );
-
- unlock_dialog(dlg, &lck);
-
- return tdata;
-}
-
-/*
- * High level function to disconnect dialog's session. Depending on dialog's
- * state, this function will either send CANCEL, final response, or BYE to
- * trigger the disconnection. A status code must be supplied, which will be
- * sent if dialog will be transmitting a final response to INVITE.
- */
-PJ_DEF(pjsip_tx_data*) pjsip_dlg_disconnect( pjsip_dlg *dlg,
- int status_code )
-{
- pjsip_tx_data *tdata = NULL;
-
- pj_assert(dlg != NULL);
- if (!dlg) {
- return NULL;
- }
-
- switch (dlg->state) {
- case PJSIP_DIALOG_STATE_INCOMING:
- tdata = pjsip_dlg_answer(dlg, status_code);
- break;
-
- case PJSIP_DIALOG_STATE_CALLING:
- tdata = pjsip_dlg_cancel(dlg);
- break;
-
- case PJSIP_DIALOG_STATE_PROCEEDING:
- if (dlg->role == PJSIP_ROLE_UAC) {
- tdata = pjsip_dlg_cancel(dlg);
- } else {
- tdata = pjsip_dlg_answer(dlg, status_code);
- }
- break;
-
- case PJSIP_DIALOG_STATE_ESTABLISHED:
- tdata = pjsip_dlg_bye(dlg);
- break;
-
- default:
- PJ_LOG(4,(dlg->obj_name, "Invalid state %s in pjsip_dlg_disconnect()",
- dlg_state_names[dlg->state]));
- break;
- }
-
- return tdata;
-}
-
-/*
- * Handling of the receipt of 2xx/INVITE response.
- */
-static void dlg_on_recv_2xx_invite( pjsip_dlg *dlg,
- pjsip_event *event )
-{
- pjsip_msg *msg;
- pjsip_contact_hdr *contact;
- pjsip_hdr *hdr, *end_hdr;
- pjsip_method method;
- pjsip_tx_data *ack_tdata;
-
- /* Get the message */
- msg = event->src.rdata->msg;
-
- /* Update remote's tag information. */
- pj_strdup(dlg->pool, &dlg->remote.info->tag, &event->src.rdata->to_tag);
-
- /* Copy Contact information in the 2xx/INVITE response to dialog's.
- * remote contact
- */
- contact = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL);
- if (contact) {
- dlg->remote.contact = pjsip_hdr_clone( dlg->pool, contact );
- } else {
- /* duplicate contact from "From" header (?) */
- PJ_LOG(4,(dlg->obj_name, "Received 200/OK to INVITE with no Contact!"));
- dlg->remote.contact = pjsip_contact_hdr_create(dlg->pool);
- dlg->remote.contact->uri = dlg->remote.info->uri;
- }
-
- /* Copy Record-Route header (in reverse order) as dialog's route-set,
- * overwriting previous route-set, if any, even if the received route-set
- * is empty.
- */
- 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_hdr_clone(dlg->pool, hdr);
- pjsip_routing_hdr_set_route(r);
- pj_list_insert_before(&dlg->route_set, r);
- }
- }
-
- /* On receipt of 200/INVITE response, send ACK.
- * This ack must be saved and retransmitted whenever we receive
- * 200/INVITE retransmission, until 64*T1 seconds elapsed.
- */
- pjsip_method_set( &method, PJSIP_ACK_METHOD);
- ack_tdata = pjsip_dlg_create_request( dlg, &method, dlg->invite_tsx->cseq);
- if (ack_tdata == NULL) {
- //PJ_TODO(HANDLE_CREATE_ACK_FAILURE)
- PJ_LOG(2, (dlg->obj_name, "Error sending ACK msg: can't create request"));
- return;
- }
-
- /* Send with the transaction. */
- pjsip_tsx_on_tx_ack( dlg->invite_tsx, ack_tdata);
-
- /* Decrement reference counter because pjsip_dlg_create_request
- * automatically increments the request.
- */
- pjsip_tx_data_dec_ref( ack_tdata );
-}
-
-/*
- * State NULL, before any events have been received.
- */
-static int dlg_on_state_null( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
- event->src_type == PJSIP_EVENT_RX_MSG)
- {
- pjsip_hdr *hdr, *hdr_list;
-
- pj_assert(tsx->method.id == PJSIP_INVITE_METHOD);
-
- /* Save the INVITE transaction. */
- dlg->invite_tsx = tsx;
-
- /* Change state to INCOMING */
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_INCOMING, event);
-
- /* Create response buffer. */
- tsx->last_tx = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, 100);
- pjsip_tx_data_add_ref(tsx->last_tx);
-
- /* Copy the Record-Route headers into dialog's route_set, maintaining
- * the order.
- */
- pj_list_init(&dlg->route_set);
- hdr_list = &event->src.rdata->msg->hdr;
- hdr = hdr_list->next;
- while (hdr != hdr_list) {
- if (hdr->type == PJSIP_H_RECORD_ROUTE) {
- pjsip_route_hdr *route;
- route = pjsip_hdr_clone(dlg->pool, hdr);
- pjsip_routing_hdr_set_route(route);
- pj_list_insert_before(&dlg->route_set, route);
- }
- hdr = hdr->next;
- }
-
- /* Notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
-
- } else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
- event->src_type == PJSIP_EVENT_TX_MSG)
- {
- pj_assert(tsx->method.id == PJSIP_INVITE_METHOD);
-
- /* Save the INVITE transaction. */
- dlg->invite_tsx = tsx;
-
- /* Change state to CALLING. */
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_CALLING, event);
-
- /* Notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
-
- } else {
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
- }
-
- return 0;
-}
-
-/*
- * State INCOMING is after the (callee) dialog has been initialized with
- * the incoming request, but before any responses is sent by the dialog.
- */
-static int dlg_on_state_incoming( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- return dlg_on_state_proceeding_callee( dlg, tsx, event );
-}
-
-/*
- * State CALLING is after the (caller) dialog has sent outgoing invitation
- * but before any responses are received.
- */
-static int dlg_on_state_calling( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- if (tsx == dlg->invite_tsx) {
- return dlg_on_state_proceeding_caller( dlg, tsx, event );
- }
- return 0;
-}
-
-/*
- * State PROCEEDING is after provisional response is received.
- * Since the processing is similar to state CALLING, this function is also
- * called for CALLING state.
- */
-static int dlg_on_state_proceeding_caller( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- int dlg_is_terminated = 0;
-
- /* We only care about our INVITE transaction.
- * Ignore other transaction progression (such as CANCEL).
- */
- if (tsx != dlg->invite_tsx) {
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
- return 0;
- }
-
- if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED) {
- switch (tsx->state) {
- case PJSIP_TSX_STATE_PROCEEDING:
- if (dlg->state != PJSIP_DIALOG_STATE_PROCEEDING) {
- /* Change state to PROCEEDING */
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_PROCEEDING, event);
-
- /* Notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
- } else {
- /* Also notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
- }
- break;
-
- case PJSIP_TSX_STATE_COMPLETED:
- /* Change dialog state. */
- if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
- /* Update remote target, take it from the contact hdr. */
- pjsip_contact_hdr *contact;
- contact = pjsip_msg_find_hdr(event->src.rdata->msg,
- PJSIP_H_CONTACT, NULL);
- if (contact) {
- dlg->remote.target = pjsip_uri_clone(dlg->pool, contact->uri);
- } else {
- PJ_LOG(4,(dlg->obj_name,
- "Warning: found no Contact hdr in 200/OK"));
- }
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_CONNECTING, event);
- } else if (tsx->status_code==401 || tsx->status_code==407) {
- /* Handle Authentication challenge. */
- pjsip_tx_data *tdata;
- tdata = pjsip_auth_reinit_req( dlg->ua->endpt,
- dlg->pool, &dlg->auth_sess,
- dlg->cred_count, dlg->cred_info,
- tsx->last_tx, event->src.rdata);
- if (tdata) {
- /* Re-use original request, with a new transaction.
- * Need not to worry about CSeq, dialog will take care.
- */
- pjsip_dlg_send_msg(dlg, tdata);
- return 0;
- } else {
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
- }
- } else {
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
- }
-
- /* Notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
-
- /* Send ACK when dialog is connected. */
- if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
- pj_assert(event->src_type == PJSIP_EVENT_RX_MSG);
- dlg_on_recv_2xx_invite(dlg, event);
- }
- break;
-
- case PJSIP_TSX_STATE_TERMINATED:
- /*
- * Transaction is terminated because of timeout or transport error.
- * To let the application go to normal state progression, call the
- * callback twice. First is to emulate disconnection, and then call
- * again (with state TERMINATED) to destroy the dialog.
- */
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
-
- /* The INVITE transaction will be destroyed, so release reference
- * to it.
- */
- dlg->invite_tsx = NULL;
-
- /* We should terminate the dialog now.
- * But it's possible that we have other pending transactions (for
- * example, outgoing CANCEL is in progress).
- * So destroy the dialog only if there's no other transaction.
- */
- if (dlg->pending_tsx_count == 0) {
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event);
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
- dlg_is_terminated = 1;
- }
- break;
-
- default:
- pj_assert(0);
- break;
- }
- } else {
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
- }
- return dlg_is_terminated ? -1 : 0;
-}
-
-/*
- * State PROCEEDING for UAS is after the callee send provisional response.
- * This function is also called for INCOMING state.
- */
-static int dlg_on_state_proceeding_callee( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- int dlg_is_terminated = 0;
-
- pj_assert( dlg->invite_tsx != NULL );
-
- if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
- event->src_type == PJSIP_EVENT_TX_MSG &&
- tsx == dlg->invite_tsx)
- {
- switch (tsx->state) {
- case PJSIP_TSX_STATE_PROCEEDING:
- /* Change state to PROCEEDING */
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_PROCEEDING, event);
-
- /* Notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
- break;
-
- case PJSIP_TSX_STATE_COMPLETED:
- case PJSIP_TSX_STATE_TERMINATED:
- /* Change dialog state. */
- if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_CONNECTING, event);
- } else {
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
- }
-
- /* Notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
-
- /* If transaction is terminated in non-2xx situation,
- * terminate dialog as well. This happens when something unexpected
- * occurs, such as transport error.
- */
- if (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
- !PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200))
- {
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event);
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
- dlg_is_terminated = 1;
- }
- break;
-
- default:
- pj_assert(0);
- break;
- }
-
- } else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
- event->src_type == PJSIP_EVENT_RX_MSG &&
- tsx->method.id == PJSIP_CANCEL_METHOD)
- {
- pjsip_tx_data *tdata;
-
- /* Check if sequence number matches the pending INVITE. */
- if (dlg->invite_tsx==NULL ||
- pj_strcmp(&tsx->branch, &dlg->invite_tsx->branch) != 0)
- {
- PJ_LOG(4, (dlg->obj_name, "Received CANCEL with no matching INVITE"));
-
- /* No matching INVITE transaction found. */
- tdata = pjsip_endpt_create_response(dlg->ua->endpt,
- event->src.rdata,
- PJSIP_SC_CALL_TSX_DOES_NOT_EXIST );
- pjsip_tsx_on_tx_msg(tsx, tdata);
- return 0;
- }
-
- /* Always respond the CANCEL with 200/CANCEL no matter what. */
- tdata = pjsip_endpt_create_response(dlg->ua->endpt,
- event->src.rdata,
- 200 );
- pjsip_tsx_on_tx_msg( tsx, tdata );
-
- /* Respond the INVITE transaction with 487, only if transaction has
- * not completed.
- */
- if (dlg->invite_tsx->last_tx) {
- if (dlg->invite_tsx->status_code < 200) {
- tdata = dlg->invite_tsx->last_tx;
- tdata->msg->line.status.code = 487;
- tdata->msg->line.status.reason = *pjsip_get_status_text(487);
- /* Reset packet buffer. */
- pjsip_tx_data_invalidate_msg(tdata);
- pjsip_tsx_on_tx_msg( dlg->invite_tsx, tdata );
- } else {
- PJ_LOG(4, (dlg->obj_name, "Received CANCEL with no effect, "
- "Transaction already terminated "
- "with status %d",
- dlg->invite_tsx->status_code));
- }
- } else {
- tdata = pjsip_endpt_create_response(dlg->ua->endpt,
- event->src.rdata,
- 487);
- pjsip_tsx_on_tx_msg( dlg->invite_tsx, tdata );
- }
- } else {
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
- }
-
- return dlg_is_terminated ? -1 : 0;
-}
-
-static int dlg_on_state_proceeding( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- if (dlg->role == PJSIP_ROLE_UAC) {
- return dlg_on_state_proceeding_caller( dlg, tsx, event );
- } else {
- return dlg_on_state_proceeding_callee( dlg, tsx, event );
- }
-}
-
-static int dlg_on_state_connecting( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- if (tsx == dlg->invite_tsx) {
- if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
- (tsx->state == PJSIP_TSX_STATE_TERMINATED ||
- tsx->state == PJSIP_TSX_STATE_COMPLETED ||
- tsx->state == PJSIP_TSX_STATE_CONFIRMED))
- {
- if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_ESTABLISHED, event);
- } else {
- /* Probably because we never get the ACK, or transport error
- * when sending ACK.
- */
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
- }
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
- } else {
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
- }
- } else {
- /* Handle case when transaction is started when dialog is connecting
- * (e.g. BYE requests cross wire.
- */
- if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
- event->src_type == PJSIP_EVENT_RX_MSG &&
- tsx->role == PJSIP_ROLE_UAS)
- {
- pjsip_tx_data *response;
-
- if (tsx->status_code >= 200)
- return 0;
-
- if (tsx->method.id == PJSIP_BYE_METHOD) {
- /* Set state to DISCONNECTED. */
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
-
- /* Notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
-
- response = pjsip_endpt_create_response( dlg->ua->endpt,
- event->src.rdata, 200);
- } else {
- response = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata,
- PJSIP_SC_INTERNAL_SERVER_ERROR);
- }
-
- if (response)
- pjsip_tsx_on_tx_msg(tsx, response);
-
- return 0;
- }
- }
- return 0;
-}
-
-static int dlg_on_state_established( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- PJ_UNUSED_ARG(tsx)
-
- if (tsx && tsx->method.id == PJSIP_BYE_METHOD) {
- /* Set state to DISCONNECTED. */
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
-
- /* Notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
-
- /* Answer with 200/BYE. */
- if (event->src_type == PJSIP_EVENT_RX_MSG) {
- pjsip_tx_data *tdata;
- tdata = pjsip_endpt_create_response(dlg->ua->endpt,
- event->src.rdata,
- 200 );
- if (tdata)
- pjsip_tsx_on_tx_msg( tsx, tdata );
- }
- } else if (tsx && event->src_type == PJSIP_EVENT_RX_MSG) {
- pjsip_method_e method = event->src.rdata->cseq->method.id;
-
- PJ_TODO(PROPERLY_HANDLE_REINVITATION)
-
- /* Reinvitation. The message may be INVITE or an ACK. */
- if (method == PJSIP_INVITE_METHOD) {
- if (dlg->invite_tsx && dlg->invite_tsx->status_code < 200) {
- /* Section 14.2: A UAS that receives a second INVITE before it
- * sends the final response to a first INVITE with a lower
- * CSeq sequence number on the same dialog MUST return a 500
- * (Server Internal Error) response to the second INVITE and
- * MUST include a Retry-After header field with a randomly
- * chosen value of between 0 and 10 seconds.
- */
- pjsip_retry_after_hdr *hdr;
- pjsip_tx_data *tdata =
- pjsip_endpt_create_response(dlg->ua->endpt,
- event->src.rdata, 500);
-
- if (!tdata)
- return 0;
-
- /* Add Retry-After. */
- hdr = pjsip_retry_after_hdr_create(tdata->pool);
- hdr->ivalue = 9;
- pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
-
- /* Send. */
- pjsip_tsx_on_tx_msg(tsx, tdata);
-
- return 0;
- }
-
- /* Keep this as our current INVITE transaction. */
- dlg->invite_tsx = tsx;
-
- /* Create response buffer. */
- tsx->last_tx = pjsip_endpt_create_response( dlg->ua->endpt,
- event->src.rdata, 100);
- pjsip_tx_data_add_ref(tsx->last_tx);
-
- }
-
- /* Notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_MID_CALL_REQUEST, event);
-
- } else {
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
- }
-
- return 0;
-}
-
-static int dlg_on_state_disconnected( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- PJ_UNUSED_ARG(tsx)
-
- /* Handle case when transaction is started when dialog is disconnected
- * (e.g. BYE requests cross wire.
- */
- if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
- event->src_type == PJSIP_EVENT_RX_MSG &&
- tsx->role == PJSIP_ROLE_UAS)
- {
- pjsip_tx_data *response = NULL;
-
- if (tsx->status_code >= 200)
- return 0;
-
- if (tsx->method.id == PJSIP_BYE_METHOD) {
- response = pjsip_endpt_create_response( dlg->ua->endpt,
- event->src.rdata, 200);
- } else {
- response = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata,
- PJSIP_SC_INTERNAL_SERVER_ERROR);
- }
- if (response)
- pjsip_tsx_on_tx_msg(tsx, response);
-
- return 0;
- }
- /* Handle case when outgoing BYE was rejected with 401/407 */
- else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
- event->src_type == PJSIP_EVENT_RX_MSG &&
- tsx->role == PJSIP_ROLE_UAC)
- {
- if (tsx->status_code==401 || tsx->status_code==407) {
- pjsip_tx_data *tdata;
- tdata = pjsip_auth_reinit_req( dlg->ua->endpt, dlg->pool,
- &dlg->auth_sess,
- dlg->cred_count, dlg->cred_info,
- tsx->last_tx, event->src.rdata);
- if (tdata) {
- pjsip_dlg_send_msg(dlg, tdata);
- }
- }
- }
-
-
- if (dlg->pending_tsx_count == 0) {
- /* Set state to TERMINATED. */
- dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event);
-
- /* Notify application. */
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
-
- return -1;
- } else {
- dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
- }
-
- return 0;
-}
-
-static int dlg_on_state_terminated( pjsip_dlg *dlg,
- pjsip_transaction *tsx,
- pjsip_event *event)
-{
- PJ_UNUSED_ARG(dlg)
- PJ_UNUSED_ARG(tsx)
- PJ_UNUSED_ARG(event)
-
- return -1;
-}
-
+/* $Id$ */ +/* + * Copyright (C) 2003-2006 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_mod_ua/sip_dialog.h> +#include <pjsip_mod_ua/sip_ua.h> +#include <pjsip_mod_ua/sip_ua_private.h> +#include <pjsip/sip_transport.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_types.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_uri.h> +#include <pjsip/sip_util.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_parser.h> +#include <pj/string.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/guid.h> +#include <pj/except.h> +#include <pj/pool.h> + +/* TLS to keep dialog lock record (initialized by UA) */ +int pjsip_dlg_lock_tls_id; + +struct dialog_lock_data +{ + struct dialog_lock_data *prev; + pjsip_dlg *dlg; + int is_alive; +}; + +/* + * Static function prototypes. + */ +static void dlg_create_request_throw( pjsip_tx_data **p_tdata, + pjsip_dlg *dlg, + const pjsip_method *method, + int cseq ); +static int dlg_on_all_state_pre( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_all_state_post( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_state_null( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_state_incoming( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_state_calling( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_state_proceeding( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_state_proceeding_caller( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_state_proceeding_callee( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_state_connecting( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_state_established( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_state_disconnected( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); +static int dlg_on_state_terminated( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); + +/* + * Dialog state handlers. + */ +static int (*dlg_state_handlers[])(struct pjsip_dlg *, pjsip_transaction *, + pjsip_event *) = +{ + &dlg_on_state_null, + &dlg_on_state_incoming, + &dlg_on_state_calling, + &dlg_on_state_proceeding, + &dlg_on_state_connecting, + &dlg_on_state_established, + &dlg_on_state_disconnected, + &dlg_on_state_terminated, +}; + +/* + * Dialog state names. + */ +static const char* dlg_state_names[] = +{ + "STATE_NULL", + "STATE_INCOMING", + "STATE_CALLING", + "STATE_PROCEEDING", + "STATE_CONNECTING", + "STATE_ESTABLISHED", + "STATE_DISCONNECTED", + "STATE_TERMINATED", +}; + + +/* + * Get the dialog string state, normally for logging purpose. + */ +const char *pjsip_dlg_state_str(pjsip_dlg_state_e state) +{ + return dlg_state_names[state]; +} + +/* Lock dialog mutex. */ +static void lock_dialog(pjsip_dlg *dlg, struct dialog_lock_data *lck) +{ + struct dialog_lock_data *prev; + + pj_mutex_lock(dlg->mutex); + prev = pj_thread_local_get(pjsip_dlg_lock_tls_id); + lck->prev = prev; + lck->dlg = dlg; + lck->is_alive = 1; + pj_thread_local_set(pjsip_dlg_lock_tls_id, lck); +} + +/* Carefully unlock dialog mutex, protect against situation when the dialog + * has already been destroyed. + */ +static pj_status_t unlock_dialog(pjsip_dlg *dlg, struct dialog_lock_data *lck) +{ + pj_assert(pj_thread_local_get(pjsip_dlg_lock_tls_id) == lck); + pj_assert(dlg == lck->dlg); + + pj_thread_local_set(pjsip_dlg_lock_tls_id, lck->prev); + if (lck->is_alive) + pj_mutex_unlock(dlg->mutex); + + return lck->is_alive ? 0 : -1; +} + +/* + * This is called by dialog's FSM to change dialog's state. + */ +static void dlg_set_state( pjsip_dlg *dlg, pjsip_dlg_state_e state, + pjsip_event *event) +{ + PJ_UNUSED_ARG(event); + + PJ_LOG(4, (dlg->obj_name, "State %s-->%s (ev=%s, src=%s, data=%p)", + pjsip_dlg_state_str(dlg->state), pjsip_dlg_state_str(state), + event ? pjsip_event_str(event->type) : "", + event ? pjsip_event_str(event->src_type) : "", + event ? event->src.data : NULL)); + + dlg->state = state; + dlg->handle_tsx_event = dlg_state_handlers[state]; +} + +/* + * Invoke dialog's callback. + * This function is called by dialog's FSM, and interpret the event and call + * the corresponding callback registered by application. + */ +static void dlg_call_dlg_callback( pjsip_dlg *dlg, pjsip_dlg_event_e dlg_event, + pjsip_event *event ) +{ + pjsip_dlg_callback *cb = dlg->ua->dlg_cb; + if (!cb) { + PJ_LOG(4,(dlg->obj_name, "Can not call callback (none registered).")); + return; + } + + /* Low level event: call the all-events callback. */ + if (cb->on_all_events) { + (*cb->on_all_events)(dlg, dlg_event, event); + } + + /* Low level event: call the tx/rx callback if this is a tx/rx event. */ + if (event->type == PJSIP_EVENT_BEFORE_TX && cb->on_before_tx) + { + (*cb->on_before_tx)(dlg, event->obj.tsx, event->src.tdata, event->data.long_data); + } + else if (event->type == PJSIP_EVENT_TX_MSG && + event->src_type == PJSIP_EVENT_TX_MSG && cb->on_tx_msg) + { + (*cb->on_tx_msg)(dlg, event->obj.tsx, event->src.tdata); + } + else if (event->type == PJSIP_EVENT_RX_MSG && + event->src_type == PJSIP_EVENT_RX_MSG && cb->on_rx_msg) { + (*cb->on_rx_msg)(dlg, event->obj.tsx, event->src.rdata); + } + + /* Now trigger high level events. + * High level event should only occurs when dialog's state has changed, + * except for on_provisional, which may be called multiple times whenever + * response message is sent + */ + if (dlg->state == PJSIP_DIALOG_STATE_PROCEEDING && + (event->type== PJSIP_EVENT_TSX_STATE_CHANGED) && + event->obj.tsx == dlg->invite_tsx) + { + /* Sent/received provisional responses. */ + if (cb->on_provisional) + (*cb->on_provisional)(dlg, event->obj.tsx, event); + } + + if (dlg_event == PJSIP_DIALOG_EVENT_MID_CALL_REQUEST) { + if (cb->on_mid_call_events) + (*cb->on_mid_call_events)(dlg, event); + return; + } + + if (dlg_event != PJSIP_DIALOG_EVENT_STATE_CHANGED) + return; + + if (dlg->state == PJSIP_DIALOG_STATE_INCOMING) { + + /* New incoming dialog. */ + pj_assert (event->src_type == PJSIP_EVENT_RX_MSG); + (*cb->on_incoming)(dlg, event->obj.tsx, event->src.rdata); + + } else if (dlg->state == PJSIP_DIALOG_STATE_CALLING) { + + /* Dialog has just sent the first INVITE. */ + if (cb->on_calling) { + (*cb->on_calling)(dlg, event->obj.tsx, event->src.tdata); + } + + } else if (dlg->state == PJSIP_DIALOG_STATE_DISCONNECTED) { + + if (cb->on_disconnected) + (*cb->on_disconnected)(dlg, event); + + } else if (dlg->state == PJSIP_DIALOG_STATE_TERMINATED) { + + if (cb->on_terminated) + (*cb->on_terminated)(dlg); + + pjsip_ua_destroy_dialog(dlg); + + } else if (dlg->state == PJSIP_DIALOG_STATE_CONNECTING) { + + if (cb->on_connecting) + (*cb->on_connecting)(dlg, event); + + } else if (dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) { + + if (cb->on_established) + (*cb->on_established)(dlg, event); + } +} + +/* + * This callback receives event from the transaction layer (via User Agent), + * or from dialog timer (for 200/INVITE or ACK retransmission). + */ +void pjsip_dlg_on_tsx_event( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + int status = 0; + struct dialog_lock_data lck; + + PJ_LOG(4, (dlg->obj_name, "state=%s (evt=%s, src=%s)", + pjsip_dlg_state_str(dlg->state), + pjsip_event_str(event->type), + pjsip_event_str(event->src_type))); + + + lock_dialog(dlg, &lck); + + status = dlg_on_all_state_pre( dlg, tsx, event); + + if (status==0) { + status = (*dlg->handle_tsx_event)(dlg, tsx, event); + } + if (status==0) { + status = dlg_on_all_state_post( dlg, tsx, event); + } + + unlock_dialog(dlg, &lck); +} + +/* + * This function contains common processing in all states, and it is called + * before the FSM is invoked. + */ +static int dlg_on_all_state_pre( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + PJ_UNUSED_ARG(event) + + if (event->type != PJSIP_EVENT_TSX_STATE_CHANGED) + return 0; + + if (tsx && (tsx->state==PJSIP_TSX_STATE_CALLING || + tsx->state==PJSIP_TSX_STATE_TRYING)) + { + ++dlg->pending_tsx_count; + + } else if (tsx && tsx->state==PJSIP_TSX_STATE_DESTROYED) + { + --dlg->pending_tsx_count; + if (tsx == dlg->invite_tsx) + dlg->invite_tsx = NULL; + } + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + tsx->handle_ack = 1; + } + return 0; +} + + +/* + * This function contains common processing in all states, and it is called + * after the FSM is invoked. + */ +static int dlg_on_all_state_post( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + PJ_UNUSED_ARG(event) + + if (tsx && tsx->state==PJSIP_TSX_STATE_DESTROYED) { + if (dlg->pending_tsx_count == 0 && + dlg->state != PJSIP_DIALOG_STATE_CONNECTING && + dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED && + dlg->state != PJSIP_DIALOG_STATE_TERMINATED) + { + dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event); + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + return -1; + } + } + + return 0; +} + + +/* + * Internal function to initialize dialog, contains common initialization + * for both UAS and UAC dialog. + */ +static pj_status_t dlg_init( pjsip_dlg *dlg ) +{ + /* Init mutex. */ + dlg->mutex = pj_mutex_create(dlg->pool, "mdlg%p", 0); + if (!dlg->mutex) { + PJ_PERROR((dlg->obj_name, "pj_mutex_create()")); + return -1; + } + + /* Init route-set (Initially empty) */ + pj_list_init(&dlg->route_set); + + /* Init auth credential list. */ + pj_list_init(&dlg->auth_sess); + + return PJ_SUCCESS; +} + +/* + * This one is called just before dialog is destroyed. + * It is called while mutex is held. + */ +PJ_DEF(void) pjsip_on_dialog_destroyed( pjsip_dlg *dlg ) +{ + struct dialog_lock_data *lck; + + //PJ_TODO(CHECK_THIS); + pj_assert(dlg->pending_tsx_count == 0); + + /* Mark dialog as dead. */ + lck = pj_thread_local_get(pjsip_dlg_lock_tls_id); + while (lck) { + if (lck->dlg == dlg) + lck->is_alive = 0; + lck = lck->prev; + } +} + +/* + * Initialize dialog from the received request. + * This is an internal function which is called by the User Agent (sip_ua.c), + * and it will initialize most of dialog's properties with values from the + * received message. + */ +pj_status_t pjsip_dlg_init_from_rdata( pjsip_dlg *dlg, pjsip_rx_data *rdata ) +{ + pjsip_msg *msg = rdata->msg; + pjsip_to_hdr *to; + pjsip_contact_hdr *contact; + pjsip_name_addr *name_addr; + pjsip_url *url; + unsigned flag; + pjsip_event event; + + pj_assert(dlg && rdata); + + PJ_LOG(5, (dlg->obj_name, "init_from_rdata(%p)", rdata)); + + /* Must be an INVITE request. */ + pj_assert(msg->type == PJSIP_REQUEST_MSG && + msg->line.req.method.id == PJSIP_INVITE_METHOD); + + /* Init general dialog data. */ + if (dlg_init(dlg) != PJ_SUCCESS) { + return -1; + } + + /* Get the To header. */ + to = rdata->to; + + /* Copy URI in the To header as our local URI. */ + dlg->local.info = pjsip_hdr_clone( dlg->pool, to); + + /* Set tag in the local info. */ + dlg->local.info->tag = dlg->local.tag; + + /* Create local Contact to be advertised in the response. + * At the moment, just copy URI from the local URI as our contact. + */ + dlg->local.contact = pjsip_contact_hdr_create( dlg->pool ); + dlg->local.contact->star = 0; + name_addr = (pjsip_name_addr *)dlg->local.info->uri; + dlg->local.contact->uri = (pjsip_uri*) name_addr; + url = (pjsip_url*) name_addr->uri; + //url->port = rdata->via->sent_by.port; + //url->port = pj_sockaddr_get_port( pjsip_transport_get_local_addr(rdata->transport) ); + + /* Save remote URI. */ + dlg->remote.info = pjsip_hdr_clone( dlg->pool, rdata->from ); + pjsip_fromto_set_to( dlg->remote.info ); + pj_strdup( dlg->pool, &dlg->remote.tag, &rdata->from->tag ); + + /* Save remote Contact. */ + contact = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL); + if (contact) { + dlg->remote.contact = pjsip_hdr_clone( dlg->pool, contact ); + } else { + PJ_LOG(3,(dlg->obj_name, "No Contact header in INVITE from %s", + pj_sockaddr_get_str_addr(&rdata->addr))); + dlg->remote.contact = pjsip_contact_hdr_create( dlg->pool ); + dlg->remote.contact->uri = dlg->remote.info->uri; + } + + /* Save Call-ID. */ + dlg->call_id = pjsip_cid_hdr_create(dlg->pool); + pj_strdup( dlg->pool, &dlg->call_id->id, &rdata->call_id ); + + /* Initialize local CSeq and save remote CSeq.*/ + dlg->local.cseq = rdata->timestamp.sec & 0xFFFF; + dlg->remote.cseq = rdata->cseq->cseq; + + /* Secure? */ + flag = pjsip_transport_get_flag(rdata->transport); + dlg->secure = (flag & PJSIP_TRANSPORT_SECURE) != 0; + + /* Initial state is NULL. */ + event.type = event.src_type = PJSIP_EVENT_RX_MSG; + event.src.rdata = rdata; + dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, &event); + + PJ_LOG(5, (dlg->obj_name, "init_from_rdata(%p) complete", rdata)); + return PJ_SUCCESS; +} + +/* + * Set the contact details. + */ +PJ_DEF(pj_status_t) pjsip_dlg_set_contact( pjsip_dlg *dlg, + const pj_str_t *contact ) +{ + pjsip_uri *local_uri; + pj_str_t tmp; + + pj_strdup_with_null(dlg->pool, &tmp, contact); + local_uri = pjsip_parse_uri( dlg->pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (local_uri == NULL) { + PJ_LOG(2, (dlg->obj_name, "set_contact: invalid URI")); + return -1; + } + + dlg->local.contact->star = 0; + dlg->local.contact->uri = local_uri; + return 0; +} + +/* + * Set route set. + */ +PJ_DEF(pj_status_t) pjsip_dlg_set_route_set( pjsip_dlg *dlg, + const pjsip_route_hdr *route_set ) +{ + pjsip_route_hdr *hdr; + + pj_list_init(&dlg->route_set); + hdr = route_set->next; + while (hdr != route_set) { + pjsip_route_hdr *cloned = pjsip_hdr_clone(dlg->pool, hdr); + pj_list_insert_before( &dlg->route_set, cloned); + hdr = hdr->next; + } + return 0; +} + +/* + * Set route set without cloning the header. + */ +PJ_DEF(pj_status_t) pjsip_dlg_set_route_set_np( pjsip_dlg *dlg, + pjsip_route_hdr *route_set) +{ + pjsip_route_hdr *hdr; + + pj_list_init(&dlg->route_set); + hdr = route_set->next; + while (hdr != route_set) { + pj_list_insert_before( &dlg->route_set, hdr); + hdr = hdr->next; + } + return 0; +} + +/* + * Application calls this function when it wants to initiate an outgoing + * dialog (incoming dialogs are created automatically by UA when it receives + * INVITE, by calling pjsip_dlg_init_from_rdata()). + * This function should initialize most of the dialog's properties. + */ +PJ_DEF(pj_status_t) pjsip_dlg_init( pjsip_dlg *dlg, + const pj_str_t *c_local_info, + const pj_str_t *c_remote_info, + const pj_str_t *c_target) +{ + pj_time_val tv; + pjsip_event event; + pj_str_t buf; + + if (!dlg || !c_local_info || !c_remote_info) { + pj_assert(dlg && c_local_info && c_remote_info); + return -1; + } + + PJ_LOG(5, (dlg->obj_name, "initializing")); + + /* Init general dialog */ + if (dlg_init(dlg) != PJ_SUCCESS) { + return -1; + } + + /* Duplicate local info. */ + pj_strdup_with_null( dlg->pool, &buf, c_local_info); + + /* Build local URI. */ + dlg->local.target = pjsip_parse_uri(dlg->pool, buf.ptr, buf.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (dlg->local.target == NULL) { + PJ_LOG(2, (dlg->obj_name, + "pjsip_dlg_init: invalid local URI %s", buf.ptr)); + return -1; + } + + /* Set local URI. */ + dlg->local.info = pjsip_from_hdr_create(dlg->pool); + dlg->local.info->uri = dlg->local.target; + dlg->local.info->tag = dlg->local.tag; + + /* Create local Contact to be advertised in the response. */ + dlg->local.contact = pjsip_contact_hdr_create( dlg->pool ); + dlg->local.contact->star = 0; + dlg->local.contact->uri = dlg->local.target; + + /* Set remote URI. */ + dlg->remote.info = pjsip_to_hdr_create(dlg->pool); + + /* Duplicate to buffer. */ + pj_strdup_with_null( dlg->pool, &buf, c_remote_info); + + /* Build remote info. */ + dlg->remote.info->uri = pjsip_parse_uri( dlg->pool, buf.ptr, buf.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (dlg->remote.info->uri == NULL) { + PJ_LOG(2, (dlg->obj_name, + "pjsip_dlg_init: invalid remote URI %s", buf.ptr)); + return -1; + } + + /* Set remote Contact initially equal to the remote URI. */ + dlg->remote.contact = pjsip_contact_hdr_create(dlg->pool); + dlg->remote.contact->star = 0; + dlg->remote.contact->uri = dlg->remote.info->uri; + + /* Set initial remote target. */ + if (c_target != NULL) { + pj_strdup_with_null( dlg->pool, &buf, c_target); + dlg->remote.target = pjsip_parse_uri( dlg->pool, buf.ptr, buf.slen, 0); + if (dlg->remote.target == NULL) { + PJ_LOG(2, (dlg->obj_name, + "pjsip_dlg_init: invalid remote target %s", buf.ptr)); + return -1; + } + } else { + dlg->remote.target = dlg->remote.info->uri; + } + + /* Create globally unique Call-ID */ + dlg->call_id = pjsip_cid_hdr_create(dlg->pool); + pj_create_unique_string( dlg->pool, &dlg->call_id->id ); + + /* Local and remote CSeq */ + pj_gettimeofday(&tv); + dlg->local.cseq = tv.sec & 0xFFFF; + dlg->remote.cseq = 0; + + /* Initial state is NULL. */ + event.type = event.src_type = PJSIP_EVENT_TX_MSG; + event.src.data = NULL; + dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, &event); + + /* Done. */ + PJ_LOG(4, (dlg->obj_name, "%s dialog initialized, From: %.*s, To: %.*s", + pjsip_role_name(dlg->role), + c_local_info->slen, c_local_info->ptr, + c_remote_info->slen, c_remote_info->ptr)); + + return PJ_SUCCESS; +} + +/* + * Set credentials. + */ +PJ_DEF(pj_status_t) pjsip_dlg_set_credentials( pjsip_dlg *dlg, + int count, + const pjsip_cred_info *cred) +{ + if (count > 0) { + dlg->cred_info = pj_pool_alloc(dlg->pool, count * sizeof(pjsip_cred_info)); + pj_memcpy(dlg->cred_info, cred, count * sizeof(pjsip_cred_info)); + } + dlg->cred_count = count; + return 0; +} + +/* + * 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 void dlg_create_request_throw( pjsip_tx_data **p_tdata, + pjsip_dlg *dlg, + const pjsip_method *method, + int cseq ) +{ + pjsip_tx_data *tdata; + pjsip_contact_hdr *contact; + pjsip_route_hdr *route, *end_list; + + /* Contact Header field. + * Contact can only be present in requests that establish dialog (in the + * core SIP spec, only INVITE). + */ + if (method->id == PJSIP_INVITE_METHOD) + contact = dlg->local.contact; + else + contact = NULL; + + tdata = pjsip_endpt_create_request_from_hdr( dlg->ua->endpt, + method, + dlg->remote.target, + dlg->local.info, + dlg->remote.info, + contact, + dlg->call_id, + cseq, + NULL); + if (!tdata) { + PJ_THROW(1); + return; + } + + /* 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_hdr_shallow_clone( tdata->pool, route ); + pjsip_routing_hdr_set_route(r); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)r); + } + + /* Copy authorization headers. */ + pjsip_auth_init_req( dlg->pool, tdata, &dlg->auth_sess, + dlg->cred_count, dlg->cred_info); + + *p_tdata = tdata; +} + + +/* + * This function is called by application to create new outgoing request + * message for this dialog. After the request is created, application can + * modify the message (such adding headers), and eventually send the request. + */ +PJ_DEF(pjsip_tx_data*) pjsip_dlg_create_request( pjsip_dlg *dlg, + const pjsip_method *method, + int cseq) +{ + PJ_USE_EXCEPTION; + struct dialog_lock_data lck; + pjsip_tx_data *tdata = NULL; + + pj_assert(dlg != NULL && method != NULL); + if (!dlg || !method) { + return NULL; + } + + PJ_LOG(5, (dlg->obj_name, "Creating request")); + + /* Lock dialog. */ + lock_dialog(dlg, &lck); + + /* Use outgoing CSeq and increment it by one. */ + if (cseq < 0) + cseq = dlg->local.cseq + 1; + + PJ_LOG(5, (dlg->obj_name, "creating request %.*s cseq=%d", + method->name.slen, method->name.ptr, cseq)); + + /* Create the request. */ + PJ_TRY { + dlg_create_request_throw(&tdata, dlg, method, cseq); + PJ_LOG(5, (dlg->obj_name, "request data %s created", tdata->obj_name)); + } + PJ_DEFAULT { + /* Failed! Delete transmit data. */ + if (tdata) { + pjsip_tx_data_dec_ref( tdata ); + tdata = NULL; + } + } + PJ_END; + + /* Unlock dialog. */ + unlock_dialog(dlg, &lck); + + return tdata; +} + +/* + * Sends request. + * Select the transport for the request message + */ +static pj_status_t dlg_send_request( pjsip_dlg *dlg, pjsip_tx_data *tdata ) +{ + pjsip_transaction *tsx; + pj_status_t status = PJ_SUCCESS; + struct dialog_lock_data lck; + + pj_assert(dlg != NULL && tdata != NULL); + if (!dlg || !tdata) { + return -1; + } + + PJ_LOG(5, (dlg->obj_name, "sending request %s", tdata->obj_name)); + + /* Lock dialog. */ + lock_dialog(dlg, &lck); + + /* Create a new transaction. */ + tsx = pjsip_endpt_create_tsx( dlg->ua->endpt ); + if (!tsx) { + unlock_dialog(dlg, &lck); + return -1; + } + + PJ_LOG(4, (dlg->obj_name, "Created new UAC transaction: %s", tsx->obj_name)); + + /* Initialize transaction */ + tsx->module_data[dlg->ua->mod_id] = dlg; + status = pjsip_tsx_init_uac( tsx, tdata ); + if (status != PJ_SUCCESS) { + unlock_dialog(dlg, &lck); + pjsip_endpt_destroy_tsx( dlg->ua->endpt, tsx ); + return -1; + } + pjsip_endpt_register_tsx( dlg->ua->endpt, tsx ); + + /* Start the transaction. */ + pjsip_tsx_on_tx_msg(tsx, tdata); + + /* Unlock dialog. */ + unlock_dialog(dlg, &lck); + + return status; +} + +/* + * This function can be called by application to send ANY outgoing message + * to remote party. + */ +PJ_DEF(pj_status_t) pjsip_dlg_send_msg( pjsip_dlg *dlg, pjsip_tx_data *tdata ) +{ + pj_status_t status; + int tsx_status; + struct dialog_lock_data lck; + + pj_assert(dlg != NULL && tdata != NULL); + if (!dlg || !tdata) { + return -1; + } + + lock_dialog(dlg, &lck); + + if (tdata->msg->type == PJSIP_REQUEST_MSG) { + int request_cseq; + pjsip_msg *msg = tdata->msg; + pjsip_cseq_hdr *cseq_hdr; + + switch (msg->line.req.method.id) { + case PJSIP_CANCEL_METHOD: + + /* Check the INVITE transaction state. */ + tsx_status = dlg->invite_tsx->status_code; + if (tsx_status >= 200) { + /* Already terminated. Can't cancel. */ + status = -1; + goto on_return; + } + + /* If we've got provisional response, then send CANCEL and wait for + * the response to INVITE to arrive. Otherwise just send CANCEL and + * terminate the INVITE. + */ + if (tsx_status < 100) { + pjsip_tsx_terminate( dlg->invite_tsx, + PJSIP_SC_REQUEST_TERMINATED); + status = 0; + goto on_return; + } + + status = 0; + request_cseq = dlg->invite_tsx->cseq; + break; + + case PJSIP_ACK_METHOD: + /* Sending ACK outside of transaction is not supported at present! */ + pj_assert(0); + status = 0; + request_cseq = dlg->local.cseq; + break; + + case PJSIP_INVITE_METHOD: + /* For an initial INVITE, reset dialog state to NULL so we get + * 'normal' UAC notifications such as on_provisional(), etc. + * Initial INVITE is the request that is sent when the dialog has + * not been established yet. It's not necessarily the first INVITE + * sent, as when the Authorization fails, subsequent INVITE are also + * considered as an initial INVITE. + */ + if (dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) { + /* Set state to NULL. */ + dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, NULL); + + } else { + /* This is a re-INVITE */ + } + status = 0; + request_cseq = dlg->local.cseq + 1; + break; + + default: + status = 0; + request_cseq = dlg->local.cseq + 1; + break; + } + + if (status != 0) + goto on_return; + + /* Update dialog's local CSeq, if necessary. */ + if (request_cseq != dlg->local.cseq) + dlg->local.cseq = request_cseq; + + /* Update CSeq header in the request. */ + cseq_hdr = (pjsip_cseq_hdr*) pjsip_msg_find_hdr( tdata->msg, + PJSIP_H_CSEQ, NULL); + pj_assert(cseq_hdr != NULL); + + /* Update the CSeq */ + cseq_hdr->cseq = request_cseq; + + /* Force the whole message to be re-printed. */ + pjsip_tx_data_invalidate_msg( tdata ); + + /* Now send the request. */ + status = dlg_send_request(dlg, tdata); + + } else { + /* + * This is only valid for sending response to INVITE! + */ + pjsip_cseq_hdr *cseq_hdr; + + if (dlg->invite_tsx == NULL || dlg->invite_tsx->status_code >= 200) { + status = -1; + goto on_return; + } + + cseq_hdr = (pjsip_cseq_hdr*) pjsip_msg_find_hdr( tdata->msg, + PJSIP_H_CSEQ, NULL); + pj_assert(cseq_hdr); + + if (cseq_hdr->method.id != PJSIP_INVITE_METHOD) { + status = -1; + goto on_return; + } + + pj_assert(cseq_hdr->cseq == dlg->invite_tsx->cseq); + + pjsip_tsx_on_tx_msg(dlg->invite_tsx, tdata); + status = 0; + } + +on_return: + /* Unlock dialog. */ + unlock_dialog(dlg, &lck); + + /* Whatever happen delete the message. */ + pjsip_tx_data_dec_ref( tdata ); + + return status; +} + +/* + * Sends outgoing invitation. + */ +PJ_DEF(pjsip_tx_data*) pjsip_dlg_invite( pjsip_dlg *dlg ) +{ + pjsip_method method; + struct dialog_lock_data lck; + const pjsip_allow_hdr *allow_hdr; + pjsip_tx_data *tdata; + + pj_assert(dlg != NULL); + if (!dlg) { + return NULL; + } + + PJ_LOG(4, (dlg->obj_name, "request to send invitation")); + + /* Lock dialog. */ + lock_dialog(dlg, &lck); + + /* Create request. */ + pjsip_method_set( &method, PJSIP_INVITE_METHOD); + tdata = pjsip_dlg_create_request( dlg, &method, -1 ); + if (tdata == NULL) { + unlock_dialog(dlg, &lck); + return NULL; + } + + /* Invite SHOULD contain "Allow" header. */ + allow_hdr = pjsip_endpt_get_allow_hdr( dlg->ua->endpt ); + if (allow_hdr) { + pjsip_msg_add_hdr( tdata->msg, + pjsip_hdr_shallow_clone( tdata->pool, allow_hdr)); + } + + /* Unlock dialog. */ + unlock_dialog(dlg, &lck); + + return tdata; +} + +/* + * Cancel pending outgoing dialog invitation. + */ +PJ_DEF(pjsip_tx_data*) pjsip_dlg_cancel( pjsip_dlg *dlg ) +{ + pjsip_tx_data *tdata = NULL; + struct dialog_lock_data lck; + + pj_assert(dlg != NULL); + if (!dlg) { + return NULL; + } + + PJ_LOG(4, (dlg->obj_name, "request to cancel invitation")); + + lock_dialog(dlg, &lck); + + /* Check the INVITE transaction. */ + if (dlg->invite_tsx == NULL || dlg->role != PJSIP_ROLE_UAC) { + PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_cancel failed: " + "no INVITE transaction found")); + goto on_return; + } + + /* Construct the CANCEL request. */ + tdata = pjsip_endpt_create_cancel( dlg->ua->endpt, + dlg->invite_tsx->last_tx ); + if (tdata == NULL) { + PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_cancel failed: " + "unable to construct request")); + goto on_return; + } + + /* Add reference counter to tdata. */ + pjsip_tx_data_add_ref(tdata); + +on_return: + unlock_dialog(dlg, &lck); + return tdata; +} + + +/* + * Answer incoming dialog invitation, with either provisional responses + * or a final response. + */ +PJ_DEF(pjsip_tx_data*) pjsip_dlg_answer( pjsip_dlg *dlg, int code ) +{ + pjsip_tx_data *tdata = NULL; + pjsip_msg *msg; + struct dialog_lock_data lck; + + pj_assert(dlg != NULL); + if (!dlg) { + return NULL; + } + + PJ_LOG(4, (dlg->obj_name, "pjsip_dlg_answer: code=%d", code)); + + /* Lock dialog. */ + lock_dialog(dlg, &lck); + + /* Must have pending INVITE. */ + if (dlg->invite_tsx == NULL) { + PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: no INVITE transaction found")); + goto on_return; + } + /* Must be UAS. */ + if (dlg->role != PJSIP_ROLE_UAS) { + PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: not UAS")); + goto on_return; + } + /* Must have not answered with final response before. */ + if (dlg->invite_tsx->status_code >= 200) { + PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: transaction already terminated " + "with status %d", dlg->invite_tsx->status_code)); + goto on_return; + } + + /* Get transmit data and the message. + * We will rewrite the message with a new status code. + */ + only if tdata is not pending!!! + tdata = dlg->invite_tsx->last_tx; + msg = tdata->msg; + + /* Set status code and reason phrase. */ + if (code < 100 || code >= 700) code = 500; + msg->line.status.code = code; + msg->line.status.reason = *pjsip_get_status_text(code); + + /* For 2xx response, Contact and Record-Route must be added. */ + if (PJSIP_IS_STATUS_IN_CLASS(code,200)) { + const pjsip_allow_hdr *allow_hdr; + + if (pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL) == NULL) { + pjsip_contact_hdr *contact; + contact = pjsip_hdr_shallow_clone( tdata->pool, dlg->local.contact); + pjsip_msg_add_hdr( msg, (pjsip_hdr*)contact ); + } + + /* 2xx response MUST contain "Allow" header. */ + allow_hdr = pjsip_endpt_get_allow_hdr( dlg->ua->endpt ); + if (allow_hdr) { + pjsip_msg_add_hdr( msg, pjsip_hdr_shallow_clone( tdata->pool, allow_hdr)); + } + } + + /* for all but 100 responses, To-tag must be set. */ + if (code != 100) { + pjsip_to_hdr *to; + to = pjsip_msg_find_hdr( msg, PJSIP_H_TO, NULL); + to->tag = dlg->local.tag; + } + + /* Reset packet buffer. */ + pjsip_tx_data_invalidate_msg(tdata); + + /* Add reference counter */ + pjsip_tx_data_add_ref(tdata); + +on_return: + + /* Unlock dialog. */ + unlock_dialog(dlg, &lck); + + return tdata; +} + + +/* + * Send BYE request to terminate the dialog's session. + */ +PJ_DEF(pjsip_tx_data*) pjsip_dlg_bye( pjsip_dlg *dlg ) +{ + pjsip_method method; + struct dialog_lock_data lck; + pjsip_tx_data *tdata; + + if (!dlg) { + pj_assert(dlg != NULL); + return NULL; + } + + PJ_LOG(4, (dlg->obj_name, "request to terminate session")); + + lock_dialog(dlg, &lck); + + pjsip_method_set( &method, PJSIP_BYE_METHOD); + tdata = pjsip_dlg_create_request( dlg, &method, -1 ); + + unlock_dialog(dlg, &lck); + + return tdata; +} + +/* + * High level function to disconnect dialog's session. Depending on dialog's + * state, this function will either send CANCEL, final response, or BYE to + * trigger the disconnection. A status code must be supplied, which will be + * sent if dialog will be transmitting a final response to INVITE. + */ +PJ_DEF(pjsip_tx_data*) pjsip_dlg_disconnect( pjsip_dlg *dlg, + int status_code ) +{ + pjsip_tx_data *tdata = NULL; + + pj_assert(dlg != NULL); + if (!dlg) { + return NULL; + } + + switch (dlg->state) { + case PJSIP_DIALOG_STATE_INCOMING: + tdata = pjsip_dlg_answer(dlg, status_code); + break; + + case PJSIP_DIALOG_STATE_CALLING: + tdata = pjsip_dlg_cancel(dlg); + break; + + case PJSIP_DIALOG_STATE_PROCEEDING: + if (dlg->role == PJSIP_ROLE_UAC) { + tdata = pjsip_dlg_cancel(dlg); + } else { + tdata = pjsip_dlg_answer(dlg, status_code); + } + break; + + case PJSIP_DIALOG_STATE_ESTABLISHED: + tdata = pjsip_dlg_bye(dlg); + break; + + default: + PJ_LOG(4,(dlg->obj_name, "Invalid state %s in pjsip_dlg_disconnect()", + dlg_state_names[dlg->state])); + break; + } + + return tdata; +} + +/* + * Handling of the receipt of 2xx/INVITE response. + */ +static void dlg_on_recv_2xx_invite( pjsip_dlg *dlg, + pjsip_event *event ) +{ + pjsip_msg *msg; + pjsip_contact_hdr *contact; + pjsip_hdr *hdr, *end_hdr; + pjsip_method method; + pjsip_tx_data *ack_tdata; + + /* Get the message */ + msg = event->src.rdata->msg; + + /* Update remote's tag information. */ + pj_strdup(dlg->pool, &dlg->remote.info->tag, &event->src.rdata->to_tag); + + /* Copy Contact information in the 2xx/INVITE response to dialog's. + * remote contact + */ + contact = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL); + if (contact) { + dlg->remote.contact = pjsip_hdr_clone( dlg->pool, contact ); + } else { + /* duplicate contact from "From" header (?) */ + PJ_LOG(4,(dlg->obj_name, "Received 200/OK to INVITE with no Contact!")); + dlg->remote.contact = pjsip_contact_hdr_create(dlg->pool); + dlg->remote.contact->uri = dlg->remote.info->uri; + } + + /* Copy Record-Route header (in reverse order) as dialog's route-set, + * overwriting previous route-set, if any, even if the received route-set + * is empty. + */ + 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_hdr_clone(dlg->pool, hdr); + pjsip_routing_hdr_set_route(r); + pj_list_insert_before(&dlg->route_set, r); + } + } + + /* On receipt of 200/INVITE response, send ACK. + * This ack must be saved and retransmitted whenever we receive + * 200/INVITE retransmission, until 64*T1 seconds elapsed. + */ + pjsip_method_set( &method, PJSIP_ACK_METHOD); + ack_tdata = pjsip_dlg_create_request( dlg, &method, dlg->invite_tsx->cseq); + if (ack_tdata == NULL) { + //PJ_TODO(HANDLE_CREATE_ACK_FAILURE) + PJ_LOG(2, (dlg->obj_name, "Error sending ACK msg: can't create request")); + return; + } + + /* Send with the transaction. */ + pjsip_tsx_on_tx_ack( dlg->invite_tsx, ack_tdata); + + /* Decrement reference counter because pjsip_dlg_create_request + * automatically increments the request. + */ + pjsip_tx_data_dec_ref( ack_tdata ); +} + +/* + * State NULL, before any events have been received. + */ +static int dlg_on_state_null( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && + event->src_type == PJSIP_EVENT_RX_MSG) + { + pjsip_hdr *hdr, *hdr_list; + + pj_assert(tsx->method.id == PJSIP_INVITE_METHOD); + + /* Save the INVITE transaction. */ + dlg->invite_tsx = tsx; + + /* Change state to INCOMING */ + dlg_set_state(dlg, PJSIP_DIALOG_STATE_INCOMING, event); + + /* Create response buffer. */ + tsx->last_tx = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, 100); + pjsip_tx_data_add_ref(tsx->last_tx); + + /* Copy the Record-Route headers into dialog's route_set, maintaining + * the order. + */ + pj_list_init(&dlg->route_set); + hdr_list = &event->src.rdata->msg->hdr; + hdr = hdr_list->next; + while (hdr != hdr_list) { + if (hdr->type == PJSIP_H_RECORD_ROUTE) { + pjsip_route_hdr *route; + route = pjsip_hdr_clone(dlg->pool, hdr); + pjsip_routing_hdr_set_route(route); + pj_list_insert_before(&dlg->route_set, route); + } + hdr = hdr->next; + } + + /* Notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + + } else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && + event->src_type == PJSIP_EVENT_TX_MSG) + { + pj_assert(tsx->method.id == PJSIP_INVITE_METHOD); + + /* Save the INVITE transaction. */ + dlg->invite_tsx = tsx; + + /* Change state to CALLING. */ + dlg_set_state(dlg, PJSIP_DIALOG_STATE_CALLING, event); + + /* Notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + + } else { + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); + } + + return 0; +} + +/* + * State INCOMING is after the (callee) dialog has been initialized with + * the incoming request, but before any responses is sent by the dialog. + */ +static int dlg_on_state_incoming( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + return dlg_on_state_proceeding_callee( dlg, tsx, event ); +} + +/* + * State CALLING is after the (caller) dialog has sent outgoing invitation + * but before any responses are received. + */ +static int dlg_on_state_calling( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + if (tsx == dlg->invite_tsx) { + return dlg_on_state_proceeding_caller( dlg, tsx, event ); + } + return 0; +} + +/* + * State PROCEEDING is after provisional response is received. + * Since the processing is similar to state CALLING, this function is also + * called for CALLING state. + */ +static int dlg_on_state_proceeding_caller( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + int dlg_is_terminated = 0; + + /* We only care about our INVITE transaction. + * Ignore other transaction progression (such as CANCEL). + */ + if (tsx != dlg->invite_tsx) { + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); + return 0; + } + + if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED) { + switch (tsx->state) { + case PJSIP_TSX_STATE_PROCEEDING: + if (dlg->state != PJSIP_DIALOG_STATE_PROCEEDING) { + /* Change state to PROCEEDING */ + dlg_set_state(dlg, PJSIP_DIALOG_STATE_PROCEEDING, event); + + /* Notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + } else { + /* Also notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); + } + break; + + case PJSIP_TSX_STATE_COMPLETED: + /* Change dialog state. */ + if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { + /* Update remote target, take it from the contact hdr. */ + pjsip_contact_hdr *contact; + contact = pjsip_msg_find_hdr(event->src.rdata->msg, + PJSIP_H_CONTACT, NULL); + if (contact) { + dlg->remote.target = pjsip_uri_clone(dlg->pool, contact->uri); + } else { + PJ_LOG(4,(dlg->obj_name, + "Warning: found no Contact hdr in 200/OK")); + } + dlg_set_state(dlg, PJSIP_DIALOG_STATE_CONNECTING, event); + } else if (tsx->status_code==401 || tsx->status_code==407) { + /* Handle Authentication challenge. */ + pjsip_tx_data *tdata; + tdata = pjsip_auth_reinit_req( dlg->ua->endpt, + dlg->pool, &dlg->auth_sess, + dlg->cred_count, dlg->cred_info, + tsx->last_tx, event->src.rdata); + if (tdata) { + /* Re-use original request, with a new transaction. + * Need not to worry about CSeq, dialog will take care. + */ + pjsip_dlg_send_msg(dlg, tdata); + return 0; + } else { + dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); + } + } else { + dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); + } + + /* Notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + + /* Send ACK when dialog is connected. */ + if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { + pj_assert(event->src_type == PJSIP_EVENT_RX_MSG); + dlg_on_recv_2xx_invite(dlg, event); + } + break; + + case PJSIP_TSX_STATE_TERMINATED: + /* + * Transaction is terminated because of timeout or transport error. + * To let the application go to normal state progression, call the + * callback twice. First is to emulate disconnection, and then call + * again (with state TERMINATED) to destroy the dialog. + */ + dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + + /* The INVITE transaction will be destroyed, so release reference + * to it. + */ + dlg->invite_tsx = NULL; + + /* We should terminate the dialog now. + * But it's possible that we have other pending transactions (for + * example, outgoing CANCEL is in progress). + * So destroy the dialog only if there's no other transaction. + */ + if (dlg->pending_tsx_count == 0) { + dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event); + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + dlg_is_terminated = 1; + } + break; + + default: + pj_assert(0); + break; + } + } else { + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); + } + return dlg_is_terminated ? -1 : 0; +} + +/* + * State PROCEEDING for UAS is after the callee send provisional response. + * This function is also called for INCOMING state. + */ +static int dlg_on_state_proceeding_callee( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + int dlg_is_terminated = 0; + + pj_assert( dlg->invite_tsx != NULL ); + + if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && + event->src_type == PJSIP_EVENT_TX_MSG && + tsx == dlg->invite_tsx) + { + switch (tsx->state) { + case PJSIP_TSX_STATE_PROCEEDING: + /* Change state to PROCEEDING */ + dlg_set_state(dlg, PJSIP_DIALOG_STATE_PROCEEDING, event); + + /* Notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + break; + + case PJSIP_TSX_STATE_COMPLETED: + case PJSIP_TSX_STATE_TERMINATED: + /* Change dialog state. */ + if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { + dlg_set_state(dlg, PJSIP_DIALOG_STATE_CONNECTING, event); + } else { + dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); + } + + /* Notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + + /* If transaction is terminated in non-2xx situation, + * terminate dialog as well. This happens when something unexpected + * occurs, such as transport error. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED && + !PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) + { + dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event); + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + dlg_is_terminated = 1; + } + break; + + default: + pj_assert(0); + break; + } + + } else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && + event->src_type == PJSIP_EVENT_RX_MSG && + tsx->method.id == PJSIP_CANCEL_METHOD) + { + pjsip_tx_data *tdata; + + /* Check if sequence number matches the pending INVITE. */ + if (dlg->invite_tsx==NULL || + pj_strcmp(&tsx->branch, &dlg->invite_tsx->branch) != 0) + { + PJ_LOG(4, (dlg->obj_name, "Received CANCEL with no matching INVITE")); + + /* No matching INVITE transaction found. */ + tdata = pjsip_endpt_create_response(dlg->ua->endpt, + event->src.rdata, + PJSIP_SC_CALL_TSX_DOES_NOT_EXIST ); + pjsip_tsx_on_tx_msg(tsx, tdata); + return 0; + } + + /* Always respond the CANCEL with 200/CANCEL no matter what. */ + tdata = pjsip_endpt_create_response(dlg->ua->endpt, + event->src.rdata, + 200 ); + pjsip_tsx_on_tx_msg( tsx, tdata ); + + /* Respond the INVITE transaction with 487, only if transaction has + * not completed. + */ + if (dlg->invite_tsx->last_tx) { + if (dlg->invite_tsx->status_code < 200) { + tdata = dlg->invite_tsx->last_tx; + tdata->msg->line.status.code = 487; + tdata->msg->line.status.reason = *pjsip_get_status_text(487); + /* Reset packet buffer. */ + pjsip_tx_data_invalidate_msg(tdata); + pjsip_tsx_on_tx_msg( dlg->invite_tsx, tdata ); + } else { + PJ_LOG(4, (dlg->obj_name, "Received CANCEL with no effect, " + "Transaction already terminated " + "with status %d", + dlg->invite_tsx->status_code)); + } + } else { + tdata = pjsip_endpt_create_response(dlg->ua->endpt, + event->src.rdata, + 487); + pjsip_tsx_on_tx_msg( dlg->invite_tsx, tdata ); + } + } else { + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); + } + + return dlg_is_terminated ? -1 : 0; +} + +static int dlg_on_state_proceeding( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + if (dlg->role == PJSIP_ROLE_UAC) { + return dlg_on_state_proceeding_caller( dlg, tsx, event ); + } else { + return dlg_on_state_proceeding_callee( dlg, tsx, event ); + } +} + +static int dlg_on_state_connecting( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + if (tsx == dlg->invite_tsx) { + if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && + (tsx->state == PJSIP_TSX_STATE_TERMINATED || + tsx->state == PJSIP_TSX_STATE_COMPLETED || + tsx->state == PJSIP_TSX_STATE_CONFIRMED)) + { + if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { + dlg_set_state(dlg, PJSIP_DIALOG_STATE_ESTABLISHED, event); + } else { + /* Probably because we never get the ACK, or transport error + * when sending ACK. + */ + dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); + } + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + } else { + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); + } + } else { + /* Handle case when transaction is started when dialog is connecting + * (e.g. BYE requests cross wire. + */ + if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && + event->src_type == PJSIP_EVENT_RX_MSG && + tsx->role == PJSIP_ROLE_UAS) + { + pjsip_tx_data *response; + + if (tsx->status_code >= 200) + return 0; + + if (tsx->method.id == PJSIP_BYE_METHOD) { + /* Set state to DISCONNECTED. */ + dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); + + /* Notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + + response = pjsip_endpt_create_response( dlg->ua->endpt, + event->src.rdata, 200); + } else { + response = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR); + } + + if (response) + pjsip_tsx_on_tx_msg(tsx, response); + + return 0; + } + } + return 0; +} + +static int dlg_on_state_established( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + PJ_UNUSED_ARG(tsx) + + if (tsx && tsx->method.id == PJSIP_BYE_METHOD) { + /* Set state to DISCONNECTED. */ + dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event); + + /* Notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + + /* Answer with 200/BYE. */ + if (event->src_type == PJSIP_EVENT_RX_MSG) { + pjsip_tx_data *tdata; + tdata = pjsip_endpt_create_response(dlg->ua->endpt, + event->src.rdata, + 200 ); + if (tdata) + pjsip_tsx_on_tx_msg( tsx, tdata ); + } + } else if (tsx && event->src_type == PJSIP_EVENT_RX_MSG) { + pjsip_method_e method = event->src.rdata->cseq->method.id; + + PJ_TODO(PROPERLY_HANDLE_REINVITATION) + + /* Reinvitation. The message may be INVITE or an ACK. */ + if (method == PJSIP_INVITE_METHOD) { + if (dlg->invite_tsx && dlg->invite_tsx->status_code < 200) { + /* Section 14.2: A UAS that receives a second INVITE before it + * sends the final response to a first INVITE with a lower + * CSeq sequence number on the same dialog MUST return a 500 + * (Server Internal Error) response to the second INVITE and + * MUST include a Retry-After header field with a randomly + * chosen value of between 0 and 10 seconds. + */ + pjsip_retry_after_hdr *hdr; + pjsip_tx_data *tdata = + pjsip_endpt_create_response(dlg->ua->endpt, + event->src.rdata, 500); + + if (!tdata) + return 0; + + /* Add Retry-After. */ + hdr = pjsip_retry_after_hdr_create(tdata->pool); + hdr->ivalue = 9; + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); + + /* Send. */ + pjsip_tsx_on_tx_msg(tsx, tdata); + + return 0; + } + + /* Keep this as our current INVITE transaction. */ + dlg->invite_tsx = tsx; + + /* Create response buffer. */ + tsx->last_tx = pjsip_endpt_create_response( dlg->ua->endpt, + event->src.rdata, 100); + pjsip_tx_data_add_ref(tsx->last_tx); + + } + + /* Notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_MID_CALL_REQUEST, event); + + } else { + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); + } + + return 0; +} + +static int dlg_on_state_disconnected( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + PJ_UNUSED_ARG(tsx) + + /* Handle case when transaction is started when dialog is disconnected + * (e.g. BYE requests cross wire. + */ + if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && + event->src_type == PJSIP_EVENT_RX_MSG && + tsx->role == PJSIP_ROLE_UAS) + { + pjsip_tx_data *response = NULL; + + if (tsx->status_code >= 200) + return 0; + + if (tsx->method.id == PJSIP_BYE_METHOD) { + response = pjsip_endpt_create_response( dlg->ua->endpt, + event->src.rdata, 200); + } else { + response = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR); + } + if (response) + pjsip_tsx_on_tx_msg(tsx, response); + + return 0; + } + /* Handle case when outgoing BYE was rejected with 401/407 */ + else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED && + event->src_type == PJSIP_EVENT_RX_MSG && + tsx->role == PJSIP_ROLE_UAC) + { + if (tsx->status_code==401 || tsx->status_code==407) { + pjsip_tx_data *tdata; + tdata = pjsip_auth_reinit_req( dlg->ua->endpt, dlg->pool, + &dlg->auth_sess, + dlg->cred_count, dlg->cred_info, + tsx->last_tx, event->src.rdata); + if (tdata) { + pjsip_dlg_send_msg(dlg, tdata); + } + } + } + + + if (dlg->pending_tsx_count == 0) { + /* Set state to TERMINATED. */ + dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event); + + /* Notify application. */ + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event); + + return -1; + } else { + dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event); + } + + return 0; +} + +static int dlg_on_state_terminated( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event) +{ + PJ_UNUSED_ARG(dlg) + PJ_UNUSED_ARG(tsx) + PJ_UNUSED_ARG(event) + + return -1; +} + |