From 5f1de1bbb341ea1dc1d27d9bf35764b643ef904a Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Mon, 21 Nov 2005 01:55:47 +0000 Subject: Set svn:eol-style property git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@65 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/src/pjsip-ua/sip_dialog.c | 3604 +++++++++++++++++------------------ pjsip/src/pjsip-ua/sip_reg.c | 1022 +++++----- pjsip/src/pjsip-ua/sip_ua.c | 1012 +++++----- pjsip/src/pjsip-ua/sip_ua_private.h | 72 +- 4 files changed, 2855 insertions(+), 2855 deletions(-) (limited to 'pjsip/src/pjsip-ua') 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 - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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; +} + diff --git a/pjsip/src/pjsip-ua/sip_reg.c b/pjsip/src/pjsip-ua/sip_reg.c index e7bf9bd6..1b22170e 100644 --- a/pjsip/src/pjsip-ua/sip_reg.c +++ b/pjsip/src/pjsip-ua/sip_reg.c @@ -1,511 +1,511 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define REFRESH_TIMER 1 -#define DELAY_BEFORE_REFRESH 5 -#define THIS_FILE "sip_regc.c" - -/** - * SIP client registration structure. - */ -struct pjsip_regc -{ - pj_pool_t *pool; - pjsip_endpoint *endpt; - pj_bool_t _delete_flag; - int pending_tsx; - - void *token; - pjsip_regc_cb *cb; - - pj_str_t str_srv_url; - pjsip_uri *srv_url; - pjsip_cid_hdr *cid_hdr; - pjsip_cseq_hdr *cseq_hdr; - pjsip_from_hdr *from_hdr; - pjsip_to_hdr *to_hdr; - char *contact_buf; - pjsip_generic_string_hdr *contact_hdr; - pjsip_expires_hdr *expires_hdr; - pjsip_contact_hdr *unreg_contact_hdr; - pjsip_expires_hdr *unreg_expires_hdr; - pj_uint32_t expires; - - /* Credentials. */ - int cred_count; - pjsip_cred_info *cred_info; - - /* Authorization sessions. */ - pjsip_auth_session auth_sess_list; - - /* Auto refresh registration. */ - pj_bool_t auto_reg; - pj_timer_entry timer; -}; - - - -PJ_DEF(pjsip_regc*) pjsip_regc_create( pjsip_endpoint *endpt, void *token, - pjsip_regc_cb *cb) -{ - pj_pool_t *pool; - pjsip_regc *regc; - - if (cb == NULL) - return NULL; - - pool = pjsip_endpt_create_pool(endpt, "regc%p", 1024, 1024); - regc = pj_pool_calloc(pool, 1, sizeof(struct pjsip_regc)); - - regc->pool = pool; - regc->endpt = endpt; - regc->token = token; - regc->cb = cb; - regc->contact_buf = pj_pool_alloc(pool, PJSIP_REGC_CONTACT_BUF_SIZE); - regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED; - - pj_list_init(®c->auth_sess_list); - - return regc; -} - - -PJ_DEF(void) pjsip_regc_destroy(pjsip_regc *regc) -{ - if (regc->pending_tsx) { - regc->_delete_flag = 1; - regc->cb = NULL; - } else { - pjsip_endpt_destroy_pool(regc->endpt, regc->pool); - } -} - - -PJ_DEF(pj_pool_t*) pjsip_regc_get_pool(pjsip_regc *regc) -{ - return regc->pool; -} - -static void set_expires( pjsip_regc *regc, pj_uint32_t expires) -{ - if (expires != regc->expires) { - regc->expires_hdr = pjsip_expires_hdr_create(regc->pool); - regc->expires_hdr->ivalue = expires; - } else { - regc->expires_hdr = NULL; - } -} - - -static pj_status_t set_contact( pjsip_regc *regc, - int contact_cnt, - const pj_str_t contact[] ) -{ - int i; - char *s; - const pj_str_t contact_STR = { "Contact", 7}; - - /* Concatenate contacts. */ - for (i=0, s=regc->contact_buf; icontact_buf) + contact[i].slen + 2 > PJSIP_REGC_CONTACT_BUF_SIZE) { - return -1; - } - pj_memcpy(s, contact[i].ptr, contact[i].slen); - s += contact[i].slen; - - if (i != contact_cnt - 1) { - *s++ = ','; - *s++ = ' '; - } - } - - /* Set "Contact" header. */ - regc->contact_hdr = pjsip_generic_string_hdr_create( regc->pool, &contact_STR); - regc->contact_hdr->hvalue.ptr = regc->contact_buf; - regc->contact_hdr->hvalue.slen = (s - regc->contact_buf); - - return 0; -} - - -PJ_DEF(pj_status_t) pjsip_regc_init( pjsip_regc *regc, - const pj_str_t *srv_url, - const pj_str_t *from_url, - const pj_str_t *to_url, - int contact_cnt, - const pj_str_t contact[], - pj_uint32_t expires) -{ - pj_str_t tmp; - - /* Copy server URL. */ - pj_strdup_with_null(regc->pool, ®c->str_srv_url, srv_url); - - /* Set server URL. */ - tmp = regc->str_srv_url; - regc->srv_url = pjsip_parse_uri( regc->pool, tmp.ptr, tmp.slen, 0); - if (regc->srv_url == NULL) { - return -1; - } - - /* Set "From" header. */ - pj_strdup_with_null(regc->pool, &tmp, from_url); - regc->from_hdr = pjsip_from_hdr_create(regc->pool); - regc->from_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen, - PJSIP_PARSE_URI_AS_NAMEADDR); - if (!regc->from_hdr->uri) { - PJ_LOG(4,(THIS_FILE, "regc: invalid source URI %.*s", from_url->slen, from_url->ptr)); - return -1; - } - - /* Set "To" header. */ - pj_strdup_with_null(regc->pool, &tmp, to_url); - regc->to_hdr = pjsip_to_hdr_create(regc->pool); - regc->to_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen, - PJSIP_PARSE_URI_AS_NAMEADDR); - if (!regc->to_hdr->uri) { - PJ_LOG(4,(THIS_FILE, "regc: invalid target URI %.*s", to_url->slen, to_url->ptr)); - return -1; - } - - - /* Set "Contact" header. */ - if (set_contact( regc, contact_cnt, contact) != 0) - return -1; - - /* Set "Expires" header, if required. */ - set_expires( regc, expires); - - /* Set "Call-ID" header. */ - regc->cid_hdr = pjsip_cid_hdr_create(regc->pool); - pj_create_unique_string(regc->pool, ®c->cid_hdr->id); - - /* Set "CSeq" header. */ - regc->cseq_hdr = pjsip_cseq_hdr_create(regc->pool); - regc->cseq_hdr->cseq = 0; - pjsip_method_set( ®c->cseq_hdr->method, PJSIP_REGISTER_METHOD); - - /* Create "Contact" header used in unregistration. */ - regc->unreg_contact_hdr = pjsip_contact_hdr_create(regc->pool); - regc->unreg_contact_hdr->star = 1; - - /* Create "Expires" header used in unregistration. */ - regc->unreg_expires_hdr = pjsip_expires_hdr_create( regc->pool); - regc->unreg_expires_hdr->ivalue = 0; - - /* Done. */ - return 0; -} - -PJ_DEF(pj_status_t) pjsip_regc_set_credentials( pjsip_regc *regc, - int count, - const pjsip_cred_info cred[] ) -{ - if (count > 0) { - regc->cred_info = pj_pool_alloc(regc->pool, count * sizeof(pjsip_cred_info)); - pj_memcpy(regc->cred_info, cred, count * sizeof(pjsip_cred_info)); - } - regc->cred_count = count; - return 0; -} - -static pjsip_tx_data *create_request(pjsip_regc *regc) -{ - pjsip_tx_data *tdata; - pjsip_msg *msg; - - /* Create transmit data. */ - tdata = pjsip_endpt_create_tdata(regc->endpt); - if (!tdata) { - return NULL; - } - - /* Create request message. */ - msg = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG); - tdata->msg = msg; - - /* Initialize request line. */ - pjsip_method_set(&msg->line.req.method, PJSIP_REGISTER_METHOD); - msg->line.req.uri = regc->srv_url; - - /* Add headers. */ - pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->from_hdr); - pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->to_hdr); - pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->cid_hdr); - pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->cseq_hdr); - - /* Add cached authorization headers. */ - pjsip_auth_init_req( regc->pool, tdata, ®c->auth_sess_list, - regc->cred_count, regc->cred_info ); - - /* Add reference counter to transmit data. */ - pjsip_tx_data_add_ref(tdata); - - return tdata; -} - - -PJ_DEF(pjsip_tx_data*) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg) -{ - pjsip_msg *msg; - pjsip_tx_data *tdata; - - tdata = create_request(regc); - if (!tdata) - return NULL; - - msg = tdata->msg; - pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->contact_hdr); - if (regc->expires_hdr) - pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->expires_hdr); - - if (regc->timer.id != 0) { - pjsip_endpt_cancel_timer(regc->endpt, ®c->timer); - regc->timer.id = 0; - } - - regc->auto_reg = autoreg; - - return tdata; -} - - -PJ_DEF(pjsip_tx_data*) pjsip_regc_unregister(pjsip_regc *regc) -{ - pjsip_tx_data *tdata; - pjsip_msg *msg; - - if (regc->timer.id != 0) { - pjsip_endpt_cancel_timer(regc->endpt, ®c->timer); - regc->timer.id = 0; - } - - tdata = create_request(regc); - if (!tdata) - return NULL; - - msg = tdata->msg; - pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_contact_hdr); - pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_expires_hdr); - - return tdata; -} - - -PJ_DEF(pj_status_t) pjsip_regc_update_contact( pjsip_regc *regc, - int contact_cnt, - const pj_str_t contact[] ) -{ - return set_contact( regc, contact_cnt, contact ); -} - - -PJ_DEF(pj_status_t) pjsip_regc_update_expires( pjsip_regc *regc, - pj_uint32_t expires ) -{ - set_expires( regc, expires ); - return 0; -} - - -static void call_callback(pjsip_regc *regc, int status, const pj_str_t *reason, - pjsip_rx_data *rdata, pj_int32_t expiration, - int contact_cnt, pjsip_contact_hdr *contact[]) -{ - struct pjsip_regc_cbparam cbparam; - - - cbparam.regc = regc; - cbparam.token = regc->token; - cbparam.code = status; - cbparam.reason = *reason; - cbparam.rdata = rdata; - cbparam.contact_cnt = contact_cnt; - cbparam.expiration = expiration; - if (contact_cnt) { - pj_memcpy( cbparam.contact, contact, - contact_cnt*sizeof(pjsip_contact_hdr*)); - } - - (*regc->cb)(&cbparam); -} - -static void regc_refresh_timer_cb( pj_timer_heap_t *timer_heap, - struct pj_timer_entry *entry) -{ - pjsip_regc *regc = entry->user_data; - pjsip_tx_data *tdata; - - PJ_UNUSED_ARG(timer_heap) - - entry->id = 0; - tdata = pjsip_regc_register(regc, 1); - if (tdata) { - pjsip_regc_send(regc, tdata); - } else { - pj_str_t reason = pj_str("Unable to create txdata"); - call_callback(regc, -1, &reason, NULL, -1, 0, NULL); - } -} - -static void tsx_callback(void *token, pjsip_event *event) -{ - pjsip_regc *regc = token; - pjsip_transaction *tsx = event->obj.tsx; - - /* If registration data has been deleted by user then remove registration - * data from transaction's callback, and don't call callback. - */ - if (regc->_delete_flag) { - --regc->pending_tsx; - - } else if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED || - tsx->status_code == PJSIP_SC_UNAUTHORIZED) - { - pjsip_rx_data *rdata = event->src.rdata; - pjsip_tx_data *tdata; - - tdata = pjsip_auth_reinit_req( regc->endpt, - regc->pool, ®c->auth_sess_list, - regc->cred_count, regc->cred_info, - tsx->last_tx, event->src.rdata ); - - if (tdata) { - --regc->pending_tsx; - pjsip_regc_send(regc, tdata); - return; - } else { - call_callback(regc, tsx->status_code, &rdata->msg->line.status.reason, - rdata, -1, 0, NULL); - --regc->pending_tsx; - } - } else { - int contact_cnt = 0; - pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT]; - pjsip_rx_data *rdata; - pj_int32_t expiration = 0xFFFF; - - if (tsx->status_code/100 == 2) { - int i; - pjsip_contact_hdr *hdr; - pjsip_msg *msg; - pjsip_expires_hdr *expires; - - rdata = event->src.rdata; - msg = rdata->msg; - hdr = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL); - while (hdr) { - contact[contact_cnt++] = hdr; - hdr = hdr->next; - if (hdr == (void*)&msg->hdr) - break; - hdr = pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, hdr); - } - - expires = pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); - - if (expires) - expiration = expires->ivalue; - - for (i=0; iexpires >= 0 && hdr->expires < expiration) - expiration = contact[i]->expires; - } - - if (regc->auto_reg && expiration != 0 && expiration != 0xFFFF) { - pj_time_val delay = { 0, 0}; - - delay.sec = expiration - DELAY_BEFORE_REFRESH; - if (regc->expires != PJSIP_REGC_EXPIRATION_NOT_SPECIFIED && - delay.sec > (pj_int32_t)regc->expires) - { - delay.sec = regc->expires; - } - if (delay.sec < DELAY_BEFORE_REFRESH) - delay.sec = DELAY_BEFORE_REFRESH; - regc->timer.cb = ®c_refresh_timer_cb; - regc->timer.id = REFRESH_TIMER; - regc->timer.user_data = regc; - pjsip_endpt_schedule_timer( regc->endpt, ®c->timer, &delay); - } - - } else { - rdata = (event->src_type==PJSIP_EVENT_RX_MSG) ? event->src.rdata : NULL; - } - - - /* Call callback. */ - if (expiration == 0xFFFF) expiration = -1; - call_callback(regc, tsx->status_code, - (rdata ? &rdata->msg->line.status.reason - : pjsip_get_status_text(tsx->status_code)), - rdata, expiration, - contact_cnt, contact); - - --regc->pending_tsx; - } - - /* Delete the record if user destroy regc during the callback. */ - if (regc->_delete_flag && regc->pending_tsx==0) { - pjsip_regc_destroy(regc); - } -} - -PJ_DEF(void) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata) -{ - int status; - - /* Make sure we don't have pending transaction. */ - if (regc->pending_tsx) { - pj_str_t reason = pj_str("Transaction in progress"); - call_callback(regc, -1, &reason, NULL, -1, 0, NULL); - pjsip_tx_data_dec_ref( tdata ); - return; - } - - /* Invalidate message buffer. */ - pjsip_tx_data_invalidate_msg(tdata); - - /* Increment CSeq */ - regc->cseq_hdr->cseq++; - - /* Send. */ - status = pjsip_endpt_send_request(regc->endpt, tdata, -1, regc, &tsx_callback); - if (status==0) - ++regc->pending_tsx; - else { - pj_str_t reason = pj_str("Unable to send request."); - call_callback(regc, status, &reason, NULL, -1, 0, NULL); - } -} - - +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REFRESH_TIMER 1 +#define DELAY_BEFORE_REFRESH 5 +#define THIS_FILE "sip_regc.c" + +/** + * SIP client registration structure. + */ +struct pjsip_regc +{ + pj_pool_t *pool; + pjsip_endpoint *endpt; + pj_bool_t _delete_flag; + int pending_tsx; + + void *token; + pjsip_regc_cb *cb; + + pj_str_t str_srv_url; + pjsip_uri *srv_url; + pjsip_cid_hdr *cid_hdr; + pjsip_cseq_hdr *cseq_hdr; + pjsip_from_hdr *from_hdr; + pjsip_to_hdr *to_hdr; + char *contact_buf; + pjsip_generic_string_hdr *contact_hdr; + pjsip_expires_hdr *expires_hdr; + pjsip_contact_hdr *unreg_contact_hdr; + pjsip_expires_hdr *unreg_expires_hdr; + pj_uint32_t expires; + + /* Credentials. */ + int cred_count; + pjsip_cred_info *cred_info; + + /* Authorization sessions. */ + pjsip_auth_session auth_sess_list; + + /* Auto refresh registration. */ + pj_bool_t auto_reg; + pj_timer_entry timer; +}; + + + +PJ_DEF(pjsip_regc*) pjsip_regc_create( pjsip_endpoint *endpt, void *token, + pjsip_regc_cb *cb) +{ + pj_pool_t *pool; + pjsip_regc *regc; + + if (cb == NULL) + return NULL; + + pool = pjsip_endpt_create_pool(endpt, "regc%p", 1024, 1024); + regc = pj_pool_calloc(pool, 1, sizeof(struct pjsip_regc)); + + regc->pool = pool; + regc->endpt = endpt; + regc->token = token; + regc->cb = cb; + regc->contact_buf = pj_pool_alloc(pool, PJSIP_REGC_CONTACT_BUF_SIZE); + regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED; + + pj_list_init(®c->auth_sess_list); + + return regc; +} + + +PJ_DEF(void) pjsip_regc_destroy(pjsip_regc *regc) +{ + if (regc->pending_tsx) { + regc->_delete_flag = 1; + regc->cb = NULL; + } else { + pjsip_endpt_destroy_pool(regc->endpt, regc->pool); + } +} + + +PJ_DEF(pj_pool_t*) pjsip_regc_get_pool(pjsip_regc *regc) +{ + return regc->pool; +} + +static void set_expires( pjsip_regc *regc, pj_uint32_t expires) +{ + if (expires != regc->expires) { + regc->expires_hdr = pjsip_expires_hdr_create(regc->pool); + regc->expires_hdr->ivalue = expires; + } else { + regc->expires_hdr = NULL; + } +} + + +static pj_status_t set_contact( pjsip_regc *regc, + int contact_cnt, + const pj_str_t contact[] ) +{ + int i; + char *s; + const pj_str_t contact_STR = { "Contact", 7}; + + /* Concatenate contacts. */ + for (i=0, s=regc->contact_buf; icontact_buf) + contact[i].slen + 2 > PJSIP_REGC_CONTACT_BUF_SIZE) { + return -1; + } + pj_memcpy(s, contact[i].ptr, contact[i].slen); + s += contact[i].slen; + + if (i != contact_cnt - 1) { + *s++ = ','; + *s++ = ' '; + } + } + + /* Set "Contact" header. */ + regc->contact_hdr = pjsip_generic_string_hdr_create( regc->pool, &contact_STR); + regc->contact_hdr->hvalue.ptr = regc->contact_buf; + regc->contact_hdr->hvalue.slen = (s - regc->contact_buf); + + return 0; +} + + +PJ_DEF(pj_status_t) pjsip_regc_init( pjsip_regc *regc, + const pj_str_t *srv_url, + const pj_str_t *from_url, + const pj_str_t *to_url, + int contact_cnt, + const pj_str_t contact[], + pj_uint32_t expires) +{ + pj_str_t tmp; + + /* Copy server URL. */ + pj_strdup_with_null(regc->pool, ®c->str_srv_url, srv_url); + + /* Set server URL. */ + tmp = regc->str_srv_url; + regc->srv_url = pjsip_parse_uri( regc->pool, tmp.ptr, tmp.slen, 0); + if (regc->srv_url == NULL) { + return -1; + } + + /* Set "From" header. */ + pj_strdup_with_null(regc->pool, &tmp, from_url); + regc->from_hdr = pjsip_from_hdr_create(regc->pool); + regc->from_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (!regc->from_hdr->uri) { + PJ_LOG(4,(THIS_FILE, "regc: invalid source URI %.*s", from_url->slen, from_url->ptr)); + return -1; + } + + /* Set "To" header. */ + pj_strdup_with_null(regc->pool, &tmp, to_url); + regc->to_hdr = pjsip_to_hdr_create(regc->pool); + regc->to_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (!regc->to_hdr->uri) { + PJ_LOG(4,(THIS_FILE, "regc: invalid target URI %.*s", to_url->slen, to_url->ptr)); + return -1; + } + + + /* Set "Contact" header. */ + if (set_contact( regc, contact_cnt, contact) != 0) + return -1; + + /* Set "Expires" header, if required. */ + set_expires( regc, expires); + + /* Set "Call-ID" header. */ + regc->cid_hdr = pjsip_cid_hdr_create(regc->pool); + pj_create_unique_string(regc->pool, ®c->cid_hdr->id); + + /* Set "CSeq" header. */ + regc->cseq_hdr = pjsip_cseq_hdr_create(regc->pool); + regc->cseq_hdr->cseq = 0; + pjsip_method_set( ®c->cseq_hdr->method, PJSIP_REGISTER_METHOD); + + /* Create "Contact" header used in unregistration. */ + regc->unreg_contact_hdr = pjsip_contact_hdr_create(regc->pool); + regc->unreg_contact_hdr->star = 1; + + /* Create "Expires" header used in unregistration. */ + regc->unreg_expires_hdr = pjsip_expires_hdr_create( regc->pool); + regc->unreg_expires_hdr->ivalue = 0; + + /* Done. */ + return 0; +} + +PJ_DEF(pj_status_t) pjsip_regc_set_credentials( pjsip_regc *regc, + int count, + const pjsip_cred_info cred[] ) +{ + if (count > 0) { + regc->cred_info = pj_pool_alloc(regc->pool, count * sizeof(pjsip_cred_info)); + pj_memcpy(regc->cred_info, cred, count * sizeof(pjsip_cred_info)); + } + regc->cred_count = count; + return 0; +} + +static pjsip_tx_data *create_request(pjsip_regc *regc) +{ + pjsip_tx_data *tdata; + pjsip_msg *msg; + + /* Create transmit data. */ + tdata = pjsip_endpt_create_tdata(regc->endpt); + if (!tdata) { + return NULL; + } + + /* Create request message. */ + msg = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG); + tdata->msg = msg; + + /* Initialize request line. */ + pjsip_method_set(&msg->line.req.method, PJSIP_REGISTER_METHOD); + msg->line.req.uri = regc->srv_url; + + /* Add headers. */ + pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->from_hdr); + pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->to_hdr); + pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->cid_hdr); + pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->cseq_hdr); + + /* Add cached authorization headers. */ + pjsip_auth_init_req( regc->pool, tdata, ®c->auth_sess_list, + regc->cred_count, regc->cred_info ); + + /* Add reference counter to transmit data. */ + pjsip_tx_data_add_ref(tdata); + + return tdata; +} + + +PJ_DEF(pjsip_tx_data*) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg) +{ + pjsip_msg *msg; + pjsip_tx_data *tdata; + + tdata = create_request(regc); + if (!tdata) + return NULL; + + msg = tdata->msg; + pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->contact_hdr); + if (regc->expires_hdr) + pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->expires_hdr); + + if (regc->timer.id != 0) { + pjsip_endpt_cancel_timer(regc->endpt, ®c->timer); + regc->timer.id = 0; + } + + regc->auto_reg = autoreg; + + return tdata; +} + + +PJ_DEF(pjsip_tx_data*) pjsip_regc_unregister(pjsip_regc *regc) +{ + pjsip_tx_data *tdata; + pjsip_msg *msg; + + if (regc->timer.id != 0) { + pjsip_endpt_cancel_timer(regc->endpt, ®c->timer); + regc->timer.id = 0; + } + + tdata = create_request(regc); + if (!tdata) + return NULL; + + msg = tdata->msg; + pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_contact_hdr); + pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_expires_hdr); + + return tdata; +} + + +PJ_DEF(pj_status_t) pjsip_regc_update_contact( pjsip_regc *regc, + int contact_cnt, + const pj_str_t contact[] ) +{ + return set_contact( regc, contact_cnt, contact ); +} + + +PJ_DEF(pj_status_t) pjsip_regc_update_expires( pjsip_regc *regc, + pj_uint32_t expires ) +{ + set_expires( regc, expires ); + return 0; +} + + +static void call_callback(pjsip_regc *regc, int status, const pj_str_t *reason, + pjsip_rx_data *rdata, pj_int32_t expiration, + int contact_cnt, pjsip_contact_hdr *contact[]) +{ + struct pjsip_regc_cbparam cbparam; + + + cbparam.regc = regc; + cbparam.token = regc->token; + cbparam.code = status; + cbparam.reason = *reason; + cbparam.rdata = rdata; + cbparam.contact_cnt = contact_cnt; + cbparam.expiration = expiration; + if (contact_cnt) { + pj_memcpy( cbparam.contact, contact, + contact_cnt*sizeof(pjsip_contact_hdr*)); + } + + (*regc->cb)(&cbparam); +} + +static void regc_refresh_timer_cb( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pjsip_regc *regc = entry->user_data; + pjsip_tx_data *tdata; + + PJ_UNUSED_ARG(timer_heap) + + entry->id = 0; + tdata = pjsip_regc_register(regc, 1); + if (tdata) { + pjsip_regc_send(regc, tdata); + } else { + pj_str_t reason = pj_str("Unable to create txdata"); + call_callback(regc, -1, &reason, NULL, -1, 0, NULL); + } +} + +static void tsx_callback(void *token, pjsip_event *event) +{ + pjsip_regc *regc = token; + pjsip_transaction *tsx = event->obj.tsx; + + /* If registration data has been deleted by user then remove registration + * data from transaction's callback, and don't call callback. + */ + if (regc->_delete_flag) { + --regc->pending_tsx; + + } else if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED || + tsx->status_code == PJSIP_SC_UNAUTHORIZED) + { + pjsip_rx_data *rdata = event->src.rdata; + pjsip_tx_data *tdata; + + tdata = pjsip_auth_reinit_req( regc->endpt, + regc->pool, ®c->auth_sess_list, + regc->cred_count, regc->cred_info, + tsx->last_tx, event->src.rdata ); + + if (tdata) { + --regc->pending_tsx; + pjsip_regc_send(regc, tdata); + return; + } else { + call_callback(regc, tsx->status_code, &rdata->msg->line.status.reason, + rdata, -1, 0, NULL); + --regc->pending_tsx; + } + } else { + int contact_cnt = 0; + pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT]; + pjsip_rx_data *rdata; + pj_int32_t expiration = 0xFFFF; + + if (tsx->status_code/100 == 2) { + int i; + pjsip_contact_hdr *hdr; + pjsip_msg *msg; + pjsip_expires_hdr *expires; + + rdata = event->src.rdata; + msg = rdata->msg; + hdr = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL); + while (hdr) { + contact[contact_cnt++] = hdr; + hdr = hdr->next; + if (hdr == (void*)&msg->hdr) + break; + hdr = pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, hdr); + } + + expires = pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); + + if (expires) + expiration = expires->ivalue; + + for (i=0; iexpires >= 0 && hdr->expires < expiration) + expiration = contact[i]->expires; + } + + if (regc->auto_reg && expiration != 0 && expiration != 0xFFFF) { + pj_time_val delay = { 0, 0}; + + delay.sec = expiration - DELAY_BEFORE_REFRESH; + if (regc->expires != PJSIP_REGC_EXPIRATION_NOT_SPECIFIED && + delay.sec > (pj_int32_t)regc->expires) + { + delay.sec = regc->expires; + } + if (delay.sec < DELAY_BEFORE_REFRESH) + delay.sec = DELAY_BEFORE_REFRESH; + regc->timer.cb = ®c_refresh_timer_cb; + regc->timer.id = REFRESH_TIMER; + regc->timer.user_data = regc; + pjsip_endpt_schedule_timer( regc->endpt, ®c->timer, &delay); + } + + } else { + rdata = (event->src_type==PJSIP_EVENT_RX_MSG) ? event->src.rdata : NULL; + } + + + /* Call callback. */ + if (expiration == 0xFFFF) expiration = -1; + call_callback(regc, tsx->status_code, + (rdata ? &rdata->msg->line.status.reason + : pjsip_get_status_text(tsx->status_code)), + rdata, expiration, + contact_cnt, contact); + + --regc->pending_tsx; + } + + /* Delete the record if user destroy regc during the callback. */ + if (regc->_delete_flag && regc->pending_tsx==0) { + pjsip_regc_destroy(regc); + } +} + +PJ_DEF(void) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata) +{ + int status; + + /* Make sure we don't have pending transaction. */ + if (regc->pending_tsx) { + pj_str_t reason = pj_str("Transaction in progress"); + call_callback(regc, -1, &reason, NULL, -1, 0, NULL); + pjsip_tx_data_dec_ref( tdata ); + return; + } + + /* Invalidate message buffer. */ + pjsip_tx_data_invalidate_msg(tdata); + + /* Increment CSeq */ + regc->cseq_hdr->cseq++; + + /* Send. */ + status = pjsip_endpt_send_request(regc->endpt, tdata, -1, regc, &tsx_callback); + if (status==0) + ++regc->pending_tsx; + else { + pj_str_t reason = pj_str("Unable to send request."); + call_callback(regc, status, &reason, NULL, -1, 0, NULL); + } +} + + diff --git a/pjsip/src/pjsip-ua/sip_ua.c b/pjsip/src/pjsip-ua/sip_ua.c index 11f7670c..094be07f 100644 --- a/pjsip/src/pjsip-ua/sip_ua.c +++ b/pjsip/src/pjsip-ua/sip_ua.c @@ -1,506 +1,506 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PJSIP_POOL_LEN_USER_AGENT 1024 -#define PJSIP_POOL_INC_USER_AGENT 0 - - -#define LOG_THIS "useragent.." - -/* - * Static prototypes. - */ -static pj_status_t ua_init( pjsip_endpoint *endpt, - struct pjsip_module *mod, pj_uint32_t id ); -static pj_status_t ua_start( struct pjsip_module *mod ); -static pj_status_t ua_deinit( struct pjsip_module *mod ); -static void ua_tsx_handler( struct pjsip_module *mod, pjsip_event *evt ); -static pjsip_dlg *find_dialog( pjsip_user_agent *ua, - pjsip_rx_data *rdata ); -static pj_status_t ua_register_dialog( pjsip_user_agent *ua, pjsip_dlg *dlg, - pj_str_t *key ); -PJ_DECL(void) pjsip_on_dialog_destroyed( pjsip_dlg *dlg ); - -/* - * Default UA instance. - */ -static pjsip_user_agent ua_instance; - -/* - * Module interface. - */ -static struct pjsip_module mod_ua = -{ - { "User-Agent", 10 }, /* Name. */ - 0, /* Flag */ - 128, /* Priority */ - NULL, /* User agent instance, initialized by APP. */ - 0, /* Number of methods supported (will be initialized later). */ - { 0 }, /* Array of methods (will be initialized later) */ - &ua_init, /* init_module() */ - &ua_start, /* start_module() */ - &ua_deinit, /* deinit_module() */ - &ua_tsx_handler, /* tsx_handler() */ -}; - -/* - * Initialize user agent instance. - */ -static pj_status_t ua_init( pjsip_endpoint *endpt, - struct pjsip_module *mod, pj_uint32_t id ) -{ - static pjsip_method m_invite, m_ack, m_cancel, m_bye; - pjsip_user_agent *ua = mod->mod_data; - extern int pjsip_dlg_lock_tls_id; /* defined in sip_dialog.c */ - - pjsip_method_set( &m_invite, PJSIP_INVITE_METHOD ); - pjsip_method_set( &m_ack, PJSIP_ACK_METHOD ); - pjsip_method_set( &m_cancel, PJSIP_CANCEL_METHOD ); - pjsip_method_set( &m_bye, PJSIP_BYE_METHOD ); - - mod->method_cnt = 4; - mod->methods[0] = &m_invite; - mod->methods[1] = &m_ack; - mod->methods[2] = &m_cancel; - mod->methods[3] = &m_bye; - - /* Initialize the user agent. */ - ua->endpt = endpt; - ua->pool = pjsip_endpt_create_pool(endpt, "pua%p", PJSIP_POOL_LEN_UA, - PJSIP_POOL_INC_UA); - if (!ua->pool) { - return -1; - } - ua->mod_id = id; - ua->mutex = pj_mutex_create(ua->pool, " ua%p", 0); - if (!ua->mutex) { - return -1; - } - ua->dlg_table = pj_hash_create(ua->pool, PJSIP_MAX_DIALOG_COUNT); - if (ua->dlg_table == NULL) { - return -1; - } - pj_list_init(&ua->dlg_list); - - /* Initialize dialog lock. */ - pjsip_dlg_lock_tls_id = pj_thread_local_alloc(); - if (pjsip_dlg_lock_tls_id == -1) { - return -1; - } - pj_thread_local_set(pjsip_dlg_lock_tls_id, NULL); - - return 0; -} - -/* - * Start user agent instance. - */ -static pj_status_t ua_start( struct pjsip_module *mod ) -{ - PJ_UNUSED_ARG(mod) - return 0; -} - -/* - * Destroy user agent. - */ -static pj_status_t ua_deinit( struct pjsip_module *mod ) -{ - pjsip_user_agent *ua = mod->mod_data; - - pj_mutex_unlock(ua->mutex); - - /* Release pool */ - if (ua->pool) { - pjsip_endpt_destroy_pool( ua->endpt, ua->pool ); - } - return 0; -} - -/* - * Get the module interface for the UA module. - */ -PJ_DEF(pjsip_module*) pjsip_ua_get_module(void) -{ - mod_ua.mod_data = &ua_instance; - return &mod_ua; -} - -/* - * Register callback to receive dialog notifications. - */ -PJ_DEF(void) pjsip_ua_set_dialog_callback( pjsip_user_agent *ua, - pjsip_dlg_callback *cb ) -{ - ua->dlg_cb = cb; -} - -/* - * Find dialog. - * This function is called for a new transactions, which a dialog hasn't been - * 'attached' to the transaction. - */ -static pjsip_dlg *find_dialog( pjsip_user_agent *ua, pjsip_rx_data *rdata ) -{ - pjsip_dlg *dlg; - pj_str_t *tag; - - /* Non-CANCEL requests/response can be found by looking at the tag in the - * hash table. CANCEL requests don't have tags, so instead we'll try to - * find the UAS INVITE transaction in endpoint's hash table - */ - if (rdata->cseq->method.id == PJSIP_CANCEL_METHOD) { - - /* Create key for the rdata, but this time, use INVITE as the - * method. - */ - pj_str_t key; - pjsip_role_e role; - pjsip_method invite_method; - pjsip_transaction *invite_tsx; - - if (rdata->msg->type == PJSIP_REQUEST_MSG) { - role = PJSIP_ROLE_UAS; - } else { - role = PJSIP_ROLE_UAC; - } - pjsip_method_set(&invite_method, PJSIP_INVITE_METHOD); - pjsip_tsx_create_key(rdata->pool, &key, role, &invite_method, rdata); - - /* Lookup the INVITE transaction */ - invite_tsx = pjsip_endpt_find_tsx(ua->endpt, &key); - - /* We should find the dialog attached to the INVITE transaction */ - return invite_tsx ? - (pjsip_dlg*) invite_tsx->module_data[ua->mod_id] : NULL; - - } else { - if (rdata->msg->type == PJSIP_REQUEST_MSG) { - tag = &rdata->to_tag; - } else { - tag = &rdata->from_tag; - } - /* Find the dialog in UA hash table */ - pj_mutex_lock(ua->mutex); - dlg = pj_hash_get( ua->dlg_table, tag->ptr, tag->slen ); - pj_mutex_unlock(ua->mutex); - } - - return dlg; -} - -/* - * This function receives event notification from transactions. It is called by - * endpoint. - */ -static void ua_tsx_handler( struct pjsip_module *mod, pjsip_event *event ) -{ - pjsip_user_agent *ua = mod->mod_data; - pjsip_dlg *dlg = NULL; - pjsip_transaction *tsx = event->obj.tsx; - - PJ_LOG(5, (LOG_THIS, "ua_tsx_handler(tsx=%s, evt=%s, src=%s, data=%p)", - (tsx ? tsx->obj_name : "NULL"), pjsip_event_str(event->type), - pjsip_event_str(event->src_type), event->src.data)); - - /* Special case to handle ACK which doesn't match any INVITE transactions. */ - if (event->type == PJSIP_EVENT_RX_ACK_MSG) { - /* Find the dialog based on the "tag". */ - dlg = find_dialog( ua, event->src.rdata ); - - /* We should be able to find it. */ - if (!dlg) { - PJ_LOG(4,(LOG_THIS, "Unable to find dialog for incoming ACK")); - return; - } - - /* Match CSeq with pending INVITE in dialog. */ - if (dlg->invite_tsx && dlg->invite_tsx->cseq==event->src.rdata->cseq->cseq) { - /* A match found. */ - tsx = dlg->invite_tsx; - - /* Pass the event to transaction if transaction handles ACK. */ - if (tsx->handle_ack) { - PJ_LOG(4,(LOG_THIS, "Re-routing strandled ACK to transaction")); - pjsip_tsx_on_rx_msg(tsx, event->src.rdata); - return; - } - } else { - tsx = NULL; - PJ_LOG(4,(LOG_THIS, "Unable to find INVITE tsx for incoming ACK")); - return; - } - } - - /* For discard event, transaction is NULL. */ - if (tsx == NULL) { - return; - } - - /* Try to pickup the dlg from the transaction. */ - dlg = (pjsip_dlg*) tsx->module_data[ua->mod_id]; - - if (dlg != NULL) { - - /* Nothing to do now. */ - - } else if (event->src_type == PJSIP_EVENT_RX_MSG) { - - /* This must be a new UAS transaction. */ - - /* Finds dlg that can handle this transaction. */ - dlg = find_dialog( ua, event->src.rdata); - - /* Create a new dlg if there's no existing dlg that can handle - the request, ONLY if the incoming message is an INVITE request. - */ - if (dlg==NULL && event->src.rdata->msg->type == PJSIP_REQUEST_MSG) { - - if (event->src.rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD) { - /* Create new dialog. */ - dlg = pjsip_ua_create_dialog( ua, PJSIP_ROLE_UAS ); - - if (dlg == NULL || - pjsip_dlg_init_from_rdata( dlg, event->src.rdata) != 0) - { - pjsip_tx_data *tdata; - - /* Dialog initialization has failed. Respond request with 500 */ - if (dlg) { - pjsip_ua_destroy_dialog(dlg); - } - tdata = pjsip_endpt_create_response(ua->endpt, event->src.rdata, - PJSIP_SC_INTERNAL_SERVER_ERROR); - if (tdata) { - pjsip_tsx_on_tx_msg( event->obj.tsx, tdata ); - } - return; - } - - } else { - pjsip_tx_data *tdata; - - /* Check the method */ - switch (tsx->method.id) { - case PJSIP_INVITE_METHOD: - case PJSIP_ACK_METHOD: - case PJSIP_BYE_METHOD: - case PJSIP_CANCEL_METHOD: - /* Stale non-INVITE request. - * For now, respond all stale requests with 481 (?). - */ - tdata = pjsip_endpt_create_response(ua->endpt, event->src.rdata, - PJSIP_SC_CALL_TSX_DOES_NOT_EXIST); - if (tdata) { - pjsip_tsx_on_tx_msg( event->obj.tsx, tdata ); - } - break; - } - - return; - } - } else { - /* Check the method */ - switch (tsx->method.id) { - case PJSIP_INVITE_METHOD: - case PJSIP_ACK_METHOD: - case PJSIP_BYE_METHOD: - case PJSIP_CANCEL_METHOD: - /* These methods belongs to dialog. - * If we receive these methods while no dialog is found, - * then it must be a stale responses. - */ - break; - default: - return; - } - - } - - if (dlg == NULL) { - PJ_LOG(3, (LOG_THIS, "Receives spurious rdata %p from %s:%d", - event->src.rdata, - pj_sockaddr_get_str_addr(&event->src.rdata->addr), - pj_sockaddr_get_port(&event->src.rdata->addr))); - } - - /* Set the dlg in the transaction (dlg can be NULL). */ - tsx->module_data[ua->mod_id] = dlg; - - } else { - /* This CAN happen with event->src_type == PJSIP_EVENT_TX_MSG - * if UAS is responding to a transaction which does not exist. - * Just ignore. - */ - return; - } - - /* Pass the event to the dlg. */ - if (dlg) { - pjsip_dlg_on_tsx_event(dlg, tsx, event); - } -} - -/* - * Register dialog to UA. - */ -static pj_status_t ua_register_dialog( pjsip_user_agent *ua, pjsip_dlg *dlg, - pj_str_t *key ) -{ - /* Assure that no entry with similar key exists in the hash table. */ - pj_assert( pj_hash_get( ua->dlg_table, key->ptr, key->slen) == 0); - - /* Insert entry to hash table. */ - pj_hash_set( dlg->pool, ua->dlg_table, - key->ptr, key->slen, dlg); - - /* Insert to the list. */ - pj_list_insert_before(&ua->dlg_list, dlg); - return PJ_SUCCESS; -} - -/* - * Create a new dialog. - */ -PJ_DEF(pjsip_dlg*) pjsip_ua_create_dialog( pjsip_user_agent *ua, - pjsip_role_e role ) -{ - pj_pool_t *pool; - pjsip_dlg *dlg; - - PJ_UNUSED_ARG(ua) - - /* Create pool for the dialog. */ - pool = pjsip_endpt_create_pool( ua->endpt, "pdlg%p", - PJSIP_POOL_LEN_DIALOG, - PJSIP_POOL_INC_DIALOG); - - /* Create the dialog. */ - dlg = pj_pool_calloc(pool, 1, sizeof(pjsip_dlg)); - dlg->pool = pool; - dlg->ua = ua; - dlg->role = role; - sprintf(dlg->obj_name, "dlg%p", dlg); - - /* Create mutex for the dialog. */ - dlg->mutex = pj_mutex_create(dlg->pool, "mdlg%p", 0); - if (!dlg->mutex) { - pjsip_endpt_destroy_pool(ua->endpt, pool); - return NULL; - } - - /* Create unique tag for the dialog. */ - pj_create_unique_string( pool, &dlg->local.tag ); - - /* Register dialog. */ - pj_mutex_lock(ua->mutex); - if (ua_register_dialog(ua, dlg, &dlg->local.tag) != PJ_SUCCESS) { - pj_mutex_unlock(ua->mutex); - pj_mutex_destroy(dlg->mutex); - pjsip_endpt_destroy_pool( ua->endpt, pool ); - return NULL; - } - pj_mutex_unlock(ua->mutex); - - PJ_LOG(4, (dlg->obj_name, "new %s dialog created", pjsip_role_name(role))); - return dlg; -} - -/* - * Destroy dialog. - */ -PJ_DEF(void) pjsip_ua_destroy_dialog( pjsip_dlg *dlg ) -{ - PJ_LOG(5, (dlg->obj_name, "destroying..")); - - /* Lock dialog's mutex. - * Check the mutex validity first since this function can be called - * on dialog initialization failure (which might be because mutex could not - * be allocated in the first place). - */ - if (dlg->mutex) { - pj_mutex_lock(dlg->mutex); - } - - /* This must be called while holding dialog's mutex, if any. */ - pjsip_on_dialog_destroyed(dlg); - - /* Lock UA. */ - pj_mutex_lock(dlg->ua->mutex); - - /* Erase from hash table. */ - pj_hash_set( dlg->pool, dlg->ua->dlg_table, - dlg->local.tag.ptr, dlg->local.tag.slen, NULL); - - /* Erase from the list. */ - pj_list_erase(dlg); - - /* Unlock UA. */ - pj_mutex_unlock(dlg->ua->mutex); - - /* Unlock mutex. */ - if (dlg->mutex) { - pj_mutex_unlock(dlg->mutex); - } - - /* Destroy the pool. */ - pjsip_endpt_destroy_pool( dlg->ua->endpt, dlg->pool); -} - -/* - * Dump user agent state to log file. - */ -PJ_DEF(void) pjsip_ua_dump(pjsip_user_agent *ua) -{ -#if PJ_LOG_MAX_LEVEL >= 3 - PJ_LOG(3,(LOG_THIS, "Dumping user agent")); - PJ_LOG(3,(LOG_THIS, " Pool capacity=%u, used=%u", - pj_pool_get_capacity(ua->pool), - pj_pool_get_used_size(ua->pool))); - PJ_LOG(3,(LOG_THIS, " Number of dialogs=%u", pj_hash_count(ua->dlg_table))); - - if (pj_hash_count(ua->dlg_table)) { - pjsip_dlg *dlg; - - PJ_LOG(3,(LOG_THIS, " Dumping dialog list:")); - dlg = ua->dlg_list.next; - while (dlg != (pjsip_dlg*) &ua->dlg_list) { - PJ_LOG(3, (LOG_THIS, " %s %s", dlg->obj_name, - pjsip_dlg_state_str(dlg->state))); - dlg = dlg->next; - } - } -#endif -} - +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PJSIP_POOL_LEN_USER_AGENT 1024 +#define PJSIP_POOL_INC_USER_AGENT 0 + + +#define LOG_THIS "useragent.." + +/* + * Static prototypes. + */ +static pj_status_t ua_init( pjsip_endpoint *endpt, + struct pjsip_module *mod, pj_uint32_t id ); +static pj_status_t ua_start( struct pjsip_module *mod ); +static pj_status_t ua_deinit( struct pjsip_module *mod ); +static void ua_tsx_handler( struct pjsip_module *mod, pjsip_event *evt ); +static pjsip_dlg *find_dialog( pjsip_user_agent *ua, + pjsip_rx_data *rdata ); +static pj_status_t ua_register_dialog( pjsip_user_agent *ua, pjsip_dlg *dlg, + pj_str_t *key ); +PJ_DECL(void) pjsip_on_dialog_destroyed( pjsip_dlg *dlg ); + +/* + * Default UA instance. + */ +static pjsip_user_agent ua_instance; + +/* + * Module interface. + */ +static struct pjsip_module mod_ua = +{ + { "User-Agent", 10 }, /* Name. */ + 0, /* Flag */ + 128, /* Priority */ + NULL, /* User agent instance, initialized by APP. */ + 0, /* Number of methods supported (will be initialized later). */ + { 0 }, /* Array of methods (will be initialized later) */ + &ua_init, /* init_module() */ + &ua_start, /* start_module() */ + &ua_deinit, /* deinit_module() */ + &ua_tsx_handler, /* tsx_handler() */ +}; + +/* + * Initialize user agent instance. + */ +static pj_status_t ua_init( pjsip_endpoint *endpt, + struct pjsip_module *mod, pj_uint32_t id ) +{ + static pjsip_method m_invite, m_ack, m_cancel, m_bye; + pjsip_user_agent *ua = mod->mod_data; + extern int pjsip_dlg_lock_tls_id; /* defined in sip_dialog.c */ + + pjsip_method_set( &m_invite, PJSIP_INVITE_METHOD ); + pjsip_method_set( &m_ack, PJSIP_ACK_METHOD ); + pjsip_method_set( &m_cancel, PJSIP_CANCEL_METHOD ); + pjsip_method_set( &m_bye, PJSIP_BYE_METHOD ); + + mod->method_cnt = 4; + mod->methods[0] = &m_invite; + mod->methods[1] = &m_ack; + mod->methods[2] = &m_cancel; + mod->methods[3] = &m_bye; + + /* Initialize the user agent. */ + ua->endpt = endpt; + ua->pool = pjsip_endpt_create_pool(endpt, "pua%p", PJSIP_POOL_LEN_UA, + PJSIP_POOL_INC_UA); + if (!ua->pool) { + return -1; + } + ua->mod_id = id; + ua->mutex = pj_mutex_create(ua->pool, " ua%p", 0); + if (!ua->mutex) { + return -1; + } + ua->dlg_table = pj_hash_create(ua->pool, PJSIP_MAX_DIALOG_COUNT); + if (ua->dlg_table == NULL) { + return -1; + } + pj_list_init(&ua->dlg_list); + + /* Initialize dialog lock. */ + pjsip_dlg_lock_tls_id = pj_thread_local_alloc(); + if (pjsip_dlg_lock_tls_id == -1) { + return -1; + } + pj_thread_local_set(pjsip_dlg_lock_tls_id, NULL); + + return 0; +} + +/* + * Start user agent instance. + */ +static pj_status_t ua_start( struct pjsip_module *mod ) +{ + PJ_UNUSED_ARG(mod) + return 0; +} + +/* + * Destroy user agent. + */ +static pj_status_t ua_deinit( struct pjsip_module *mod ) +{ + pjsip_user_agent *ua = mod->mod_data; + + pj_mutex_unlock(ua->mutex); + + /* Release pool */ + if (ua->pool) { + pjsip_endpt_destroy_pool( ua->endpt, ua->pool ); + } + return 0; +} + +/* + * Get the module interface for the UA module. + */ +PJ_DEF(pjsip_module*) pjsip_ua_get_module(void) +{ + mod_ua.mod_data = &ua_instance; + return &mod_ua; +} + +/* + * Register callback to receive dialog notifications. + */ +PJ_DEF(void) pjsip_ua_set_dialog_callback( pjsip_user_agent *ua, + pjsip_dlg_callback *cb ) +{ + ua->dlg_cb = cb; +} + +/* + * Find dialog. + * This function is called for a new transactions, which a dialog hasn't been + * 'attached' to the transaction. + */ +static pjsip_dlg *find_dialog( pjsip_user_agent *ua, pjsip_rx_data *rdata ) +{ + pjsip_dlg *dlg; + pj_str_t *tag; + + /* Non-CANCEL requests/response can be found by looking at the tag in the + * hash table. CANCEL requests don't have tags, so instead we'll try to + * find the UAS INVITE transaction in endpoint's hash table + */ + if (rdata->cseq->method.id == PJSIP_CANCEL_METHOD) { + + /* Create key for the rdata, but this time, use INVITE as the + * method. + */ + pj_str_t key; + pjsip_role_e role; + pjsip_method invite_method; + pjsip_transaction *invite_tsx; + + if (rdata->msg->type == PJSIP_REQUEST_MSG) { + role = PJSIP_ROLE_UAS; + } else { + role = PJSIP_ROLE_UAC; + } + pjsip_method_set(&invite_method, PJSIP_INVITE_METHOD); + pjsip_tsx_create_key(rdata->pool, &key, role, &invite_method, rdata); + + /* Lookup the INVITE transaction */ + invite_tsx = pjsip_endpt_find_tsx(ua->endpt, &key); + + /* We should find the dialog attached to the INVITE transaction */ + return invite_tsx ? + (pjsip_dlg*) invite_tsx->module_data[ua->mod_id] : NULL; + + } else { + if (rdata->msg->type == PJSIP_REQUEST_MSG) { + tag = &rdata->to_tag; + } else { + tag = &rdata->from_tag; + } + /* Find the dialog in UA hash table */ + pj_mutex_lock(ua->mutex); + dlg = pj_hash_get( ua->dlg_table, tag->ptr, tag->slen ); + pj_mutex_unlock(ua->mutex); + } + + return dlg; +} + +/* + * This function receives event notification from transactions. It is called by + * endpoint. + */ +static void ua_tsx_handler( struct pjsip_module *mod, pjsip_event *event ) +{ + pjsip_user_agent *ua = mod->mod_data; + pjsip_dlg *dlg = NULL; + pjsip_transaction *tsx = event->obj.tsx; + + PJ_LOG(5, (LOG_THIS, "ua_tsx_handler(tsx=%s, evt=%s, src=%s, data=%p)", + (tsx ? tsx->obj_name : "NULL"), pjsip_event_str(event->type), + pjsip_event_str(event->src_type), event->src.data)); + + /* Special case to handle ACK which doesn't match any INVITE transactions. */ + if (event->type == PJSIP_EVENT_RX_ACK_MSG) { + /* Find the dialog based on the "tag". */ + dlg = find_dialog( ua, event->src.rdata ); + + /* We should be able to find it. */ + if (!dlg) { + PJ_LOG(4,(LOG_THIS, "Unable to find dialog for incoming ACK")); + return; + } + + /* Match CSeq with pending INVITE in dialog. */ + if (dlg->invite_tsx && dlg->invite_tsx->cseq==event->src.rdata->cseq->cseq) { + /* A match found. */ + tsx = dlg->invite_tsx; + + /* Pass the event to transaction if transaction handles ACK. */ + if (tsx->handle_ack) { + PJ_LOG(4,(LOG_THIS, "Re-routing strandled ACK to transaction")); + pjsip_tsx_on_rx_msg(tsx, event->src.rdata); + return; + } + } else { + tsx = NULL; + PJ_LOG(4,(LOG_THIS, "Unable to find INVITE tsx for incoming ACK")); + return; + } + } + + /* For discard event, transaction is NULL. */ + if (tsx == NULL) { + return; + } + + /* Try to pickup the dlg from the transaction. */ + dlg = (pjsip_dlg*) tsx->module_data[ua->mod_id]; + + if (dlg != NULL) { + + /* Nothing to do now. */ + + } else if (event->src_type == PJSIP_EVENT_RX_MSG) { + + /* This must be a new UAS transaction. */ + + /* Finds dlg that can handle this transaction. */ + dlg = find_dialog( ua, event->src.rdata); + + /* Create a new dlg if there's no existing dlg that can handle + the request, ONLY if the incoming message is an INVITE request. + */ + if (dlg==NULL && event->src.rdata->msg->type == PJSIP_REQUEST_MSG) { + + if (event->src.rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD) { + /* Create new dialog. */ + dlg = pjsip_ua_create_dialog( ua, PJSIP_ROLE_UAS ); + + if (dlg == NULL || + pjsip_dlg_init_from_rdata( dlg, event->src.rdata) != 0) + { + pjsip_tx_data *tdata; + + /* Dialog initialization has failed. Respond request with 500 */ + if (dlg) { + pjsip_ua_destroy_dialog(dlg); + } + tdata = pjsip_endpt_create_response(ua->endpt, event->src.rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR); + if (tdata) { + pjsip_tsx_on_tx_msg( event->obj.tsx, tdata ); + } + return; + } + + } else { + pjsip_tx_data *tdata; + + /* Check the method */ + switch (tsx->method.id) { + case PJSIP_INVITE_METHOD: + case PJSIP_ACK_METHOD: + case PJSIP_BYE_METHOD: + case PJSIP_CANCEL_METHOD: + /* Stale non-INVITE request. + * For now, respond all stale requests with 481 (?). + */ + tdata = pjsip_endpt_create_response(ua->endpt, event->src.rdata, + PJSIP_SC_CALL_TSX_DOES_NOT_EXIST); + if (tdata) { + pjsip_tsx_on_tx_msg( event->obj.tsx, tdata ); + } + break; + } + + return; + } + } else { + /* Check the method */ + switch (tsx->method.id) { + case PJSIP_INVITE_METHOD: + case PJSIP_ACK_METHOD: + case PJSIP_BYE_METHOD: + case PJSIP_CANCEL_METHOD: + /* These methods belongs to dialog. + * If we receive these methods while no dialog is found, + * then it must be a stale responses. + */ + break; + default: + return; + } + + } + + if (dlg == NULL) { + PJ_LOG(3, (LOG_THIS, "Receives spurious rdata %p from %s:%d", + event->src.rdata, + pj_sockaddr_get_str_addr(&event->src.rdata->addr), + pj_sockaddr_get_port(&event->src.rdata->addr))); + } + + /* Set the dlg in the transaction (dlg can be NULL). */ + tsx->module_data[ua->mod_id] = dlg; + + } else { + /* This CAN happen with event->src_type == PJSIP_EVENT_TX_MSG + * if UAS is responding to a transaction which does not exist. + * Just ignore. + */ + return; + } + + /* Pass the event to the dlg. */ + if (dlg) { + pjsip_dlg_on_tsx_event(dlg, tsx, event); + } +} + +/* + * Register dialog to UA. + */ +static pj_status_t ua_register_dialog( pjsip_user_agent *ua, pjsip_dlg *dlg, + pj_str_t *key ) +{ + /* Assure that no entry with similar key exists in the hash table. */ + pj_assert( pj_hash_get( ua->dlg_table, key->ptr, key->slen) == 0); + + /* Insert entry to hash table. */ + pj_hash_set( dlg->pool, ua->dlg_table, + key->ptr, key->slen, dlg); + + /* Insert to the list. */ + pj_list_insert_before(&ua->dlg_list, dlg); + return PJ_SUCCESS; +} + +/* + * Create a new dialog. + */ +PJ_DEF(pjsip_dlg*) pjsip_ua_create_dialog( pjsip_user_agent *ua, + pjsip_role_e role ) +{ + pj_pool_t *pool; + pjsip_dlg *dlg; + + PJ_UNUSED_ARG(ua) + + /* Create pool for the dialog. */ + pool = pjsip_endpt_create_pool( ua->endpt, "pdlg%p", + PJSIP_POOL_LEN_DIALOG, + PJSIP_POOL_INC_DIALOG); + + /* Create the dialog. */ + dlg = pj_pool_calloc(pool, 1, sizeof(pjsip_dlg)); + dlg->pool = pool; + dlg->ua = ua; + dlg->role = role; + sprintf(dlg->obj_name, "dlg%p", dlg); + + /* Create mutex for the dialog. */ + dlg->mutex = pj_mutex_create(dlg->pool, "mdlg%p", 0); + if (!dlg->mutex) { + pjsip_endpt_destroy_pool(ua->endpt, pool); + return NULL; + } + + /* Create unique tag for the dialog. */ + pj_create_unique_string( pool, &dlg->local.tag ); + + /* Register dialog. */ + pj_mutex_lock(ua->mutex); + if (ua_register_dialog(ua, dlg, &dlg->local.tag) != PJ_SUCCESS) { + pj_mutex_unlock(ua->mutex); + pj_mutex_destroy(dlg->mutex); + pjsip_endpt_destroy_pool( ua->endpt, pool ); + return NULL; + } + pj_mutex_unlock(ua->mutex); + + PJ_LOG(4, (dlg->obj_name, "new %s dialog created", pjsip_role_name(role))); + return dlg; +} + +/* + * Destroy dialog. + */ +PJ_DEF(void) pjsip_ua_destroy_dialog( pjsip_dlg *dlg ) +{ + PJ_LOG(5, (dlg->obj_name, "destroying..")); + + /* Lock dialog's mutex. + * Check the mutex validity first since this function can be called + * on dialog initialization failure (which might be because mutex could not + * be allocated in the first place). + */ + if (dlg->mutex) { + pj_mutex_lock(dlg->mutex); + } + + /* This must be called while holding dialog's mutex, if any. */ + pjsip_on_dialog_destroyed(dlg); + + /* Lock UA. */ + pj_mutex_lock(dlg->ua->mutex); + + /* Erase from hash table. */ + pj_hash_set( dlg->pool, dlg->ua->dlg_table, + dlg->local.tag.ptr, dlg->local.tag.slen, NULL); + + /* Erase from the list. */ + pj_list_erase(dlg); + + /* Unlock UA. */ + pj_mutex_unlock(dlg->ua->mutex); + + /* Unlock mutex. */ + if (dlg->mutex) { + pj_mutex_unlock(dlg->mutex); + } + + /* Destroy the pool. */ + pjsip_endpt_destroy_pool( dlg->ua->endpt, dlg->pool); +} + +/* + * Dump user agent state to log file. + */ +PJ_DEF(void) pjsip_ua_dump(pjsip_user_agent *ua) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + PJ_LOG(3,(LOG_THIS, "Dumping user agent")); + PJ_LOG(3,(LOG_THIS, " Pool capacity=%u, used=%u", + pj_pool_get_capacity(ua->pool), + pj_pool_get_used_size(ua->pool))); + PJ_LOG(3,(LOG_THIS, " Number of dialogs=%u", pj_hash_count(ua->dlg_table))); + + if (pj_hash_count(ua->dlg_table)) { + pjsip_dlg *dlg; + + PJ_LOG(3,(LOG_THIS, " Dumping dialog list:")); + dlg = ua->dlg_list.next; + while (dlg != (pjsip_dlg*) &ua->dlg_list) { + PJ_LOG(3, (LOG_THIS, " %s %s", dlg->obj_name, + pjsip_dlg_state_str(dlg->state))); + dlg = dlg->next; + } + } +#endif +} + diff --git a/pjsip/src/pjsip-ua/sip_ua_private.h b/pjsip/src/pjsip-ua/sip_ua_private.h index 93657ddf..8a174afe 100644 --- a/pjsip/src/pjsip-ua/sip_ua_private.h +++ b/pjsip/src/pjsip-ua/sip_ua_private.h @@ -1,36 +1,36 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ -#ifndef __PJSIP_UA_PRIVATE_H__ -#define __PJSIP_UA_PRIVATE_H__ - - -/* - * Internal dialog functions. - */ -pj_status_t pjsip_dlg_init_from_rdata( pjsip_dlg *dlg, - pjsip_rx_data *rdata ); - - -void pjsip_dlg_on_tsx_event( pjsip_dlg *dlg, - pjsip_transaction *tsx, - pjsip_event *event); - - -#endif /* __PJSIP_UA_PRIVATE_H__ */ - +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJSIP_UA_PRIVATE_H__ +#define __PJSIP_UA_PRIVATE_H__ + + +/* + * Internal dialog functions. + */ +pj_status_t pjsip_dlg_init_from_rdata( pjsip_dlg *dlg, + pjsip_rx_data *rdata ); + + +void pjsip_dlg_on_tsx_event( pjsip_dlg *dlg, + pjsip_transaction *tsx, + pjsip_event *event); + + +#endif /* __PJSIP_UA_PRIVATE_H__ */ + -- cgit v1.2.3