878 lines
27 KiB
C
878 lines
27 KiB
C
/*
|
|
* 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://github.com/pjsip/pjproject/issues/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) {
|
|
tx_data_list_t *tl_next = tl->next;
|
|
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;
|
|
}
|
|
|
|
/* 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 && tdata) {
|
|
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 (%lu pending)",
|
|
code, (unsigned long)
|
|
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;
|
|
}
|
|
|
|
|