initial commit
This commit is contained in:
commit
636959b37b
2474 files changed
+1343178
No files matched your search
@@ -0,0 +1,882 @@
|
||||
/* $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_100rel.h>
|
||||
#include <pjsip/sip_endpoint.h>
|
||||
#include <pjsip/sip_event.h>
|
||||
#include <pjsip/sip_module.h>
|
||||
#include <pjsip/sip_transaction.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/ctype.h>
|
||||
#include <pj/log.h>
|
||||
#include <pj/os.h>
|
||||
#include <pj/pool.h>
|
||||
#include <pj/rand.h>
|
||||
#include <pj/string.h>
|
||||
|
||||
#define THIS_FILE "sip_100rel.c"
|
||||
|
||||
/* PRACK method */
|
||||
PJ_DEF_DATA(const pjsip_method) pjsip_prack_method =
|
||||
{
|
||||
PJSIP_OTHER_METHOD,
|
||||
{ "PRACK", 5 }
|
||||
};
|
||||
|
||||
typedef struct dlg_data dlg_data;
|
||||
|
||||
/*
|
||||
* Static prototypes.
|
||||
*/
|
||||
static pj_status_t mod_100rel_load(pjsip_endpoint *endpt);
|
||||
|
||||
static void on_retransmit(pj_timer_heap_t *timer_heap,
|
||||
struct pj_timer_entry *entry);
|
||||
|
||||
|
||||
static const pj_str_t tag_100rel = { "100rel", 6 };
|
||||
static const pj_str_t RSEQ = { "RSeq", 4 };
|
||||
static const pj_str_t RACK = { "RAck", 4 };
|
||||
|
||||
|
||||
/* 100rel module */
|
||||
static struct mod_100rel
|
||||
{
|
||||
pjsip_module mod;
|
||||
pjsip_endpoint *endpt;
|
||||
} mod_100rel =
|
||||
{
|
||||
{
|
||||
NULL, NULL, /* prev, next. */
|
||||
{ "mod-100rel", 10 }, /* Name. */
|
||||
-1, /* Id */
|
||||
PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */
|
||||
&mod_100rel_load, /* load() */
|
||||
NULL, /* start() */
|
||||
NULL, /* stop() */
|
||||
NULL, /* unload() */
|
||||
NULL, /* on_rx_request() */
|
||||
NULL, /* on_rx_response() */
|
||||
NULL, /* on_tx_request. */
|
||||
NULL, /* on_tx_response() */
|
||||
NULL, /* on_tsx_state() */
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* List of pending transmission (may include the final response as well) */
|
||||
typedef struct tx_data_list_t
|
||||
{
|
||||
PJ_DECL_LIST_MEMBER(struct tx_data_list_t);
|
||||
pj_uint32_t rseq;
|
||||
pjsip_tx_data *tdata;
|
||||
} tx_data_list_t;
|
||||
|
||||
|
||||
/* Below, UAS and UAC roles are of the INVITE transaction */
|
||||
|
||||
/* UAS state. */
|
||||
typedef struct uas_state_t
|
||||
{
|
||||
pj_int32_t cseq;
|
||||
pj_uint32_t rseq; /* Initialized to -1 */
|
||||
tx_data_list_t tx_data_list;
|
||||
unsigned retransmit_count;
|
||||
pj_timer_entry retransmit_timer;
|
||||
} uas_state_t;
|
||||
|
||||
|
||||
/* UAC state */
|
||||
typedef struct uac_state_t
|
||||
{
|
||||
pj_str_t tag; /* To tag */
|
||||
pj_int32_t cseq;
|
||||
pj_uint32_t rseq; /* Initialized to -1 */
|
||||
struct uac_state_t *next; /* next call leg */
|
||||
} uac_state_t;
|
||||
|
||||
|
||||
/* State attached to each dialog. */
|
||||
struct dlg_data
|
||||
{
|
||||
pjsip_inv_session *inv;
|
||||
uas_state_t *uas_state;
|
||||
uac_state_t *uac_state_list;
|
||||
};
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
**
|
||||
** Module
|
||||
**
|
||||
*****************************************************************************
|
||||
*/
|
||||
static pj_status_t mod_100rel_load(pjsip_endpoint *endpt)
|
||||
{
|
||||
mod_100rel.endpt = endpt;
|
||||
pjsip_endpt_add_capability(endpt, &mod_100rel.mod,
|
||||
PJSIP_H_ALLOW, NULL,
|
||||
1, &pjsip_prack_method.name);
|
||||
pjsip_endpt_add_capability(endpt, &mod_100rel.mod,
|
||||
PJSIP_H_SUPPORTED, NULL,
|
||||
1, &tag_100rel);
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
static pjsip_require_hdr *find_req_hdr(pjsip_msg *msg)
|
||||
{
|
||||
pjsip_require_hdr *hreq;
|
||||
|
||||
hreq = (pjsip_require_hdr*)
|
||||
pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL);
|
||||
|
||||
while (hreq) {
|
||||
unsigned i;
|
||||
for (i=0; i<hreq->count; ++i) {
|
||||
if (!pj_stricmp(&hreq->values[i], &tag_100rel)) {
|
||||
return hreq;
|
||||
}
|
||||
}
|
||||
|
||||
if ((void*)hreq->next == (void*)&msg->hdr)
|
||||
return NULL;
|
||||
|
||||
hreq = (pjsip_require_hdr*)
|
||||
pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, hreq->next);
|
||||
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get PRACK method constant.
|
||||
*/
|
||||
PJ_DEF(const pjsip_method*) pjsip_get_prack_method(void)
|
||||
{
|
||||
return &pjsip_prack_method;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* init module
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt)
|
||||
{
|
||||
if (mod_100rel.mod.id != -1)
|
||||
return PJ_SUCCESS;
|
||||
|
||||
return pjsip_endpt_register_module(endpt, &mod_100rel.mod);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* API: attach 100rel support in invite session. Called by
|
||||
* sip_inv.c
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv)
|
||||
{
|
||||
dlg_data *dd;
|
||||
|
||||
/* Check that 100rel module has been initialized */
|
||||
PJ_ASSERT_RETURN(mod_100rel.mod.id >= 0, PJ_EINVALIDOP);
|
||||
|
||||
/* Create and attach as dialog usage */
|
||||
dd = PJ_POOL_ZALLOC_T(inv->dlg->pool, dlg_data);
|
||||
dd->inv = inv;
|
||||
pjsip_dlg_add_usage(inv->dlg, &mod_100rel.mod, (void*)dd);
|
||||
|
||||
PJ_LOG(5,(dd->inv->dlg->obj_name, "100rel module attached"));
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Check if incoming response has reliable provisional response feature.
|
||||
*/
|
||||
PJ_DEF(pj_bool_t) pjsip_100rel_is_reliable(pjsip_rx_data *rdata)
|
||||
{
|
||||
pjsip_msg *msg = rdata->msg_info.msg;
|
||||
|
||||
PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG, PJ_FALSE);
|
||||
|
||||
return msg->line.status.code > 100 && msg->line.status.code < 200 &&
|
||||
rdata->msg_info.require != NULL &&
|
||||
find_req_hdr(msg) != NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create PRACK request for the incoming reliable provisional response.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_100rel_create_prack( pjsip_inv_session *inv,
|
||||
pjsip_rx_data *rdata,
|
||||
pjsip_tx_data **p_tdata)
|
||||
{
|
||||
dlg_data *dd;
|
||||
uac_state_t *uac_state = NULL;
|
||||
const pj_str_t *to_tag = &rdata->msg_info.to->tag;
|
||||
pjsip_transaction *tsx;
|
||||
pjsip_msg *msg;
|
||||
pjsip_generic_string_hdr *rseq_hdr;
|
||||
pjsip_generic_string_hdr *rack_hdr;
|
||||
unsigned rseq;
|
||||
pj_str_t rack;
|
||||
char rack_buf[80];
|
||||
pjsip_tx_data *tdata;
|
||||
pj_status_t status;
|
||||
|
||||
*p_tdata = NULL;
|
||||
|
||||
dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
|
||||
PJ_ASSERT_RETURN(dd != NULL, PJSIP_ENOTINITIALIZED);
|
||||
|
||||
tsx = pjsip_rdata_get_tsx(rdata);
|
||||
msg = rdata->msg_info.msg;
|
||||
|
||||
/* Check our assumptions */
|
||||
pj_assert( tsx->role == PJSIP_ROLE_UAC &&
|
||||
tsx->method.id == PJSIP_INVITE_METHOD &&
|
||||
msg->line.status.code > 100 &&
|
||||
msg->line.status.code < 200);
|
||||
|
||||
|
||||
/* Get the RSeq header */
|
||||
rseq_hdr = (pjsip_generic_string_hdr*)
|
||||
pjsip_msg_find_hdr_by_name(msg, &RSEQ, NULL);
|
||||
if (rseq_hdr == NULL) {
|
||||
PJ_LOG(4,(dd->inv->dlg->obj_name,
|
||||
"Ignoring 100rel response with no RSeq header"));
|
||||
return PJSIP_EMISSINGHDR;
|
||||
}
|
||||
rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue);
|
||||
|
||||
/* Find UAC state for the specified call leg */
|
||||
uac_state = dd->uac_state_list;
|
||||
while (uac_state) {
|
||||
if (pj_stricmp(&uac_state->tag, to_tag)==0)
|
||||
break;
|
||||
uac_state = uac_state->next;
|
||||
}
|
||||
|
||||
/* Create new UAC state if we don't have one */
|
||||
if (uac_state == NULL) {
|
||||
uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool, uac_state_t);
|
||||
uac_state->cseq = rdata->msg_info.cseq->cseq;
|
||||
uac_state->rseq = rseq - 1;
|
||||
pj_strdup(dd->inv->dlg->pool, &uac_state->tag, to_tag);
|
||||
uac_state->next = dd->uac_state_list;
|
||||
dd->uac_state_list = uac_state;
|
||||
}
|
||||
|
||||
/* If this is from new INVITE transaction, reset UAC state. */
|
||||
if (rdata->msg_info.cseq->cseq != uac_state->cseq) {
|
||||
uac_state->cseq = rdata->msg_info.cseq->cseq;
|
||||
uac_state->rseq = rseq - 1;
|
||||
}
|
||||
|
||||
/* Ignore provisional response retransmission */
|
||||
if (rseq <= uac_state->rseq) {
|
||||
/* This should have been handled before */
|
||||
return PJ_EIGNORED;
|
||||
|
||||
/* Ignore provisional response with out-of-order RSeq */
|
||||
} else if (rseq != uac_state->rseq + 1) {
|
||||
PJ_LOG(4,(dd->inv->dlg->obj_name,
|
||||
"Ignoring 100rel response because RSeq jump "
|
||||
"(expecting %u, got %u)",
|
||||
uac_state->rseq+1, rseq));
|
||||
return PJ_EIGNORED;
|
||||
}
|
||||
|
||||
/* Update our RSeq */
|
||||
uac_state->rseq = rseq;
|
||||
|
||||
/* Create PRACK */
|
||||
status = pjsip_dlg_create_request(dd->inv->dlg, &pjsip_prack_method,
|
||||
-1, &tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
/* If this response is a forked response from a different call-leg,
|
||||
* update the req URI (https://trac.pjsip.org/repos/ticket/1364)
|
||||
*/
|
||||
if (pj_stricmp(&uac_state->tag, &dd->inv->dlg->remote.info->tag)) {
|
||||
const pjsip_contact_hdr *mhdr;
|
||||
|
||||
mhdr = (const pjsip_contact_hdr*)
|
||||
pjsip_msg_find_hdr(rdata->msg_info.msg,
|
||||
PJSIP_H_CONTACT, NULL);
|
||||
if (!mhdr || !mhdr->uri) {
|
||||
PJ_LOG(4,(dd->inv->dlg->obj_name,
|
||||
"Ignoring 100rel response with no or "
|
||||
"invalid Contact header"));
|
||||
pjsip_tx_data_dec_ref(tdata);
|
||||
return PJ_EIGNORED;
|
||||
}
|
||||
tdata->msg->line.req.uri = (pjsip_uri*)
|
||||
pjsip_uri_clone(tdata->pool, mhdr->uri);
|
||||
}
|
||||
|
||||
/* Create RAck header */
|
||||
rack.ptr = rack_buf;
|
||||
rack.slen = pj_ansi_snprintf(rack.ptr, sizeof(rack_buf),
|
||||
"%u %u %.*s",
|
||||
rseq, rdata->msg_info.cseq->cseq,
|
||||
(int)tsx->method.name.slen,
|
||||
tsx->method.name.ptr);
|
||||
if (rack.slen < 1 || rack.slen >= (int)sizeof(rack_buf)) {
|
||||
return PJ_ETOOSMALL;
|
||||
}
|
||||
rack_hdr = pjsip_generic_string_hdr_create(tdata->pool, &RACK, &rack);
|
||||
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) rack_hdr);
|
||||
|
||||
/* Done */
|
||||
*p_tdata = tdata;
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Send PRACK request.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_100rel_send_prack( pjsip_inv_session *inv,
|
||||
pjsip_tx_data *tdata)
|
||||
{
|
||||
dlg_data *dd;
|
||||
|
||||
dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
|
||||
PJ_ASSERT_ON_FAIL(dd != NULL,
|
||||
{pjsip_tx_data_dec_ref(tdata); return PJSIP_ENOTINITIALIZED; });
|
||||
|
||||
return pjsip_dlg_send_request(inv->dlg, tdata,
|
||||
mod_100rel.mod.id, (void*) dd);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Clear all responses in the transmission list */
|
||||
static void clear_all_responses(dlg_data *dd)
|
||||
{
|
||||
tx_data_list_t *tl;
|
||||
|
||||
tl = dd->uas_state->tx_data_list.next;
|
||||
while (tl != &dd->uas_state->tx_data_list) {
|
||||
pjsip_tx_data_dec_ref(tl->tdata);
|
||||
tl = tl->next;
|
||||
}
|
||||
pj_list_init(&dd->uas_state->tx_data_list);
|
||||
}
|
||||
|
||||
/*
|
||||
* Notify 100rel module that the invite session has been disconnected.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_100rel_end_session(pjsip_inv_session *inv)
|
||||
{
|
||||
dlg_data *dd;
|
||||
|
||||
dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
|
||||
if (!dd)
|
||||
return PJ_SUCCESS;
|
||||
|
||||
/* Make sure we don't have pending transmission */
|
||||
if (dd->uas_state) {
|
||||
/* Cancel the retransmit timer */
|
||||
if (dd->uas_state->retransmit_timer.id) {
|
||||
pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
|
||||
&dd->uas_state->retransmit_timer);
|
||||
dd->uas_state->retransmit_timer.id = PJ_FALSE;
|
||||
}
|
||||
if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
|
||||
/* Clear all pending responses (drop 'em) */
|
||||
clear_all_responses(dd);
|
||||
}
|
||||
}
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static void parse_rack(const pj_str_t *rack,
|
||||
pj_uint32_t *p_rseq, pj_int32_t *p_seq,
|
||||
pj_str_t *p_method)
|
||||
{
|
||||
const char *p = rack->ptr, *end = p + rack->slen;
|
||||
pj_str_t token;
|
||||
|
||||
token.ptr = (char*)p;
|
||||
while (p < end && pj_isdigit(*p))
|
||||
++p;
|
||||
token.slen = p - token.ptr;
|
||||
*p_rseq = pj_strtoul(&token);
|
||||
|
||||
++p;
|
||||
token.ptr = (char*)p;
|
||||
while (p < end && pj_isdigit(*p))
|
||||
++p;
|
||||
token.slen = p - token.ptr;
|
||||
*p_seq = pj_strtoul(&token);
|
||||
|
||||
++p;
|
||||
if (p < end) {
|
||||
p_method->ptr = (char*)p;
|
||||
p_method->slen = end - p;
|
||||
} else {
|
||||
p_method->ptr = NULL;
|
||||
p_method->slen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Handle incoming PRACK request.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_100rel_on_rx_prack( pjsip_inv_session *inv,
|
||||
pjsip_rx_data *rdata)
|
||||
{
|
||||
dlg_data *dd;
|
||||
pjsip_transaction *tsx;
|
||||
pjsip_msg *msg;
|
||||
pjsip_generic_string_hdr *rack_hdr;
|
||||
pjsip_tx_data *tdata;
|
||||
pj_uint32_t rseq;
|
||||
pj_int32_t cseq;
|
||||
pj_str_t method;
|
||||
pj_status_t status;
|
||||
|
||||
tsx = pjsip_rdata_get_tsx(rdata);
|
||||
pj_assert(tsx != NULL);
|
||||
|
||||
msg = rdata->msg_info.msg;
|
||||
|
||||
dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
|
||||
if (dd == NULL) {
|
||||
/* UAC sends us PRACK while we didn't send reliable provisional
|
||||
* response. Respond with 400 (?)
|
||||
*/
|
||||
const pj_str_t reason = pj_str("Unexpected PRACK");
|
||||
|
||||
status = pjsip_dlg_create_response(inv->dlg, rdata, 400,
|
||||
&reason, &tdata);
|
||||
if (status == PJ_SUCCESS) {
|
||||
status = pjsip_dlg_send_response(inv->dlg, tsx, tdata);
|
||||
}
|
||||
return PJSIP_ENOTINITIALIZED;
|
||||
}
|
||||
|
||||
/* Always reply with 200/OK for PRACK */
|
||||
status = pjsip_dlg_create_response(inv->dlg, rdata, 200, NULL, &tdata);
|
||||
if (status == PJ_SUCCESS) {
|
||||
status = pjsip_dlg_send_response(inv->dlg, tsx, tdata);
|
||||
}
|
||||
|
||||
/* Ignore if we don't have pending transmission */
|
||||
if (dd->uas_state == NULL || pj_list_empty(&dd->uas_state->tx_data_list)) {
|
||||
PJ_LOG(4,(dd->inv->dlg->obj_name,
|
||||
"PRACK ignored - no pending response"));
|
||||
return PJ_EIGNORED;
|
||||
}
|
||||
|
||||
/* Find RAck header */
|
||||
rack_hdr = (pjsip_generic_string_hdr*)
|
||||
pjsip_msg_find_hdr_by_name(msg, &RACK, NULL);
|
||||
if (!rack_hdr) {
|
||||
/* RAck header not found */
|
||||
PJ_LOG(4,(dd->inv->dlg->obj_name, "No RAck header"));
|
||||
return PJSIP_EMISSINGHDR;
|
||||
}
|
||||
|
||||
/* Parse RAck header */
|
||||
parse_rack(&rack_hdr->hvalue, &rseq, &cseq, &method);
|
||||
|
||||
|
||||
/* Match RAck against outgoing transmission */
|
||||
if (rseq == dd->uas_state->tx_data_list.next->rseq &&
|
||||
cseq == dd->uas_state->cseq)
|
||||
{
|
||||
/*
|
||||
* Yes this PRACK matches outgoing transmission.
|
||||
*/
|
||||
tx_data_list_t *tl = dd->uas_state->tx_data_list.next;
|
||||
|
||||
if (dd->uas_state->retransmit_timer.id) {
|
||||
pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
|
||||
&dd->uas_state->retransmit_timer);
|
||||
dd->uas_state->retransmit_timer.id = PJ_FALSE;
|
||||
}
|
||||
|
||||
/* Remove from the list */
|
||||
if (tl != &dd->uas_state->tx_data_list) {
|
||||
pj_list_erase(tl);
|
||||
|
||||
/* Destroy the response */
|
||||
pjsip_tx_data_dec_ref(tl->tdata);
|
||||
}
|
||||
|
||||
/* Schedule next packet */
|
||||
dd->uas_state->retransmit_count = 0;
|
||||
if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
|
||||
on_retransmit(NULL, &dd->uas_state->retransmit_timer);
|
||||
}
|
||||
|
||||
} else {
|
||||
/* No it doesn't match */
|
||||
PJ_LOG(4,(dd->inv->dlg->obj_name,
|
||||
"Rx PRACK with no matching reliable response"));
|
||||
return PJ_EIGNORED;
|
||||
}
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This is retransmit timer callback, called initially to send the response,
|
||||
* and subsequently when the retransmission time elapses.
|
||||
*/
|
||||
static void on_retransmit(pj_timer_heap_t *timer_heap,
|
||||
struct pj_timer_entry *entry)
|
||||
{
|
||||
dlg_data *dd;
|
||||
tx_data_list_t *tl;
|
||||
pjsip_tx_data *tdata;
|
||||
pj_bool_t final;
|
||||
pj_time_val delay;
|
||||
|
||||
PJ_UNUSED_ARG(timer_heap);
|
||||
|
||||
dd = (dlg_data*) entry->user_data;
|
||||
|
||||
entry->id = PJ_FALSE;
|
||||
|
||||
++dd->uas_state->retransmit_count;
|
||||
if (dd->uas_state->retransmit_count >= 7) {
|
||||
/* If a reliable provisional response is retransmitted for
|
||||
64*T1 seconds without reception of a corresponding PRACK,
|
||||
the UAS SHOULD reject the original request with a 5xx
|
||||
response.
|
||||
*/
|
||||
pj_str_t reason = pj_str("Reliable response timed out");
|
||||
pj_status_t status;
|
||||
|
||||
/* Clear all pending responses */
|
||||
clear_all_responses(dd);
|
||||
|
||||
/* Send 500 response */
|
||||
status = pjsip_inv_end_session(dd->inv, 500, &reason, &tdata);
|
||||
if (status == PJ_SUCCESS) {
|
||||
pjsip_dlg_send_response(dd->inv->dlg,
|
||||
dd->inv->invite_tsx,
|
||||
tdata);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pj_assert(!pj_list_empty(&dd->uas_state->tx_data_list));
|
||||
tl = dd->uas_state->tx_data_list.next;
|
||||
tdata = tl->tdata;
|
||||
|
||||
pjsip_tx_data_add_ref(tdata);
|
||||
final = tdata->msg->line.status.code >= 200;
|
||||
|
||||
if (dd->uas_state->retransmit_count == 1) {
|
||||
pj_status_t status;
|
||||
|
||||
status = pjsip_tsx_send_msg(dd->inv->invite_tsx, tdata);
|
||||
if (status != PJ_SUCCESS) {
|
||||
PJ_PERROR(3, (THIS_FILE, status,
|
||||
"Failed to send message"));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
pjsip_tsx_retransmit_no_state(dd->inv->invite_tsx, tdata);
|
||||
}
|
||||
|
||||
if (final) {
|
||||
/* This is final response, which will be retransmitted by
|
||||
* UA layer. There's no more task to do, so clear the
|
||||
* transmission list and bail out.
|
||||
*/
|
||||
clear_all_responses(dd);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Schedule next retransmission */
|
||||
if (dd->uas_state->retransmit_count < 6) {
|
||||
delay.sec = 0;
|
||||
delay.msec = (1 << dd->uas_state->retransmit_count) *
|
||||
(long)pjsip_cfg()->tsx.t1;
|
||||
pj_time_val_normalize(&delay);
|
||||
} else {
|
||||
delay.sec = 1;
|
||||
delay.msec = 500;
|
||||
}
|
||||
|
||||
|
||||
pjsip_endpt_schedule_timer(dd->inv->dlg->endpt,
|
||||
&dd->uas_state->retransmit_timer,
|
||||
&delay);
|
||||
|
||||
entry->id = PJ_TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Check if any pending response in transmission list has SDP */
|
||||
static pj_bool_t has_sdp(dlg_data *dd)
|
||||
{
|
||||
tx_data_list_t *tl;
|
||||
|
||||
tl = dd->uas_state->tx_data_list.next;
|
||||
while (tl != &dd->uas_state->tx_data_list) {
|
||||
if (tl->tdata->msg->body)
|
||||
return PJ_TRUE;
|
||||
tl = tl->next;
|
||||
}
|
||||
|
||||
return PJ_FALSE;
|
||||
}
|
||||
|
||||
|
||||
/* Send response reliably */
|
||||
PJ_DEF(pj_status_t) pjsip_100rel_tx_response(pjsip_inv_session *inv,
|
||||
pjsip_tx_data *tdata)
|
||||
{
|
||||
pjsip_cseq_hdr *cseq_hdr;
|
||||
pjsip_generic_string_hdr *rseq_hdr;
|
||||
pjsip_require_hdr *req_hdr;
|
||||
int status_code;
|
||||
dlg_data *dd;
|
||||
pjsip_tx_data *old_tdata;
|
||||
pj_status_t status;
|
||||
|
||||
PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG,
|
||||
PJSIP_ENOTRESPONSEMSG);
|
||||
|
||||
status_code = tdata->msg->line.status.code;
|
||||
|
||||
/* 100 response doesn't need PRACK */
|
||||
if (status_code == 100)
|
||||
return pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
|
||||
|
||||
|
||||
/* Get the 100rel data attached to this dialog */
|
||||
dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
|
||||
PJ_ASSERT_RETURN(dd != NULL, PJ_EINVALIDOP);
|
||||
|
||||
|
||||
/* Clone tdata.
|
||||
* We need to clone tdata because we may need to keep it in our
|
||||
* retransmission list, while the original dialog may modify it
|
||||
* if it wants to send another response.
|
||||
*/
|
||||
old_tdata = tdata;
|
||||
pjsip_tx_data_clone(old_tdata, 0, &tdata);
|
||||
pjsip_tx_data_dec_ref(old_tdata);
|
||||
|
||||
|
||||
/* Get CSeq header, and make sure this is INVITE response */
|
||||
cseq_hdr = (pjsip_cseq_hdr*)
|
||||
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
|
||||
PJ_ASSERT_RETURN(cseq_hdr != NULL, PJ_EBUG);
|
||||
PJ_ASSERT_RETURN(cseq_hdr->method.id == PJSIP_INVITE_METHOD,
|
||||
PJ_EINVALIDOP);
|
||||
|
||||
/* Remove existing Require header */
|
||||
req_hdr = find_req_hdr(tdata->msg);
|
||||
if (req_hdr) {
|
||||
pj_list_erase(req_hdr);
|
||||
}
|
||||
|
||||
/* Remove existing RSeq header */
|
||||
rseq_hdr = (pjsip_generic_string_hdr*)
|
||||
pjsip_msg_find_hdr_by_name(tdata->msg, &RSEQ, NULL);
|
||||
if (rseq_hdr)
|
||||
pj_list_erase(rseq_hdr);
|
||||
|
||||
/* Different treatment for provisional and final response */
|
||||
if (status_code/100 == 2) {
|
||||
|
||||
/* RFC 3262 Section 3: UAS Behavior:
|
||||
|
||||
The UAS MAY send a final response to the initial request
|
||||
before having received PRACKs for all unacknowledged
|
||||
reliable provisional responses, unless the final response
|
||||
is 2xx and any of the unacknowledged reliable provisional
|
||||
responses contained a session description. In that case,
|
||||
it MUST NOT send a final response until those provisional
|
||||
responses are acknowledged.
|
||||
*/
|
||||
|
||||
if (dd->uas_state && has_sdp(dd)) {
|
||||
/* Yes we have transmitted 1xx with SDP reliably.
|
||||
* In this case, must queue the 2xx response.
|
||||
*/
|
||||
tx_data_list_t *tl;
|
||||
|
||||
tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t);
|
||||
tl->tdata = tdata;
|
||||
tl->rseq = (pj_uint32_t)-1;
|
||||
pj_list_push_back(&dd->uas_state->tx_data_list, tl);
|
||||
|
||||
/* Will send later */
|
||||
status = PJ_SUCCESS;
|
||||
|
||||
PJ_LOG(4,(dd->inv->dlg->obj_name,
|
||||
"2xx response will be sent after PRACK"));
|
||||
|
||||
} else if (dd->uas_state) {
|
||||
/*
|
||||
RFC 3262 Section 3: UAS Behavior:
|
||||
|
||||
If the UAS does send a final response when reliable
|
||||
responses are still unacknowledged, it SHOULD NOT
|
||||
continue to retransmit the unacknowledged reliable
|
||||
provisional responses, but it MUST be prepared to
|
||||
process PRACK requests for those outstanding
|
||||
responses.
|
||||
*/
|
||||
|
||||
PJ_LOG(4,(dd->inv->dlg->obj_name,
|
||||
"No SDP sent so far, sending 2xx now"));
|
||||
|
||||
/* Cancel the retransmit timer */
|
||||
if (dd->uas_state->retransmit_timer.id) {
|
||||
pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
|
||||
&dd->uas_state->retransmit_timer);
|
||||
dd->uas_state->retransmit_timer.id = PJ_FALSE;
|
||||
}
|
||||
|
||||
/* Clear all pending responses (drop 'em) */
|
||||
clear_all_responses(dd);
|
||||
|
||||
/* And transmit the 2xx response */
|
||||
status=pjsip_dlg_send_response(inv->dlg,
|
||||
inv->invite_tsx, tdata);
|
||||
|
||||
} else {
|
||||
/* We didn't send any reliable provisional response */
|
||||
|
||||
/* Transmit the 2xx response */
|
||||
status=pjsip_dlg_send_response(inv->dlg,
|
||||
inv->invite_tsx, tdata);
|
||||
}
|
||||
|
||||
} else if (status_code >= 300) {
|
||||
|
||||
/*
|
||||
RFC 3262 Section 3: UAS Behavior:
|
||||
|
||||
If the UAS does send a final response when reliable
|
||||
responses are still unacknowledged, it SHOULD NOT
|
||||
continue to retransmit the unacknowledged reliable
|
||||
provisional responses, but it MUST be prepared to
|
||||
process PRACK requests for those outstanding
|
||||
responses.
|
||||
*/
|
||||
|
||||
/* Cancel the retransmit timer */
|
||||
if (dd->uas_state && dd->uas_state->retransmit_timer.id) {
|
||||
pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
|
||||
&dd->uas_state->retransmit_timer);
|
||||
dd->uas_state->retransmit_timer.id = PJ_FALSE;
|
||||
|
||||
/* Clear all pending responses (drop 'em) */
|
||||
clear_all_responses(dd);
|
||||
}
|
||||
|
||||
/* And transmit the 2xx response */
|
||||
status=pjsip_dlg_send_response(inv->dlg,
|
||||
inv->invite_tsx, tdata);
|
||||
|
||||
} else {
|
||||
/*
|
||||
* This is provisional response.
|
||||
*/
|
||||
char rseq_str[32];
|
||||
pj_str_t rseq;
|
||||
tx_data_list_t *tl;
|
||||
|
||||
/* Create UAS state if we don't have one */
|
||||
if (dd->uas_state == NULL) {
|
||||
dd->uas_state = PJ_POOL_ZALLOC_T(inv->dlg->pool,
|
||||
uas_state_t);
|
||||
dd->uas_state->cseq = cseq_hdr->cseq;
|
||||
dd->uas_state->rseq = (pj_rand() % 0x7FFF) + 1;
|
||||
pj_list_init(&dd->uas_state->tx_data_list);
|
||||
dd->uas_state->retransmit_timer.user_data = dd;
|
||||
dd->uas_state->retransmit_timer.cb = &on_retransmit;
|
||||
}
|
||||
|
||||
/* Check that CSeq match */
|
||||
PJ_ASSERT_RETURN(cseq_hdr->cseq == dd->uas_state->cseq,
|
||||
PJ_EINVALIDOP);
|
||||
|
||||
/* Add Require header */
|
||||
req_hdr = pjsip_require_hdr_create(tdata->pool);
|
||||
req_hdr->count = 1;
|
||||
req_hdr->values[0] = tag_100rel;
|
||||
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)req_hdr);
|
||||
|
||||
/* Add RSeq header */
|
||||
pj_ansi_snprintf(rseq_str, sizeof(rseq_str), "%u",
|
||||
dd->uas_state->rseq);
|
||||
rseq = pj_str(rseq_str);
|
||||
rseq_hdr = pjsip_generic_string_hdr_create(tdata->pool,
|
||||
&RSEQ, &rseq);
|
||||
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rseq_hdr);
|
||||
|
||||
/* Create list entry for this response */
|
||||
tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t);
|
||||
tl->tdata = tdata;
|
||||
tl->rseq = dd->uas_state->rseq++;
|
||||
|
||||
/* Add to queue if there's pending response, otherwise
|
||||
* transmit immediately.
|
||||
*/
|
||||
if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
|
||||
|
||||
int code = tdata->msg->line.status.code;
|
||||
|
||||
/* Will send later */
|
||||
pj_list_push_back(&dd->uas_state->tx_data_list, tl);
|
||||
status = PJ_SUCCESS;
|
||||
|
||||
PJ_LOG(4,(dd->inv->dlg->obj_name,
|
||||
"Reliable %d response enqueued (%d pending)",
|
||||
code, pj_list_size(&dd->uas_state->tx_data_list)));
|
||||
|
||||
} else {
|
||||
pj_list_push_back(&dd->uas_state->tx_data_list, tl);
|
||||
|
||||
dd->uas_state->retransmit_count = 0;
|
||||
on_retransmit(NULL, &dd->uas_state->retransmit_timer);
|
||||
status = PJ_SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,393 @@
|
||||
/* $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_replaces.h>
|
||||
#include <pjsip-ua/sip_inv.h>
|
||||
#include <pjsip/print_util.h>
|
||||
#include <pjsip/sip_endpoint.h>
|
||||
#include <pjsip/sip_errno.h>
|
||||
#include <pjsip/sip_parser.h>
|
||||
#include <pjsip/sip_transport.h>
|
||||
#include <pjsip/sip_ua_layer.h>
|
||||
#include <pjsip/sip_util.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/log.h>
|
||||
#include <pj/pool.h>
|
||||
#include <pj/string.h>
|
||||
|
||||
#define THIS_FILE "sip_replaces.c"
|
||||
|
||||
|
||||
/*
|
||||
* Replaces header vptr.
|
||||
*/
|
||||
static int replaces_hdr_print( pjsip_replaces_hdr *hdr,
|
||||
char *buf, pj_size_t size);
|
||||
static pjsip_replaces_hdr* replaces_hdr_clone( pj_pool_t *pool,
|
||||
const pjsip_replaces_hdr *hdr);
|
||||
static pjsip_replaces_hdr* replaces_hdr_shallow_clone( pj_pool_t *pool,
|
||||
const pjsip_replaces_hdr*);
|
||||
|
||||
static pjsip_hdr_vptr replaces_hdr_vptr =
|
||||
{
|
||||
(pjsip_hdr_clone_fptr) &replaces_hdr_clone,
|
||||
(pjsip_hdr_clone_fptr) &replaces_hdr_shallow_clone,
|
||||
(pjsip_hdr_print_fptr) &replaces_hdr_print,
|
||||
};
|
||||
|
||||
/* Globals */
|
||||
static pjsip_endpoint *the_endpt;
|
||||
static pj_bool_t is_initialized;
|
||||
|
||||
PJ_DEF(pjsip_replaces_hdr*) pjsip_replaces_hdr_create(pj_pool_t *pool)
|
||||
{
|
||||
pjsip_replaces_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_replaces_hdr);
|
||||
hdr->type = PJSIP_H_OTHER;
|
||||
hdr->name.ptr = "Replaces";
|
||||
hdr->name.slen = 8;
|
||||
hdr->vptr = &replaces_hdr_vptr;
|
||||
pj_list_init(hdr);
|
||||
pj_list_init(&hdr->other_param);
|
||||
return hdr;
|
||||
}
|
||||
|
||||
static int replaces_hdr_print( pjsip_replaces_hdr *hdr,
|
||||
char *buf, pj_size_t size)
|
||||
{
|
||||
char *p = buf;
|
||||
char *endbuf = buf+size;
|
||||
pj_ssize_t printed;
|
||||
const pjsip_parser_const_t *pc = pjsip_parser_const();
|
||||
|
||||
copy_advance(p, hdr->name);
|
||||
*p++ = ':';
|
||||
*p++ = ' ';
|
||||
|
||||
copy_advance(p, hdr->call_id);
|
||||
copy_advance_pair(p, ";to-tag=", 8, hdr->to_tag);
|
||||
copy_advance_pair(p, ";from-tag=", 10, hdr->from_tag);
|
||||
|
||||
if (hdr->early_only) {
|
||||
const pj_str_t str_early_only = { ";early-only", 11 };
|
||||
copy_advance(p, str_early_only);
|
||||
}
|
||||
|
||||
printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
|
||||
&pc->pjsip_TOKEN_SPEC,
|
||||
&pc->pjsip_TOKEN_SPEC, ';');
|
||||
if (printed < 0)
|
||||
return (int)printed;
|
||||
|
||||
p += printed;
|
||||
return (int)(p - buf);
|
||||
}
|
||||
|
||||
static pjsip_replaces_hdr* replaces_hdr_clone( pj_pool_t *pool,
|
||||
const pjsip_replaces_hdr *rhs)
|
||||
{
|
||||
pjsip_replaces_hdr *hdr = pjsip_replaces_hdr_create(pool);
|
||||
pj_strdup(pool, &hdr->call_id, &rhs->call_id);
|
||||
pj_strdup(pool, &hdr->to_tag, &rhs->to_tag);
|
||||
pj_strdup(pool, &hdr->from_tag, &rhs->from_tag);
|
||||
hdr->early_only = rhs->early_only;
|
||||
pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
|
||||
return hdr;
|
||||
}
|
||||
|
||||
static pjsip_replaces_hdr*
|
||||
replaces_hdr_shallow_clone( pj_pool_t *pool,
|
||||
const pjsip_replaces_hdr *rhs )
|
||||
{
|
||||
pjsip_replaces_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_replaces_hdr);
|
||||
pj_memcpy(hdr, rhs, sizeof(*hdr));
|
||||
pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
|
||||
return hdr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Parse Replaces header.
|
||||
*/
|
||||
static pjsip_hdr *parse_hdr_replaces(pjsip_parse_ctx *ctx)
|
||||
{
|
||||
pjsip_replaces_hdr *hdr = pjsip_replaces_hdr_create(ctx->pool);
|
||||
const pj_str_t to_tag = { "to-tag", 6 };
|
||||
const pj_str_t from_tag = { "from-tag", 8 };
|
||||
const pj_str_t early_only_tag = { "early-only", 10 };
|
||||
|
||||
/*pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->call_id);*/
|
||||
/* Get Call-ID (until ';' is found). using pjsip_TOKEN_SPEC doesn't work
|
||||
* because it stops parsing when '@' character is found.
|
||||
*/
|
||||
pj_scan_get_until_ch(ctx->scanner, ';', &hdr->call_id);
|
||||
|
||||
while (*ctx->scanner->curptr == ';') {
|
||||
pj_str_t pname, pvalue;
|
||||
|
||||
pj_scan_get_char(ctx->scanner);
|
||||
pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
|
||||
|
||||
if (pj_stricmp(&pname, &to_tag)==0) {
|
||||
hdr->to_tag = pvalue;
|
||||
} else if (pj_stricmp(&pname, &from_tag)==0) {
|
||||
hdr->from_tag = pvalue;
|
||||
} else if (pj_stricmp(&pname, &early_only_tag)==0) {
|
||||
hdr->early_only = PJ_TRUE;
|
||||
} else {
|
||||
pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
|
||||
param->name = pname;
|
||||
param->value = pvalue;
|
||||
pj_list_push_back(&hdr->other_param, param);
|
||||
}
|
||||
}
|
||||
pjsip_parse_end_hdr_imp( ctx->scanner );
|
||||
return (pjsip_hdr*)hdr;
|
||||
}
|
||||
|
||||
|
||||
/* Deinitialize Replaces */
|
||||
static void pjsip_replaces_deinit_module(pjsip_endpoint *endpt)
|
||||
{
|
||||
PJ_TODO(provide_initialized_flag_for_each_endpoint);
|
||||
PJ_UNUSED_ARG(endpt);
|
||||
is_initialized = PJ_FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize Replaces support in PJSIP.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_replaces_init_module(pjsip_endpoint *endpt)
|
||||
{
|
||||
pj_status_t status;
|
||||
const pj_str_t STR_REPLACES = { "replaces", 8 };
|
||||
|
||||
the_endpt = endpt;
|
||||
|
||||
if (is_initialized)
|
||||
return PJ_SUCCESS;
|
||||
|
||||
/* Register Replaces header parser */
|
||||
status = pjsip_register_hdr_parser( "Replaces", NULL,
|
||||
&parse_hdr_replaces);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
/* Register "replaces" capability */
|
||||
status = pjsip_endpt_add_capability(endpt, NULL, PJSIP_H_SUPPORTED, NULL,
|
||||
1, &STR_REPLACES);
|
||||
|
||||
/* Register deinit module to be executed when PJLIB shutdown */
|
||||
if (pjsip_endpt_atexit(endpt, &pjsip_replaces_deinit_module) != PJ_SUCCESS)
|
||||
{
|
||||
/* Failure to register this function may cause this module won't
|
||||
* work properly when the stack is restarted (without quitting
|
||||
* application).
|
||||
*/
|
||||
pj_assert(!"Failed to register Replaces deinit.");
|
||||
PJ_LOG(1, (THIS_FILE, "Failed to register Replaces deinit."));
|
||||
}
|
||||
|
||||
is_initialized = PJ_TRUE;
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Verify that incoming request with Replaces header can be processed.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_replaces_verify_request( pjsip_rx_data *rdata,
|
||||
pjsip_dialog **p_dlg,
|
||||
pj_bool_t lock_dlg,
|
||||
pjsip_tx_data **p_tdata)
|
||||
{
|
||||
const pj_str_t STR_REPLACES = { "Replaces", 8 };
|
||||
pjsip_replaces_hdr *rep_hdr;
|
||||
int code = 200;
|
||||
const char *warn_text = NULL;
|
||||
pjsip_hdr res_hdr_list;
|
||||
pjsip_dialog *dlg = NULL;
|
||||
pjsip_inv_session *inv;
|
||||
pj_status_t status = PJ_SUCCESS;
|
||||
|
||||
PJ_ASSERT_RETURN(rdata && p_dlg, PJ_EINVAL);
|
||||
|
||||
/* Check that pjsip_replaces_init_module() has been called. */
|
||||
PJ_ASSERT_RETURN(the_endpt != NULL, PJ_EINVALIDOP);
|
||||
|
||||
|
||||
/* Init output arguments */
|
||||
*p_dlg = NULL;
|
||||
if (p_tdata) *p_tdata = NULL;
|
||||
|
||||
pj_list_init(&res_hdr_list);
|
||||
|
||||
/* Find Replaces header */
|
||||
rep_hdr = (pjsip_replaces_hdr*)
|
||||
pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_REPLACES,
|
||||
NULL);
|
||||
if (!rep_hdr) {
|
||||
/* No Replaces header. No further processing is necessary. */
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/* Check that there's no other Replaces header and return 400 Bad Request
|
||||
* if not.
|
||||
*/
|
||||
if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_REPLACES,
|
||||
rep_hdr->next)) {
|
||||
code = PJSIP_SC_BAD_REQUEST;
|
||||
warn_text = "Found multiple Replaces headers";
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
/* Find the dialog identified by Replaces header (and always lock the
|
||||
* dialog no matter what application wants).
|
||||
*/
|
||||
dlg = pjsip_ua_find_dialog(&rep_hdr->call_id, &rep_hdr->to_tag,
|
||||
&rep_hdr->from_tag, PJ_TRUE);
|
||||
|
||||
/* Respond with 481 "Call/Transaction Does Not Exist" response if
|
||||
* no dialog is found.
|
||||
*/
|
||||
if (dlg == NULL) {
|
||||
code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
|
||||
warn_text = "No dialog found for Replaces request";
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
/* Get the invite session within the dialog */
|
||||
inv = pjsip_dlg_get_inv_session(dlg);
|
||||
|
||||
/* Return 481 if no invite session is present. */
|
||||
if (inv == NULL) {
|
||||
code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
|
||||
warn_text = "No INVITE session found for Replaces request";
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
/* Return 603 Declined response if invite session has already
|
||||
* terminated
|
||||
*/
|
||||
if (inv->state >= PJSIP_INV_STATE_DISCONNECTED) {
|
||||
code = PJSIP_SC_DECLINE;
|
||||
warn_text = "INVITE session already terminated";
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
/* If "early-only" flag is present, check that the invite session
|
||||
* has not been confirmed yet. If the session has been confirmed,
|
||||
* return 486 "Busy Here" response.
|
||||
*/
|
||||
if (rep_hdr->early_only && inv->state >= PJSIP_INV_STATE_CONNECTING) {
|
||||
code = PJSIP_SC_BUSY_HERE;
|
||||
warn_text = "INVITE session already established";
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
/* If the Replaces header field matches an early dialog that was not
|
||||
* initiated by this UA, it returns a 481 (Call/Transaction Does Not
|
||||
* Exist) response to the new INVITE.
|
||||
*/
|
||||
if (inv->state <= PJSIP_INV_STATE_EARLY && inv->role != PJSIP_ROLE_UAC)
|
||||
{
|
||||
/* Really return 481 only if call haven't reached early state or
|
||||
* accept-replace-in-early-state (ticket #1587) is not allowed.
|
||||
*/
|
||||
if (inv->state != PJSIP_INV_STATE_EARLY ||
|
||||
pjsip_cfg()->endpt.accept_replace_in_early_state == PJ_FALSE)
|
||||
{
|
||||
code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
|
||||
warn_text = "Found early INVITE session but not initiated by "
|
||||
"this UA";
|
||||
goto on_return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Looks like everything is okay!!
|
||||
*/
|
||||
*p_dlg = dlg;
|
||||
status = PJ_SUCCESS;
|
||||
code = 200;
|
||||
|
||||
on_return:
|
||||
|
||||
/* Create response if necessary */
|
||||
if (code != 200) {
|
||||
/* If we have dialog we must unlock it */
|
||||
if (dlg)
|
||||
pjsip_dlg_dec_lock(dlg);
|
||||
|
||||
/* Create response */
|
||||
if (p_tdata) {
|
||||
pjsip_tx_data *tdata;
|
||||
const pjsip_hdr *h;
|
||||
|
||||
status = pjsip_endpt_create_response(the_endpt, rdata, code,
|
||||
NULL, &tdata);
|
||||
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
/* Add response headers. */
|
||||
h = res_hdr_list.next;
|
||||
while (h != &res_hdr_list) {
|
||||
pjsip_hdr *cloned;
|
||||
|
||||
cloned = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, h);
|
||||
PJ_ASSERT_RETURN(cloned, PJ_ENOMEM);
|
||||
|
||||
pjsip_msg_add_hdr(tdata->msg, cloned);
|
||||
|
||||
h = h->next;
|
||||
}
|
||||
|
||||
/* Add warn text, if any */
|
||||
if (warn_text) {
|
||||
pjsip_warning_hdr *warn_hdr;
|
||||
pj_str_t warn_value = pj_str((char*)warn_text);
|
||||
|
||||
warn_hdr=pjsip_warning_hdr_create(tdata->pool, 399,
|
||||
pjsip_endpt_name(the_endpt),
|
||||
&warn_value);
|
||||
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)warn_hdr);
|
||||
}
|
||||
|
||||
*p_tdata = tdata;
|
||||
}
|
||||
|
||||
/* Can not return PJ_SUCCESS when response message is produced.
|
||||
* Ref: PROTOS test ~#2490
|
||||
*/
|
||||
if (status == PJ_SUCCESS)
|
||||
status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
|
||||
|
||||
} else {
|
||||
/* If application doesn't want to lock the dialog, unlock it */
|
||||
if (!lock_dlg)
|
||||
pjsip_dlg_dec_lock(dlg);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,629 @@
|
||||
/* $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_xfer.h>
|
||||
#include <pjsip-simple/evsub_msg.h>
|
||||
#include <pjsip/sip_dialog.h>
|
||||
#include <pjsip/sip_errno.h>
|
||||
#include <pjsip/sip_endpoint.h>
|
||||
#include <pjsip/sip_module.h>
|
||||
#include <pjsip/sip_transport.h>
|
||||
#include <pj/assert.h>
|
||||
#include <pj/pool.h>
|
||||
#include <pj/string.h>
|
||||
|
||||
/* Subscription expiration */
|
||||
#ifndef PJSIP_XFER_EXPIRES
|
||||
# define PJSIP_XFER_EXPIRES 600
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Refer module (mod-refer)
|
||||
*/
|
||||
static struct pjsip_module mod_xfer =
|
||||
{
|
||||
NULL, NULL, /* prev, next. */
|
||||
{ "mod-refer", 9 }, /* Name. */
|
||||
-1, /* Id */
|
||||
PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */
|
||||
NULL, /* load() */
|
||||
NULL, /* start() */
|
||||
NULL, /* stop() */
|
||||
NULL, /* unload() */
|
||||
NULL, /* on_rx_request() */
|
||||
NULL, /* on_rx_response() */
|
||||
NULL, /* on_tx_request. */
|
||||
NULL, /* on_tx_response() */
|
||||
NULL, /* on_tsx_state() */
|
||||
};
|
||||
|
||||
|
||||
/* Declare PJSIP_REFER_METHOD, so that if somebody declares this in
|
||||
* sip_msg.h we can catch the error here.
|
||||
*/
|
||||
enum
|
||||
{
|
||||
PJSIP_REFER_METHOD = PJSIP_OTHER_METHOD
|
||||
};
|
||||
|
||||
PJ_DEF_DATA(const pjsip_method) pjsip_refer_method = {
|
||||
(pjsip_method_e) PJSIP_REFER_METHOD,
|
||||
{ "REFER", 5}
|
||||
};
|
||||
|
||||
PJ_DEF(const pjsip_method*) pjsip_get_refer_method()
|
||||
{
|
||||
return &pjsip_refer_method;
|
||||
}
|
||||
|
||||
/*
|
||||
* String constants
|
||||
*/
|
||||
static const pj_str_t STR_REFER = { "refer", 5 };
|
||||
static const pj_str_t STR_MESSAGE = { "message", 7 };
|
||||
static const pj_str_t STR_SIPFRAG = { "sipfrag", 7 };
|
||||
|
||||
|
||||
/*
|
||||
* Transfer struct.
|
||||
*/
|
||||
struct pjsip_xfer
|
||||
{
|
||||
pjsip_evsub *sub; /**< Event subscribtion record. */
|
||||
pjsip_dialog *dlg; /**< The dialog. */
|
||||
pjsip_evsub_user user_cb; /**< The user callback. */
|
||||
pj_str_t refer_to_uri; /**< The full Refer-To URI. */
|
||||
int last_st_code; /**< st_code sent in last NOTIFY */
|
||||
pj_str_t last_st_text; /**< st_text sent in last NOTIFY */
|
||||
};
|
||||
|
||||
|
||||
typedef struct pjsip_xfer pjsip_xfer;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Forward decl for evsub callback.
|
||||
*/
|
||||
static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
|
||||
static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
|
||||
pjsip_event *event);
|
||||
static void xfer_on_evsub_rx_refresh( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int *p_st_code,
|
||||
pj_str_t **p_st_text,
|
||||
pjsip_hdr *res_hdr,
|
||||
pjsip_msg_body **p_body);
|
||||
static void xfer_on_evsub_rx_notify( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int *p_st_code,
|
||||
pj_str_t **p_st_text,
|
||||
pjsip_hdr *res_hdr,
|
||||
pjsip_msg_body **p_body);
|
||||
static void xfer_on_evsub_client_refresh(pjsip_evsub *sub);
|
||||
static void xfer_on_evsub_server_timeout(pjsip_evsub *sub);
|
||||
|
||||
|
||||
/*
|
||||
* Event subscription callback for xference.
|
||||
*/
|
||||
static pjsip_evsub_user xfer_user =
|
||||
{
|
||||
&xfer_on_evsub_state,
|
||||
&xfer_on_evsub_tsx_state,
|
||||
&xfer_on_evsub_rx_refresh,
|
||||
&xfer_on_evsub_rx_notify,
|
||||
&xfer_on_evsub_client_refresh,
|
||||
&xfer_on_evsub_server_timeout,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the REFER subsystem.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_init_module(pjsip_endpoint *endpt)
|
||||
{
|
||||
const pj_str_t accept = { "message/sipfrag;version=2.0", 27 };
|
||||
pj_status_t status;
|
||||
|
||||
PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
|
||||
PJ_ASSERT_RETURN(mod_xfer.id == -1, PJ_EINVALIDOP);
|
||||
|
||||
status = pjsip_endpt_register_module(endpt, &mod_xfer);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
status = pjsip_endpt_add_capability( endpt, &mod_xfer, PJSIP_H_ALLOW,
|
||||
NULL, 1,
|
||||
&pjsip_get_refer_method()->name);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
status = pjsip_evsub_register_pkg(&mod_xfer, &STR_REFER,
|
||||
PJSIP_XFER_EXPIRES, 1, &accept);
|
||||
if (status != PJ_SUCCESS)
|
||||
return status;
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create transferer (sender of REFER request).
|
||||
*
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_create_uac( pjsip_dialog *dlg,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
pjsip_evsub **p_evsub )
|
||||
{
|
||||
pj_status_t status;
|
||||
pjsip_xfer *xfer;
|
||||
pjsip_evsub *sub;
|
||||
|
||||
PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
|
||||
|
||||
pjsip_dlg_inc_lock(dlg);
|
||||
|
||||
/* Create event subscription */
|
||||
status = pjsip_evsub_create_uac( dlg, &xfer_user, &STR_REFER,
|
||||
PJSIP_EVSUB_NO_EVENT_ID, &sub);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
/* Create xfer session */
|
||||
xfer = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_xfer);
|
||||
xfer->dlg = dlg;
|
||||
xfer->sub = sub;
|
||||
if (user_cb)
|
||||
pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user));
|
||||
|
||||
/* Attach to evsub */
|
||||
pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer);
|
||||
|
||||
*p_evsub = sub;
|
||||
|
||||
on_return:
|
||||
pjsip_dlg_dec_lock(dlg);
|
||||
return status;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Create transferee (receiver of REFER request).
|
||||
*
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_create_uas( pjsip_dialog *dlg,
|
||||
const pjsip_evsub_user *user_cb,
|
||||
pjsip_rx_data *rdata,
|
||||
pjsip_evsub **p_evsub )
|
||||
{
|
||||
pjsip_evsub *sub;
|
||||
pjsip_xfer *xfer;
|
||||
const pj_str_t STR_EVENT = {"Event", 5 };
|
||||
pjsip_event_hdr *event_hdr;
|
||||
pj_status_t status;
|
||||
|
||||
/* Check arguments */
|
||||
PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
|
||||
|
||||
/* Must be request message */
|
||||
PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
|
||||
PJSIP_ENOTREQUESTMSG);
|
||||
|
||||
/* Check that request is REFER */
|
||||
PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
|
||||
pjsip_get_refer_method())==0,
|
||||
PJSIP_ENOTREFER);
|
||||
|
||||
/* Lock dialog */
|
||||
pjsip_dlg_inc_lock(dlg);
|
||||
|
||||
/* The evsub framework expects an Event header in the request,
|
||||
* while a REFER request conveniently doesn't have one (pun intended!).
|
||||
* So create a dummy Event header.
|
||||
*/
|
||||
if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
|
||||
&STR_EVENT, NULL)==NULL)
|
||||
{
|
||||
event_hdr = pjsip_event_hdr_create(rdata->tp_info.pool);
|
||||
event_hdr->event_type = STR_REFER;
|
||||
pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr*)event_hdr);
|
||||
}
|
||||
|
||||
/* Create server subscription */
|
||||
status = pjsip_evsub_create_uas( dlg, &xfer_user, rdata,
|
||||
PJSIP_EVSUB_NO_EVENT_ID, &sub);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
/* Create server xfer subscription */
|
||||
xfer = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_xfer);
|
||||
xfer->dlg = dlg;
|
||||
xfer->sub = sub;
|
||||
if (user_cb)
|
||||
pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user));
|
||||
|
||||
/* Attach to evsub */
|
||||
pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer);
|
||||
|
||||
/* Done: */
|
||||
*p_evsub = sub;
|
||||
|
||||
on_return:
|
||||
pjsip_dlg_dec_lock(dlg);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Call this function to create request to initiate REFER subscription.
|
||||
*
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_initiate( pjsip_evsub *sub,
|
||||
const pj_str_t *refer_to_uri,
|
||||
pjsip_tx_data **p_tdata)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
const pj_str_t refer_to = { "Refer-To", 8};
|
||||
pjsip_tx_data *tdata;
|
||||
pjsip_generic_string_hdr *hdr;
|
||||
pj_status_t status;
|
||||
|
||||
/* sub and p_tdata argument must be valid. */
|
||||
PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL);
|
||||
|
||||
|
||||
/* Get the xfer object. */
|
||||
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
|
||||
|
||||
/* refer_to_uri argument MAY be NULL for subsequent REFER requests,
|
||||
* but it MUST be specified in the first REFER.
|
||||
*/
|
||||
PJ_ASSERT_RETURN((refer_to_uri || xfer->refer_to_uri.slen), PJ_EINVAL);
|
||||
|
||||
/* Lock dialog. */
|
||||
pjsip_dlg_inc_lock(xfer->dlg);
|
||||
|
||||
/* Create basic REFER request */
|
||||
status = pjsip_evsub_initiate(sub, pjsip_get_refer_method(),
|
||||
PJSIP_EXPIRES_NOT_SPECIFIED, &tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
/* Save Refer-To URI. */
|
||||
if (refer_to_uri == NULL) {
|
||||
refer_to_uri = &xfer->refer_to_uri;
|
||||
} else {
|
||||
pj_strdup(xfer->dlg->pool, &xfer->refer_to_uri, refer_to_uri);
|
||||
}
|
||||
|
||||
/* Create and add Refer-To header. */
|
||||
hdr = pjsip_generic_string_hdr_create(tdata->pool, &refer_to,
|
||||
refer_to_uri);
|
||||
if (!hdr) {
|
||||
pjsip_tx_data_dec_ref(tdata);
|
||||
status = PJ_ENOMEM;
|
||||
goto on_return;
|
||||
}
|
||||
|
||||
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
|
||||
|
||||
|
||||
/* Done. */
|
||||
*p_tdata = tdata;
|
||||
|
||||
status = PJ_SUCCESS;
|
||||
|
||||
on_return:
|
||||
pjsip_dlg_dec_lock(xfer->dlg);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Accept the incoming REFER request by sending 2xx response.
|
||||
*
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_accept( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int st_code,
|
||||
const pjsip_hdr *hdr_list )
|
||||
{
|
||||
/*
|
||||
* Don't need to add custom headers, so just call basic
|
||||
* evsub response.
|
||||
*/
|
||||
return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* For notifier, create NOTIFY request to subscriber, and set the state
|
||||
* of the subscription.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_notify( pjsip_evsub *sub,
|
||||
pjsip_evsub_state state,
|
||||
int xfer_st_code,
|
||||
const pj_str_t *xfer_st_text,
|
||||
pjsip_tx_data **p_tdata)
|
||||
{
|
||||
pjsip_tx_data *tdata;
|
||||
pjsip_xfer *xfer;
|
||||
pjsip_param *param;
|
||||
const pj_str_t reason = { "noresource", 10 };
|
||||
char *body;
|
||||
int bodylen;
|
||||
pjsip_msg_body *msg_body;
|
||||
pj_status_t status;
|
||||
|
||||
|
||||
/* Check arguments. */
|
||||
PJ_ASSERT_RETURN(sub, PJ_EINVAL);
|
||||
|
||||
/* Get the xfer object. */
|
||||
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
|
||||
|
||||
|
||||
/* Lock object. */
|
||||
pjsip_dlg_inc_lock(xfer->dlg);
|
||||
|
||||
/* Create the NOTIFY request.
|
||||
* Note that reason is only used when state is TERMINATED, and
|
||||
* the defined termination reason for REFER is "noresource".
|
||||
*/
|
||||
status = pjsip_evsub_notify( sub, state, NULL, &reason, &tdata);
|
||||
if (status != PJ_SUCCESS)
|
||||
goto on_return;
|
||||
|
||||
|
||||
/* Check status text */
|
||||
if (xfer_st_text==NULL || xfer_st_text->slen==0)
|
||||
xfer_st_text = pjsip_get_status_text(xfer_st_code);
|
||||
|
||||
/* Save st_code and st_text, for current_notify() */
|
||||
xfer->last_st_code = xfer_st_code;
|
||||
pj_strdup(xfer->dlg->pool, &xfer->last_st_text, xfer_st_text);
|
||||
|
||||
/* Create sipfrag content. */
|
||||
body = (char*) pj_pool_alloc(tdata->pool, 128);
|
||||
bodylen = pj_ansi_snprintf(body, 128, "SIP/2.0 %u %.*s\r\n",
|
||||
xfer_st_code,
|
||||
(int)xfer_st_text->slen,
|
||||
xfer_st_text->ptr);
|
||||
PJ_ASSERT_ON_FAIL(bodylen > 0 && bodylen < 128,
|
||||
{status=PJ_EBUG; pjsip_tx_data_dec_ref(tdata);
|
||||
goto on_return; });
|
||||
|
||||
|
||||
/* Create SIP message body. */
|
||||
msg_body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body);
|
||||
pjsip_media_type_init(&msg_body->content_type, (pj_str_t*)&STR_MESSAGE,
|
||||
(pj_str_t*)&STR_SIPFRAG);
|
||||
msg_body->data = body;
|
||||
msg_body->len = bodylen;
|
||||
msg_body->print_body = &pjsip_print_text_body;
|
||||
msg_body->clone_data = &pjsip_clone_text_data;
|
||||
|
||||
param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
|
||||
param->name = pj_str("version");
|
||||
param->value = pj_str("2.0");
|
||||
pj_list_push_back(&msg_body->content_type.param, param);
|
||||
|
||||
/* Attach sipfrag body. */
|
||||
tdata->msg->body = msg_body;
|
||||
|
||||
|
||||
/* Done. */
|
||||
*p_tdata = tdata;
|
||||
|
||||
|
||||
on_return:
|
||||
pjsip_dlg_dec_lock(xfer->dlg);
|
||||
return status;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Send current state and the last sipfrag body.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_current_notify( pjsip_evsub *sub,
|
||||
pjsip_tx_data **p_tdata )
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
pj_status_t status;
|
||||
|
||||
|
||||
/* Check arguments. */
|
||||
PJ_ASSERT_RETURN(sub, PJ_EINVAL);
|
||||
|
||||
/* Get the xfer object. */
|
||||
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
|
||||
|
||||
pjsip_dlg_inc_lock(xfer->dlg);
|
||||
|
||||
status = pjsip_xfer_notify(sub, pjsip_evsub_get_state(sub),
|
||||
xfer->last_st_code, &xfer->last_st_text,
|
||||
p_tdata);
|
||||
|
||||
pjsip_dlg_dec_lock(xfer->dlg);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Send request message.
|
||||
*/
|
||||
PJ_DEF(pj_status_t) pjsip_xfer_send_request( pjsip_evsub *sub,
|
||||
pjsip_tx_data *tdata)
|
||||
{
|
||||
return pjsip_evsub_send_request(sub, tdata);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This callback is called by event subscription when subscription
|
||||
* state has changed.
|
||||
*/
|
||||
static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_evsub_state)
|
||||
(*xfer->user_cb.on_evsub_state)(sub, event);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when transaction state has changed.
|
||||
*/
|
||||
static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
|
||||
pjsip_event *event)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_tsx_state)
|
||||
(*xfer->user_cb.on_tsx_state)(sub, tsx, event);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when REFER is received to refresh subscription.
|
||||
*/
|
||||
static void xfer_on_evsub_rx_refresh( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int *p_st_code,
|
||||
pj_str_t **p_st_text,
|
||||
pjsip_hdr *res_hdr,
|
||||
pjsip_msg_body **p_body)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_rx_refresh) {
|
||||
(*xfer->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
|
||||
res_hdr, p_body);
|
||||
|
||||
} else {
|
||||
/* Implementors MUST send NOTIFY if it implements on_rx_refresh
|
||||
* (implementor == "us" from evsub point of view.
|
||||
*/
|
||||
pjsip_tx_data *tdata;
|
||||
pj_status_t status;
|
||||
|
||||
if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
|
||||
status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
|
||||
xfer->last_st_code,
|
||||
&xfer->last_st_text,
|
||||
&tdata);
|
||||
} else {
|
||||
status = pjsip_xfer_current_notify(sub, &tdata);
|
||||
}
|
||||
|
||||
if (status == PJ_SUCCESS)
|
||||
pjsip_xfer_send_request(sub, tdata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Called when NOTIFY is received.
|
||||
*/
|
||||
static void xfer_on_evsub_rx_notify( pjsip_evsub *sub,
|
||||
pjsip_rx_data *rdata,
|
||||
int *p_st_code,
|
||||
pj_str_t **p_st_text,
|
||||
pjsip_hdr *res_hdr,
|
||||
pjsip_msg_body **p_body)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_rx_notify)
|
||||
(*xfer->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text,
|
||||
res_hdr, p_body);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called when it's time to send SUBSCRIBE.
|
||||
*/
|
||||
static void xfer_on_evsub_client_refresh(pjsip_evsub *sub)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_client_refresh) {
|
||||
(*xfer->user_cb.on_client_refresh)(sub);
|
||||
} else {
|
||||
pj_status_t status;
|
||||
pjsip_tx_data *tdata;
|
||||
|
||||
status = pjsip_evsub_initiate(sub, NULL, PJSIP_XFER_EXPIRES, &tdata);
|
||||
if (status == PJ_SUCCESS)
|
||||
pjsip_xfer_send_request(sub, tdata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Called when no refresh is received after the interval.
|
||||
*/
|
||||
static void xfer_on_evsub_server_timeout(pjsip_evsub *sub)
|
||||
{
|
||||
pjsip_xfer *xfer;
|
||||
|
||||
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
|
||||
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
|
||||
|
||||
if (xfer->user_cb.on_server_timeout) {
|
||||
(*xfer->user_cb.on_server_timeout)(sub);
|
||||
} else {
|
||||
pj_status_t status;
|
||||
pjsip_tx_data *tdata;
|
||||
|
||||
status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
|
||||
xfer->last_st_code,
|
||||
&xfer->last_st_text, &tdata);
|
||||
if (status == PJ_SUCCESS)
|
||||
pjsip_xfer_send_request(sub, tdata);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user