summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip-ua/sip_100rel.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip/src/pjsip-ua/sip_100rel.c')
-rw-r--r--pjsip/src/pjsip-ua/sip_100rel.c905
1 files changed, 905 insertions, 0 deletions
diff --git a/pjsip/src/pjsip-ua/sip_100rel.c b/pjsip/src/pjsip-ua/sip_100rel.c
new file mode 100644
index 0000000..07122c4
--- /dev/null
+++ b/pjsip/src/pjsip-ua/sip_100rel.c
@@ -0,0 +1,905 @@
+/* $Id: sip_100rel.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * 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);
+
+
+const pj_str_t tag_100rel = { "100rel", 6 };
+const pj_str_t RSEQ = { "RSeq", 4 };
+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_strcmp(&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_strcmp(&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);
+ 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);
+
+}
+
+
+/*
+ * 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) {
+ pj_assert(!dd->uas_state->retransmit_timer.id);
+ pj_assert(pj_list_empty(&dd->uas_state->tx_data_list));
+ }
+
+ 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;
+ }
+}
+
+/* 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);
+}
+
+
+/*
+ * 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) {
+ pjsip_tsx_send_msg(dd->inv->invite_tsx, tdata);
+ } 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) *
+ 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;
+}
+
+
+/* Clone response. */
+static pjsip_tx_data *clone_tdata(dlg_data *dd,
+ const pjsip_tx_data *src)
+{
+ pjsip_tx_data *dst;
+ const pjsip_hdr *hsrc;
+ pjsip_msg *msg;
+ pj_status_t status;
+
+ status = pjsip_endpt_create_tdata(dd->inv->dlg->endpt, &dst);
+ if (status != PJ_SUCCESS)
+ return NULL;
+
+ msg = pjsip_msg_create(dst->pool, PJSIP_RESPONSE_MSG);
+ dst->msg = msg;
+ pjsip_tx_data_add_ref(dst);
+
+ /* Duplicate status line */
+ msg->line.status.code = src->msg->line.status.code;
+ pj_strdup(dst->pool, &msg->line.status.reason,
+ &src->msg->line.status.reason);
+
+ /* Duplicate all headers */
+ hsrc = src->msg->hdr.next;
+ while (hsrc != &src->msg->hdr) {
+ pjsip_hdr *h = (pjsip_hdr*) pjsip_hdr_clone(dst->pool, hsrc);
+ pjsip_msg_add_hdr(msg, h);
+ hsrc = hsrc->next;
+ }
+
+ /* Duplicate message body */
+ if (src->msg->body)
+ msg->body = pjsip_msg_body_clone(dst->pool, src->msg->body);
+
+ PJ_LOG(5,(dd->inv->dlg->obj_name,
+ "Reliable response %s created",
+ pjsip_tx_data_get_info(dst)));
+
+ return dst;
+}
+
+
+/* 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;
+ tdata = clone_tdata(dd, old_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;
+ 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;
+}
+
+