/* 
 * 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/sip_util.h>
#include <pjsip/sip_transport.h>
#include <pjsip/sip_msg.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_event.h>
#include <pjsip/sip_transaction.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_errno.h>
#include <pj/array.h>
#include <pj/log.h>
#include <pj/string.h>
#include <pj/guid.h>
#include <pj/pool.h>
#include <pj/except.h>
#include <pj/rand.h>
#include <pj/assert.h>
#include <pj/errno.h>

#define THIS_FILE    "sip_util.c"

static const char *event_str[] = 
{
    "UNIDENTIFIED",
    "TIMER",
    "TX_MSG",
    "RX_MSG",
    "TRANSPORT_ERROR",
    "TSX_STATE",
    "USER",
};

static pj_str_t str_TEXT = { "text", 4},
                str_PLAIN = { "plain", 5 };

/* Add URI to target-set */
PJ_DEF(pj_status_t) pjsip_target_set_add_uri( pjsip_target_set *tset,
                                              pj_pool_t *pool,
                                              const pjsip_uri *uri,
                                              int q1000)
{
    pjsip_target *t, *pos = NULL;

    PJ_ASSERT_RETURN(tset && pool && uri, PJ_EINVAL);

    /* Set q-value to 1 if it is not set */
    if (q1000 <= 0)
        q1000 = 1000;

    /* Scan all the elements to see for duplicates, and at the same time
     * get the position where the new element should be inserted to
     * based on the q-value.
     */
    t = tset->head.next;
    while (t != &tset->head) {
        if (pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI, t->uri, uri)==PJ_SUCCESS)
            return PJ_EEXISTS;
        if (pos==NULL && t->q1000 < q1000)
            pos = t;
        t = t->next;
    }

    /* Create new element */
    t = PJ_POOL_ZALLOC_T(pool, pjsip_target);
    t->uri = (pjsip_uri*)pjsip_uri_clone(pool, uri);
    t->q1000 = q1000;

    /* Insert */
    if (pos == NULL)
        pj_list_push_back(&tset->head, t);
    else
        pj_list_insert_before(pos, t);

    /* Set current target if this is the first URI */
    if (tset->current == NULL)
        tset->current = t;

    return PJ_SUCCESS;
}

/* Add URI's in the Contact header in the message to target-set */
PJ_DEF(pj_status_t) pjsip_target_set_add_from_msg( pjsip_target_set *tset,
                                                   pj_pool_t *pool,
                                                   const pjsip_msg *msg)
{
    const pjsip_hdr *hdr;
    unsigned added = 0;

    PJ_ASSERT_RETURN(tset && pool && msg, PJ_EINVAL);

    /* Scan for Contact headers and add the URI */
    hdr = msg->hdr.next;
    while (hdr != &msg->hdr) {
        if (hdr->type == PJSIP_H_CONTACT) {
            const pjsip_contact_hdr *cn_hdr = (const pjsip_contact_hdr*)hdr;

            if (!cn_hdr->star) {
                pj_status_t rc;
                rc = pjsip_target_set_add_uri(tset, pool, cn_hdr->uri, 
                                              cn_hdr->q1000);
                if (rc == PJ_SUCCESS)
                    ++added;
            }
        }
        hdr = hdr->next;
    }

    return added ? PJ_SUCCESS : PJ_EEXISTS;
}


/* Get next target, if any */
PJ_DEF(pjsip_target*) pjsip_target_set_get_next(const pjsip_target_set *tset)
{
    const pjsip_target *t, *next = NULL;

    t = tset->head.next;
    while (t != &tset->head) {
        if (PJSIP_IS_STATUS_IN_CLASS(t->code, 200)) {
            /* No more target since one target has been successful */
            return NULL;
        }
        if (PJSIP_IS_STATUS_IN_CLASS(t->code, 600)) {
            /* No more target since one target returned global error */
            return NULL;
        }
        if (t->code==0 && next==NULL) {
            /* This would be the next target as long as we don't find
             * targets with 2xx or 6xx status after this.
             */
            next = t;
        }
        t = t->next;
    }

    return (pjsip_target*)next;
}


/* Set current target */
PJ_DEF(pj_status_t) pjsip_target_set_set_current( pjsip_target_set *tset,
                                                  pjsip_target *target)
{
    PJ_ASSERT_RETURN(tset && target, PJ_EINVAL);
    PJ_ASSERT_RETURN(pj_list_find_node(tset, target) != NULL, PJ_ENOTFOUND);

    tset->current = target;

    return PJ_SUCCESS;
}


/* Assign status to a target */
PJ_DEF(pj_status_t) pjsip_target_assign_status( pjsip_target *target,
                                                pj_pool_t *pool,
                                                int status_code,
                                                const pj_str_t *reason)
{
    PJ_ASSERT_RETURN(target && pool && status_code && reason, PJ_EINVAL);

    target->code = (pjsip_status_code)status_code;
    pj_strdup(pool, &target->reason, reason);

    return PJ_SUCCESS;
}



/*
 * Initialize transmit data (msg) with the headers and optional body.
 * This will just put the headers in the message as it is. Be carefull
 * when calling this function because once a header is put in a message, 
 * it CAN NOT be put in other message until the first message is deleted, 
 * because the way the header is put in the list.
 * That's why the session will shallow_clone it's headers before calling
 * this function.
 */
static void init_request_throw( pjsip_endpoint *endpt,
                                pjsip_tx_data *tdata, 
                                pjsip_method *method,
                                pjsip_uri *param_target,
                                pjsip_from_hdr *param_from,
                                pjsip_to_hdr *param_to, 
                                pjsip_contact_hdr *param_contact,
                                pjsip_cid_hdr *param_call_id,
                                pjsip_cseq_hdr *param_cseq, 
                                const pj_str_t *param_text)
{
    pjsip_msg *msg;
    pjsip_msg_body *body;
    pjsip_via_hdr *via;
    const pjsip_hdr *endpt_hdr;

    /* Create the message. */
    msg = tdata->msg = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG);

    /* Init request URI. */
    pj_memcpy(&msg->line.req.method, method, sizeof(*method));
    msg->line.req.uri = param_target;

    /* Add additional request headers from endpoint. */
    endpt_hdr = pjsip_endpt_get_request_headers(endpt)->next;
    while (endpt_hdr != pjsip_endpt_get_request_headers(endpt)) {
        pjsip_hdr *hdr = (pjsip_hdr*) 
                         pjsip_hdr_shallow_clone(tdata->pool, endpt_hdr);
        pjsip_msg_add_hdr( tdata->msg, hdr );
        endpt_hdr = endpt_hdr->next;
    }

    /* Add From header. */
    if (param_from->tag.slen == 0)
        pj_create_unique_string(tdata->pool, &param_from->tag);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_from);

    /* Add To header. */
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_to);

    /* Add Contact header. */
    if (param_contact) {
        pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_contact);
    }

    /* Add Call-ID header. */
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_call_id);

    /* Add CSeq header. */
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_cseq);

    /* Add a blank Via header in the front of the message. */
    via = pjsip_via_hdr_create(tdata->pool);
    via->rport_param = pjsip_cfg()->endpt.disable_rport ? -1 : 0;
    pjsip_msg_insert_first_hdr(msg, (pjsip_hdr*)via);

    /* Add header params as request headers */
    if (PJSIP_URI_SCHEME_IS_SIP(param_target) || 
        PJSIP_URI_SCHEME_IS_SIPS(param_target)) 
    {
        pjsip_sip_uri *uri = (pjsip_sip_uri*) pjsip_uri_get_uri(param_target);
        pjsip_param *hparam;

        hparam = uri->header_param.next;
        while (hparam != &uri->header_param) {
            pjsip_generic_string_hdr *hdr;

            hdr = pjsip_generic_string_hdr_create(tdata->pool, 
                                                  &hparam->name,
                                                  &hparam->value);
            pjsip_msg_add_hdr(msg, (pjsip_hdr*)hdr);
            hparam = hparam->next;
        }
    }

    /* Create message body. */
    if (param_text) {
        body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body);
        body->content_type.type = str_TEXT;
        body->content_type.subtype = str_PLAIN;
        body->data = pj_pool_alloc(tdata->pool, param_text->slen );
        pj_memcpy(body->data, param_text->ptr, param_text->slen);
        body->len = (unsigned)param_text->slen;
        body->print_body = &pjsip_print_text_body;
        msg->body = body;
    }

    PJ_LOG(5,(THIS_FILE, "%s created.", 
                         pjsip_tx_data_get_info(tdata)));

}

/*
 * Create arbitrary request.
 */
PJ_DEF(pj_status_t) pjsip_endpt_create_request(  pjsip_endpoint *endpt, 
                                                 const pjsip_method *method,
                                                 const pj_str_t *param_target,
                                                 const pj_str_t *param_from,
                                                 const pj_str_t *param_to, 
                                                 const pj_str_t *param_contact,
                                                 const pj_str_t *param_call_id,
                                                 int param_cseq, 
                                                 const pj_str_t *param_text,
                                                 pjsip_tx_data **p_tdata)
{
    pjsip_uri *target;
    pjsip_tx_data *tdata;
    pjsip_from_hdr *from;
    pjsip_to_hdr *to;
    pjsip_contact_hdr *contact;
    pjsip_cseq_hdr *cseq = NULL;    /* = NULL, warning in VC6 */
    pjsip_cid_hdr *call_id;
    pj_str_t tmp;
    pj_status_t status;
    const pj_str_t STR_CONTACT = { "Contact", 7 };
    PJ_USE_EXCEPTION;

    status = pjsip_endpt_create_tdata(endpt, &tdata);
    if (status != PJ_SUCCESS)
        return status;

    /* Init reference counter to 1. */
    pjsip_tx_data_add_ref(tdata);

    PJ_TRY {
        /* Request target. */
        pj_strdup_with_null(tdata->pool, &tmp, param_target);
        target = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, 0);
        if (target == NULL) {
            status = PJSIP_EINVALIDREQURI;
            goto on_error;
        }

        /* From */
        from = pjsip_from_hdr_create(tdata->pool);
        pj_strdup_with_null(tdata->pool, &tmp, param_from);
        from->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, 
                                     PJSIP_PARSE_URI_AS_NAMEADDR);
        if (from->uri == NULL) {
            status = PJSIP_EINVALIDHDR;
            goto on_error;
        }
        pj_create_unique_string(tdata->pool, &from->tag);

        /* To */
        to = pjsip_to_hdr_create(tdata->pool);
        pj_strdup_with_null(tdata->pool, &tmp, param_to);
        to->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, 
                                   PJSIP_PARSE_URI_AS_NAMEADDR);
        if (to->uri == NULL) {
            status = PJSIP_EINVALIDHDR;
            goto on_error;
        }

        /* Contact. */
        if (param_contact) {
            pj_strdup_with_null(tdata->pool, &tmp, param_contact);
            contact = (pjsip_contact_hdr*)
                      pjsip_parse_hdr(tdata->pool, &STR_CONTACT, tmp.ptr, 
                                      tmp.slen, NULL);
            if (contact == NULL) {
                status = PJSIP_EINVALIDHDR;
                goto on_error;
            }
        } else {
            contact = NULL;
        }

        /* Call-ID */
        call_id = pjsip_cid_hdr_create(tdata->pool);
        if (param_call_id != NULL && param_call_id->slen)
            pj_strdup(tdata->pool, &call_id->id, param_call_id);
        else
            pj_create_unique_string(tdata->pool, &call_id->id);

        /* CSeq */
        cseq = pjsip_cseq_hdr_create(tdata->pool);
        if (param_cseq >= 0)
            cseq->cseq = param_cseq;
        else
            cseq->cseq = pj_rand() & 0xFFFF;

        /* Method */
        pjsip_method_copy(tdata->pool, &cseq->method, method);

        /* Create the request. */
        init_request_throw( endpt, tdata, &cseq->method, target, from, to, 
                            contact, call_id, cseq, param_text);
    }
    PJ_CATCH_ANY {
        status = PJ_ENOMEM;
        goto on_error;
    }
    PJ_END

    *p_tdata = tdata;
    return PJ_SUCCESS;

on_error:
    pjsip_tx_data_dec_ref(tdata);
    return status;
}

PJ_DEF(pj_status_t) pjsip_endpt_create_request_from_hdr( pjsip_endpoint *endpt,
                                     const pjsip_method *method,
                                     const pjsip_uri *param_target,
                                     const pjsip_from_hdr *param_from,
                                     const pjsip_to_hdr *param_to,
                                     const pjsip_contact_hdr *param_contact,
                                     const pjsip_cid_hdr *param_call_id,
                                     int param_cseq,
                                     const pj_str_t *param_text,
                                     pjsip_tx_data **p_tdata)
{
    pjsip_uri *target;
    pjsip_tx_data *tdata;
    pjsip_from_hdr *from;
    pjsip_to_hdr *to;
    pjsip_contact_hdr *contact;
    pjsip_cid_hdr *call_id;
    pjsip_cseq_hdr *cseq = NULL; /* The NULL because warning in VC6 */
    pj_status_t status;
    PJ_USE_EXCEPTION;

    /* Check arguments. */
    PJ_ASSERT_RETURN(endpt && method && param_target && param_from &&
                     param_to && p_tdata, PJ_EINVAL);

    /* Create new transmit data. */
    status = pjsip_endpt_create_tdata(endpt, &tdata);
    if (status != PJ_SUCCESS)
        return status;

    /* Set initial reference counter to 1. */
    pjsip_tx_data_add_ref(tdata);

    PJ_TRY {
        /* Duplicate target URI and headers. */
        target = (pjsip_uri*) pjsip_uri_clone(tdata->pool, param_target);
        from = (pjsip_from_hdr*) pjsip_hdr_clone(tdata->pool, param_from);
        pjsip_fromto_hdr_set_from(from);
        to = (pjsip_to_hdr*) pjsip_hdr_clone(tdata->pool, param_to);
        pjsip_fromto_hdr_set_to(to);
        if (param_contact) {
            contact = (pjsip_contact_hdr*) 
                      pjsip_hdr_clone(tdata->pool, param_contact);
        } else {
            contact = NULL;
        }
        call_id = pjsip_cid_hdr_create(tdata->pool);
        if (param_call_id != NULL && param_call_id->id.slen)
            pj_strdup(tdata->pool, &call_id->id, &param_call_id->id);
        else
            pj_create_unique_string(tdata->pool, &call_id->id);

        cseq = pjsip_cseq_hdr_create(tdata->pool);
        if (param_cseq >= 0)
            cseq->cseq = param_cseq;
        else
            cseq->cseq = pj_rand() % 0xFFFF;
        pjsip_method_copy(tdata->pool, &cseq->method, method);

        /* Copy headers to the request. */
        init_request_throw(endpt, tdata, &cseq->method, target, from, to, 
                           contact, call_id, cseq, param_text);
    }
    PJ_CATCH_ANY {
        status = PJ_ENOMEM;
        goto on_error;
    }
    PJ_END;

    *p_tdata = tdata;
    return PJ_SUCCESS;

on_error:
    pjsip_tx_data_dec_ref(tdata);
    return status;
}

/*
 * Construct a minimal response message for the received request.
 */
PJ_DEF(pj_status_t) pjsip_endpt_create_response( pjsip_endpoint *endpt,
                                                 const pjsip_rx_data *rdata,
                                                 int st_code,
                                                 const pj_str_t *st_text,
                                                 pjsip_tx_data **p_tdata)
{
    pjsip_tx_data *tdata;
    pjsip_msg *msg, *req_msg;
    pjsip_hdr *hdr;
    pjsip_to_hdr *to_hdr;
    pjsip_via_hdr *top_via = NULL, *via;
    pjsip_rr_hdr *rr;
    pj_status_t status;

    /* Check arguments. */
    PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL);

    /* Check status code. */
    PJ_ASSERT_RETURN(st_code >= 100 && st_code <= 699, PJ_EINVAL);

    /* rdata must be a request message. */
    req_msg = rdata->msg_info.msg;
    pj_assert(req_msg->type == PJSIP_REQUEST_MSG);

    /* Request MUST NOT be ACK request! */
    PJ_ASSERT_RETURN(req_msg->line.req.method.id != PJSIP_ACK_METHOD,
                     PJ_EINVALIDOP);

    /* Create a new transmit buffer. */
    status = pjsip_endpt_create_tdata( endpt, &tdata);
    if (status != PJ_SUCCESS)
        return status;

    /* Set initial reference count to 1. */
    pjsip_tx_data_add_ref(tdata);

    /* Create new response message. */
    tdata->msg = msg = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG);

    /* Set status code and reason text. */
    msg->line.status.code = st_code;
    if (st_text)
        pj_strdup(tdata->pool, &msg->line.status.reason, st_text);
    else
        msg->line.status.reason = *pjsip_get_status_text(st_code);

    /* Set TX data attributes. */
    tdata->rx_timestamp = rdata->pkt_info.timestamp;

    /* Copy all the via headers, in order. */
    via = rdata->msg_info.via;
    while (via) {
        pjsip_via_hdr *new_via;

        new_via = (pjsip_via_hdr*)pjsip_hdr_clone(tdata->pool, via);
        if (top_via == NULL)
            top_via = new_via;

        pjsip_msg_add_hdr( msg, (pjsip_hdr*)new_via);
        via = via->next;
        if (via != (void*)&req_msg->hdr)
            via = (pjsip_via_hdr*) 
                  pjsip_msg_find_hdr(req_msg, PJSIP_H_VIA, via);
        else
            break;
    }

    /* Copy all Record-Route headers, in order. */
    rr = (pjsip_rr_hdr*) 
         pjsip_msg_find_hdr(req_msg, PJSIP_H_RECORD_ROUTE, NULL);
    while (rr) {
        pjsip_msg_add_hdr(msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rr));
        rr = rr->next;
        if (rr != (void*)&req_msg->hdr)
            rr = (pjsip_rr_hdr*) pjsip_msg_find_hdr(req_msg, 
                                                    PJSIP_H_RECORD_ROUTE, rr);
        else
            break;
    }

    /* Copy Call-ID header. */
    hdr = (pjsip_hdr*) pjsip_msg_find_hdr( req_msg, PJSIP_H_CALL_ID, NULL);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr));

    /* Copy From header. */
    hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.from);
    pjsip_msg_add_hdr( msg, hdr);

    /* Copy To header. */
    to_hdr = (pjsip_to_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.to);
    pjsip_msg_add_hdr( msg, (pjsip_hdr*)to_hdr);

    /* Must add To tag in the response (Section 8.2.6.2), except if this is
     * 100 (Trying) response. Same tag must be created for the same request
     * (e.g. same tag in provisional and final response). The easiest way
     * to do this is to derive the tag from Via branch parameter (or to
     * use it directly).
     */
    if (to_hdr->tag.slen==0 && st_code > 100 && top_via) {
        to_hdr->tag = top_via->branch_param;
    }

    /* Copy CSeq header. */
    hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.cseq);
    pjsip_msg_add_hdr( msg, hdr);

    /* All done. */
    *p_tdata = tdata;

    PJ_LOG(5,(THIS_FILE, "%s created", pjsip_tx_data_get_info(tdata)));
    return PJ_SUCCESS;
}


/*
 * Construct ACK for 3xx-6xx final response (according to chapter 17.1.1 of
 * RFC3261). Note that the generation of ACK for 2xx response is different,
 * and one must not use this function to generate such ACK.
 */
PJ_DEF(pj_status_t) pjsip_endpt_create_ack( pjsip_endpoint *endpt,
                                            const pjsip_tx_data *tdata,
                                            const pjsip_rx_data *rdata,
                                            pjsip_tx_data **ack_tdata)
{
    pjsip_tx_data *ack = NULL;
    const pjsip_msg *invite_msg;
    const pjsip_from_hdr *from_hdr;
    const pjsip_to_hdr *to_hdr;
    const pjsip_cid_hdr *cid_hdr;
    const pjsip_cseq_hdr *cseq_hdr;
    const pjsip_hdr *hdr;
    pjsip_hdr *via;
    pjsip_to_hdr *to;
    pj_status_t status;

    /* rdata must be a non-2xx final response. */
    pj_assert(rdata->msg_info.msg->type==PJSIP_RESPONSE_MSG &&
              rdata->msg_info.msg->line.status.code >= 300);

    /* Initialize return value to NULL. */
    *ack_tdata = NULL;

    /* The original INVITE message. */
    invite_msg = tdata->msg;

    /* Get the headers from original INVITE request. */
#   define FIND_HDR(m,HNAME) pjsip_msg_find_hdr(m, PJSIP_H_##HNAME, NULL)

    from_hdr = (const pjsip_from_hdr*) FIND_HDR(invite_msg, FROM);
    PJ_ASSERT_ON_FAIL(from_hdr != NULL, goto on_missing_hdr);

    to_hdr = (const pjsip_to_hdr*) FIND_HDR(invite_msg, TO);
    PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);

    cid_hdr = (const pjsip_cid_hdr*) FIND_HDR(invite_msg, CALL_ID);
    PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);

    cseq_hdr = (const pjsip_cseq_hdr*) FIND_HDR(invite_msg, CSEQ);
    PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);

#   undef FIND_HDR

    /* Create new request message from the headers. */
    status = pjsip_endpt_create_request_from_hdr(endpt, 
                                                 pjsip_get_ack_method(),
                                                 tdata->msg->line.req.uri,
                                                 from_hdr, to_hdr,
                                                 NULL, cid_hdr,
                                                 cseq_hdr->cseq, NULL,
                                                 &ack);

    if (status != PJ_SUCCESS)
        return status;

    /* Update tag in To header with the one from the response (if any). */
    to = (pjsip_to_hdr*) pjsip_msg_find_hdr(ack->msg, PJSIP_H_TO, NULL);
    pj_strdup(ack->pool, &to->tag, &rdata->msg_info.to->tag);


    /* Clear Via headers in the new request. */
    while ((via=(pjsip_hdr*)pjsip_msg_find_hdr(ack->msg, PJSIP_H_VIA, NULL)) != NULL)
        pj_list_erase(via);

    /* Must contain single Via, just as the original INVITE. */
    hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_VIA, NULL);
    pjsip_msg_insert_first_hdr( ack->msg, 
                                (pjsip_hdr*) pjsip_hdr_clone(ack->pool,hdr) );

    /* If the original INVITE has Route headers, those header fields MUST 
     * appear in the ACK.
     */
    hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_ROUTE, NULL);
    while (hdr != NULL) {
        pjsip_msg_add_hdr( ack->msg, 
                           (pjsip_hdr*) pjsip_hdr_clone(ack->pool, hdr) );
        hdr = hdr->next;
        if (hdr == &invite_msg->hdr)
            break;
        hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_ROUTE, hdr);
    }

    /* We're done.
     * "tdata" parameter now contains the ACK message.
     */
    *ack_tdata = ack;
    return PJ_SUCCESS;

on_missing_hdr:
    if (ack)
        pjsip_tx_data_dec_ref(ack);
    return PJSIP_EMISSINGHDR;
}


/*
 * Construct CANCEL request for the previously sent request, according to
 * chapter 9.1 of RFC3261.
 */
PJ_DEF(pj_status_t) pjsip_endpt_create_cancel( pjsip_endpoint *endpt,
                                               const pjsip_tx_data *req_tdata,
                                               pjsip_tx_data **p_tdata)
{
    pjsip_tx_data *cancel_tdata = NULL;
    const pjsip_from_hdr *from_hdr;
    const pjsip_to_hdr *to_hdr;
    const pjsip_cid_hdr *cid_hdr;
    const pjsip_cseq_hdr *cseq_hdr;
    const pjsip_hdr *hdr;
    pjsip_hdr *via;
    pj_status_t status;

    /* The transmit buffer must INVITE request. */
    PJ_ASSERT_RETURN(req_tdata->msg->type == PJSIP_REQUEST_MSG &&
                     req_tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD,
                     PJ_EINVAL);

    /* Get the headers from original INVITE request. */
#   define FIND_HDR(m,HNAME) pjsip_msg_find_hdr(m, PJSIP_H_##HNAME, NULL)

    from_hdr = (const pjsip_from_hdr*) FIND_HDR(req_tdata->msg, FROM);
    PJ_ASSERT_ON_FAIL(from_hdr != NULL, goto on_missing_hdr);

    to_hdr = (const pjsip_to_hdr*) FIND_HDR(req_tdata->msg, TO);
    PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);

    cid_hdr = (const pjsip_cid_hdr*) FIND_HDR(req_tdata->msg, CALL_ID);
    PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);

    cseq_hdr = (const pjsip_cseq_hdr*) FIND_HDR(req_tdata->msg, CSEQ);
    PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);

#   undef FIND_HDR

    /* Create new request message from the headers. */
    status = pjsip_endpt_create_request_from_hdr(endpt, 
                                                 pjsip_get_cancel_method(),
                                                 req_tdata->msg->line.req.uri,
                                                 from_hdr, to_hdr,
                                                 NULL, cid_hdr,
                                                 cseq_hdr->cseq, NULL,
                                                 &cancel_tdata);

    if (status != PJ_SUCCESS)
        return status;

    /* Clear Via headers in the new request. */
    while ((via=(pjsip_hdr*)pjsip_msg_find_hdr(cancel_tdata->msg, PJSIP_H_VIA, NULL)) != NULL)
        pj_list_erase(via);


    /* Must only have single Via which matches the top-most Via in the 
     * request being cancelled. 
     */
    hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg, PJSIP_H_VIA, NULL);
    if (hdr) {
        pjsip_msg_insert_first_hdr(cancel_tdata->msg, 
                                   (pjsip_hdr*)pjsip_hdr_clone(cancel_tdata->pool, hdr));
    }

    /* If the original request has Route header, the CANCEL request must also
     * has exactly the same.
     * Copy "Route" header from the request.
     */
    hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg, PJSIP_H_ROUTE, NULL);
    while (hdr != NULL) {
        pjsip_msg_add_hdr(cancel_tdata->msg, 
                          (pjsip_hdr*) pjsip_hdr_clone(cancel_tdata->pool, hdr));
        hdr = hdr->next;
        if (hdr != &req_tdata->msg->hdr)
            hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg, 
                                                  PJSIP_H_ROUTE, hdr);
        else
            break;
    }

    /* Must also copy the saved strict route header, otherwise CANCEL will be
     * sent with swapped Route and request URI!
     */
    if (req_tdata->saved_strict_route) {
        cancel_tdata->saved_strict_route = (pjsip_route_hdr*)
            pjsip_hdr_clone(cancel_tdata->pool, req_tdata->saved_strict_route);
    }

    /* Copy the destination info from the original request */
    pj_memcpy(&cancel_tdata->dest_info, &req_tdata->dest_info,
              sizeof(req_tdata->dest_info));

    /* Finally, copy the destination host name from the original request */
    pj_strdup(cancel_tdata->pool, &cancel_tdata->dest_info.name,
              &req_tdata->dest_info.name);

    /* Done.
     * Return the transmit buffer containing the CANCEL request.
     */
    *p_tdata = cancel_tdata;
    return PJ_SUCCESS;

on_missing_hdr:
    if (cancel_tdata)
        pjsip_tx_data_dec_ref(cancel_tdata);
    return PJSIP_EMISSINGHDR;
}


/* Fill-up destination information from a target URI */
PJ_DEF(pj_status_t) pjsip_get_dest_info(const pjsip_uri *target_uri,
                                        const pjsip_uri *request_uri,
                                        pj_pool_t *pool,
                                        pjsip_host_info *dest_info)
{
    /* The target URI must be a SIP/SIPS URL so we can resolve it's address.
     * Otherwise we're in trouble (i.e. there's no host part in tel: URL).
     */
    pj_bzero(dest_info, sizeof(*dest_info));

    /* When request URI uses sips scheme, secure transport
     * (the default is TLS) must always be used regardless
     * of the target scheme or transport type (see ticket #1740).
     */
    if (PJSIP_URI_SCHEME_IS_SIPS(target_uri) || 
        (pjsip_cfg()->endpt.disable_tls_switch == 0 && request_uri &&
         PJSIP_URI_SCHEME_IS_SIPS(request_uri)))
    {
        pjsip_uri *uri = (pjsip_uri*) target_uri;
        const pjsip_sip_uri *url=(const pjsip_sip_uri*)pjsip_uri_get_uri(uri);
        unsigned flag, tp_flag;

        if (!PJSIP_URI_SCHEME_IS_SIPS(target_uri)) {
            PJ_LOG(4,(THIS_FILE, "Automatic switch to secure transport as "
                                 "request-URI uses ""sips"" scheme."));
        }

        if (url->maddr_param.slen)
            pj_strdup(pool, &dest_info->addr.host, &url->maddr_param);
        else
            pj_strdup(pool, &dest_info->addr.host, &url->host);
        dest_info->addr.port = url->port;
        dest_info->type = 
            pjsip_transport_get_type_from_name(&url->transport_param);

        /* Double-check that the transport parameter match.
         * Sample case:     sips:host;transport=tcp
         * See https://github.com/pjsip/pjproject/issues/1319
         */
        flag = pjsip_transport_get_flag_from_type(dest_info->type);
        tp_flag = PJSIP_TRANSPORT_SECURE;
        /* There doesn't seem to be any requirement for SIPS to use
         * reliable transport, so we disable this.
         */
        // tp_flag |= PJSIP_TRANSPORT_RELIABLE;
        if ((flag & tp_flag) != tp_flag) {
            pjsip_transport_type_e t;

            t = pjsip_transport_get_type_from_flag(tp_flag);
            if (t != PJSIP_TRANSPORT_UNSPECIFIED)
                dest_info->type = t;
        }

        dest_info->flag =
            pjsip_transport_get_flag_from_type(dest_info->type);

    } else if (PJSIP_URI_SCHEME_IS_SIP(target_uri)) {
        pjsip_uri *uri = (pjsip_uri*) target_uri;
        const pjsip_sip_uri *url=(const pjsip_sip_uri*)pjsip_uri_get_uri(uri);
        if (url->maddr_param.slen)
            pj_strdup(pool, &dest_info->addr.host, &url->maddr_param);
        else
            pj_strdup(pool, &dest_info->addr.host, &url->host);
        dest_info->addr.port = url->port;
        dest_info->type = 
            pjsip_transport_get_type_from_name(&url->transport_param);
        dest_info->flag = 
            pjsip_transport_get_flag_from_type(dest_info->type);
    } else {
        /* Should have never reached here; app should have configured route
         * set when sending to tel: URI
        pj_assert(!"Unsupported URI scheme!");
         */
        PJ_TODO(SUPPORT_REQUEST_ADDR_RESOLUTION_FOR_TEL_URI);
        return PJSIP_ENOROUTESET;
    }

    /* Handle IPv6 (https://github.com/pjsip/pjproject/issues/861) */
    if (dest_info->type != PJSIP_TRANSPORT_UNSPECIFIED && 
        pj_strchr(&dest_info->addr.host, ':'))
    {
        dest_info->type = (pjsip_transport_type_e)
                          ((int)dest_info->type | PJSIP_TRANSPORT_IPV6);
    }

    return PJ_SUCCESS;
}


/*
 * Find which destination to be used to send the request message, based
 * on the request URI and Route headers in the message. The procedure
 * used here follows the guidelines on sending the request in RFC 3261
 * chapter 8.1.2.
 */
PJ_DEF(pj_status_t) pjsip_get_request_dest(const pjsip_tx_data *tdata,
                                           pjsip_host_info *dest_info )
{
    const pjsip_uri *target_uri;
    const pjsip_route_hdr *first_route_hdr;
    
    PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG, 
                     PJSIP_ENOTREQUESTMSG);
    PJ_ASSERT_RETURN(dest_info != NULL, PJ_EINVAL);

    /* Get the first "Route" header from the message.
     */
    first_route_hdr = (const pjsip_route_hdr*) 
                      pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
    if (first_route_hdr) {
        target_uri = first_route_hdr->name_addr.uri;
    } else {
        target_uri = tdata->msg->line.req.uri;
    }

    return pjsip_get_dest_info(target_uri, tdata->msg->line.req.uri,
                               (pj_pool_t*)tdata->pool, dest_info);
}


/*
 * Process route-set found in the request and calculate
 * the destination to be used to send the request message, based
 * on the request URI and Route headers in the message. The procedure
 * used here follows the guidelines on sending the request in RFC 3261
 * chapter 8.1.2.
 */
PJ_DEF(pj_status_t) pjsip_process_route_set(pjsip_tx_data *tdata,
                                            pjsip_host_info *dest_info )
{
    const pjsip_uri *new_request_uri, *target_uri;
    const pjsip_name_addr *topmost_route_uri;
    pjsip_route_hdr *first_route_hdr, *last_route_hdr;
    pj_status_t status;
    
    PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG, 
                     PJSIP_ENOTREQUESTMSG);
    PJ_ASSERT_RETURN(dest_info != NULL, PJ_EINVAL);

    /* If the request contains strict route, check that the strict route
     * has been restored to its original values before processing the
     * route set. The strict route is restored to the original values
     * with pjsip_restore_strict_route_set(). If caller did not restore
     * the strict route before calling this function, we need to call it
     * here, or otherwise the strict-route and Request-URI will be swapped
     * twice!
     */
    if (tdata->saved_strict_route != NULL) {
        pjsip_restore_strict_route_set(tdata);
    }
    PJ_ASSERT_RETURN(tdata->saved_strict_route==NULL, PJ_EBUG);

    /* Find the first and last "Route" headers from the message. */
    last_route_hdr = first_route_hdr = (pjsip_route_hdr*)
        pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
    if (first_route_hdr) {
        topmost_route_uri = &first_route_hdr->name_addr;
        while (last_route_hdr->next != (void*)&tdata->msg->hdr) {
            pjsip_route_hdr *hdr;
            hdr = (pjsip_route_hdr*)
                  pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, 
                                     last_route_hdr->next);
            if (!hdr)
                break;
            last_route_hdr = hdr;
        }
    } else {
        topmost_route_uri = NULL;
    }

    /* If Route headers exist, and the first element indicates loose-route,
     * the URI is taken from the Request-URI, and we keep all existing Route
     * headers intact.
     * If Route headers exist, and the first element DOESN'T indicate loose
     * route, the URI is taken from the first Route header, and remove the
     * first Route header from the message.
     * Otherwise if there's no Route headers, the URI is taken from the
     * Request-URI.
     */
    if (topmost_route_uri) {
        pj_bool_t has_lr_param;

        if (PJSIP_URI_SCHEME_IS_SIP(topmost_route_uri) ||
            PJSIP_URI_SCHEME_IS_SIPS(topmost_route_uri))
        {
            const pjsip_sip_uri *url = (const pjsip_sip_uri*)
                pjsip_uri_get_uri((const void*)topmost_route_uri);
            has_lr_param = url->lr_param;
        } else {
            has_lr_param = 0;
        }

        if (has_lr_param) {
            new_request_uri = tdata->msg->line.req.uri;
            /* We shouldn't need to delete topmost Route if it has lr param.
             * But seems like it breaks some proxy implementation, so we
             * delete it anyway.
             */
            /*
            pj_list_erase(first_route_hdr);
            if (first_route_hdr == last_route_hdr)
                last_route_hdr = NULL;
            */
        } else {
            new_request_uri = (const pjsip_uri*) 
                              pjsip_uri_get_uri((pjsip_uri*)topmost_route_uri);
            pj_list_erase(first_route_hdr);
            tdata->saved_strict_route = first_route_hdr;
            if (first_route_hdr == last_route_hdr)
                first_route_hdr = last_route_hdr = NULL;
        }

        target_uri = (pjsip_uri*)topmost_route_uri;

    } else {
        target_uri = new_request_uri = tdata->msg->line.req.uri;
    }

    /* Fill up the destination host/port from the URI. */
    status = pjsip_get_dest_info(target_uri, new_request_uri, tdata->pool,
                                 dest_info);
    if (status != PJ_SUCCESS)
        return status;

    /* If transport selector is set, set destination type accordingly */
    if ((tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT ||
         tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER) &&
        tdata->tp_sel.u.ptr)
    {
        pjsip_transport_type_e tp_type = PJSIP_TRANSPORT_UNSPECIFIED;

        if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT)
            tp_type = tdata->tp_sel.u.transport->key.type;
        else if (tdata->tp_sel.type == PJSIP_TPSELECTOR_LISTENER)
            tp_type = tdata->tp_sel.u.listener->type;

        /* Check if the transport selector type is compatible with
         * the destination type.
         */
        if (dest_info->type != PJSIP_TRANSPORT_UNSPECIFIED &&
            ((dest_info->type | PJSIP_TRANSPORT_IPV6) !=
             (tp_type | PJSIP_TRANSPORT_IPV6)))
        {
            PJ_LOG(4,(THIS_FILE, "Unsuitable transport selected to "
                                 "reach destination"));
            return PJSIP_ETPNOTSUITABLE;
        }
        dest_info->type = tp_type;
    }

    /* If target URI is different than request URI, replace 
     * request URI add put the original URI in the last Route header.
     */
    if (new_request_uri && new_request_uri!=tdata->msg->line.req.uri) {
        pjsip_route_hdr *route = pjsip_route_hdr_create(tdata->pool);
        route->name_addr.uri = (pjsip_uri*) 
                               pjsip_uri_get_uri(tdata->msg->line.req.uri);
        if (last_route_hdr)
            pj_list_insert_after(last_route_hdr, route);
        else
            pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)route);
        tdata->msg->line.req.uri = (pjsip_uri*)new_request_uri;
    }

    /* Success. */
    return PJ_SUCCESS;  
}


/*
 * Swap the request URI and strict route back to the original position
 * before #pjsip_process_route_set() function is called. This function
 * should only used internally by PJSIP client authentication module.
 */
PJ_DEF(void) pjsip_restore_strict_route_set(pjsip_tx_data *tdata)
{
    pjsip_route_hdr *first_route_hdr, *last_route_hdr;

    /* Check if we have found strict route before */
    if (tdata->saved_strict_route == NULL) {
        /* This request doesn't contain strict route */
        return;
    }

    /* Find the first "Route" headers from the message. */
    first_route_hdr = (pjsip_route_hdr*)
                      pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);

    if (first_route_hdr == NULL) {
        /* User has modified message route? We don't expect this! */
        pj_assert(!"Message route was modified?");
        tdata->saved_strict_route = NULL;
        return;
    }

    /* Find last Route header */
    last_route_hdr = first_route_hdr;
    while (last_route_hdr->next != (void*)&tdata->msg->hdr) {
        pjsip_route_hdr *hdr;
        hdr = (pjsip_route_hdr*)
              pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, 
                                 last_route_hdr->next);
        if (!hdr)
            break;
        last_route_hdr = hdr;
    }

    /* Put the last Route header as request URI, delete last Route
     * header, and insert the saved strict route as the first Route.
     */
    tdata->msg->line.req.uri = last_route_hdr->name_addr.uri;
    pj_list_insert_before(first_route_hdr, tdata->saved_strict_route);
    pj_list_erase(last_route_hdr);

    /* Reset */
    tdata->saved_strict_route = NULL;
}


/* Transport callback for sending stateless request. 
 * This is one of the most bizzare function in pjsip, so
 * good luck if you happen to debug this function!!
 */
static void stateless_send_transport_cb( void *token,
                                         pjsip_tx_data *tdata,
                                         pj_ssize_t sent )
{
    pjsip_send_state *stateless_data = (pjsip_send_state*) token;
    pj_status_t need_update_via = PJ_TRUE;

    PJ_UNUSED_ARG(tdata);
    pj_assert(tdata == stateless_data->tdata);

    for (;;) {
        pj_status_t status;
        pj_bool_t cont;

        pj_sockaddr_t *cur_addr;
        pjsip_transport_type_e cur_addr_type;
        int cur_addr_len;

        pjsip_via_hdr *via;

        if (sent == -PJ_EPENDING) {
            /* This is the initial process.
             * When the process started, this function will be called by
             * stateless_send_resolver_callback() with sent argument set to
             * -PJ_EPENDING.
             */
            cont = PJ_TRUE;
        } else {
            /* There are two conditions here:
             * (1) Message is sent (i.e. sent > 0),
             * (2) Failure (i.e. sent <= 0)
             */
            cont = (sent > 0) ? PJ_FALSE :
                   (tdata->dest_info.cur_addr<tdata->dest_info.addr.count-1);
            if (stateless_data->app_cb) {
                (*stateless_data->app_cb)(stateless_data, sent, &cont);
            } else {
                /* Doesn't have application callback.
                 * Terminate the process.
                 */
                cont = PJ_FALSE;
            }
        }

        /* Finished with this transport. */
        if (stateless_data->cur_transport) {
            pjsip_transport_dec_ref(stateless_data->cur_transport);
            stateless_data->cur_transport = NULL;
        }

        /* Done if application doesn't want to continue. */
        if (sent > 0 || !cont) {
            pjsip_tx_data_dec_ref(tdata);
            return;
        }

        /* Try next address, if any, and only when this is not the 
         * first invocation. 
         */
        if (sent != -PJ_EPENDING) {
            tdata->dest_info.cur_addr++;
        }

        /* Have next address? */
        if (tdata->dest_info.cur_addr >= tdata->dest_info.addr.count) {
            /* This only happens when a rather buggy application has
             * sent 'cont' to PJ_TRUE when the initial value was PJ_FALSE.
             * In this case just stop the processing; we don't need to
             * call the callback again as application has been informed
             * before.
             */
            pjsip_tx_data_dec_ref(tdata);
            return;
        }

        /* Keep current server address information handy. */
        cur_addr = &tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr;
        cur_addr_type = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].type;
        cur_addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len;

        /* Acquire transport. */
        status = pjsip_endpt_acquire_transport2(stateless_data->endpt,
                                                cur_addr_type,
                                                cur_addr,
                                                cur_addr_len,
                                                &tdata->tp_sel,
                                                tdata,
                                                &stateless_data->cur_transport);
        if (status != PJ_SUCCESS) {
            sent = -status;
            continue;
        }

        /* Modify Via header. */
        via = (pjsip_via_hdr*) pjsip_msg_find_hdr( tdata->msg,
                                                   PJSIP_H_VIA, NULL);
        if (!via) {
            /* Shouldn't happen if request was created with PJSIP API! 
             * But we handle the case anyway for robustness.
             */
            pj_assert(!"Via header not found!");
            via = pjsip_via_hdr_create(tdata->pool);
            pjsip_msg_insert_first_hdr(tdata->msg, (pjsip_hdr*)via);
        }

        if (tdata->msg->line.req.method.id == PJSIP_CANCEL_METHOD) {
            if (via->sent_by.host.slen > 0) {
                /* Don't update Via header on a CANCEL request if the sent-by
                 * parameter is already set since it needs to match the 
                 * original request. */
                need_update_via = PJ_FALSE;
            }
        }

        if (via->branch_param.slen == 0) {
            pj_str_t tmp;
            via->branch_param.ptr = (char*)pj_pool_alloc(tdata->pool,
                                                         PJSIP_MAX_BRANCH_LEN);
            via->branch_param.slen = PJSIP_MAX_BRANCH_LEN;
            pj_memcpy(via->branch_param.ptr, PJSIP_RFC3261_BRANCH_ID,
                      PJSIP_RFC3261_BRANCH_LEN);
            tmp.ptr = via->branch_param.ptr + PJSIP_RFC3261_BRANCH_LEN + 2;
            *(tmp.ptr-2) = 80; *(tmp.ptr-1) = 106;
            pj_generate_unique_string(&tmp);
        }

        if (need_update_via) {
            via->transport = pj_str(stateless_data->cur_transport->type_name);

            if (tdata->via_addr.host.slen > 0 &&
                (!tdata->via_tp ||
                    tdata->via_tp == (void*)stateless_data->cur_transport ||
                    tdata->msg->line.req.method.id == PJSIP_CANCEL_METHOD))
            {
                via->sent_by = tdata->via_addr;

                /* Better also update tdata via_tp, e.g: CANCEL may need to
                 * refer to original INVITE tdata.
                 */
                tdata->via_tp = stateless_data->cur_transport;
            } else {
                via->sent_by = stateless_data->cur_transport->local_name;

                /* Better also update tdata via_tp & via_addr, e.g: CANCEL
                 * may need to refer to original INVITE tdata.
                 */
                tdata->via_tp = stateless_data->cur_transport;
                tdata->via_addr = via->sent_by;
            }
            
            via->rport_param = pjsip_cfg()->endpt.disable_rport ? -1 : 0;

            /* Add/remove "alias" param to/from Via header on connection 
             * oriented/less transport, if configured.
             */
            if (pjsip_cfg()->endpt.req_has_via_alias &&
                tdata->msg->type == PJSIP_REQUEST_MSG)
            {
                const pj_str_t ALIAS_STR = {"alias", 5};
                pjsip_param *alias_param;
                pj_bool_t is_datagram;

                alias_param = pjsip_param_find(&via->other_param, &ALIAS_STR);
                is_datagram = (stateless_data->cur_transport->flag & 
                               PJSIP_TRANSPORT_DATAGRAM);
                if (!is_datagram && !alias_param) {
                    alias_param = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_param);
                    alias_param->name = ALIAS_STR;
                    pj_list_push_back(&via->other_param, alias_param);
                } else if (is_datagram && alias_param) {
                    pj_list_erase(alias_param);
                }
            }
        }

        pjsip_tx_data_invalidate_msg(tdata);

        /* Send message using this transport. */
        status = pjsip_transport_send( stateless_data->cur_transport,
                                       tdata,
                                       cur_addr,
                                       cur_addr_len,
                                       stateless_data,
                                       &stateless_send_transport_cb);
        if (status == PJ_SUCCESS) {
            /* Recursively call this function. */
            sent = tdata->buf.cur - tdata->buf.start;
            stateless_send_transport_cb( stateless_data, tdata, sent );
            return;
        } else if (status == PJ_EPENDING) {
            /* This callback will be called later. */
            return;
        } else {
            /* Recursively call this function. */
            sent = -status;
            stateless_send_transport_cb( stateless_data, tdata, sent );
            return;
        }
    }

}

/* Resort addresses based on preferred address family.
 * Note that the order of addresses within the same address family will
 * be preserved.
 */
static void resort_address(pjsip_server_addresses *addr, int af)
{
    unsigned i = 0, j = 0;

    while (j < addr->count) {
        if (addr->entry[j].addr.addr.sa_family == af) {
            if (i != j) {
                pjsip_server_address_record temp;

                pj_memcpy(&temp, &addr->entry[j], sizeof(temp));
                pj_array_insert(addr->entry, sizeof(addr->entry[0]),
                                j, i, &temp);
            }
            i++;
        }
        j++;
    }
}

/* Resolver callback for sending stateless request. */
static void 
stateless_send_resolver_callback( pj_status_t status,
                                  void *token,
                                  const struct pjsip_server_addresses *addr)
{
    pjsip_send_state *stateless_data = (pjsip_send_state*) token;
    pjsip_tx_data *tdata = stateless_data->tdata;

    /* Fail on server resolution. */
    if (status != PJ_SUCCESS) {
        if (stateless_data->app_cb) {
            pj_bool_t cont = PJ_FALSE;
            (*stateless_data->app_cb)(stateless_data, -status, &cont);
        }
        pjsip_tx_data_dec_ref(tdata);
        return;
    }

    /* Copy server addresses */
    if (addr && addr != &tdata->dest_info.addr) {
        unsigned i;
        pj_memcpy( &tdata->dest_info.addr, addr, 
                   sizeof(pjsip_server_addresses));
        for (i = 0; i < addr->count; ++i) {
            pj_strdup(tdata->pool, &tdata->dest_info.addr.entry[i].name, &addr->entry[i].name);
        }
    }
    pj_assert(tdata->dest_info.addr.count != 0);

    /* RFC 3261 section 18.1.1:
     * If a request is within 200 bytes of the path MTU, or if it is larger
     * than 1300 bytes and the path MTU is unknown, the request MUST be sent
     * using an RFC 2914 [43] congestion controlled transport protocol, such
     * as TCP.
     */
    if (pjsip_cfg()->endpt.disable_tcp_switch==0 &&
        tdata->msg->type == PJSIP_REQUEST_MSG &&
        tdata->dest_info.addr.count > 0 && 
        tdata->dest_info.addr.entry[0].type == PJSIP_TRANSPORT_UDP)
    {
        int len;

        /* Encode the request */
        status = pjsip_tx_data_encode(tdata);
        if (status != PJ_SUCCESS) {
            if (stateless_data->app_cb) {
                pj_bool_t cont = PJ_FALSE;
                (*stateless_data->app_cb)(stateless_data, -status, &cont);
            }
            pjsip_tx_data_dec_ref(tdata);
            return;
        }

        /* Check if request message is larger than 1300 bytes. */
        len = (int)(tdata->buf.cur - tdata->buf.start);
        if (len >= PJSIP_UDP_SIZE_THRESHOLD) {
            int i;
            int count = tdata->dest_info.addr.count;

            PJ_LOG(5,(THIS_FILE, "%s exceeds UDP size threshold (%u), "
                                 "sending with TCP",
                                 pjsip_tx_data_get_info(tdata),
                                 PJSIP_UDP_SIZE_THRESHOLD));

            /* Insert "TCP version" of resolved UDP addresses at the
             * beginning.
             */
            if (count * 2 > PJSIP_MAX_RESOLVED_ADDRESSES)
                count = PJSIP_MAX_RESOLVED_ADDRESSES / 2;
            for (i = 0; i < count; ++i) {
                pj_memcpy(&tdata->dest_info.addr.entry[i+count],
                          &tdata->dest_info.addr.entry[i],
                          sizeof(tdata->dest_info.addr.entry[0]));
                tdata->dest_info.addr.entry[i].type = PJSIP_TRANSPORT_TCP;
            }
            tdata->dest_info.addr.count = count * 2;
        }
    }

    if (tdata->tp_sel.type == PJSIP_TPSELECTOR_IP_VER) {
        PJ_LOG(5, (THIS_FILE, "Resorting target addresses based on "
                   "%s preference",
                   tdata->tp_sel.u.ip_ver <= PJSIP_TPSELECTOR_PREFER_IPV4?
                   "IPv4": "IPv6"));
        if (tdata->tp_sel.u.ip_ver == PJSIP_TPSELECTOR_PREFER_IPV4)
            resort_address(&tdata->dest_info.addr, pj_AF_INET());
        else if (tdata->tp_sel.u.ip_ver == PJSIP_TPSELECTOR_PREFER_IPV6)
            resort_address(&tdata->dest_info.addr, pj_AF_INET6());
    }

    /* Process the addresses. */
    stateless_send_transport_cb( stateless_data, tdata, -PJ_EPENDING);
}

/*
 * Send stateless request.
 * The sending process consists of several stages:
 *  - determine which host to contact (#pjsip_get_request_addr).
 *  - resolve the host (#pjsip_endpt_resolve)
 *  - establish transport (#pjsip_endpt_acquire_transport)
 *  - send the message (#pjsip_transport_send)
 */
PJ_DEF(pj_status_t) pjsip_endpt_send_request_stateless(pjsip_endpoint *endpt, 
                                   pjsip_tx_data *tdata,
                                   void *token,
                                   pjsip_send_callback cb)
{
    pjsip_host_info dest_info;
    pjsip_send_state *stateless_data;
    pj_status_t status;

    PJ_ASSERT_RETURN(endpt && tdata, PJ_EINVAL);

    /* Get destination name to contact. */
    status = pjsip_process_route_set(tdata, &dest_info);
    if (status != PJ_SUCCESS)
        return status;

    /* Keep stateless data. */
    stateless_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_send_state);
    stateless_data->token = token;
    stateless_data->endpt = endpt;
    stateless_data->tdata = tdata;
    stateless_data->app_cb = cb;

    /* If destination info has not been initialized (this applies for most
     * all requests except CANCEL), resolve destination host. The processing
     * then resumed when the resolving callback is called. For CANCEL, the
     * destination info must have been copied from the original INVITE so
     * proceed to sending the request directly.
     */
    if (tdata->dest_info.addr.count == 0) {
        /* Copy the destination host name to TX data */
        if (!tdata->dest_info.name.slen) {
            pj_strdup(tdata->pool, &tdata->dest_info.name,
                      &dest_info.addr.host);
        } else {
            /* Check if:
             * - User configures transport to use a specific IP version
             * - The IP version doesn't match with destination info
             */
            if (tdata->tp_sel.type == PJSIP_TPSELECTOR_IP_VER &&
                ((tdata->tp_sel.u.ip_ver == PJSIP_TPSELECTOR_USE_IPV4_ONLY &&
                  (dest_info.type & PJSIP_TRANSPORT_IPV6) != 0) ||
                 (tdata->tp_sel.u.ip_ver == PJSIP_TPSELECTOR_USE_IPV6_ONLY &&
                  (dest_info.type & PJSIP_TRANSPORT_IPV6) == 0)))
            {
                PJ_LOG(5, (THIS_FILE, "Using initial dest %.*s",
                           (int)tdata->dest_info.name.slen,
                           tdata->dest_info.name.ptr));
                pj_strdup(tdata->pool, &dest_info.addr.host,
                          &tdata->dest_info.name);
                if (tdata->tp_sel.u.ip_ver == PJSIP_TPSELECTOR_USE_IPV4_ONLY) {
                    dest_info.type &= ~PJSIP_TRANSPORT_IPV6;
                } else {
                    dest_info.type |= PJSIP_TRANSPORT_IPV6;
                }
            }
        }

        pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, stateless_data,
                             &stateless_send_resolver_callback);
    } else {
        PJ_LOG(5,(THIS_FILE, "%s: skipping target resolution because "
                             "address is already set",
                             pjsip_tx_data_get_info(tdata)));
        stateless_send_resolver_callback(PJ_SUCCESS, stateless_data,
                                         &tdata->dest_info.addr);
    }
    return PJ_SUCCESS;
}


/*
 * Send raw data to a destination.
 */
PJ_DEF(pj_status_t) pjsip_endpt_send_raw( pjsip_endpoint *endpt,
                                          pjsip_transport_type_e tp_type,
                                          const pjsip_tpselector *sel,
                                          const void *raw_data,
                                          pj_size_t data_len,
                                          const pj_sockaddr_t *addr,
                                          int addr_len,
                                          void *token,
                                          pjsip_tp_send_callback cb)
{
    return pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(endpt), tp_type, sel,
                                NULL, raw_data, data_len, addr, addr_len,
                                token, cb);
}


/* Callback data for sending raw data */
struct send_raw_data
{
    pjsip_endpoint          *endpt;
    pjsip_tx_data           *tdata;
    pjsip_tpselector        *sel;
    void                    *app_token;
    pjsip_tp_send_callback   app_cb;
};


/* Resolver callback for sending raw data. */
static void send_raw_resolver_callback( pj_status_t status,
                                        void *token,
                                        const pjsip_server_addresses *addr)
{
    struct send_raw_data *sraw_data = (struct send_raw_data*) token;

    if (status != PJ_SUCCESS) {
        if (sraw_data->app_cb) {
            (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata,
                                 -status);
        }
    } else {
        pj_size_t data_len;

        pj_assert(addr->count != 0);

        /* Avoid tdata destroyed by pjsip_tpmgr_send_raw(). */
        pjsip_tx_data_add_ref(sraw_data->tdata);

        data_len = sraw_data->tdata->buf.cur - sraw_data->tdata->buf.start;
        status = pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(sraw_data->endpt),
                                      addr->entry[0].type,
                                      sraw_data->sel, sraw_data->tdata,
                                      sraw_data->tdata->buf.start, data_len,
                                      &addr->entry[0].addr, 
                                      addr->entry[0].addr_len, 
                                      sraw_data->app_token,
                                      sraw_data->app_cb);
        if (status == PJ_SUCCESS) {
            (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata,
                                 data_len);
        } else if (status != PJ_EPENDING) {
            (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata,
                                 -status);
        }
    }

    if (sraw_data->sel) {
        pjsip_tpselector_dec_ref(sraw_data->sel);
    }
    pjsip_tx_data_dec_ref(sraw_data->tdata);
}


/*
 * Send raw data to the specified destination URI. 
 */
PJ_DEF(pj_status_t) pjsip_endpt_send_raw_to_uri(pjsip_endpoint *endpt,
                                                const pj_str_t *p_dst_uri,
                                                const pjsip_tpselector *sel,
                                                const void *raw_data,
                                                pj_size_t data_len,
                                                void *token,
                                                pjsip_tp_send_callback cb)
{
    pjsip_tx_data *tdata;
    struct send_raw_data *sraw_data;
    pj_str_t dst_uri;
    pjsip_uri *uri;
    pjsip_host_info dest_info;
    pj_status_t status;

    /* Allocate buffer */
    status = pjsip_endpt_create_tdata(endpt, &tdata);
    if (status != PJ_SUCCESS)
        return status;

    pjsip_tx_data_add_ref(tdata);

    /* Duplicate URI since parser requires URI to be NULL terminated */
    pj_strdup_with_null(tdata->pool, &dst_uri, p_dst_uri);

    /* Parse URI */
    uri = pjsip_parse_uri(tdata->pool, dst_uri.ptr, dst_uri.slen, 0);
    if (uri == NULL) {
        pjsip_tx_data_dec_ref(tdata);
        return PJSIP_EINVALIDURI;
    }

    /* Build destination info. */
    status = pjsip_get_dest_info(uri, NULL, tdata->pool, &dest_info);
    if (status != PJ_SUCCESS) {
        pjsip_tx_data_dec_ref(tdata);
        return status;
    }

    /* Copy data (note: data_len may be zero!) */
    tdata->buf.start = (char*) pj_pool_alloc(tdata->pool, data_len+1);
    tdata->buf.end = tdata->buf.start + data_len + 1;
    if (data_len)
        pj_memcpy(tdata->buf.start, raw_data, data_len);
    tdata->buf.cur = tdata->buf.start + data_len;

    /* Init send_raw_data */
    sraw_data = PJ_POOL_ZALLOC_T(tdata->pool, struct send_raw_data);
    sraw_data->endpt = endpt;
    sraw_data->tdata = tdata;
    sraw_data->app_token = token;
    sraw_data->app_cb = cb;

    if (sel) {
        sraw_data->sel = PJ_POOL_ALLOC_T(tdata->pool, pjsip_tpselector);
        pj_memcpy(sraw_data->sel, sel, sizeof(pjsip_tpselector));
        pjsip_tpselector_add_ref(sraw_data->sel);
    }

    /* Copy the destination host name to TX data */
    pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host);

    /* Resolve destination host.
     * The processing then resumed when the resolving callback is called.
     */
    pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, sraw_data,
                         &send_raw_resolver_callback);
    return PJ_SUCCESS;
}


/*
 * Determine which address (and transport) to use to send response message
 * based on the received request. This function follows the specification
 * in section 18.2.2 of RFC 3261 and RFC 3581 for calculating the destination
 * address and transport.
 */
PJ_DEF(pj_status_t) pjsip_get_response_addr( pj_pool_t *pool,
                                             pjsip_rx_data *rdata,
                                             pjsip_response_addr *res_addr )
{
    pjsip_transport *src_transport = rdata->tp_info.transport;

    /* Check arguments. */
    PJ_ASSERT_RETURN(pool && rdata && res_addr, PJ_EINVAL);

    /* rdata must be a request message! */
    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
                     PJ_EINVAL);

    /* All requests must have "received" parameter.
     * This must always be done in transport layer.
     */
    pj_assert(rdata->msg_info.via->recvd_param.slen != 0);

    /* Do the calculation based on RFC 3261 Section 18.2.2 and RFC 3581 */

    if (PJSIP_TRANSPORT_IS_RELIABLE(src_transport)) {
        /* For reliable protocol such as TCP or SCTP, or TLS over those, the
         * response MUST be sent using the existing connection to the source
         * of the original request that created the transaction, if that 
         * connection is still open. 
         * If that connection is no longer open, the server SHOULD open a 
         * connection to the IP address in the received parameter, if present,
         * using the port in the sent-by value, or the default port for that 
         * transport, if no port is specified. 
         * If that connection attempt fails, the server SHOULD use the 
         * procedures in [4] for servers in order to determine the IP address
         * and port to open the connection and send the response to.
         */
        res_addr->transport = rdata->tp_info.transport;
        pj_memcpy(&res_addr->addr, &rdata->pkt_info.src_addr,
                  rdata->pkt_info.src_addr_len);
        res_addr->addr_len = rdata->pkt_info.src_addr_len;
        res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
        res_addr->dst_host.flag = src_transport->flag;
        pj_strdup( pool, &res_addr->dst_host.addr.host, 
                   &rdata->msg_info.via->recvd_param);
        res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
        if (res_addr->dst_host.addr.port == 0) {
            res_addr->dst_host.addr.port = 
                pjsip_transport_get_default_port_for_type(res_addr->dst_host.type);
        }

    } else if (rdata->msg_info.via->maddr_param.slen) {
        /* Otherwise, if the Via header field value contains a maddr parameter,
         * the response MUST be forwarded to the address listed there, using 
         * the port indicated in sent-by, or port 5060 if none is present. 
         * If the address is a multicast address, the response SHOULD be sent 
         * using the TTL indicated in the ttl parameter, or with a TTL of 1 if
         * that parameter is not present. 
         */
        res_addr->transport = NULL;
        res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
        res_addr->dst_host.flag = src_transport->flag;
        pj_strdup( pool, &res_addr->dst_host.addr.host, 
                   &rdata->msg_info.via->maddr_param);
        res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
        if (res_addr->dst_host.addr.port == 0)
            res_addr->dst_host.addr.port = 5060;

    } else if (rdata->msg_info.via->rport_param >= 0) {
        /* There is both a "received" parameter and an "rport" parameter, 
         * the response MUST be sent to the IP address listed in the "received"
         * parameter, and the port in the "rport" parameter. 
         * The response MUST be sent from the same address and port that the 
         * corresponding request was received on.
         */
        res_addr->transport = rdata->tp_info.transport;
        pj_memcpy(&res_addr->addr, &rdata->pkt_info.src_addr,
                  rdata->pkt_info.src_addr_len);
        res_addr->addr_len = rdata->pkt_info.src_addr_len;
        res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
        res_addr->dst_host.flag = src_transport->flag;
        pj_strdup( pool, &res_addr->dst_host.addr.host, 
                   &rdata->msg_info.via->recvd_param);
        res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
        if (res_addr->dst_host.addr.port == 0) {
            res_addr->dst_host.addr.port = 
                pjsip_transport_get_default_port_for_type(res_addr->dst_host.type);
        }

    } else {
        res_addr->transport = NULL;
        res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
        res_addr->dst_host.flag = src_transport->flag;
        pj_strdup( pool, &res_addr->dst_host.addr.host, 
                   &rdata->msg_info.via->recvd_param);
        res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
        if (res_addr->dst_host.addr.port == 0) {
            res_addr->dst_host.addr.port = 
                pjsip_transport_get_default_port_for_type(res_addr->dst_host.type);
        }
    }

    return PJ_SUCCESS;
}

/*
 * Callback called by transport during send_response.
 */
static void send_response_transport_cb(void *token, pjsip_tx_data *tdata,
                                       pj_ssize_t sent)
{
    pjsip_send_state *send_state = (pjsip_send_state*) token;
    pj_bool_t cont = PJ_FALSE;

    /* Call callback, if any. */
    if (send_state->app_cb)
        (*send_state->app_cb)(send_state, sent, &cont);

    /* Decrement transport reference counter. */
    pjsip_transport_dec_ref(send_state->cur_transport);

    /* Decrement transmit data ref counter. */
    pjsip_tx_data_dec_ref(tdata);
}

/*
 * Resolver calback during send_response.
 */
static void send_response_resolver_cb( pj_status_t status, void *token,
                                       const pjsip_server_addresses *addr )
{
    pjsip_send_state *send_state = (pjsip_send_state*) token;
    unsigned i;

    if (status != PJ_SUCCESS) {
        if (send_state->app_cb) {
            pj_bool_t cont = PJ_FALSE;
            (*send_state->app_cb)(send_state, -status, &cont);
        }
        pjsip_tx_data_dec_ref(send_state->tdata);
        return;
    }

    /* Only handle the first address resolved. */

    /* Acquire transport. */
    status = pjsip_endpt_acquire_transport2(send_state->endpt, 
                                            addr->entry[0].type,
                                            &addr->entry[0].addr,
                                            addr->entry[0].addr_len,
                                            &send_state->tdata->tp_sel,
                                            send_state->tdata,
                                            &send_state->cur_transport);
    if (status != PJ_SUCCESS) {
        if (send_state->app_cb) {
            pj_bool_t cont = PJ_FALSE;
            (*send_state->app_cb)(send_state, -status, &cont);
        }
        pjsip_tx_data_dec_ref(send_state->tdata);
        return;
    }

    /* Update address in send_state. */
    pj_memcpy(&send_state->tdata->dest_info.addr, addr, sizeof(*addr));
    for (i = 0; i < send_state->tdata->dest_info.addr.count; ++i) {
        pj_strdup(send_state->tdata->pool,
                  &send_state->tdata->dest_info.addr.entry[i].name,
                  &addr->entry[i].name);
    }

    /* Send response using the transoprt. */
    status = pjsip_transport_send( send_state->cur_transport, 
                                   send_state->tdata,
                                   &addr->entry[0].addr,
                                   addr->entry[0].addr_len,
                                   send_state,
                                   &send_response_transport_cb);
    if (status == PJ_SUCCESS) {
        pj_ssize_t sent = send_state->tdata->buf.cur - 
                          send_state->tdata->buf.start;
        send_response_transport_cb(send_state, send_state->tdata, sent);

    } else if (status == PJ_EPENDING) {
        /* Transport callback will be called later. */
    } else {
        send_response_transport_cb(send_state, send_state->tdata, -status);
    }
}

/*
 * Send response.
 */
PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt,
                                               pjsip_response_addr *res_addr,
                                               pjsip_tx_data *tdata,
                                               void *token,
                                               pjsip_send_callback cb)
{
    /* Determine which transports and addresses to send the response,
     * based on Section 18.2.2 of RFC 3261.
     */
    pjsip_send_state *send_state;
    pj_status_t status;

    /* Create structure to keep the sending state. */
    send_state = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_send_state);
    send_state->endpt = endpt;
    send_state->tdata = tdata;
    send_state->token = token;
    send_state->app_cb = cb;

    if (res_addr->transport != NULL) {
        send_state->cur_transport = res_addr->transport;
        pjsip_transport_add_ref(send_state->cur_transport);

        status = pjsip_transport_send( send_state->cur_transport, tdata, 
                                       &res_addr->addr,
                                       res_addr->addr_len,
                                       send_state,
                                       &send_response_transport_cb );
        if (status == PJ_SUCCESS) {
            pj_ssize_t sent = tdata->buf.cur - tdata->buf.start;
            send_response_transport_cb(send_state, tdata, sent);
            return PJ_SUCCESS;
        } else if (status == PJ_EPENDING) {
            /* Callback will be called later. */
            return PJ_SUCCESS;
        } else {
            pjsip_transport_dec_ref(send_state->cur_transport);
            return status;
        }
    } else {
        /* Copy the destination host name to TX data */
        if (!tdata->dest_info.name.slen) {
            pj_strdup(tdata->pool, &tdata->dest_info.name, 
                      &res_addr->dst_host.addr.host);
        }

        pjsip_endpt_resolve(endpt, tdata->pool, &res_addr->dst_host, 
                            send_state, &send_response_resolver_cb);
        return PJ_SUCCESS;
    }
}

/*
 * Send response combo
 */
PJ_DEF(pj_status_t) pjsip_endpt_send_response2( pjsip_endpoint *endpt,
                                                pjsip_rx_data *rdata,
                                                pjsip_tx_data *tdata,
                                                void *token,
                                                pjsip_send_callback cb)
{
    pjsip_response_addr res_addr;
    pj_status_t status;

    status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
    if (status != PJ_SUCCESS) {
        pjsip_tx_data_dec_ref(tdata);
        return PJ_SUCCESS;
    }

    status = pjsip_endpt_send_response(endpt, &res_addr, tdata, token, cb);
    return status;
}


/*
 * Send response
 */
PJ_DEF(pj_status_t) pjsip_endpt_respond_stateless( pjsip_endpoint *endpt,
                                                   pjsip_rx_data *rdata,
                                                   int st_code,
                                                   const pj_str_t *st_text,
                                                   const pjsip_hdr *hdr_list,
                                                   const pjsip_msg_body *body)
{
    pj_status_t status;
    pjsip_response_addr res_addr;
    pjsip_tx_data *tdata;
    pjsip_transaction *tsx;

    /* Verify arguments. */
    PJ_ASSERT_RETURN(endpt && rdata, PJ_EINVAL);
    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
                     PJSIP_ENOTREQUESTMSG);

    /* Check that no UAS transaction has been created for this request. 
     * If UAS transaction has been created for this request and its state
     * is not TERMINATED/DESTROYED, application MUST send the response
     * statefully using that transaction.
     */
    tsx = pjsip_rdata_get_tsx(rdata);
    if (tsx && tsx->state < PJSIP_TSX_STATE_TERMINATED)
        return PJ_EINVALIDOP;

    /* Create response message */
    status = pjsip_endpt_create_response( endpt, rdata, st_code, st_text, 
                                          &tdata);
    if (status != PJ_SUCCESS)
        return status;

    /* Add the message headers, if any */
    if (hdr_list) {
        const pjsip_hdr *hdr = hdr_list->next;
        while (hdr != hdr_list) {
            pjsip_msg_add_hdr(tdata->msg, 
                              (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr) );
            hdr = hdr->next;
        }
    }

    /* Add the message body, if any. */
    if (body) {
        tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body );
        if (tdata->msg->body == NULL) {
            pjsip_tx_data_dec_ref(tdata);
            return status;
        }
    }

    /* Get where to send request. */
    status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr );
    if (status != PJ_SUCCESS) {
        pjsip_tx_data_dec_ref(tdata);
        return status;
    }

    /* Send! */
    status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL );
    if (status != PJ_SUCCESS) {
        pjsip_tx_data_dec_ref(tdata);
        return status;
    }

    return PJ_SUCCESS;
}


/*
 * Get the event string from the event ID.
 */
PJ_DEF(const char *) pjsip_event_str(pjsip_event_id_e e)
{
    return event_str[e];
}

