/* $Id$ | |
* | |
*/ | |
/* | |
* PJSIP - SIP Stack | |
* (C)2003-2005 Benny Prijono <bennylp@bulukucing.org> | |
* | |
* Author: | |
* Benny Prijono <bennylp@bulukucing.org> | |
* | |
* This library is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU Lesser General Public | |
* License as published by the Free Software Foundation; either | |
* version 2.1 of the License, or (at your option) any later version. | |
* | |
* This library 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 | |
* Lesser General Public License for more details. | |
* | |
* You should have received a copy of the GNU Lesser General Public | |
* License along with this library; 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_misc.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. | |
*/ | |
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; | |
} | |