/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#include <pjsip-ua/sip_regc.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_parser.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_transaction.h>
#include <pjsip/sip_event.h>
#include <pjsip/sip_util.h>
#include <pjsip/sip_auth_msg.h>
#include <pjsip/sip_errno.h>
#include <pj/assert.h>
#include <pj/guid.h>
#include <pj/lock.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/log.h>
#include <pj/rand.h>
#include <pj/string.h>


#define REFRESH_TIMER           1
#define DELAY_BEFORE_REFRESH    PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH

/* This defines the minimum value for the refresh timer in milliseconds.
 * If the calculated expiry for the refresh timer falls below this; it will be clamped to this value.
 */
#define MIN_REFRESH_MSEC        500
#define THIS_FILE               "sip_reg.c"

/* Outgoing transaction timeout when server sends 100 but never replies
 * with final response. Value is in MILLISECONDS!
 */
#define REGC_TSX_TIMEOUT        33000

#define NOEXP                   PJSIP_REGC_EXPIRATION_NOT_SPECIFIED

static const pj_str_t XUID_PARAM_NAME = { "x-uid", 5 };


/* Current/pending operation */
enum regc_op
{
    REGC_IDLE,
    REGC_REGISTERING,
    REGC_UNREGISTERING
};

/**
 * SIP client registration structure.
 */
struct pjsip_regc
{
    pj_pool_t                   *pool;
    pjsip_endpoint              *endpt;
    pj_lock_t                   *lock;
    pj_bool_t                    _delete_flag;
    pj_bool_t                    has_tsx;
    pj_atomic_t                 *busy_ctr;
    enum regc_op                 current_op;

    pj_bool_t                    add_xuid_param;

    void                        *token;
    pjsip_regc_cb               *cb;
    pjsip_regc_tsx_cb           *tsx_cb;

    pj_str_t                     str_srv_url;
    pjsip_uri                   *srv_url;
    pjsip_cid_hdr               *cid_hdr;
    pjsip_cseq_hdr              *cseq_hdr;
    pj_str_t                     from_uri;
    pjsip_from_hdr              *from_hdr;
    pjsip_to_hdr                *to_hdr;
    pjsip_contact_hdr            contact_hdr_list;
    pjsip_contact_hdr            removed_contact_hdr_list;
    pjsip_expires_hdr           *expires_hdr;
    pj_uint32_t                  expires;
    pj_uint32_t                  expires_requested;
    pj_uint32_t                  delay_before_refresh;
    pjsip_route_hdr              route_set;
    pjsip_hdr                    hdr_list;
    pjsip_host_port              via_addr;
    const void                  *via_tp;

    /* Authorization sessions. */
    pjsip_auth_clt_sess          auth_sess;

    /* Auto refresh registration. */
    pj_bool_t                    auto_reg;
    pj_time_val                  last_reg;
    pj_time_val                  next_reg;
    pj_timer_entry               timer;

    /* Transport selector */
    pjsip_tpselector             tp_sel;

    /* Last transport used. We acquire the transport to keep
     * it open.
     */
    pjsip_transport             *last_transport;

    /* Transport being used by pending transaction, for informational purpose,
     * we don't keep this transport.
     */
    pjsip_transport             *info_transport;
};


/* Declaration of async auth send implementation */
static pj_status_t async_auth_send_impl(pjsip_auth_clt_sess *auth_sess,
                                        void *user_data,
                                        pjsip_tx_data *tdata);

/* Declaration of async auth abandon implementation */
static void async_auth_abandon_impl(pjsip_auth_clt_sess *auth_sess,
                                    void *user_data);


PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
                                       pjsip_regc_cb *cb,
                                       pjsip_regc **p_regc)
{
    pj_pool_t *pool;
    pjsip_regc *regc;
    pj_status_t status;

    /* Verify arguments. */
    PJ_ASSERT_RETURN(endpt && cb && p_regc, PJ_EINVAL);

    pool = pjsip_endpt_create_pool(endpt, "regc%p", 1024, 1024);
    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);

    regc = PJ_POOL_ZALLOC_T(pool, pjsip_regc);

    regc->pool = pool;
    regc->endpt = endpt;
    regc->token = token;
    regc->cb = cb;
    regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED;
    regc->add_xuid_param = pjsip_cfg()->regc.add_xuid_param;

    status = pj_lock_create_recursive_mutex(pool, pool->obj_name, 
                                            &regc->lock);
    if (status != PJ_SUCCESS) {
        pj_pool_release(pool);
        return status;
    }

    status = pj_atomic_create(pool, 0, &regc->busy_ctr);
    if (status != PJ_SUCCESS) {
        pj_lock_destroy(regc->lock);
        pj_pool_release(pool);
        return status;
    }

    status = pjsip_auth_clt_init(&regc->auth_sess, endpt, regc->pool, 0);
    if (status != PJ_SUCCESS)
        return status;

    pj_list_init(&regc->route_set);
    pj_list_init(&regc->hdr_list);
    pj_list_init(&regc->contact_hdr_list);
    pj_list_init(&regc->removed_contact_hdr_list);

    /* Done */
    *p_regc = regc;
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pjsip_regc_destroy(pjsip_regc *regc)
{
    return pjsip_regc_destroy2(regc, PJ_TRUE);
}

PJ_DEF(pj_status_t) pjsip_regc_destroy2(pjsip_regc *regc, pj_bool_t force)
{
    PJ_ASSERT_RETURN(regc, PJ_EINVAL);

    pj_lock_acquire(regc->lock);

    if (!force && regc->has_tsx) {
        pj_lock_release(regc->lock);
        return PJ_EBUSY;
    }

    if (regc->has_tsx || pj_atomic_get(regc->busy_ctr) != 0) {
        regc->_delete_flag = 1;
        regc->cb = NULL;
        pj_lock_release(regc->lock);
    } else {
        pjsip_tpselector_dec_ref(&regc->tp_sel);
        if (regc->last_transport) {
            pjsip_transport_dec_ref(regc->last_transport);
            regc->last_transport = NULL;
        }
        if (regc->timer.id != 0) {
            pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
            regc->timer.id = 0;
        }
        pj_atomic_destroy(regc->busy_ctr);
        pj_lock_release(regc->lock);
        pj_lock_destroy(regc->lock);
        regc->lock = NULL;

        pjsip_auth_clt_deinit(&regc->auth_sess);
        pjsip_endpt_release_pool(regc->endpt, regc->pool);
    }

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjsip_regc_get_info( pjsip_regc *regc,
                                         pjsip_regc_info *info )
{
    PJ_ASSERT_RETURN(regc && info, PJ_EINVAL);

    pj_lock_acquire(regc->lock);

    info->server_uri = regc->str_srv_url;
    info->client_uri = regc->from_uri;
    info->is_busy = (pj_atomic_get(regc->busy_ctr) || regc->has_tsx);
    info->auto_reg = regc->auto_reg;
    info->interval = regc->expires;
    info->transport = regc->has_tsx? regc->info_transport :
                                     regc->last_transport;
    
    if (regc->has_tsx)
        info->next_reg = 0;
    else if (regc->auto_reg == 0)
        info->next_reg = 0;
    else if (regc->expires == PJSIP_REGC_EXPIRATION_NOT_SPECIFIED)
        info->next_reg = regc->expires;
    else {
        pj_time_val now, next_reg;

        next_reg = regc->next_reg;
        pj_gettimeofday(&now);
        if (PJ_TIME_VAL_GT(next_reg, now)) {
            PJ_TIME_VAL_SUB(next_reg, now);
            info->next_reg = next_reg.sec;
        } else {
            info->next_reg = 0;
        }
    }

    pj_lock_release(regc->lock);

    return PJ_SUCCESS;
}


PJ_DEF(pj_pool_t*) pjsip_regc_get_pool(pjsip_regc *regc)
{
    return regc->pool;
}

static void set_expires( pjsip_regc *regc, pj_uint32_t expires)
{
    if (expires != regc->expires) {
        regc->expires_hdr = pjsip_expires_hdr_create(regc->pool, expires);
    } else {
        regc->expires_hdr = NULL;
    }
}


static pj_status_t set_contact( pjsip_regc *regc,
                                int contact_cnt,
                                const pj_str_t contact[] )
{
    const pj_str_t CONTACT = { "Contact", 7 };
    pjsip_contact_hdr *h;
    int i;
    
    /* Save existing contact list to removed_contact_hdr_list and
     * clear contact_hdr_list.
     */
    pj_list_merge_last(&regc->removed_contact_hdr_list, 
                       &regc->contact_hdr_list);

    /* Set the expiration of Contacts in to removed_contact_hdr_list 
     * zero.
     */
    h = regc->removed_contact_hdr_list.next;
    while (h != &regc->removed_contact_hdr_list) {
        h->expires = 0;
        h = h->next;
    }

    /* Process new contacts */
    for (i=0; i<contact_cnt; ++i) {
        pjsip_contact_hdr *hdr;
        pj_str_t tmp;

        pj_strdup_with_null(regc->pool, &tmp, &contact[i]);
        hdr = (pjsip_contact_hdr*)
              pjsip_parse_hdr(regc->pool, &CONTACT, tmp.ptr, tmp.slen, NULL);
        if (hdr == NULL) {
            PJ_LOG(4,(THIS_FILE, "Invalid Contact: \"%.*s\"", 
                     (int)tmp.slen, tmp.ptr));
            return PJSIP_EINVALIDURI;
        }

        /* Find the new contact in old contact list. If found, remove
         * the old header from the old header list.
         */
        h = regc->removed_contact_hdr_list.next;
        while (h != &regc->removed_contact_hdr_list) {
            int rc;

            rc = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, 
                               h->uri, hdr->uri);
            if (rc == 0) {
                /* Match */
                pj_list_erase(h);
                break;
            }

            h = h->next;
        }

        /* If add_xuid_param option is enabled and Contact URI is sip/sips,
         * add xuid parameter to assist matching the Contact URI in the 
         * REGISTER response later.
         */
        if (regc->add_xuid_param && (PJSIP_URI_SCHEME_IS_SIP(hdr->uri) ||
                                     PJSIP_URI_SCHEME_IS_SIPS(hdr->uri))) 
        {
            pjsip_param *xuid_param;
            pjsip_sip_uri *sip_uri;

            xuid_param = PJ_POOL_ZALLOC_T(regc->pool, pjsip_param);
            xuid_param->name = XUID_PARAM_NAME;
            pj_create_unique_string(regc->pool, &xuid_param->value);

            sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(hdr->uri);
            pj_list_push_back(&sip_uri->other_param, xuid_param);
        }

        pj_list_push_back(&regc->contact_hdr_list, hdr);
    }

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjsip_regc_init( pjsip_regc *regc,
                                     const pj_str_t *srv_url,
                                     const pj_str_t *from_url,
                                     const pj_str_t *to_url,
                                     int contact_cnt,
                                     const pj_str_t contact[],
                                     pj_uint32_t expires)
{
    pj_str_t tmp;
    pj_status_t status;

    PJ_ASSERT_RETURN(regc && srv_url && from_url && to_url && 
                     expires, PJ_EINVAL);

    /* Copy server URL. */
    pj_strdup_with_null(regc->pool, &regc->str_srv_url, srv_url);

    /* Set server URL. */
    tmp = regc->str_srv_url;
    regc->srv_url = pjsip_parse_uri( regc->pool, tmp.ptr, tmp.slen, 0);
    if (regc->srv_url == NULL) {
        return PJSIP_EINVALIDURI;
    }

    /* Set "From" header. */
    pj_strdup_with_null(regc->pool, &regc->from_uri, from_url);
    tmp = regc->from_uri;
    regc->from_hdr = pjsip_from_hdr_create(regc->pool);
    regc->from_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen, 
                                          PJSIP_PARSE_URI_AS_NAMEADDR);
    if (!regc->from_hdr->uri) {
        PJ_LOG(4,(THIS_FILE, "regc: invalid source URI %.*s", 
                  (int)from_url->slen, from_url->ptr));
        return PJSIP_EINVALIDURI;
    }

    /* Set "To" header. */
    pj_strdup_with_null(regc->pool, &tmp, to_url);
    regc->to_hdr = pjsip_to_hdr_create(regc->pool);
    regc->to_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen, 
                                        PJSIP_PARSE_URI_AS_NAMEADDR);
    if (!regc->to_hdr->uri) {
        PJ_LOG(4,(THIS_FILE, "regc: invalid target URI %.*s",
                  (int)to_url->slen, to_url->ptr));
        return PJSIP_EINVALIDURI;
    }


    /* Set "Contact" header. */
    status = set_contact( regc, contact_cnt, contact);
    if (status != PJ_SUCCESS)
        return status;

    /* Set "Expires" header, if required. */
    set_expires( regc, expires);
    regc->delay_before_refresh = DELAY_BEFORE_REFRESH;

    /* Set "Call-ID" header. */
    regc->cid_hdr = pjsip_cid_hdr_create(regc->pool);
    pj_create_unique_string(regc->pool, &regc->cid_hdr->id);

    /* Set "CSeq" header. */
    regc->cseq_hdr = pjsip_cseq_hdr_create(regc->pool);
    regc->cseq_hdr->cseq = pj_rand() % 0xFFFF;
    pjsip_method_set( &regc->cseq_hdr->method, PJSIP_REGISTER_METHOD);

    /* Done. */
    return PJ_SUCCESS;
}

PJ_DEF(void) pjsip_regc_add_ref( pjsip_regc *regc )
{
    pj_assert(regc);
    pj_atomic_inc(regc->busy_ctr);
}

PJ_DEF(pj_status_t) pjsip_regc_dec_ref( pjsip_regc *regc )
{
    pj_assert(regc);
    if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) {
        pjsip_regc_destroy(regc);
        return PJ_EGONE;
    }
    
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pjsip_regc_set_credentials( pjsip_regc *regc,
                                                int count,
                                                const pjsip_cred_info cred[] )
{
    PJ_ASSERT_RETURN(regc && count && cred, PJ_EINVAL);
    return pjsip_auth_clt_set_credentials(&regc->auth_sess, count, cred);
}

PJ_DEF(pj_status_t) pjsip_regc_set_prefs( pjsip_regc *regc,
                                          const pjsip_auth_clt_pref *pref)
{
    PJ_ASSERT_RETURN(regc && pref, PJ_EINVAL);
    return pjsip_auth_clt_set_prefs(&regc->auth_sess, pref);
}

PJ_DEF(pj_status_t) pjsip_regc_set_route_set( pjsip_regc *regc,
                                              const pjsip_route_hdr *route_set)
{
    const pjsip_route_hdr *chdr;

    PJ_ASSERT_RETURN(regc && route_set, PJ_EINVAL);

    pj_list_init(&regc->route_set);

    chdr = route_set->next;
    while (chdr != route_set) {
        pj_list_push_back(&regc->route_set, pjsip_hdr_clone(regc->pool, chdr));
        chdr = chdr->next;
    }

    return PJ_SUCCESS;
}


/*
 * Bind client registration to a specific transport/listener. 
 */
PJ_DEF(pj_status_t) pjsip_regc_set_transport( pjsip_regc *regc,
                                              const pjsip_tpselector *sel)
{
    PJ_ASSERT_RETURN(regc && sel, PJ_EINVAL);

    pjsip_tpselector_dec_ref(&regc->tp_sel);
    pj_memcpy(&regc->tp_sel, sel, sizeof(*sel));
    pjsip_tpselector_add_ref(&regc->tp_sel);

    return PJ_SUCCESS;
}

/* Release transport */
PJ_DEF(pj_status_t) pjsip_regc_release_transport(pjsip_regc *regc)
{
    PJ_ASSERT_RETURN(regc, PJ_EINVAL);
    if (regc->last_transport) {
        pjsip_transport_dec_ref(regc->last_transport);
        regc->last_transport = NULL;
    }
    if (regc->info_transport) {
        regc->info_transport = NULL;
    }
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjsip_regc_add_headers( pjsip_regc *regc,
                                            const pjsip_hdr *hdr_list)
{
    const pjsip_hdr *hdr;

    PJ_ASSERT_RETURN(regc && hdr_list, PJ_EINVAL);

    //This is "add" operation, so don't remove headers.
    //pj_list_init(&regc->hdr_list);

    hdr = hdr_list->next;
    while (hdr != hdr_list) {
        pj_list_push_back(&regc->hdr_list, pjsip_hdr_clone(regc->pool, hdr));
        hdr = hdr->next;
    }

    return PJ_SUCCESS;
}

static pj_status_t create_request(pjsip_regc *regc, 
                                  pjsip_tx_data **p_tdata)
{
    pj_status_t status;
    pjsip_tx_data *tdata;

    PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);

    /* Create the request. */
    status = pjsip_endpt_create_request_from_hdr( regc->endpt, 
                                                  pjsip_get_register_method(),
                                                  regc->srv_url,
                                                  regc->from_hdr,
                                                  regc->to_hdr,
                                                  NULL,
                                                  regc->cid_hdr,
                                                  regc->cseq_hdr->cseq,
                                                  NULL,
                                                  &tdata);
    if (status != PJ_SUCCESS)
        return status;

    /* Add cached authorization headers. */
    pjsip_auth_clt_init_req( &regc->auth_sess, tdata );

    /* Add Route headers from route set, ideally after Via header */
    if (!pj_list_empty(&regc->route_set)) {
        pjsip_hdr *route_pos;
        const pjsip_route_hdr *route;

        route_pos = (pjsip_hdr*)
                    pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
        if (!route_pos)
            route_pos = &tdata->msg->hdr;

        route = regc->route_set.next;
        while (route != &regc->route_set) {
            pjsip_hdr *new_hdr = (pjsip_hdr*)
                                 pjsip_hdr_clone(tdata->pool, route);
            pj_list_insert_after(route_pos, new_hdr);
            route_pos = new_hdr;
            route = route->next;
        }
    }

    /* Add additional request headers */
    if (!pj_list_empty(&regc->hdr_list)) {
        const pjsip_hdr *hdr;

        hdr = regc->hdr_list.next;
        while (hdr != &regc->hdr_list) {
            pjsip_hdr *new_hdr = (pjsip_hdr*)
                                 pjsip_hdr_clone(tdata->pool, hdr);
            pjsip_msg_add_hdr(tdata->msg, new_hdr);
            hdr = hdr->next;
        }
    }

    /* Done. */
    *p_tdata = tdata;
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg,
                                        pjsip_tx_data **p_tdata)
{
    pjsip_msg *msg;
    pjsip_contact_hdr *hdr;
    const pjsip_hdr *h_allow;
    pj_status_t status;
    pjsip_tx_data *tdata;

    PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);

    pj_lock_acquire(regc->lock);
    
    regc->expires_requested = 1;

    status = create_request(regc, &tdata);
    if (status != PJ_SUCCESS) {
        pj_lock_release(regc->lock);
        return status;
    }

    msg = tdata->msg;

    /* Add Contact headers. */
    hdr = regc->contact_hdr_list.next;
    while (hdr != &regc->contact_hdr_list) {
        pjsip_msg_add_hdr(msg, (pjsip_hdr*)
                               pjsip_hdr_clone(tdata->pool, hdr));
        hdr = hdr->next;
    }

    /* Also add bindings which are to be removed */
    while (!pj_list_empty(&regc->removed_contact_hdr_list)) {
        hdr = regc->removed_contact_hdr_list.next;
        pjsip_msg_add_hdr(msg, (pjsip_hdr*)
                               pjsip_hdr_clone(tdata->pool, hdr));
        pj_list_erase(hdr);
    }
    

    if (regc->expires_hdr)
        pjsip_msg_add_hdr(msg, (pjsip_hdr*)
                               pjsip_hdr_clone(tdata->pool,
                                               regc->expires_hdr));

    if (regc->timer.id != 0) {
        pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
        regc->timer.id = 0;
    }

    /* Add Allow header (https://github.com/pjsip/pjproject/issues/1039) */
    h_allow = pjsip_endpt_get_capability(regc->endpt, PJSIP_H_ALLOW, NULL);
    if (h_allow) {
        pjsip_msg_add_hdr(msg, (pjsip_hdr*)
                               pjsip_hdr_clone(tdata->pool, h_allow));

    }

    regc->auto_reg = autoreg;

    pj_lock_release(regc->lock);

    /* Done */
    *p_tdata = tdata;
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjsip_regc_unregister(pjsip_regc *regc,
                                          pjsip_tx_data **p_tdata)
{
    pjsip_tx_data *tdata;
    pjsip_msg *msg;
    pjsip_hdr *hdr;
    pj_status_t status;

    PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);

    pj_lock_acquire(regc->lock);

    if (regc->timer.id != 0) {
        pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
        regc->timer.id = 0;
    }

    regc->expires_requested = 0;

    status = create_request(regc, &tdata);
    if (status != PJ_SUCCESS) {
        pj_lock_release(regc->lock);
        return status;
    }

    msg = tdata->msg;

    /* Add Contact headers. */
    hdr = (pjsip_hdr*)regc->contact_hdr_list.next;
    while ((void*)hdr != (void*)&regc->contact_hdr_list) {
        pjsip_msg_add_hdr(msg, (pjsip_hdr*)
                               pjsip_hdr_clone(tdata->pool, hdr));
        hdr = hdr->next;
    }

    /* Also add bindings which are to be removed */
    while (!pj_list_empty(&regc->removed_contact_hdr_list)) {
        hdr = (pjsip_hdr*)regc->removed_contact_hdr_list.next;
        pjsip_msg_add_hdr(msg, (pjsip_hdr*)
                               pjsip_hdr_clone(tdata->pool, hdr));
        pj_list_erase(hdr);
    }

    /* Add Expires:0 header */
    hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0);
    pjsip_msg_add_hdr(msg, hdr);

    pj_lock_release(regc->lock);

    *p_tdata = tdata;
    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc,
                                              pjsip_tx_data **p_tdata)
{
    pjsip_tx_data *tdata;
    pjsip_contact_hdr *hcontact;
    pjsip_hdr *hdr;
    pjsip_msg *msg;
    pj_status_t status;

    PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL);

    pj_lock_acquire(regc->lock);

    if (regc->timer.id != 0) {
        pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
        regc->timer.id = 0;
    }

    status = create_request(regc, &tdata);
    if (status != PJ_SUCCESS) {
        pj_lock_release(regc->lock);
        return status;
    }

    msg = tdata->msg;

    /* Clear removed_contact_hdr_list */
    pj_list_init(&regc->removed_contact_hdr_list);

    /* Add Contact:* header */
    hcontact = pjsip_contact_hdr_create(tdata->pool);
    hcontact->star = 1;
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)hcontact);
    
    /* Add Expires:0 header */
    hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0);
    pjsip_msg_add_hdr(msg, hdr);

    pj_lock_release(regc->lock);

    *p_tdata = tdata;
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjsip_regc_update_contact(  pjsip_regc *regc,
                                                int contact_cnt,
                                                const pj_str_t contact[] )
{
    pj_status_t status;

    PJ_ASSERT_RETURN(regc, PJ_EINVAL);

    pj_lock_acquire(regc->lock);
    status = set_contact( regc, contact_cnt, contact );
    pj_lock_release(regc->lock);

    return status;
}


PJ_DEF(pj_status_t) pjsip_regc_update_expires(  pjsip_regc *regc,
                                                pj_uint32_t expires )
{
    PJ_ASSERT_RETURN(regc, PJ_EINVAL);

    pj_lock_acquire(regc->lock);
    set_expires( regc, expires );
    pj_lock_release(regc->lock);

    return PJ_SUCCESS;
}

static void cbparam_init( struct pjsip_regc_cbparam *cbparam,
                          pjsip_regc *regc, 
                          pj_status_t status, int st_code, 
                          const pj_str_t *reason,
                          pjsip_rx_data *rdata, pj_uint32_t expiration,
                          int contact_cnt, pjsip_contact_hdr *contact[],
                          pj_bool_t is_unreg)
{
    cbparam->regc = regc;
    cbparam->token = regc->token;
    cbparam->status = status;
    cbparam->code = st_code;
    cbparam->reason = *reason;
    cbparam->rdata = rdata;
    cbparam->contact_cnt = contact_cnt;
    cbparam->expiration = (expiration != PJSIP_REGC_EXPIRATION_NOT_SPECIFIED?
                           expiration: regc->expires_requested);
    cbparam->is_unreg = is_unreg;
    if (contact_cnt) {
        pj_memcpy( cbparam->contact, contact, 
                   contact_cnt*sizeof(pjsip_contact_hdr*));
    }
}

static void call_callback(pjsip_regc *regc, pj_status_t status, int st_code, 
                          const pj_str_t *reason,
                          pjsip_rx_data *rdata, pj_uint32_t expiration,
                          int contact_cnt, pjsip_contact_hdr *contact[],
                          pj_bool_t is_unreg)
{
    struct pjsip_regc_cbparam cbparam;

    if (!regc->cb)
        return;

    cbparam_init(&cbparam, regc, status, st_code, reason, rdata, expiration,
                 contact_cnt, contact, is_unreg);
    (*regc->cb)(&cbparam);
}

static void regc_refresh_timer_cb( pj_timer_heap_t *timer_heap,
                                   struct pj_timer_entry *entry)
{
    pjsip_regc *regc = (pjsip_regc*) entry->user_data;
    pjsip_tx_data *tdata;
    pj_status_t status;
    
    PJ_UNUSED_ARG(timer_heap);

    /* Temporarily increase busy flag to prevent regc from being deleted
     * in pjsip_regc_send() or in the callback
     */
    pjsip_regc_add_ref(regc);

    entry->id = 0;
    status = pjsip_regc_register(regc, 1, &tdata);
    if (status == PJ_SUCCESS) {
        status = pjsip_regc_send(regc, tdata);
    } 
    
    if (status != PJ_SUCCESS && regc->cb) {
        char errmsg[PJ_ERR_MSG_SIZE];
        pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg));
        call_callback(regc, status, 400, &reason, NULL, NOEXP, 0, NULL,
                      PJ_FALSE);
    }

    /* Delete the record if user destroy regc during the callback. */
    pjsip_regc_dec_ref(regc);
}

static void schedule_registration ( pjsip_regc *regc, pj_uint32_t expiration )
{
    if (regc->auto_reg && expiration > 0 && expiration != NOEXP) {
        const pj_time_val min_delay = {MIN_REFRESH_MSEC / 1000,
                                       MIN_REFRESH_MSEC % 1000};
        pj_time_val delay = {0, 0};

        pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(regc->endpt),
                                       &regc->timer, 0);

        /* prevent underflow in case the remote answers with an unexpectedly low Expires field */
        if (expiration > regc->delay_before_refresh) {
            delay.sec = expiration - regc->delay_before_refresh;
        }
        if (regc->expires != PJSIP_REGC_EXPIRATION_NOT_SPECIFIED && 
            delay.sec > (pj_int32_t)regc->expires) {
            delay.sec = regc->expires;
        }
        if (delay.sec < DELAY_BEFORE_REFRESH) {
            if (expiration <= DELAY_BEFORE_REFRESH) {
                /* Very small expiration: refresh before expiry. */
                delay.sec  = (expiration > 1) ? (long)expiration - 1 : 0;
                delay.msec = (expiration > 1) ? 0 : 500;
            } else {
                delay.sec = DELAY_BEFORE_REFRESH;
            }
        }

        /* make sure we don't go below the minimum */
        if (PJ_TIME_VAL_LT(delay, min_delay)) {
            delay = min_delay;
        }
        regc->timer.cb = &regc_refresh_timer_cb;
        regc->timer.id = REFRESH_TIMER;
        regc->timer.user_data = regc;
        pjsip_endpt_schedule_timer( regc->endpt, &regc->timer, &delay);
        pj_gettimeofday(&regc->last_reg);
        regc->next_reg = regc->last_reg;
        PJ_TIME_VAL_ADD(regc->next_reg, delay);
    }
}

PJ_DEF(pj_status_t) pjsip_regc_set_reg_tsx_cb( pjsip_regc *regc,
                                               pjsip_regc_tsx_cb *tsx_cb)
{
    PJ_ASSERT_RETURN(regc, PJ_EINVAL);
    regc->tsx_cb = tsx_cb;
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjsip_regc_set_via_sent_by( pjsip_regc *regc,
                                                pjsip_host_port *via_addr,
                                                pjsip_transport *via_tp)
{
    PJ_ASSERT_RETURN(regc, PJ_EINVAL);

    if (!via_addr)
        pj_bzero(&regc->via_addr, sizeof(regc->via_addr));
    else {
        if (pj_strcmp(&regc->via_addr.host, &via_addr->host))
            pj_strdup(regc->pool, &regc->via_addr.host, &via_addr->host);
        regc->via_addr.port = via_addr->port;
    }
    regc->via_tp = via_tp;

    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t)
pjsip_regc_set_delay_before_refresh( pjsip_regc *regc,
                                     pj_uint32_t delay )
{
    PJ_ASSERT_RETURN(regc, PJ_EINVAL);

    if (delay > regc->expires)
        return PJ_ETOOBIG;

    pj_lock_acquire(regc->lock);

    if (regc->delay_before_refresh != delay)
    {
        regc->delay_before_refresh = delay;

        if (regc->timer.id != 0) {
            /* Cancel registration timer */
            pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
            regc->timer.id = 0;

            /* Schedule next registration */
            schedule_registration(regc, regc->expires);
        }
    }

    pj_lock_release(regc->lock);

    return PJ_SUCCESS;
}


static pj_uint32_t calculate_response_expiration(const pjsip_regc *regc,
                                                 const pjsip_rx_data *rdata,
                                                 unsigned *contact_cnt,
                                                 unsigned max_contact,
                                                 pjsip_contact_hdr *contacts[])
{
    pj_uint32_t expiration = NOEXP;
    const pjsip_msg *msg = rdata->msg_info.msg;
    const pjsip_hdr *hdr;

    /* Enumerate all Contact headers in the response */
    *contact_cnt = 0;
    for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) {
        if (hdr->type == PJSIP_H_CONTACT && 
            *contact_cnt < max_contact) 
        {
            contacts[*contact_cnt] = (pjsip_contact_hdr*)hdr;
            ++(*contact_cnt);
        }
    }

    if (regc->current_op == REGC_REGISTERING) {
        pj_bool_t has_our_contact = PJ_FALSE;
        const pjsip_expires_hdr *expires;

        /* Get Expires header */
        expires = (const pjsip_expires_hdr*)
                  pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);

        /* Try to find the Contact URIs that we register, in the response
         * to get the expires value. We'll try both with comparing the URI
         * and comparing the extension param only.
         */
        if (pjsip_cfg()->regc.check_contact || regc->add_xuid_param) {
            unsigned i;
            for (i=0; i<*contact_cnt; ++i) {
                const pjsip_contact_hdr *our_hdr;

                our_hdr = (const pjsip_contact_hdr*) 
                          regc->contact_hdr_list.next;

                /* Match with our Contact header(s) */
                while ((void*)our_hdr != (void*)&regc->contact_hdr_list) {

                    const pjsip_uri *uri1, *uri2;
                    pj_bool_t matched = PJ_FALSE;

                    /* Exclude the display name when comparing the URI 
                     * since server may not return it.
                     */
                    uri1 = (const pjsip_uri*)
                           pjsip_uri_get_uri(contacts[i]->uri);
                    uri2 = (const pjsip_uri*)
                           pjsip_uri_get_uri(our_hdr->uri);

                    /* First try with exact matching, according to RFC 3261
                     * Section 19.1.4 URI Comparison
                     */
                    if (pjsip_cfg()->regc.check_contact) {
                        matched = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR,
                                                uri1, uri2)==0;
                    }

                    /* If no match is found, try with matching the extension
                     * parameter only if extension parameter was added.
                     */
                    if (!matched && regc->add_xuid_param &&
                        (PJSIP_URI_SCHEME_IS_SIP(uri1) ||
                         PJSIP_URI_SCHEME_IS_SIPS(uri1)) && 
                        (PJSIP_URI_SCHEME_IS_SIP(uri2) ||
                         PJSIP_URI_SCHEME_IS_SIPS(uri2))) 
                    {
                        const pjsip_sip_uri *sip_uri1, *sip_uri2;
                        const pjsip_param *p1, *p2;
                
                        sip_uri1 = (const pjsip_sip_uri*)uri1;
                        sip_uri2 = (const pjsip_sip_uri*)uri2;

                        p1 = pjsip_param_cfind(&sip_uri1->other_param,
                                               &XUID_PARAM_NAME);
                        p2 = pjsip_param_cfind(&sip_uri2->other_param,
                                               &XUID_PARAM_NAME);
                        matched = p1 && p2 &&
                                  pj_strcmp(&p1->value, &p2->value)==0;

                    }

                    if (matched) {
                        has_our_contact = PJ_TRUE;

                        if (contacts[i]->expires != PJSIP_EXPIRES_NOT_SPECIFIED
                            && contacts[i]->expires < expiration) 
                        {
                            /* Get the lowest expiration time. */
                            expiration = contacts[i]->expires;
                        }

                        break;
                    }

                    our_hdr = our_hdr->next;

                } /* while ((void.. */

            }  /* for (i=.. */

            /* If matching Contact header(s) are found but the
             * header doesn't contain expires parameter, get the
             * expiration value from the Expires header. And
             * if Expires header is not present, get the expiration
             * value from the request.
             */
            if (has_our_contact && expiration == NOEXP) {
                if (expires) {
                    expiration = expires->ivalue;
                } else if (regc->expires_hdr) {
                    expiration = regc->expires_hdr->ivalue;
                } else {
                    /* We didn't request explicit expiration value,
                     * and server doesn't specify it either. This 
                     * shouldn't happen unless we have a broken
                     * registrar.
                     */
                    expiration = 3600;
                }
            }

        }

        /* If we still couldn't get matching Contact header(s), it means
         * there must be something wrong with the  registrar (e.g. it may
         * have modified the URI's in the response, which is prohibited).
         */
        if (expiration==NOEXP) {
            /* If the number of Contact headers in the response matches 
             * ours, they're all probably ours. Get the expiration
             * from there if this is the case, or from Expires header
             * if we don't have exact Contact header count, or
             * from the request as the last resort.
             */
            pj_size_t our_contact_cnt;

            our_contact_cnt = pj_list_size(&regc->contact_hdr_list);

            if (*contact_cnt == our_contact_cnt && *contact_cnt &&
                contacts[0]->expires != PJSIP_EXPIRES_NOT_SPECIFIED) 
            {
                expiration = contacts[0]->expires;
            } else if (expires)
                expiration = expires->ivalue;
            else if (regc->expires_hdr)
                expiration = regc->expires_hdr->ivalue;
            else
                expiration = 3600;
        }

    } else {
        /* Just assume that the unregistration has been successful. */
        expiration = 0;
    }

    /* Must have expiration value by now */
    pj_assert(expiration != NOEXP);

    return expiration;
}


/* Sending authentication */
static pj_status_t async_auth_send_impl(pjsip_auth_clt_sess *auth_sess,
                                        void *user_data,
                                        pjsip_tx_data *tdata)
{
    pj_status_t status;
    pjsip_regc *regc = (pjsip_regc*)user_data;

    PJ_UNUSED_ARG(auth_sess);

    status = pjsip_regc_send(regc, tdata);
    if (status != PJ_SUCCESS) {
        char errmsg[PJ_ERR_MSG_SIZE];
        pj_bool_t is_unreg = (regc->current_op == REGC_UNREGISTERING);
        pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg));
        /* call_callback() is invoked here without holding regc->lock.
         * This function is called either from within the async challenge
         * callback (lock already released before the callback fires) or
         * from outside it (no lock held by the caller), so this is safe
         * and consistent with the rest of the codebase.
         */
        call_callback(regc, status, 0, &reason, NULL, NOEXP, 0, NULL,
                      is_unreg);
    }

    return status;
}


/* Called when the application abandons a pending async auth challenge. */
static void async_auth_abandon_impl(pjsip_auth_clt_sess *auth_sess,
                                    void *user_data)
{
    pjsip_regc *regc = (pjsip_regc*)user_data;
    pj_bool_t is_unreg = (regc->current_op == REGC_UNREGISTERING);
    pj_str_t reason = { "Authentication abandoned", 24 };

    PJ_UNUSED_ARG(auth_sess);

    /* Called without regc->lock held (same convention as send_impl). */
    call_callback(regc, PJ_ECANCELLED, PJSIP_SC_UNAUTHORIZED, &reason,
                  NULL, NOEXP, 0, NULL, is_unreg);
}


static void regc_tsx_callback(void *token, pjsip_event *event)
{
    pj_status_t status;
    pjsip_regc *regc = (pjsip_regc*) token;
    pjsip_transaction *tsx = event->body.tsx_state.tsx;
    pj_bool_t handled = PJ_TRUE;
    pj_bool_t update_contact = PJ_FALSE;

    pjsip_regc_add_ref(regc);
    pj_lock_acquire(regc->lock);

    /* Decrement pending transaction counter. */
    pj_assert(regc->has_tsx);
    regc->has_tsx = PJ_FALSE;

    /* Add reference to the transport */
    if (tsx->transport != regc->last_transport) {
        if (regc->last_transport) {
            pjsip_transport_dec_ref(regc->last_transport);
            regc->last_transport = NULL;
        }

        if (tsx->transport) {
            regc->last_transport = tsx->transport;
            pjsip_transport_add_ref(regc->last_transport);
        }
    }

    if (regc->_delete_flag == 0 && regc->tsx_cb &&
        regc->current_op == REGC_REGISTERING)
    {
        struct pjsip_regc_tsx_cb_param param;

        param.contact_cnt = -1;
        cbparam_init(&param.cbparam, regc, PJ_SUCCESS, tsx->status_code,
                     &tsx->status_text,
                     (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ? 
                      event->body.tsx_state.src.rdata : NULL,
                     NOEXP, 0, NULL, PJ_FALSE);

        /* Call regc tsx callback before handling any response */
        pj_lock_release(regc->lock);
        (*regc->tsx_cb)(&param);
        pj_lock_acquire(regc->lock);

        if (param.contact_cnt >= 0) {
            /* Since we receive non-2xx response, it means that (some) contact
             * bindings haven't been established so we can safely remove these
             * contact headers. This is to avoid removing non-existent contact
             * bindings later.
             */
            if (tsx->status_code/100 != 2) {
                pjsip_contact_hdr *h;

                h = regc->contact_hdr_list.next;
                while (h != &regc->contact_hdr_list) {
                    pjsip_contact_hdr *next = h->next;

                    if (h->expires == PJSIP_EXPIRES_NOT_SPECIFIED) {
                        pj_list_erase(h);
                    }
                    h = next;
                }
            }

            /* Update contact address */
            pjsip_regc_update_contact(regc, param.contact_cnt, param.contact);
            update_contact = PJ_TRUE;
        }
    }

    /* Handle 401/407 challenge (even when _delete_flag is set) */
    if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED ||
        tsx->status_code == PJSIP_SC_UNAUTHORIZED)
    {
        pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
        pjsip_tx_data *tdata;
        pj_bool_t is_unreg;
        pjsip_auth_clt_async_on_chal_param chal_param = { 0 };

        /* Capture is_unreg but keep current_op alive so that
         * async auth callbacks can read it if needed.  It will be
         * reset to REGC_IDLE below (sync fallback) or by
         * pjsip_regc_send() on the async success path.
         */
        is_unreg = (regc->current_op == REGC_UNREGISTERING);

        if (update_contact) {
            pjsip_msg *msg;
            pjsip_hdr *hdr, *ins_hdr;
            pjsip_contact_hdr *chdr;

            /* Delete Contact headers, but we shouldn't delete headers
             * which are supposed to remove contact bindings since
             * we cannot reconstruct those headers.
             */
            msg = tsx->last_tx->msg;
            hdr = msg->hdr.next;
            ins_hdr = &msg->hdr;
            while (hdr != &msg->hdr) {
                pjsip_hdr *next = hdr->next;

                if (hdr->type == PJSIP_H_CONTACT) {
                    chdr = (pjsip_contact_hdr *)hdr;
                    if (chdr->expires != 0) {
                        pj_list_erase(hdr);
                        ins_hdr = next;
                    }
                }
                hdr = next;
            }

            /* Add Contact headers. */
            chdr = regc->contact_hdr_list.next;
            while (chdr != &regc->contact_hdr_list) {
                pj_list_insert_before(ins_hdr, (pjsip_hdr*)
                    pjsip_hdr_clone(tsx->last_tx->pool, chdr));
                chdr = chdr->next;
            }

            /* Also add bindings which are to be removed */
            while (!pj_list_empty(&regc->removed_contact_hdr_list)) {
                chdr = regc->removed_contact_hdr_list.next;
                pj_list_insert_before(ins_hdr, (pjsip_hdr*)
                    pjsip_hdr_clone(tsx->last_tx->pool, chdr));
                pj_list_erase(chdr);
            }
        }

        if (regc->_delete_flag != 0) {
            pjsip_auth_clt_set_parent(&regc->auth_sess, NULL);
        }

        /* Check if application handles the authentication.
         * Release the lock to prevent deadlock in case application calls
         * pjsip_auth_clt_async_send_req() synchronously from within the
         * callback (see ticket #2260 and #1247).
         * It should be safe to do this since the regc's refcount has been
         * incremented.
         */
        {
            pjsip_auth_clt_async_impl_token *auth_token;
            auth_token = PJ_POOL_ZALLOC_T(tsx->pool,
                                          pjsip_auth_clt_async_impl_token);
            auth_token->user_data    = regc;
            auth_token->send_impl    = &async_auth_send_impl;
            auth_token->abandon_impl = &async_auth_abandon_impl;
            auth_token->grp_lock     = tsx->grp_lock;
            pj_grp_lock_add_ref(tsx->grp_lock);

            pj_bzero(&chal_param, sizeof(chal_param));
            chal_param.rdata = rdata;
            chal_param.tdata = tsx->last_tx;
            pj_lock_release(regc->lock);
            status = pjsip_auth_clt_async_impl_on_challenge(
                                                    &regc->auth_sess,
                                                    auth_token, &chal_param);
            pj_lock_acquire(regc->lock);
            if (status != PJ_SUCCESS)
                pj_grp_lock_dec_ref(tsx->grp_lock);
        }
        if (status != PJ_SUCCESS) {
            /* Application does not handle the authentication, so let's
             * handle it here now.  Reset current_op now since we deferred
             * it above for the async path.
             */
            regc->current_op = REGC_IDLE;

            /* Reinit request with auth response. */
            status = pjsip_auth_clt_reinit_req( &regc->auth_sess,
                                                rdata, 
                                                tsx->last_tx,
                                                &tdata);

            /* Send the request. */
            if (status == PJ_SUCCESS && tdata) {
                /* Need to unlock the regc temporarily while sending the
                 * message to prevent deadlock (see ticket #2260 and #1247).
                 * It should be safe to do this since the regc's refcount has
                 * been incremented.
                 */
                pj_lock_release(regc->lock);
                status = pjsip_regc_send(regc, tdata);
                pj_lock_acquire(regc->lock);
            }

            if (status != PJ_SUCCESS) {
                /* Only call callback if application is still interested
                 * in it.
                 */
                if (regc->_delete_flag == 0) {
                    /* Should be safe to release the lock temporarily.
                     * We do this to avoid deadlock. 
                     */
                    pj_lock_release(regc->lock);
                    call_callback(regc, status, tsx->status_code, 
                                  &rdata->msg_info.msg->line.status.reason,
                                  rdata, NOEXP, 0, NULL, is_unreg);
                    pj_lock_acquire(regc->lock);
                }
            }
        }

    } else if (regc->_delete_flag) {

        /* User has called pjsip_regc_destroy(), so don't call callback. 
         * This regc will be destroyed later in this function.
         */

        /* Just reset current op */
        regc->current_op = REGC_IDLE;

    } else if (tsx->status_code == PJSIP_SC_INTERVAL_TOO_BRIEF &&
               regc->current_op == REGC_REGISTERING)
    {
        /* Handle 423 response automatically:
         *  - set requested expiration to Min-Expires header, ONLY IF
         *    the original request is a registration (as opposed to
         *    unregistration) and the requested expiration was indeed
         *    lower than Min-Expires)
         *  - resend the request
         */
        pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
        pjsip_min_expires_hdr *me_hdr;
        pjsip_tx_data *tdata;
        pj_uint32_t min_exp;

        /* reset current op */
        regc->current_op = REGC_IDLE;

        /* Update requested expiration */
        me_hdr = (pjsip_min_expires_hdr*)
                 pjsip_msg_find_hdr(rdata->msg_info.msg,
                                    PJSIP_H_MIN_EXPIRES, NULL);
        if (me_hdr) {
            min_exp = me_hdr->ivalue;
        } else {
            /* Broken server, Min-Expires doesn't exist.
             * Just guestimate then, BUT ONLY if if this is the
             * first time we received such response.
             */
            enum {
                /* Note: changing this value would require changing couple of
                 *       Python test scripts.
                 */
                UNSPECIFIED_MIN_EXPIRES = 3601
            };
            if (!regc->expires_hdr ||
                 regc->expires_hdr->ivalue != UNSPECIFIED_MIN_EXPIRES)
            {
                min_exp = UNSPECIFIED_MIN_EXPIRES;
            } else {
                handled = PJ_FALSE;
                PJ_LOG(4,(THIS_FILE, "Registration failed: 423 response "
                                     "without Min-Expires header is invalid"));
                goto handle_err;
            }
        }

        if (regc->expires_hdr && regc->expires_hdr->ivalue >= min_exp) {
            /* But we already send with greater expiration time, why does
             * the server send us with 423? Oh well, just fail the request.
             */
            handled = PJ_FALSE;
            PJ_LOG(4,(THIS_FILE, "Registration failed: invalid "
                                 "Min-Expires header value in response"));
            goto handle_err;
        }

        set_expires(regc, min_exp);

        status = pjsip_regc_register(regc, regc->auto_reg, &tdata);
        if (status == PJ_SUCCESS) {
            /* Need to unlock the regc temporarily while sending the message
             * to prevent deadlock (see ticket #2260 and #1247).
             * It should be safe to do this since the regc's refcount has been
             * incremented.
             */
            pj_lock_release(regc->lock);
            status = pjsip_regc_send(regc, tdata);
            pj_lock_acquire(regc->lock);
        }

        if (status != PJ_SUCCESS) {
            /* Only call callback if application is still interested
             * in it.
             */
            if (!regc->_delete_flag) {
                /* Should be safe to release the lock temporarily.
                 * We do this to avoid deadlock.
                 */
                pj_lock_release(regc->lock);
                call_callback(regc, status, tsx->status_code,
                              &rdata->msg_info.msg->line.status.reason,
                              rdata, NOEXP, 0, NULL, PJ_FALSE);
                pj_lock_acquire(regc->lock);
            }
        }

    } else {
        handled = PJ_FALSE;
    }

handle_err:
    if (!handled) {
        pjsip_rx_data *rdata;
        pj_uint32_t expiration = NOEXP;
        unsigned contact_cnt = 0;
        pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT];
        pj_bool_t is_unreg;

        if (tsx->status_code/100 == 2) {

            rdata = event->body.tsx_state.src.rdata;

            /* Calculate expiration */
            expiration = calculate_response_expiration(regc, rdata, 
                                                       &contact_cnt,
                                                       PJSIP_REGC_MAX_CONTACT,
                                                       contact);

#if PJSIP_REGISTER_ALLOW_EXP_REFRESH
            if (expiration == 0 && regc->current_op != REGC_UNREGISTERING) {
                /* Response contain expires contact param 0, allow client to
                 * continue refresh registration.
                 * Refer to: https://github.com/pjsip/pjproject/pull/2809
                 */
                const pjsip_msg *msg = rdata->msg_info.msg;
                const pjsip_expires_hdr *expires;
                expires = (const pjsip_expires_hdr*) pjsip_msg_find_hdr(msg, 
                                                        PJSIP_H_EXPIRES, NULL);

                if (expires) {
                    expiration = expires->ivalue;
                } else if (regc->expires_hdr && regc->expires_hdr->ivalue) {
                    expiration = regc->expires_hdr->ivalue;
                } else {
                    expiration = 3600;
                }
                PJ_LOG(4, (THIS_FILE, "Modify response's expiration from 0 "
                           "to %d", expiration));
            }
#endif
            /* Schedule next registration */
            schedule_registration(regc, expiration);

        } else {
            rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ? 
                        event->body.tsx_state.src.rdata : NULL;
        }

        /* Update registration */
        // if (expiration==NOEXP) expiration=-1;
        regc->expires = expiration;

        /* Mark operation as complete */
        is_unreg = (regc->current_op == REGC_UNREGISTERING);
        regc->current_op = REGC_IDLE;

        /* Call callback. */
        /* Should be safe to release the lock temporarily.
         * We do this to avoid deadlock. 
         */
        pj_lock_release(regc->lock);
        call_callback(regc, PJ_SUCCESS, tsx->status_code, 
                      (rdata ? &rdata->msg_info.msg->line.status.reason 
                        : &tsx->status_text),
                      rdata, expiration, 
                      contact_cnt, contact, is_unreg);
        pj_lock_acquire(regc->lock);
    }

    pj_lock_release(regc->lock);

    /* Delete the record if user destroy regc during the callback. */
    pjsip_regc_dec_ref(regc);
}

PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
{
    pj_status_t status;
    pjsip_cseq_hdr *cseq_hdr;
    pjsip_expires_hdr *expires_hdr;
    pj_int32_t cseq;

    pjsip_regc_add_ref(regc);
    pj_lock_acquire(regc->lock);

    /* Make sure we don't have pending transaction. */
    if (regc->has_tsx) {
        PJ_LOG(4,(THIS_FILE, "Unable to send request, regc has another "
                             "transaction pending"));
        pjsip_tx_data_dec_ref( tdata );
        pj_lock_release(regc->lock);
        pj_atomic_dec(regc->busy_ctr);
        return PJSIP_EBUSY;
    }

    /* Just regc->has_tsx check above should be enough. This assertion check
     * may cause problem, e.g: when regc_tsx_callback() invokes callback,
     * lock is released and 'has_tsx' is set to FALSE and 'current_op' has
     * not been updated to REGC_IDLE yet.
     */
    //pj_assert(regc->current_op == REGC_IDLE);

    /* Invalidate message buffer. */
    pjsip_tx_data_invalidate_msg(tdata);

    /* Increment CSeq */
    cseq = ++regc->cseq_hdr->cseq;
    cseq_hdr = (pjsip_cseq_hdr*)
               pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
    cseq_hdr->cseq = cseq;

    /* Find Expires header */
    expires_hdr = (pjsip_expires_hdr*)
                  pjsip_msg_find_hdr(tdata->msg, PJSIP_H_EXPIRES, NULL);

    /* Bind to transport selector */
    pjsip_tx_data_set_transport(tdata, &regc->tp_sel);

    regc->has_tsx = PJ_TRUE;

    /* Set current operation based on the value of Expires header */
    if (expires_hdr && expires_hdr->ivalue==0)
        regc->current_op = REGC_UNREGISTERING;
    else
        regc->current_op = REGC_REGISTERING;
    
    if (expires_hdr && expires_hdr->ivalue)
        regc->expires_requested = expires_hdr->ivalue;

    /* Prevent deletion of tdata, e.g: when something wrong in sending,
     * we need tdata to retrieve the transport.
     */
    pjsip_tx_data_add_ref(tdata);

    /* If via_addr is set, use this address for the Via header. */
    if (regc->via_addr.host.slen > 0) {
        tdata->via_addr = regc->via_addr;
        tdata->via_tp = regc->via_tp;
    }

    /* Need to unlock the regc temporarily while sending the message to
     * prevent deadlock (https://github.com/pjsip/pjproject/issues/1247).
     * It should be safe to do this since the regc's refcount has been
     * incremented.
     */
    pj_lock_release(regc->lock);

    /* Now send the message */
    status = pjsip_endpt_send_request(regc->endpt, tdata, REGC_TSX_TIMEOUT,
                                      regc, &regc_tsx_callback);
 
     /* Reacquire the lock */
    pj_lock_acquire(regc->lock);

    if (status!=PJ_SUCCESS) {
        /* On failure, regc_tsx_callback() may not be called, so we need
         * to reset regc->has_tsx here (see also ticket #1936).
         * But note that we are releasing the lock when sending the request
         * above, so there can be a race with another registration send.
         */
        if (cseq == regc->cseq_hdr->cseq) {
            regc->has_tsx = PJ_FALSE;
        }

        PJ_PERROR(4,(THIS_FILE, status, "Error sending request"));
    }

    /* Get last transport used and add reference to it */
    //if (tdata->tp_info.transport != regc->last_transport &&
    //    status==PJ_SUCCESS)
    //{
    //    if (regc->last_transport) {
    //        pjsip_transport_dec_ref(regc->last_transport);
    //        regc->last_transport = NULL;
    //    }

    //    if (tdata->tp_info.transport) {
    //        regc->last_transport = tdata->tp_info.transport;
    //        pjsip_transport_add_ref(regc->last_transport);
    //    }
    //}
    // Update: don't add_ref() or use the transport info from tdata other than
    // for informational purpose (e.g: comparing the pointers to check
    // if a disconnected transport is the registration transport), see
    // pjsip_tx_data.tp_info docs about its valid period.
    // Note that the send operation may be async and transport may
    // have been destroyed here.
    regc->info_transport = status==PJ_SUCCESS? tdata->tp_info.transport :
                                               NULL;

    /* Release tdata */
    pjsip_tx_data_dec_ref(tdata);

    pj_lock_release(regc->lock);

    /* Delete the record if user destroy regc during the callback. */
    pjsip_regc_dec_ref(regc);

    return status;
}

PJ_DEF(pj_status_t) pjsip_regc_set_auth_sess( pjsip_regc *regc,
                                              pjsip_auth_clt_sess *session )
{
    PJ_ASSERT_RETURN(regc, PJ_EINVAL);
    return pjsip_auth_clt_set_parent(&regc->auth_sess, session);
}


PJ_DEF(pjsip_auth_clt_sess*) pjsip_regc_get_auth_sess( pjsip_regc *regc )
{
    PJ_ASSERT_RETURN(regc, NULL);
    return &regc->auth_sess;
}
