From f3ab456a17af1c89a6e3be4d20c5944853df1cb0 Mon Sep 17 00:00:00 2001 From: "David M. Lee" Date: Mon, 7 Jan 2013 14:24:28 -0600 Subject: Import pjproject-2.0.1 --- pjsip/src/pjsip-ua/sip_reg.c | 1333 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1333 insertions(+) create mode 100644 pjsip/src/pjsip-ua/sip_reg.c (limited to 'pjsip/src/pjsip-ua/sip_reg.c') diff --git a/pjsip/src/pjsip-ua/sip_reg.c b/pjsip/src/pjsip-ua/sip_reg.c new file mode 100644 index 0000000..bd1fb21 --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_reg.c @@ -0,0 +1,1333 @@ +/* $Id: sip_reg.c 4173 2012-06-20 10:39:05Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * 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 + + +#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 + +enum { NOEXP = 0x1FFFFFFF }; + +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; + + 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 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) +{ + PJ_ASSERT_RETURN(regc, PJ_EINVAL); + + pj_lock_acquire(regc->lock); + 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_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 < 0) + 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; ipool, &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 && + contact_cnt && contact && 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(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_shallow_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_shallow_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); + + 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_shallow_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_shallow_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_shallow_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; + } + + 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_shallow_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 call_callback(pjsip_regc *regc, pj_status_t status, int st_code, + 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; + + + if (!regc->cb) + return; + + 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; + 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 = (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 + */ + pj_atomic_inc(regc->busy_ctr); + + 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, -1, 0, NULL); + } + + /* Delete the record if user destroy regc during the callback. */ + if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) { + pjsip_regc_destroy(regc); + } +} + +static void schedule_registration ( pjsip_regc *regc, pj_int32_t expiration ) +{ + if (regc->auto_reg && expiration > 0) { + pj_time_val delay = { 0, 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_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 + regc->via_addr = *via_addr; + 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; + + 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); + } + } + + return PJ_SUCCESS; +} + + +static pj_int32_t calculate_response_expiration(const pjsip_regc *regc, + const pjsip_rx_data *rdata, + unsigned *contact_cnt, + unsigned max_contact, + pjsip_contact_hdr *contacts[]) +{ + pj_int32_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 >= 0 && + 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. + */ + unsigned our_contact_cnt; + + our_contact_cnt = pj_list_size(®c->contact_hdr_list); + + if (*contact_cnt == our_contact_cnt && *contact_cnt && + contacts[0]->expires >= 0) + { + 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_atomic_inc(regc->busy_ctr); + 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); + } + } + + /* 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; + + /* reset current op */ + regc->current_op = REGC_IDLE; + + status = pjsip_auth_clt_reinit_req( ®c->auth_sess, + rdata, + tsx->last_tx, + &tdata); + + if (status == PJ_SUCCESS) { + status = pjsip_regc_send(regc, tdata); + } + + 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, -1, 0, NULL); + 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_int32_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) { + status = pjsip_regc_send(regc, tdata); + } + + 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, -1, 0, NULL); + pj_lock_acquire(regc->lock); + } + } + + } else { + handled = PJ_FALSE; + } + +handle_err: + if (!handled) { + pjsip_rx_data *rdata; + pj_int32_t expiration = NOEXP; + unsigned contact_cnt = 0; + pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT]; + + 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 */ + 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); + pj_lock_acquire(regc->lock); + } + + pj_lock_release(regc->lock); + + /* Delete the record if user destroy regc during the callback. */ + if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) { + pjsip_regc_destroy(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_uint32_t cseq; + + pj_atomic_inc(regc->busy_ctr); + 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; + } + + 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; + + /* 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); + if (status!=PJ_SUCCESS) { + PJ_LOG(4,(THIS_FILE, "Error sending request, status=%d", status)); + } + + /* Reacquire the lock */ + pj_lock_acquire(regc->lock); + + /* 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. */ + if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) { + pjsip_regc_destroy(regc); + } + + return status; +} + + -- cgit v1.2.3