1516 lines
41 KiB
C
1516 lines
41 KiB
C
/* $Id$ */
|
|
/*
|
|
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
|
|
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
#include <pjsip-ua/sip_regc.h>
|
|
#include <pjsip/sip_endpoint.h>
|
|
#include <pjsip/sip_parser.h>
|
|
#include <pjsip/sip_module.h>
|
|
#include <pjsip/sip_transaction.h>
|
|
#include <pjsip/sip_event.h>
|
|
#include <pjsip/sip_util.h>
|
|
#include <pjsip/sip_auth_msg.h>
|
|
#include <pjsip/sip_errno.h>
|
|
#include <pj/assert.h>
|
|
#include <pj/guid.h>
|
|
#include <pj/lock.h>
|
|
#include <pj/os.h>
|
|
#include <pj/pool.h>
|
|
#include <pj/log.h>
|
|
#include <pj/rand.h>
|
|
#include <pj/string.h>
|
|
|
|
|
|
#define REFRESH_TIMER 1
|
|
#define DELAY_BEFORE_REFRESH PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH
|
|
#define THIS_FILE "sip_reg.c"
|
|
|
|
/* Outgoing transaction timeout when server sends 100 but never replies
|
|
* with final response. Value is in MILISECONDS!
|
|
*/
|
|
#define REGC_TSX_TIMEOUT 33000
|
|
|
|
#define NOEXP PJSIP_REGC_EXPIRATION_NOT_SPECIFIED
|
|
|
|
static const pj_str_t XUID_PARAM_NAME = { "x-uid", 5 };
|
|
|
|
|
|
/* Current/pending operation */
|
|
enum regc_op
|
|
{
|
|
REGC_IDLE,
|
|
REGC_REGISTERING,
|
|
REGC_UNREGISTERING
|
|
};
|
|
|
|
/**
|
|
* SIP client registration structure.
|
|
*/
|
|
struct pjsip_regc
|
|
{
|
|
pj_pool_t *pool;
|
|
pjsip_endpoint *endpt;
|
|
pj_lock_t *lock;
|
|
pj_bool_t _delete_flag;
|
|
pj_bool_t has_tsx;
|
|
pj_atomic_t *busy_ctr;
|
|
enum regc_op current_op;
|
|
|
|
pj_bool_t add_xuid_param;
|
|
|
|
void *token;
|
|
pjsip_regc_cb *cb;
|
|
pjsip_regc_tsx_cb *tsx_cb;
|
|
|
|
pj_str_t str_srv_url;
|
|
pjsip_uri *srv_url;
|
|
pjsip_cid_hdr *cid_hdr;
|
|
pjsip_cseq_hdr *cseq_hdr;
|
|
pj_str_t from_uri;
|
|
pjsip_from_hdr *from_hdr;
|
|
pjsip_to_hdr *to_hdr;
|
|
pjsip_contact_hdr contact_hdr_list;
|
|
pjsip_contact_hdr removed_contact_hdr_list;
|
|
pjsip_expires_hdr *expires_hdr;
|
|
pj_uint32_t expires;
|
|
pj_uint32_t expires_requested;
|
|
pj_uint32_t delay_before_refresh;
|
|
pjsip_route_hdr route_set;
|
|
pjsip_hdr hdr_list;
|
|
pjsip_host_port via_addr;
|
|
const void *via_tp;
|
|
|
|
/* Authorization sessions. */
|
|
pjsip_auth_clt_sess auth_sess;
|
|
|
|
/* Auto refresh registration. */
|
|
pj_bool_t auto_reg;
|
|
pj_time_val last_reg;
|
|
pj_time_val next_reg;
|
|
pj_timer_entry timer;
|
|
|
|
/* Transport selector */
|
|
pjsip_tpselector tp_sel;
|
|
|
|
/* Last transport used. We acquire the transport to keep
|
|
* it open.
|
|
*/
|
|
pjsip_transport *last_transport;
|
|
};
|
|
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
|
|
pjsip_regc_cb *cb,
|
|
pjsip_regc **p_regc)
|
|
{
|
|
pj_pool_t *pool;
|
|
pjsip_regc *regc;
|
|
pj_status_t status;
|
|
|
|
/* Verify arguments. */
|
|
PJ_ASSERT_RETURN(endpt && cb && p_regc, PJ_EINVAL);
|
|
|
|
pool = pjsip_endpt_create_pool(endpt, "regc%p", 1024, 1024);
|
|
PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
|
|
|
|
regc = PJ_POOL_ZALLOC_T(pool, pjsip_regc);
|
|
|
|
regc->pool = pool;
|
|
regc->endpt = endpt;
|
|
regc->token = token;
|
|
regc->cb = cb;
|
|
regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED;
|
|
regc->add_xuid_param = pjsip_cfg()->regc.add_xuid_param;
|
|
|
|
status = pj_lock_create_recursive_mutex(pool, pool->obj_name,
|
|
®c->lock);
|
|
if (status != PJ_SUCCESS) {
|
|
pj_pool_release(pool);
|
|
return status;
|
|
}
|
|
|
|
status = pj_atomic_create(pool, 0, ®c->busy_ctr);
|
|
if (status != PJ_SUCCESS) {
|
|
pj_lock_destroy(regc->lock);
|
|
pj_pool_release(pool);
|
|
return status;
|
|
}
|
|
|
|
status = pjsip_auth_clt_init(®c->auth_sess, endpt, regc->pool, 0);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
pj_list_init(®c->route_set);
|
|
pj_list_init(®c->hdr_list);
|
|
pj_list_init(®c->contact_hdr_list);
|
|
pj_list_init(®c->removed_contact_hdr_list);
|
|
|
|
/* Done */
|
|
*p_regc = regc;
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_destroy(pjsip_regc *regc)
|
|
{
|
|
return pjsip_regc_destroy2(regc, PJ_TRUE);
|
|
}
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_destroy2(pjsip_regc *regc, pj_bool_t force)
|
|
{
|
|
PJ_ASSERT_RETURN(regc, PJ_EINVAL);
|
|
|
|
pj_lock_acquire(regc->lock);
|
|
if (!force && regc->has_tsx) {
|
|
pj_lock_release(regc->lock);
|
|
return PJ_EBUSY;
|
|
}
|
|
|
|
if (regc->has_tsx || pj_atomic_get(regc->busy_ctr) != 0) {
|
|
regc->_delete_flag = 1;
|
|
regc->cb = NULL;
|
|
pj_lock_release(regc->lock);
|
|
} else {
|
|
pjsip_tpselector_dec_ref(®c->tp_sel);
|
|
if (regc->last_transport) {
|
|
pjsip_transport_dec_ref(regc->last_transport);
|
|
regc->last_transport = NULL;
|
|
}
|
|
if (regc->timer.id != 0) {
|
|
pjsip_endpt_cancel_timer(regc->endpt, ®c->timer);
|
|
regc->timer.id = 0;
|
|
}
|
|
pj_atomic_destroy(regc->busy_ctr);
|
|
pj_lock_release(regc->lock);
|
|
pj_lock_destroy(regc->lock);
|
|
regc->lock = NULL;
|
|
|
|
pjsip_auth_clt_deinit(®c->auth_sess);
|
|
pjsip_endpt_release_pool(regc->endpt, regc->pool);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_get_info( pjsip_regc *regc,
|
|
pjsip_regc_info *info )
|
|
{
|
|
PJ_ASSERT_RETURN(regc && info, PJ_EINVAL);
|
|
|
|
pj_lock_acquire(regc->lock);
|
|
|
|
info->server_uri = regc->str_srv_url;
|
|
info->client_uri = regc->from_uri;
|
|
info->is_busy = (pj_atomic_get(regc->busy_ctr) || regc->has_tsx);
|
|
info->auto_reg = regc->auto_reg;
|
|
info->interval = regc->expires;
|
|
info->transport = regc->last_transport;
|
|
|
|
if (regc->has_tsx)
|
|
info->next_reg = 0;
|
|
else if (regc->auto_reg == 0)
|
|
info->next_reg = 0;
|
|
else if (regc->expires == PJSIP_REGC_EXPIRATION_NOT_SPECIFIED)
|
|
info->next_reg = regc->expires;
|
|
else {
|
|
pj_time_val now, next_reg;
|
|
|
|
next_reg = regc->next_reg;
|
|
pj_gettimeofday(&now);
|
|
PJ_TIME_VAL_SUB(next_reg, now);
|
|
info->next_reg = next_reg.sec;
|
|
}
|
|
|
|
pj_lock_release(regc->lock);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
PJ_DEF(pj_pool_t*) pjsip_regc_get_pool(pjsip_regc *regc)
|
|
{
|
|
return regc->pool;
|
|
}
|
|
|
|
static void set_expires( pjsip_regc *regc, pj_uint32_t expires)
|
|
{
|
|
if (expires != regc->expires) {
|
|
regc->expires_hdr = pjsip_expires_hdr_create(regc->pool, expires);
|
|
} else {
|
|
regc->expires_hdr = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static pj_status_t set_contact( pjsip_regc *regc,
|
|
int contact_cnt,
|
|
const pj_str_t contact[] )
|
|
{
|
|
const pj_str_t CONTACT = { "Contact", 7 };
|
|
pjsip_contact_hdr *h;
|
|
int i;
|
|
|
|
/* Save existing contact list to removed_contact_hdr_list and
|
|
* clear contact_hdr_list.
|
|
*/
|
|
pj_list_merge_last(®c->removed_contact_hdr_list,
|
|
®c->contact_hdr_list);
|
|
|
|
/* Set the expiration of Contacts in to removed_contact_hdr_list
|
|
* zero.
|
|
*/
|
|
h = regc->removed_contact_hdr_list.next;
|
|
while (h != ®c->removed_contact_hdr_list) {
|
|
h->expires = 0;
|
|
h = h->next;
|
|
}
|
|
|
|
/* Process new contacts */
|
|
for (i=0; i<contact_cnt; ++i) {
|
|
pjsip_contact_hdr *hdr;
|
|
pj_str_t tmp;
|
|
|
|
pj_strdup_with_null(regc->pool, &tmp, &contact[i]);
|
|
hdr = (pjsip_contact_hdr*)
|
|
pjsip_parse_hdr(regc->pool, &CONTACT, tmp.ptr, tmp.slen, NULL);
|
|
if (hdr == NULL) {
|
|
PJ_LOG(4,(THIS_FILE, "Invalid Contact: \"%.*s\"",
|
|
(int)tmp.slen, tmp.ptr));
|
|
return PJSIP_EINVALIDURI;
|
|
}
|
|
|
|
/* Find the new contact in old contact list. If found, remove
|
|
* the old header from the old header list.
|
|
*/
|
|
h = regc->removed_contact_hdr_list.next;
|
|
while (h != ®c->removed_contact_hdr_list) {
|
|
int rc;
|
|
|
|
rc = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR,
|
|
h->uri, hdr->uri);
|
|
if (rc == 0) {
|
|
/* Match */
|
|
pj_list_erase(h);
|
|
break;
|
|
}
|
|
|
|
h = h->next;
|
|
}
|
|
|
|
/* If add_xuid_param option is enabled and Contact URI is sip/sips,
|
|
* add xuid parameter to assist matching the Contact URI in the
|
|
* REGISTER response later.
|
|
*/
|
|
if (regc->add_xuid_param && (PJSIP_URI_SCHEME_IS_SIP(hdr->uri) ||
|
|
PJSIP_URI_SCHEME_IS_SIPS(hdr->uri)))
|
|
{
|
|
pjsip_param *xuid_param;
|
|
pjsip_sip_uri *sip_uri;
|
|
|
|
xuid_param = PJ_POOL_ZALLOC_T(regc->pool, pjsip_param);
|
|
xuid_param->name = XUID_PARAM_NAME;
|
|
pj_create_unique_string(regc->pool, &xuid_param->value);
|
|
|
|
sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(hdr->uri);
|
|
pj_list_push_back(&sip_uri->other_param, xuid_param);
|
|
}
|
|
|
|
pj_list_push_back(®c->contact_hdr_list, hdr);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_init( pjsip_regc *regc,
|
|
const pj_str_t *srv_url,
|
|
const pj_str_t *from_url,
|
|
const pj_str_t *to_url,
|
|
int contact_cnt,
|
|
const pj_str_t contact[],
|
|
pj_uint32_t expires)
|
|
{
|
|
pj_str_t tmp;
|
|
pj_status_t status;
|
|
|
|
PJ_ASSERT_RETURN(regc && srv_url && from_url && to_url &&
|
|
expires, PJ_EINVAL);
|
|
|
|
/* 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 PJSIP_EINVALIDURI;
|
|
}
|
|
|
|
/* Set "From" header. */
|
|
pj_strdup_with_null(regc->pool, ®c->from_uri, from_url);
|
|
tmp = regc->from_uri;
|
|
regc->from_hdr = pjsip_from_hdr_create(regc->pool);
|
|
regc->from_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen,
|
|
PJSIP_PARSE_URI_AS_NAMEADDR);
|
|
if (!regc->from_hdr->uri) {
|
|
PJ_LOG(4,(THIS_FILE, "regc: invalid source URI %.*s",
|
|
from_url->slen, from_url->ptr));
|
|
return PJSIP_EINVALIDURI;
|
|
}
|
|
|
|
/* Set "To" header. */
|
|
pj_strdup_with_null(regc->pool, &tmp, to_url);
|
|
regc->to_hdr = pjsip_to_hdr_create(regc->pool);
|
|
regc->to_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen,
|
|
PJSIP_PARSE_URI_AS_NAMEADDR);
|
|
if (!regc->to_hdr->uri) {
|
|
PJ_LOG(4,(THIS_FILE, "regc: invalid target URI %.*s", to_url->slen, to_url->ptr));
|
|
return PJSIP_EINVALIDURI;
|
|
}
|
|
|
|
|
|
/* Set "Contact" header. */
|
|
status = set_contact( regc, contact_cnt, contact);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
/* Set "Expires" header, if required. */
|
|
set_expires( regc, expires);
|
|
regc->delay_before_refresh = DELAY_BEFORE_REFRESH;
|
|
|
|
/* Set "Call-ID" header. */
|
|
regc->cid_hdr = pjsip_cid_hdr_create(regc->pool);
|
|
pj_create_unique_string(regc->pool, ®c->cid_hdr->id);
|
|
|
|
/* Set "CSeq" header. */
|
|
regc->cseq_hdr = pjsip_cseq_hdr_create(regc->pool);
|
|
regc->cseq_hdr->cseq = pj_rand() % 0xFFFF;
|
|
pjsip_method_set( ®c->cseq_hdr->method, PJSIP_REGISTER_METHOD);
|
|
|
|
/* Done. */
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
PJ_DEF(void) pjsip_regc_add_ref( pjsip_regc *regc )
|
|
{
|
|
pj_assert(regc);
|
|
pj_atomic_inc(regc->busy_ctr);
|
|
}
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_dec_ref( pjsip_regc *regc )
|
|
{
|
|
pj_assert(regc);
|
|
if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) {
|
|
pjsip_regc_destroy(regc);
|
|
return PJ_EGONE;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_set_credentials( pjsip_regc *regc,
|
|
int count,
|
|
const pjsip_cred_info cred[] )
|
|
{
|
|
PJ_ASSERT_RETURN(regc && count && cred, PJ_EINVAL);
|
|
return pjsip_auth_clt_set_credentials(®c->auth_sess, count, cred);
|
|
}
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_set_prefs( pjsip_regc *regc,
|
|
const pjsip_auth_clt_pref *pref)
|
|
{
|
|
PJ_ASSERT_RETURN(regc && pref, PJ_EINVAL);
|
|
return pjsip_auth_clt_set_prefs(®c->auth_sess, pref);
|
|
}
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_set_route_set( pjsip_regc *regc,
|
|
const pjsip_route_hdr *route_set)
|
|
{
|
|
const pjsip_route_hdr *chdr;
|
|
|
|
PJ_ASSERT_RETURN(regc && route_set, PJ_EINVAL);
|
|
|
|
pj_list_init(®c->route_set);
|
|
|
|
chdr = route_set->next;
|
|
while (chdr != route_set) {
|
|
pj_list_push_back(®c->route_set, pjsip_hdr_clone(regc->pool, chdr));
|
|
chdr = chdr->next;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Bind client registration to a specific transport/listener.
|
|
*/
|
|
PJ_DEF(pj_status_t) pjsip_regc_set_transport( pjsip_regc *regc,
|
|
const pjsip_tpselector *sel)
|
|
{
|
|
PJ_ASSERT_RETURN(regc && sel, PJ_EINVAL);
|
|
|
|
pjsip_tpselector_dec_ref(®c->tp_sel);
|
|
pj_memcpy(®c->tp_sel, sel, sizeof(*sel));
|
|
pjsip_tpselector_add_ref(®c->tp_sel);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
/* Release transport */
|
|
PJ_DEF(pj_status_t) pjsip_regc_release_transport(pjsip_regc *regc)
|
|
{
|
|
PJ_ASSERT_RETURN(regc, PJ_EINVAL);
|
|
if (regc->last_transport) {
|
|
pjsip_transport_dec_ref(regc->last_transport);
|
|
regc->last_transport = NULL;
|
|
}
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_add_headers( pjsip_regc *regc,
|
|
const pjsip_hdr *hdr_list)
|
|
{
|
|
const pjsip_hdr *hdr;
|
|
|
|
PJ_ASSERT_RETURN(regc && hdr_list, PJ_EINVAL);
|
|
|
|
//This is "add" operation, so don't remove headers.
|
|
//pj_list_init(®c->hdr_list);
|
|
|
|
hdr = hdr_list->next;
|
|
while (hdr != hdr_list) {
|
|
pj_list_push_back(®c->hdr_list, pjsip_hdr_clone(regc->pool, hdr));
|
|
hdr = hdr->next;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static pj_status_t create_request(pjsip_regc *regc,
|
|
pjsip_tx_data **p_tdata)
|
|
{
|
|
pj_status_t status;
|
|
pjsip_tx_data *tdata;
|
|
|
|
PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);
|
|
|
|
/* Create the request. */
|
|
status = pjsip_endpt_create_request_from_hdr( regc->endpt,
|
|
pjsip_get_register_method(),
|
|
regc->srv_url,
|
|
regc->from_hdr,
|
|
regc->to_hdr,
|
|
NULL,
|
|
regc->cid_hdr,
|
|
regc->cseq_hdr->cseq,
|
|
NULL,
|
|
&tdata);
|
|
if (status != PJ_SUCCESS)
|
|
return status;
|
|
|
|
/* Add cached authorization headers. */
|
|
pjsip_auth_clt_init_req( ®c->auth_sess, tdata );
|
|
|
|
/* Add Route headers from route set, ideally after Via header */
|
|
if (!pj_list_empty(®c->route_set)) {
|
|
pjsip_hdr *route_pos;
|
|
const pjsip_route_hdr *route;
|
|
|
|
route_pos = (pjsip_hdr*)
|
|
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
|
|
if (!route_pos)
|
|
route_pos = &tdata->msg->hdr;
|
|
|
|
route = regc->route_set.next;
|
|
while (route != ®c->route_set) {
|
|
pjsip_hdr *new_hdr = (pjsip_hdr*)
|
|
pjsip_hdr_clone(tdata->pool, route);
|
|
pj_list_insert_after(route_pos, new_hdr);
|
|
route_pos = new_hdr;
|
|
route = route->next;
|
|
}
|
|
}
|
|
|
|
/* Add additional request headers */
|
|
if (!pj_list_empty(®c->hdr_list)) {
|
|
const pjsip_hdr *hdr;
|
|
|
|
hdr = regc->hdr_list.next;
|
|
while (hdr != ®c->hdr_list) {
|
|
pjsip_hdr *new_hdr = (pjsip_hdr*)
|
|
pjsip_hdr_clone(tdata->pool, hdr);
|
|
pjsip_msg_add_hdr(tdata->msg, new_hdr);
|
|
hdr = hdr->next;
|
|
}
|
|
}
|
|
|
|
/* Done. */
|
|
*p_tdata = tdata;
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg,
|
|
pjsip_tx_data **p_tdata)
|
|
{
|
|
pjsip_msg *msg;
|
|
pjsip_contact_hdr *hdr;
|
|
const pjsip_hdr *h_allow;
|
|
pj_status_t status;
|
|
pjsip_tx_data *tdata;
|
|
|
|
PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);
|
|
|
|
pj_lock_acquire(regc->lock);
|
|
|
|
regc->expires_requested = 1;
|
|
|
|
status = create_request(regc, &tdata);
|
|
if (status != PJ_SUCCESS) {
|
|
pj_lock_release(regc->lock);
|
|
return status;
|
|
}
|
|
|
|
msg = tdata->msg;
|
|
|
|
/* Add Contact headers. */
|
|
hdr = regc->contact_hdr_list.next;
|
|
while (hdr != ®c->contact_hdr_list) {
|
|
pjsip_msg_add_hdr(msg, (pjsip_hdr*)
|
|
pjsip_hdr_clone(tdata->pool, hdr));
|
|
hdr = hdr->next;
|
|
}
|
|
|
|
/* Also add bindings which are to be removed */
|
|
while (!pj_list_empty(®c->removed_contact_hdr_list)) {
|
|
hdr = regc->removed_contact_hdr_list.next;
|
|
pjsip_msg_add_hdr(msg, (pjsip_hdr*)
|
|
pjsip_hdr_clone(tdata->pool, hdr));
|
|
pj_list_erase(hdr);
|
|
}
|
|
|
|
|
|
if (regc->expires_hdr)
|
|
pjsip_msg_add_hdr(msg, (pjsip_hdr*)
|
|
pjsip_hdr_clone(tdata->pool,
|
|
regc->expires_hdr));
|
|
|
|
if (regc->timer.id != 0) {
|
|
pjsip_endpt_cancel_timer(regc->endpt, ®c->timer);
|
|
regc->timer.id = 0;
|
|
}
|
|
|
|
/* Add Allow header (http://trac.pjsip.org/repos/ticket/1039) */
|
|
h_allow = pjsip_endpt_get_capability(regc->endpt, PJSIP_H_ALLOW, NULL);
|
|
if (h_allow) {
|
|
pjsip_msg_add_hdr(msg, (pjsip_hdr*)
|
|
pjsip_hdr_clone(tdata->pool, h_allow));
|
|
|
|
}
|
|
|
|
regc->auto_reg = autoreg;
|
|
|
|
pj_lock_release(regc->lock);
|
|
|
|
/* Done */
|
|
*p_tdata = tdata;
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_unregister(pjsip_regc *regc,
|
|
pjsip_tx_data **p_tdata)
|
|
{
|
|
pjsip_tx_data *tdata;
|
|
pjsip_msg *msg;
|
|
pjsip_hdr *hdr;
|
|
pj_status_t status;
|
|
|
|
PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);
|
|
|
|
pj_lock_acquire(regc->lock);
|
|
|
|
if (regc->timer.id != 0) {
|
|
pjsip_endpt_cancel_timer(regc->endpt, ®c->timer);
|
|
regc->timer.id = 0;
|
|
}
|
|
|
|
regc->expires_requested = 0;
|
|
|
|
status = create_request(regc, &tdata);
|
|
if (status != PJ_SUCCESS) {
|
|
pj_lock_release(regc->lock);
|
|
return status;
|
|
}
|
|
|
|
msg = tdata->msg;
|
|
|
|
/* Add Contact headers. */
|
|
hdr = (pjsip_hdr*)regc->contact_hdr_list.next;
|
|
while ((void*)hdr != (void*)®c->contact_hdr_list) {
|
|
pjsip_msg_add_hdr(msg, (pjsip_hdr*)
|
|
pjsip_hdr_clone(tdata->pool, hdr));
|
|
hdr = hdr->next;
|
|
}
|
|
|
|
/* Also add bindings which are to be removed */
|
|
while (!pj_list_empty(®c->removed_contact_hdr_list)) {
|
|
hdr = (pjsip_hdr*)regc->removed_contact_hdr_list.next;
|
|
pjsip_msg_add_hdr(msg, (pjsip_hdr*)
|
|
pjsip_hdr_clone(tdata->pool, hdr));
|
|
pj_list_erase(hdr);
|
|
}
|
|
|
|
/* Add Expires:0 header */
|
|
hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0);
|
|
pjsip_msg_add_hdr(msg, hdr);
|
|
|
|
pj_lock_release(regc->lock);
|
|
|
|
*p_tdata = tdata;
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc,
|
|
pjsip_tx_data **p_tdata)
|
|
{
|
|
pjsip_tx_data *tdata;
|
|
pjsip_contact_hdr *hcontact;
|
|
pjsip_hdr *hdr;
|
|
pjsip_msg *msg;
|
|
pj_status_t status;
|
|
|
|
PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);
|
|
|
|
pj_lock_acquire(regc->lock);
|
|
|
|
if (regc->timer.id != 0) {
|
|
pjsip_endpt_cancel_timer(regc->endpt, ®c->timer);
|
|
regc->timer.id = 0;
|
|
}
|
|
|
|
status = create_request(regc, &tdata);
|
|
if (status != PJ_SUCCESS) {
|
|
pj_lock_release(regc->lock);
|
|
return status;
|
|
}
|
|
|
|
msg = tdata->msg;
|
|
|
|
/* Clear removed_contact_hdr_list */
|
|
pj_list_init(®c->removed_contact_hdr_list);
|
|
|
|
/* Add Contact:* header */
|
|
hcontact = pjsip_contact_hdr_create(tdata->pool);
|
|
hcontact->star = 1;
|
|
pjsip_msg_add_hdr(msg, (pjsip_hdr*)hcontact);
|
|
|
|
/* Add Expires:0 header */
|
|
hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0);
|
|
pjsip_msg_add_hdr(msg, hdr);
|
|
|
|
pj_lock_release(regc->lock);
|
|
|
|
*p_tdata = tdata;
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_update_contact( pjsip_regc *regc,
|
|
int contact_cnt,
|
|
const pj_str_t contact[] )
|
|
{
|
|
pj_status_t status;
|
|
|
|
PJ_ASSERT_RETURN(regc, PJ_EINVAL);
|
|
|
|
pj_lock_acquire(regc->lock);
|
|
status = set_contact( regc, contact_cnt, contact );
|
|
pj_lock_release(regc->lock);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_update_expires( pjsip_regc *regc,
|
|
pj_uint32_t expires )
|
|
{
|
|
PJ_ASSERT_RETURN(regc, PJ_EINVAL);
|
|
|
|
pj_lock_acquire(regc->lock);
|
|
set_expires( regc, expires );
|
|
pj_lock_release(regc->lock);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static void cbparam_init( struct pjsip_regc_cbparam *cbparam,
|
|
pjsip_regc *regc,
|
|
pj_status_t status, int st_code,
|
|
const pj_str_t *reason,
|
|
pjsip_rx_data *rdata, pj_uint32_t expiration,
|
|
int contact_cnt, pjsip_contact_hdr *contact[],
|
|
pj_bool_t is_unreg)
|
|
{
|
|
cbparam->regc = regc;
|
|
cbparam->token = regc->token;
|
|
cbparam->status = status;
|
|
cbparam->code = st_code;
|
|
cbparam->reason = *reason;
|
|
cbparam->rdata = rdata;
|
|
cbparam->contact_cnt = contact_cnt;
|
|
cbparam->expiration = (expiration != PJSIP_REGC_EXPIRATION_NOT_SPECIFIED?
|
|
expiration: regc->expires_requested);
|
|
cbparam->is_unreg = is_unreg;
|
|
if (contact_cnt) {
|
|
pj_memcpy( cbparam->contact, contact,
|
|
contact_cnt*sizeof(pjsip_contact_hdr*));
|
|
}
|
|
}
|
|
|
|
static void call_callback(pjsip_regc *regc, pj_status_t status, int st_code,
|
|
const pj_str_t *reason,
|
|
pjsip_rx_data *rdata, pj_uint32_t expiration,
|
|
int contact_cnt, pjsip_contact_hdr *contact[],
|
|
pj_bool_t is_unreg)
|
|
{
|
|
struct pjsip_regc_cbparam cbparam;
|
|
|
|
if (!regc->cb)
|
|
return;
|
|
|
|
cbparam_init(&cbparam, regc, status, st_code, reason, rdata, expiration,
|
|
contact_cnt, contact, is_unreg);
|
|
(*regc->cb)(&cbparam);
|
|
}
|
|
|
|
static void regc_refresh_timer_cb( pj_timer_heap_t *timer_heap,
|
|
struct pj_timer_entry *entry)
|
|
{
|
|
pjsip_regc *regc = (pjsip_regc*) entry->user_data;
|
|
pjsip_tx_data *tdata;
|
|
pj_status_t status;
|
|
|
|
PJ_UNUSED_ARG(timer_heap);
|
|
|
|
/* Temporarily increase busy flag to prevent regc from being deleted
|
|
* in pjsip_regc_send() or in the callback
|
|
*/
|
|
pjsip_regc_add_ref(regc);
|
|
|
|
entry->id = 0;
|
|
status = pjsip_regc_register(regc, 1, &tdata);
|
|
if (status == PJ_SUCCESS) {
|
|
status = pjsip_regc_send(regc, tdata);
|
|
}
|
|
|
|
if (status != PJ_SUCCESS && regc->cb) {
|
|
char errmsg[PJ_ERR_MSG_SIZE];
|
|
pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg));
|
|
call_callback(regc, status, 400, &reason, NULL, NOEXP, 0, NULL,
|
|
PJ_FALSE);
|
|
}
|
|
|
|
/* Delete the record if user destroy regc during the callback. */
|
|
pjsip_regc_dec_ref(regc);
|
|
}
|
|
|
|
static void schedule_registration ( pjsip_regc *regc, pj_uint32_t expiration )
|
|
{
|
|
if (regc->auto_reg && expiration > 0 && expiration != NOEXP) {
|
|
pj_time_val delay = { 0, 0};
|
|
|
|
pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(regc->endpt),
|
|
®c->timer, 0);
|
|
|
|
delay.sec = expiration - regc->delay_before_refresh;
|
|
if (regc->expires != PJSIP_REGC_EXPIRATION_NOT_SPECIFIED &&
|
|
delay.sec > (pj_int32_t)regc->expires)
|
|
{
|
|
delay.sec = regc->expires;
|
|
}
|
|
if (delay.sec < DELAY_BEFORE_REFRESH)
|
|
delay.sec = DELAY_BEFORE_REFRESH;
|
|
regc->timer.cb = ®c_refresh_timer_cb;
|
|
regc->timer.id = REFRESH_TIMER;
|
|
regc->timer.user_data = regc;
|
|
pjsip_endpt_schedule_timer( regc->endpt, ®c->timer, &delay);
|
|
pj_gettimeofday(®c->last_reg);
|
|
regc->next_reg = regc->last_reg;
|
|
regc->next_reg.sec += delay.sec;
|
|
}
|
|
}
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_set_reg_tsx_cb( pjsip_regc *regc,
|
|
pjsip_regc_tsx_cb *tsx_cb)
|
|
{
|
|
PJ_ASSERT_RETURN(regc, PJ_EINVAL);
|
|
regc->tsx_cb = tsx_cb;
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_set_via_sent_by( pjsip_regc *regc,
|
|
pjsip_host_port *via_addr,
|
|
pjsip_transport *via_tp)
|
|
{
|
|
PJ_ASSERT_RETURN(regc, PJ_EINVAL);
|
|
|
|
if (!via_addr)
|
|
pj_bzero(®c->via_addr, sizeof(regc->via_addr));
|
|
else {
|
|
if (pj_strcmp(®c->via_addr.host, &via_addr->host))
|
|
pj_strdup(regc->pool, ®c->via_addr.host, &via_addr->host);
|
|
regc->via_addr.port = via_addr->port;
|
|
}
|
|
regc->via_tp = via_tp;
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
PJ_DEF(pj_status_t)
|
|
pjsip_regc_set_delay_before_refresh( pjsip_regc *regc,
|
|
pj_uint32_t delay )
|
|
{
|
|
PJ_ASSERT_RETURN(regc, PJ_EINVAL);
|
|
|
|
if (delay > regc->expires)
|
|
return PJ_ETOOBIG;
|
|
|
|
pj_lock_acquire(regc->lock);
|
|
|
|
if (regc->delay_before_refresh != delay)
|
|
{
|
|
regc->delay_before_refresh = delay;
|
|
|
|
if (regc->timer.id != 0) {
|
|
/* Cancel registration timer */
|
|
pjsip_endpt_cancel_timer(regc->endpt, ®c->timer);
|
|
regc->timer.id = 0;
|
|
|
|
/* Schedule next registration */
|
|
schedule_registration(regc, regc->expires);
|
|
}
|
|
}
|
|
|
|
pj_lock_release(regc->lock);
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
|
|
static pj_uint32_t calculate_response_expiration(const pjsip_regc *regc,
|
|
const pjsip_rx_data *rdata,
|
|
unsigned *contact_cnt,
|
|
unsigned max_contact,
|
|
pjsip_contact_hdr *contacts[])
|
|
{
|
|
pj_uint32_t expiration = NOEXP;
|
|
const pjsip_msg *msg = rdata->msg_info.msg;
|
|
const pjsip_hdr *hdr;
|
|
|
|
/* Enumerate all Contact headers in the response */
|
|
*contact_cnt = 0;
|
|
for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) {
|
|
if (hdr->type == PJSIP_H_CONTACT &&
|
|
*contact_cnt < max_contact)
|
|
{
|
|
contacts[*contact_cnt] = (pjsip_contact_hdr*)hdr;
|
|
++(*contact_cnt);
|
|
}
|
|
}
|
|
|
|
if (regc->current_op == REGC_REGISTERING) {
|
|
pj_bool_t has_our_contact = PJ_FALSE;
|
|
const pjsip_expires_hdr *expires;
|
|
|
|
/* Get Expires header */
|
|
expires = (const pjsip_expires_hdr*)
|
|
pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
|
|
|
|
/* Try to find the Contact URIs that we register, in the response
|
|
* to get the expires value. We'll try both with comparing the URI
|
|
* and comparing the extension param only.
|
|
*/
|
|
if (pjsip_cfg()->regc.check_contact || regc->add_xuid_param) {
|
|
unsigned i;
|
|
for (i=0; i<*contact_cnt; ++i) {
|
|
const pjsip_contact_hdr *our_hdr;
|
|
|
|
our_hdr = (const pjsip_contact_hdr*)
|
|
regc->contact_hdr_list.next;
|
|
|
|
/* Match with our Contact header(s) */
|
|
while ((void*)our_hdr != (void*)®c->contact_hdr_list) {
|
|
|
|
const pjsip_uri *uri1, *uri2;
|
|
pj_bool_t matched = PJ_FALSE;
|
|
|
|
/* Exclude the display name when comparing the URI
|
|
* since server may not return it.
|
|
*/
|
|
uri1 = (const pjsip_uri*)
|
|
pjsip_uri_get_uri(contacts[i]->uri);
|
|
uri2 = (const pjsip_uri*)
|
|
pjsip_uri_get_uri(our_hdr->uri);
|
|
|
|
/* First try with exact matching, according to RFC 3261
|
|
* Section 19.1.4 URI Comparison
|
|
*/
|
|
if (pjsip_cfg()->regc.check_contact) {
|
|
matched = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR,
|
|
uri1, uri2)==0;
|
|
}
|
|
|
|
/* If no match is found, try with matching the extension
|
|
* parameter only if extension parameter was added.
|
|
*/
|
|
if (!matched && regc->add_xuid_param &&
|
|
(PJSIP_URI_SCHEME_IS_SIP(uri1) ||
|
|
PJSIP_URI_SCHEME_IS_SIPS(uri1)) &&
|
|
(PJSIP_URI_SCHEME_IS_SIP(uri2) ||
|
|
PJSIP_URI_SCHEME_IS_SIPS(uri2)))
|
|
{
|
|
const pjsip_sip_uri *sip_uri1, *sip_uri2;
|
|
const pjsip_param *p1, *p2;
|
|
|
|
sip_uri1 = (const pjsip_sip_uri*)uri1;
|
|
sip_uri2 = (const pjsip_sip_uri*)uri2;
|
|
|
|
p1 = pjsip_param_cfind(&sip_uri1->other_param,
|
|
&XUID_PARAM_NAME);
|
|
p2 = pjsip_param_cfind(&sip_uri2->other_param,
|
|
&XUID_PARAM_NAME);
|
|
matched = p1 && p2 &&
|
|
pj_strcmp(&p1->value, &p2->value)==0;
|
|
|
|
}
|
|
|
|
if (matched) {
|
|
has_our_contact = PJ_TRUE;
|
|
|
|
if (contacts[i]->expires != PJSIP_EXPIRES_NOT_SPECIFIED
|
|
&& contacts[i]->expires < expiration)
|
|
{
|
|
/* Get the lowest expiration time. */
|
|
expiration = contacts[i]->expires;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
our_hdr = our_hdr->next;
|
|
|
|
} /* while ((void.. */
|
|
|
|
} /* for (i=.. */
|
|
|
|
/* If matching Contact header(s) are found but the
|
|
* header doesn't contain expires parameter, get the
|
|
* expiration value from the Expires header. And
|
|
* if Expires header is not present, get the expiration
|
|
* value from the request.
|
|
*/
|
|
if (has_our_contact && expiration == NOEXP) {
|
|
if (expires) {
|
|
expiration = expires->ivalue;
|
|
} else if (regc->expires_hdr) {
|
|
expiration = regc->expires_hdr->ivalue;
|
|
} else {
|
|
/* We didn't request explicit expiration value,
|
|
* and server doesn't specify it either. This
|
|
* shouldn't happen unless we have a broken
|
|
* registrar.
|
|
*/
|
|
expiration = 3600;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* If we still couldn't get matching Contact header(s), it means
|
|
* there must be something wrong with the registrar (e.g. it may
|
|
* have modified the URI's in the response, which is prohibited).
|
|
*/
|
|
if (expiration==NOEXP) {
|
|
/* If the number of Contact headers in the response matches
|
|
* ours, they're all probably ours. Get the expiration
|
|
* from there if this is the case, or from Expires header
|
|
* if we don't have exact Contact header count, or
|
|
* from the request as the last resort.
|
|
*/
|
|
pj_size_t our_contact_cnt;
|
|
|
|
our_contact_cnt = pj_list_size(®c->contact_hdr_list);
|
|
|
|
if (*contact_cnt == our_contact_cnt && *contact_cnt &&
|
|
contacts[0]->expires != PJSIP_EXPIRES_NOT_SPECIFIED)
|
|
{
|
|
expiration = contacts[0]->expires;
|
|
} else if (expires)
|
|
expiration = expires->ivalue;
|
|
else if (regc->expires_hdr)
|
|
expiration = regc->expires_hdr->ivalue;
|
|
else
|
|
expiration = 3600;
|
|
}
|
|
|
|
} else {
|
|
/* Just assume that the unregistration has been successful. */
|
|
expiration = 0;
|
|
}
|
|
|
|
/* Must have expiration value by now */
|
|
pj_assert(expiration != NOEXP);
|
|
|
|
return expiration;
|
|
}
|
|
|
|
static void regc_tsx_callback(void *token, pjsip_event *event)
|
|
{
|
|
pj_status_t status;
|
|
pjsip_regc *regc = (pjsip_regc*) token;
|
|
pjsip_transaction *tsx = event->body.tsx_state.tsx;
|
|
pj_bool_t handled = PJ_TRUE;
|
|
pj_bool_t update_contact = PJ_FALSE;
|
|
|
|
pjsip_regc_add_ref(regc);
|
|
pj_lock_acquire(regc->lock);
|
|
|
|
/* Decrement pending transaction counter. */
|
|
pj_assert(regc->has_tsx);
|
|
regc->has_tsx = PJ_FALSE;
|
|
|
|
/* Add reference to the transport */
|
|
if (tsx->transport != regc->last_transport) {
|
|
if (regc->last_transport) {
|
|
pjsip_transport_dec_ref(regc->last_transport);
|
|
regc->last_transport = NULL;
|
|
}
|
|
|
|
if (tsx->transport) {
|
|
regc->last_transport = tsx->transport;
|
|
pjsip_transport_add_ref(regc->last_transport);
|
|
}
|
|
}
|
|
|
|
if (regc->_delete_flag == 0 && regc->tsx_cb &&
|
|
regc->current_op == REGC_REGISTERING)
|
|
{
|
|
struct pjsip_regc_tsx_cb_param param;
|
|
|
|
param.contact_cnt = -1;
|
|
cbparam_init(¶m.cbparam, regc, PJ_SUCCESS, tsx->status_code,
|
|
&tsx->status_text,
|
|
(event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ?
|
|
event->body.tsx_state.src.rdata : NULL,
|
|
NOEXP, 0, NULL, PJ_FALSE);
|
|
|
|
/* Call regc tsx callback before handling any response */
|
|
pj_lock_release(regc->lock);
|
|
(*regc->tsx_cb)(¶m);
|
|
pj_lock_acquire(regc->lock);
|
|
|
|
if (param.contact_cnt >= 0) {
|
|
/* Since we receive non-2xx response, it means that (some) contact
|
|
* bindings haven't been established so we can safely remove these
|
|
* contact headers. This is to avoid removing non-existent contact
|
|
* bindings later.
|
|
*/
|
|
if (tsx->status_code/100 != 2) {
|
|
pjsip_contact_hdr *h;
|
|
|
|
h = regc->contact_hdr_list.next;
|
|
while (h != ®c->contact_hdr_list) {
|
|
pjsip_contact_hdr *next = h->next;
|
|
|
|
if (h->expires == PJSIP_EXPIRES_NOT_SPECIFIED) {
|
|
pj_list_erase(h);
|
|
}
|
|
h = next;
|
|
}
|
|
}
|
|
|
|
/* Update contact address */
|
|
pjsip_regc_update_contact(regc, param.contact_cnt, param.contact);
|
|
update_contact = PJ_TRUE;
|
|
}
|
|
}
|
|
|
|
/* Handle 401/407 challenge (even when _delete_flag is set) */
|
|
if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED ||
|
|
tsx->status_code == PJSIP_SC_UNAUTHORIZED)
|
|
{
|
|
pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
|
|
pjsip_tx_data *tdata;
|
|
pj_bool_t is_unreg;
|
|
|
|
/* reset current op */
|
|
is_unreg = (regc->current_op == REGC_UNREGISTERING);
|
|
regc->current_op = REGC_IDLE;
|
|
|
|
if (update_contact) {
|
|
pjsip_msg *msg;
|
|
pjsip_hdr *hdr, *ins_hdr;
|
|
pjsip_contact_hdr *chdr;
|
|
|
|
/* Delete Contact headers, but we shouldn't delete headers
|
|
* which are supposed to remove contact bindings since
|
|
* we cannot reconstruct those headers.
|
|
*/
|
|
msg = tsx->last_tx->msg;
|
|
hdr = msg->hdr.next;
|
|
ins_hdr = &msg->hdr;
|
|
while (hdr != &msg->hdr) {
|
|
pjsip_hdr *next = hdr->next;
|
|
|
|
if (hdr->type == PJSIP_H_CONTACT) {
|
|
chdr = (pjsip_contact_hdr *)hdr;
|
|
if (chdr->expires != 0) {
|
|
pj_list_erase(hdr);
|
|
ins_hdr = next;
|
|
}
|
|
}
|
|
hdr = next;
|
|
}
|
|
|
|
/* Add Contact headers. */
|
|
chdr = regc->contact_hdr_list.next;
|
|
while (chdr != ®c->contact_hdr_list) {
|
|
pj_list_insert_before(ins_hdr, (pjsip_hdr*)
|
|
pjsip_hdr_clone(tsx->last_tx->pool, chdr));
|
|
chdr = chdr->next;
|
|
}
|
|
|
|
/* Also add bindings which are to be removed */
|
|
while (!pj_list_empty(®c->removed_contact_hdr_list)) {
|
|
chdr = regc->removed_contact_hdr_list.next;
|
|
pj_list_insert_before(ins_hdr, (pjsip_hdr*)
|
|
pjsip_hdr_clone(tsx->last_tx->pool, chdr));
|
|
pj_list_erase(chdr);
|
|
}
|
|
}
|
|
|
|
status = pjsip_auth_clt_reinit_req( ®c->auth_sess,
|
|
rdata,
|
|
tsx->last_tx,
|
|
&tdata);
|
|
|
|
if (status == PJ_SUCCESS) {
|
|
/* Need to unlock the regc temporarily while sending the message
|
|
* to prevent deadlock (see ticket #2260 and #1247).
|
|
* It should be safe to do this since the regc's refcount has been
|
|
* incremented.
|
|
*/
|
|
pj_lock_release(regc->lock);
|
|
status = pjsip_regc_send(regc, tdata);
|
|
pj_lock_acquire(regc->lock);
|
|
}
|
|
|
|
if (status != PJ_SUCCESS) {
|
|
|
|
/* Only call callback if application is still interested
|
|
* in it.
|
|
*/
|
|
if (regc->_delete_flag == 0) {
|
|
/* Should be safe to release the lock temporarily.
|
|
* We do this to avoid deadlock.
|
|
*/
|
|
pj_lock_release(regc->lock);
|
|
call_callback(regc, status, tsx->status_code,
|
|
&rdata->msg_info.msg->line.status.reason,
|
|
rdata, NOEXP, 0, NULL, is_unreg);
|
|
pj_lock_acquire(regc->lock);
|
|
}
|
|
}
|
|
|
|
} else if (regc->_delete_flag) {
|
|
|
|
/* User has called pjsip_regc_destroy(), so don't call callback.
|
|
* This regc will be destroyed later in this function.
|
|
*/
|
|
|
|
/* Just reset current op */
|
|
regc->current_op = REGC_IDLE;
|
|
|
|
} else if (tsx->status_code == PJSIP_SC_INTERVAL_TOO_BRIEF &&
|
|
regc->current_op == REGC_REGISTERING)
|
|
{
|
|
/* Handle 423 response automatically:
|
|
* - set requested expiration to Min-Expires header, ONLY IF
|
|
* the original request is a registration (as opposed to
|
|
* unregistration) and the requested expiration was indeed
|
|
* lower than Min-Expires)
|
|
* - resend the request
|
|
*/
|
|
pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
|
|
pjsip_min_expires_hdr *me_hdr;
|
|
pjsip_tx_data *tdata;
|
|
pj_uint32_t min_exp;
|
|
|
|
/* reset current op */
|
|
regc->current_op = REGC_IDLE;
|
|
|
|
/* Update requested expiration */
|
|
me_hdr = (pjsip_min_expires_hdr*)
|
|
pjsip_msg_find_hdr(rdata->msg_info.msg,
|
|
PJSIP_H_MIN_EXPIRES, NULL);
|
|
if (me_hdr) {
|
|
min_exp = me_hdr->ivalue;
|
|
} else {
|
|
/* Broken server, Min-Expires doesn't exist.
|
|
* Just guestimate then, BUT ONLY if if this is the
|
|
* first time we received such response.
|
|
*/
|
|
enum {
|
|
/* Note: changing this value would require changing couple of
|
|
* Python test scripts.
|
|
*/
|
|
UNSPECIFIED_MIN_EXPIRES = 3601
|
|
};
|
|
if (!regc->expires_hdr ||
|
|
regc->expires_hdr->ivalue != UNSPECIFIED_MIN_EXPIRES)
|
|
{
|
|
min_exp = UNSPECIFIED_MIN_EXPIRES;
|
|
} else {
|
|
handled = PJ_FALSE;
|
|
PJ_LOG(4,(THIS_FILE, "Registration failed: 423 response "
|
|
"without Min-Expires header is invalid"));
|
|
goto handle_err;
|
|
}
|
|
}
|
|
|
|
if (regc->expires_hdr && regc->expires_hdr->ivalue >= min_exp) {
|
|
/* But we already send with greater expiration time, why does
|
|
* the server send us with 423? Oh well, just fail the request.
|
|
*/
|
|
handled = PJ_FALSE;
|
|
PJ_LOG(4,(THIS_FILE, "Registration failed: invalid "
|
|
"Min-Expires header value in response"));
|
|
goto handle_err;
|
|
}
|
|
|
|
set_expires(regc, min_exp);
|
|
|
|
status = pjsip_regc_register(regc, regc->auto_reg, &tdata);
|
|
if (status == PJ_SUCCESS) {
|
|
/* Need to unlock the regc temporarily while sending the message
|
|
* to prevent deadlock (see ticket #2260 and #1247).
|
|
* It should be safe to do this since the regc's refcount has been
|
|
* incremented.
|
|
*/
|
|
pj_lock_release(regc->lock);
|
|
status = pjsip_regc_send(regc, tdata);
|
|
pj_lock_acquire(regc->lock);
|
|
}
|
|
|
|
if (status != PJ_SUCCESS) {
|
|
/* Only call callback if application is still interested
|
|
* in it.
|
|
*/
|
|
if (!regc->_delete_flag) {
|
|
/* Should be safe to release the lock temporarily.
|
|
* We do this to avoid deadlock.
|
|
*/
|
|
pj_lock_release(regc->lock);
|
|
call_callback(regc, status, tsx->status_code,
|
|
&rdata->msg_info.msg->line.status.reason,
|
|
rdata, NOEXP, 0, NULL, PJ_FALSE);
|
|
pj_lock_acquire(regc->lock);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
handled = PJ_FALSE;
|
|
}
|
|
|
|
handle_err:
|
|
if (!handled) {
|
|
pjsip_rx_data *rdata;
|
|
pj_uint32_t expiration = NOEXP;
|
|
unsigned contact_cnt = 0;
|
|
pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT];
|
|
pj_bool_t is_unreg;
|
|
|
|
if (tsx->status_code/100 == 2) {
|
|
|
|
rdata = event->body.tsx_state.src.rdata;
|
|
|
|
/* Calculate expiration */
|
|
expiration = calculate_response_expiration(regc, rdata,
|
|
&contact_cnt,
|
|
PJSIP_REGC_MAX_CONTACT,
|
|
contact);
|
|
|
|
/* Schedule next registration */
|
|
schedule_registration(regc, expiration);
|
|
|
|
} else {
|
|
rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ?
|
|
event->body.tsx_state.src.rdata : NULL;
|
|
}
|
|
|
|
/* Update registration */
|
|
// if (expiration==NOEXP) expiration=-1;
|
|
regc->expires = expiration;
|
|
|
|
/* Mark operation as complete */
|
|
is_unreg = (regc->current_op == REGC_UNREGISTERING);
|
|
regc->current_op = REGC_IDLE;
|
|
|
|
/* Call callback. */
|
|
/* Should be safe to release the lock temporarily.
|
|
* We do this to avoid deadlock.
|
|
*/
|
|
pj_lock_release(regc->lock);
|
|
call_callback(regc, PJ_SUCCESS, tsx->status_code,
|
|
(rdata ? &rdata->msg_info.msg->line.status.reason
|
|
: &tsx->status_text),
|
|
rdata, expiration,
|
|
contact_cnt, contact, is_unreg);
|
|
pj_lock_acquire(regc->lock);
|
|
}
|
|
|
|
pj_lock_release(regc->lock);
|
|
|
|
/* Delete the record if user destroy regc during the callback. */
|
|
pjsip_regc_dec_ref(regc);
|
|
}
|
|
|
|
PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
|
|
{
|
|
pj_status_t status;
|
|
pjsip_cseq_hdr *cseq_hdr;
|
|
pjsip_expires_hdr *expires_hdr;
|
|
pj_int32_t cseq;
|
|
|
|
pjsip_regc_add_ref(regc);
|
|
pj_lock_acquire(regc->lock);
|
|
|
|
/* Make sure we don't have pending transaction. */
|
|
if (regc->has_tsx) {
|
|
PJ_LOG(4,(THIS_FILE, "Unable to send request, regc has another "
|
|
"transaction pending"));
|
|
pjsip_tx_data_dec_ref( tdata );
|
|
pj_lock_release(regc->lock);
|
|
pj_atomic_dec(regc->busy_ctr);
|
|
return PJSIP_EBUSY;
|
|
}
|
|
|
|
/* Just regc->has_tsx check above should be enough. This assertion check
|
|
* may cause problem, e.g: when regc_tsx_callback() invokes callback,
|
|
* lock is released and 'has_tsx' is set to FALSE and 'current_op' has
|
|
* not been updated to REGC_IDLE yet.
|
|
*/
|
|
//pj_assert(regc->current_op == REGC_IDLE);
|
|
|
|
/* Invalidate message buffer. */
|
|
pjsip_tx_data_invalidate_msg(tdata);
|
|
|
|
/* Increment CSeq */
|
|
cseq = ++regc->cseq_hdr->cseq;
|
|
cseq_hdr = (pjsip_cseq_hdr*)
|
|
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
|
|
cseq_hdr->cseq = cseq;
|
|
|
|
/* Find Expires header */
|
|
expires_hdr = (pjsip_expires_hdr*)
|
|
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_EXPIRES, NULL);
|
|
|
|
/* Bind to transport selector */
|
|
pjsip_tx_data_set_transport(tdata, ®c->tp_sel);
|
|
|
|
regc->has_tsx = PJ_TRUE;
|
|
|
|
/* Set current operation based on the value of Expires header */
|
|
if (expires_hdr && expires_hdr->ivalue==0)
|
|
regc->current_op = REGC_UNREGISTERING;
|
|
else
|
|
regc->current_op = REGC_REGISTERING;
|
|
|
|
if (expires_hdr && expires_hdr->ivalue)
|
|
regc->expires_requested = expires_hdr->ivalue;
|
|
|
|
/* Prevent deletion of tdata, e.g: when something wrong in sending,
|
|
* we need tdata to retrieve the transport.
|
|
*/
|
|
pjsip_tx_data_add_ref(tdata);
|
|
|
|
/* If via_addr is set, use this address for the Via header. */
|
|
if (regc->via_addr.host.slen > 0) {
|
|
tdata->via_addr = regc->via_addr;
|
|
tdata->via_tp = regc->via_tp;
|
|
}
|
|
|
|
/* Need to unlock the regc temporarily while sending the message to
|
|
* prevent deadlock (https://trac.pjsip.org/repos/ticket/1247).
|
|
* It should be safe to do this since the regc's refcount has been
|
|
* incremented.
|
|
*/
|
|
pj_lock_release(regc->lock);
|
|
|
|
/* Now send the message */
|
|
status = pjsip_endpt_send_request(regc->endpt, tdata, REGC_TSX_TIMEOUT,
|
|
regc, ®c_tsx_callback);
|
|
|
|
/* Reacquire the lock */
|
|
pj_lock_acquire(regc->lock);
|
|
|
|
if (status!=PJ_SUCCESS) {
|
|
/* On failure, regc_tsx_callback() may not be called, so we need
|
|
* to reset regc->has_tsx here (see also ticket #1936).
|
|
* But note that we are releasing the lock when sending the request
|
|
* above, so there can be a race with another registration send.
|
|
*/
|
|
if (cseq == regc->cseq_hdr->cseq) {
|
|
regc->has_tsx = PJ_FALSE;
|
|
}
|
|
|
|
PJ_PERROR(4,(THIS_FILE, status, "Error sending request"));
|
|
}
|
|
|
|
/* Get last transport used and add reference to it */
|
|
if (tdata->tp_info.transport != regc->last_transport &&
|
|
status==PJ_SUCCESS)
|
|
{
|
|
if (regc->last_transport) {
|
|
pjsip_transport_dec_ref(regc->last_transport);
|
|
regc->last_transport = NULL;
|
|
}
|
|
|
|
if (tdata->tp_info.transport) {
|
|
regc->last_transport = tdata->tp_info.transport;
|
|
pjsip_transport_add_ref(regc->last_transport);
|
|
}
|
|
}
|
|
|
|
/* Release tdata */
|
|
pjsip_tx_data_dec_ref(tdata);
|
|
|
|
pj_lock_release(regc->lock);
|
|
|
|
/* Delete the record if user destroy regc during the callback. */
|
|
pjsip_regc_dec_ref(regc);
|
|
|
|
return status;
|
|
}
|
|
|
|
|