/* $Id: sip_100rel.c 2394 2008-12-23 17:27:53Z bennylp $ */
/* 
 * Copyright (C) 2008-2009 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>

#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_int32_t	cseq;
	pj_uint32_t	rseq;	/* Initialized to -1 */
} 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;
};


/*****************************************************************************
 **
 ** 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;
    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 provisional response with no RSeq header"));
	return PJSIP_EMISSINGHDR;
    }
    rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue);

    /* Create new UAC state if we don't have one */
    if (dd->uac_state == NULL) {
	dd->uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool,
					 uac_state_t);
	dd->uac_state->cseq = rdata->msg_info.cseq->cseq;
	dd->uac_state->rseq = rseq - 1;
    }

    /* If this is from new INVITE transaction, reset UAC state */
    if (rdata->msg_info.cseq->cseq != dd->uac_state->cseq) {
	dd->uac_state->cseq = rdata->msg_info.cseq->cseq;
	dd->uac_state->rseq = rseq - 1;
    }

    /* Ignore provisional response retransmission */
    if (rseq <= dd->uac_state->rseq) {
	/* This should have been handled before */
	return PJ_EIGNORED;

    /* Ignore provisional response with out-of-order RSeq */
    } else if (rseq != dd->uac_state->rseq + 1) {
	PJ_LOG(4,(dd->inv->dlg->obj_name, 
		 "Ignoring provisional response because RSeq jump "
		 "(expecting %u, got %u)",
		 dd->uac_state->rseq+1, rseq));
	return PJ_EIGNORED;
    }

    /* Update our RSeq */
    dd->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;

    /* 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;
}


