initial commit

This commit is contained in:
Dan Bastone committed 2021-10-29 14:41:03 -04:00
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);
}
}