/* 
 * 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-simple/evsub.h>
#include <pjsip-simple/evsub_msg.h>
#include <pjsip-simple/errno.h>
#include <pjsip/sip_errno.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_dialog.h>
#include <pjsip/sip_auth.h>
#include <pjsip/sip_transaction.h>
#include <pjsip/sip_event.h>
#include <pj/assert.h>
#include <pj/guid.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>


#define THIS_FILE       "evsub.c"

/*
 * Global constant
 */

/* Let's define this enum, so that it'll trigger compilation error
 * when somebody define the same enum in sip_msg.h
 */
enum
{
    PJSIP_SUBSCRIBE_METHOD = PJSIP_OTHER_METHOD,
    PJSIP_NOTIFY_METHOD = PJSIP_OTHER_METHOD
};

PJ_DEF_DATA(const pjsip_method) pjsip_subscribe_method = 
{
    (pjsip_method_e) PJSIP_SUBSCRIBE_METHOD,
    { "SUBSCRIBE", 9 }
};

PJ_DEF_DATA(const pjsip_method) pjsip_notify_method = 
{
    (pjsip_method_e) PJSIP_NOTIFY_METHOD,
    { "NOTIFY", 6 }
};

/**
 * SUBSCRIBE method constant.
 */
PJ_DEF(const pjsip_method*) pjsip_get_subscribe_method()
{
    return &pjsip_subscribe_method;
}

/**
 * NOTIFY method constant.
 */
PJ_DEF(const pjsip_method*) pjsip_get_notify_method()
{
    return &pjsip_notify_method;
}


/*
 * Static prototypes.
 */
static void        mod_evsub_on_tsx_state(pjsip_transaction*, pjsip_event*);
static pj_status_t mod_evsub_unload(void);


/*
 * State names.
 */
static pj_str_t evsub_state_names[] = 
{
    { "NULL",       4},
    { "SENT",       4},
    { "ACCEPTED",   8},
    { "PENDING",    7},
    { "ACTIVE",     6},
    { "TERMINATED", 10},
    { "UNKNOWN",    7}
};

/*
 * Timer constants.
 */

/* Number of seconds to send SUBSCRIBE before the actual expiration */
#define TIME_UAC_REFRESH        PJSIP_EVSUB_TIME_UAC_REFRESH

/* Time to wait for the final NOTIFY after sending unsubscription */
#define TIME_UAC_TERMINATE      PJSIP_EVSUB_TIME_UAC_TERMINATE

/* If client responds NOTIFY with non-2xx final response (such as 401),
 * wait for this seconds for further NOTIFY, otherwise client will
 * unsubscribe
 */
#define TIME_UAC_WAIT_NOTIFY    PJSIP_EVSUB_TIME_UAC_WAIT_NOTIFY


/*
 * Timer id
 */
enum timer_id
{
    /* No timer. */
    TIMER_TYPE_NONE,

    /* Time to refresh client subscription. 
     * The action is to call on_client_refresh() callback.
     */
    TIMER_TYPE_UAC_REFRESH,

    /* UAS timeout after to subscription refresh. 
     * The action is to call on_server_timeout() callback.
     */
    TIMER_TYPE_UAS_TIMEOUT,

    /* UAC waiting for final NOTIFY after unsubscribing 
     * The action is to terminate.
     */
    TIMER_TYPE_UAC_TERMINATE,

    /* UAC waiting for further NOTIFY after sending non-2xx response to 
     * NOTIFY. The action is to unsubscribe.
     */
    TIMER_TYPE_UAC_WAIT_NOTIFY,

    /* Max nb of timer types. */
    TIMER_TYPE_MAX
};

static const char *timer_names[] = 
{
    "None",
    "UAC_REFRESH",
    "UAS_TIMEOUT",
    "UAC_TERMINATE",
    "UAC_WAIT_NOTIFY",
    "INVALID_TIMER"
};

/*
 * Definition of event package.
 */
struct evpkg
{
    PJ_DECL_LIST_MEMBER(struct evpkg);

    pj_str_t             pkg_name;
    pjsip_module        *pkg_mod;
    unsigned             pkg_expires;
    pjsip_accept_hdr    *pkg_accept;
};


/*
 * Event subscription module (mod-evsub).
 */
static struct mod_evsub
{
    pjsip_module             mod;
    pj_pool_t               *pool;
    pjsip_endpoint          *endpt;
    struct evpkg             pkg_list;
    pjsip_allow_events_hdr  *allow_events_hdr;

} mod_evsub = 
{
    {
        NULL, NULL,                         /* prev, next.              */
        { "mod-evsub", 9 },                 /* Name.                    */
        -1,                                 /* Id                       */
        PJSIP_MOD_PRIORITY_DIALOG_USAGE,    /* Priority                 */
        NULL,                               /* load()                   */
        NULL,                               /* start()                  */
        NULL,                               /* stop()                   */
        &mod_evsub_unload,                  /* unload()                 */
        NULL,                               /* on_rx_request()          */
        NULL,                               /* on_rx_response()         */
        NULL,                               /* on_tx_request.           */
        NULL,                               /* on_tx_response()         */
        &mod_evsub_on_tsx_state,            /* on_tsx_state()           */
    }
};


/*
 * Event subscription session.
 */
struct pjsip_evsub
{
    char                  obj_name[PJ_MAX_OBJ_NAME]; /**< Name.             */
    pj_pool_t            *pool;         /**< Pool.                          */
    pjsip_endpoint       *endpt;        /**< Endpoint instance.             */
    pjsip_dialog         *dlg;          /**< Underlying dialog.             */
    struct evpkg         *pkg;          /**< The event package.             */
    unsigned              option;       /**< Options.                       */
    pjsip_evsub_user      user;         /**< Callback.                      */
    pj_bool_t             call_cb;      /**< Notify callback?               */
    pjsip_role_e          role;         /**< UAC=subscriber, UAS=notifier   */
    pjsip_evsub_state     state;        /**< Subscription state.            */
    pj_str_t              state_str;    /**< String describing the state.   */
    pjsip_evsub_state     dst_state;    /**< Pending state to be set.       */
    pj_str_t              dst_state_str;/**< Pending state to be set.       */
    pj_str_t              term_reason;  /**< Termination reason.            */
    pjsip_method          method;       /**< Method that established subscr.*/
    pjsip_event_hdr      *event;        /**< Event description.             */
    pjsip_expires_hdr    *expires;      /**< Expires header                 */
    pjsip_accept_hdr     *accept;       /**< Local Accept header.           */
    pjsip_hdr             sub_hdr_list; /**< User-defined header.           */

    pj_time_val           refresh_time; /**< Time to refresh.               */
    pj_timer_entry        timer;        /**< Internal timer.                */
    int                   pending_tsx;  /**< Number of pending transactions.*/
    pjsip_transaction    *pending_sub;  /**< Pending UAC SUBSCRIBE tsx.     */
    pj_timer_entry       *pending_sub_timer; /**< Stop pending sub timer.   */
    pjsip_tx_data        *pending_notify;/**< Pending NOTIFY to be sent.    */
    pj_bool_t             calling_on_rx_refresh;/**< Inside on_rx_refresh()?*/
    pj_grp_lock_t        *grp_lock;     /* Session group lock       */

    void                 *mod_data[PJSIP_MAX_MODULE];   /**< Module data.   */
};


/*
 * This is the structure that will be "attached" to dialog.
 * The purpose is to allow multiple subscriptions inside a dialog.
 */
struct dlgsub
{
    PJ_DECL_LIST_MEMBER(struct dlgsub);
    pjsip_evsub *sub;
};


/* Static vars. */
static const pj_str_t STR_EVENT        = { "Event", 5 };
static const pj_str_t STR_EVENT_S      = { "o", 1 };
static const pj_str_t STR_SUB_STATE    = { "Subscription-State", 18 };
static const pj_str_t STR_TERMINATED   = { "terminated", 10 };
static const pj_str_t STR_ACTIVE       = { "active", 6 };
static const pj_str_t STR_PENDING      = { "pending", 7 };
static const pj_str_t STR_TIMEOUT      = { "timeout", 7};
static const pj_str_t STR_RETRY_AFTER  = { "Retry-After", 11 };


/*
 * On unload module.
 */
static pj_status_t mod_evsub_unload(void)
{
    pjsip_endpt_release_pool(mod_evsub.endpt, mod_evsub.pool);
    mod_evsub.pool = NULL;

    return PJ_SUCCESS;
}

/* Proto for pjsipsimple_strerror().
 * Defined in errno.c
 */
PJ_DECL(pj_str_t) pjsipsimple_strerror( pj_status_t statcode, 
                                        char *buf, pj_size_t bufsize );

/*
 * Init and register module.
 */
PJ_DEF(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt)
{
    pj_status_t status;
    pj_str_t method_tags[] = {
        { "SUBSCRIBE", 9},
        { "NOTIFY", 6}
    };

    status = pj_register_strerror(PJSIP_SIMPLE_ERRNO_START,
                                  PJ_ERRNO_SPACE_SIZE,
                                  &pjsipsimple_strerror);
    pj_assert(status == PJ_SUCCESS);

    PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
    PJ_ASSERT_RETURN(mod_evsub.mod.id == -1, PJ_EINVALIDOP);

    /* Keep endpoint for future reference: */
    mod_evsub.endpt = endpt;

    /* Init event package list: */
    pj_list_init(&mod_evsub.pkg_list);

    /* Create pool: */
    mod_evsub.pool = pjsip_endpt_create_pool(endpt, "evsub",
                                        PJSIP_POOL_EVSUB_LEN,
                                        PJSIP_POOL_EVSUB_INC);
    if (!mod_evsub.pool)
        return PJ_ENOMEM;

    /* Register module: */
    status = pjsip_endpt_register_module(endpt, &mod_evsub.mod);
    if (status  != PJ_SUCCESS)
        goto on_error;
 
    /* Create Allow-Events header: */
    mod_evsub.allow_events_hdr = pjsip_allow_events_hdr_create(mod_evsub.pool);

    /* Register SIP-event specific headers parser: */
    status = pjsip_evsub_init_parser();
    if (status  != PJ_SUCCESS)
        goto on_error;

    /* Register new methods SUBSCRIBE and NOTIFY in Allow-ed header */
    pjsip_endpt_add_capability(endpt, &mod_evsub.mod, PJSIP_H_ALLOW, NULL,
                               2, method_tags);

    /* Done. */
    return PJ_SUCCESS;

on_error:
    if (mod_evsub.pool) {
        pjsip_endpt_release_pool(endpt, mod_evsub.pool);
        mod_evsub.pool = NULL;
    }
    mod_evsub.endpt = NULL;
    return status;
}


/*
 * Get the instance of the module.
 */
PJ_DEF(pjsip_module*) pjsip_evsub_instance(void)
{
    PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, NULL);

    return &mod_evsub.mod;
}


/*
 * Get the event subscription instance in the transaction.
 */
PJ_DEF(pjsip_evsub*) pjsip_tsx_get_evsub(const pjsip_transaction *tsx)
{
    return (pjsip_evsub*) tsx->mod_data[mod_evsub.mod.id];
}


/*
 * Set event subscription's module data.
 */
PJ_DEF(void) pjsip_evsub_set_mod_data( pjsip_evsub *sub, unsigned mod_id,
                                       void *data )
{
    PJ_ASSERT_ON_FAIL(mod_id < PJSIP_MAX_MODULE, return);
    sub->mod_data[mod_id] = data;
}


/*
 * Get event subscription's module data.
 */
PJ_DEF(void*) pjsip_evsub_get_mod_data( const pjsip_evsub *sub,
                                        unsigned mod_id )
{
    PJ_ASSERT_RETURN(mod_id < PJSIP_MAX_MODULE, NULL);
    return sub->mod_data[mod_id];
}


/*
 * Find registered event package with matching name.
 */
static struct evpkg* find_pkg(const pj_str_t *event_name)
{
    struct evpkg *pkg;

    pkg = mod_evsub.pkg_list.next;
    while (pkg != &mod_evsub.pkg_list) {

        if (pj_stricmp(&pkg->pkg_name, event_name) == 0) {
            return pkg;
        }

        pkg = pkg->next;
    }

    return NULL;
}

/*
 * Register an event package
 */
PJ_DEF(pj_status_t) pjsip_evsub_register_pkg( pjsip_module *pkg_mod,
                                              const pj_str_t *event_name,
                                              unsigned expires,
                                              unsigned accept_cnt,
                                              const pj_str_t accept[])
{
    struct evpkg *pkg;
    unsigned i;

    PJ_ASSERT_RETURN(pkg_mod && event_name, PJ_EINVAL);
    
    /* Make sure accept_cnt < PJ_ARRAY_SIZE(pkg->pkg_accept->values) */
    PJ_ASSERT_RETURN(accept_cnt <= PJSIP_GENERIC_ARRAY_MAX_COUNT, 
                     PJ_ETOOMANY);

    /* Make sure evsub module has been initialized */
    PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, PJ_EINVALIDOP);

    /* Make sure no module with the specified name already registered: */
    if (find_pkg(event_name) != NULL)
        return PJSIP_SIMPLE_EPKGEXISTS;

    /* Create new event package: */

    pkg = PJ_POOL_ALLOC_T(mod_evsub.pool, struct evpkg);
    pkg->pkg_mod = pkg_mod;
    pkg->pkg_expires = expires;
    pj_strdup(mod_evsub.pool, &pkg->pkg_name, event_name);

    pkg->pkg_accept = pjsip_accept_hdr_create(mod_evsub.pool);
    pkg->pkg_accept->count = accept_cnt;
    for (i=0; i<accept_cnt; ++i) {
        pj_strdup(mod_evsub.pool, &pkg->pkg_accept->values[i], &accept[i]);
    }

    /* Add to package list: */

    pj_list_push_back(&mod_evsub.pkg_list, pkg);

    /* Add to Allow-Events header: */

    if (mod_evsub.allow_events_hdr->count !=
        PJ_ARRAY_SIZE(mod_evsub.allow_events_hdr->values))
    {
        mod_evsub.allow_events_hdr->values[mod_evsub.allow_events_hdr->count] =
            pkg->pkg_name;
        ++mod_evsub.allow_events_hdr->count;
    }
    
    /* Add to endpoint's Accept header */
    pjsip_endpt_add_capability(mod_evsub.endpt, &mod_evsub.mod,
                               PJSIP_H_ACCEPT, NULL,
                               pkg->pkg_accept->count,
                               pkg->pkg_accept->values);


    /* Done */

    PJ_LOG(5,(THIS_FILE, "Event pkg \"%.*s\" registered by %.*s",
              (int)event_name->slen, event_name->ptr,
              (int)pkg_mod->name.slen, pkg_mod->name.ptr));

    return PJ_SUCCESS;
}


/*
 * Retrieve Allow-Events header
 */
PJ_DEF(const pjsip_hdr*) pjsip_evsub_get_allow_events_hdr(
                             const pjsip_module *m)
{
    struct mod_evsub *mod;

    if (m == NULL)
        m = pjsip_evsub_instance();

    mod = (struct mod_evsub*)m;

    return (pjsip_hdr*) mod->allow_events_hdr;
}


/*
 * Update expiration time.
 */
static void update_expires( pjsip_evsub *sub, pj_uint32_t interval )
{
    pj_gettimeofday(&sub->refresh_time);
    sub->refresh_time.sec += interval;
}


/* 
 * Schedule timer.
 */
static void set_timer( pjsip_evsub *sub, int timer_id,
                       pj_uint32_t seconds)
{
    if (sub->timer.id != TIMER_TYPE_NONE) {
        PJ_LOG(5,(sub->obj_name, "%s %s timer", 
                  (timer_id==sub->timer.id ? "Updating" : "Cancelling"),
                  timer_names[sub->timer.id]));
        pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
        sub->timer.id = TIMER_TYPE_NONE;
    }

    if (timer_id != TIMER_TYPE_NONE && seconds != PJSIP_EXPIRES_NOT_SPECIFIED)
    {
        pj_time_val timeout;

        PJ_ASSERT_ON_FAIL(timer_id>TIMER_TYPE_NONE && timer_id<TIMER_TYPE_MAX,
                          return);

        timeout.sec = seconds;
        timeout.msec = 0;

        pj_timer_heap_schedule_w_grp_lock(
                            pjsip_endpt_get_timer_heap(sub->endpt),
                            &sub->timer, &timeout, timer_id, sub->grp_lock);

        PJ_LOG(5,(sub->obj_name, "Timer %s scheduled in %ld seconds", 
                  timer_names[sub->timer.id], timeout.sec));
    }
}


/*
 * Set event subscription UAS timout.
 */
PJ_DEF(void) pjsip_evsub_uas_set_timeout(pjsip_evsub *sub, pj_uint32_t seconds)
{
    set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, (pj_int32_t)seconds);
}


/*
 * Destructor.
 */
static void evsub_on_destroy(void *obj)
{
    pjsip_evsub *sub = (pjsip_evsub*)obj;

    /* Decrement dialog's session */
    pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod);
}


/*
 * Destroy session.
 */
static void evsub_destroy( pjsip_evsub *sub )
{
    struct dlgsub *dlgsub_head, *dlgsub;

    PJ_LOG(4,(sub->obj_name, "Subscription destroyed"));

    /* Kill timer */
    set_timer(sub, TIMER_TYPE_NONE, 0);

    /* Kill timer for stopping pending sub (see ticket #1807) */
    if (sub->pending_sub_timer && sub->pending_sub_timer->id == 1) {
        pjsip_endpt_cancel_timer(sub->endpt, sub->pending_sub_timer);
        sub->pending_sub_timer->id = 0;
        sub->pending_sub_timer = NULL;
    }

    /* Remove this session from dialog's list of subscription */
    dlgsub_head = (struct dlgsub *) sub->dlg->mod_data[mod_evsub.mod.id];
    dlgsub = dlgsub_head->next;
    while (dlgsub != dlgsub_head) {
        
        if (dlgsub->sub == sub) {
            pj_list_erase(dlgsub);
            break;
        }

        dlgsub = dlgsub->next;
    }

    pj_grp_lock_dec_ref(sub->grp_lock);
}

/*
 * Set subscription session state.
 */
static void set_state( pjsip_evsub *sub, pjsip_evsub_state state,
                       const pj_str_t *state_str, pjsip_event *event,
                       const pj_str_t *reason)
{
    pjsip_evsub_state prev_state = sub->state;
    pj_str_t old_state_str = sub->state_str;
    pjsip_event dummy_event;

    sub->state = state;

    if (state_str && state_str->slen)
        pj_strdup_with_null(sub->pool, &sub->state_str, state_str);
    else
        sub->state_str = evsub_state_names[state];

    if (reason && sub->term_reason.slen==0)
        pj_strdup(sub->pool, &sub->term_reason, reason);

    PJ_LOG(4,(sub->obj_name, 
              "Subscription state changed %.*s --> %.*s",
              (int)old_state_str.slen,
              old_state_str.ptr,
              (int)sub->state_str.slen,
              sub->state_str.ptr));
    pj_log_push_indent();

    /* don't call the callback with NULL event, it may crash the app! */
    if (!event) {
        PJSIP_EVENT_INIT_USER(dummy_event, 0, 0, 0, 0);
        event = &dummy_event;
    }

    if (sub->user.on_evsub_state && sub->call_cb)
        (*sub->user.on_evsub_state)(sub, event);

    if (state == PJSIP_EVSUB_STATE_TERMINATED &&
        prev_state != PJSIP_EVSUB_STATE_TERMINATED) 
    {
        /* Kill any timer. */
        set_timer(sub, TIMER_TYPE_NONE, 0);

        /* We must not destroy evsub if we're still calling the callback. */
        if (sub->pending_tsx == 0 && !sub->calling_on_rx_refresh) {
            evsub_destroy(sub);
        }
    }

    pj_log_pop_indent();
}


/*
 * Timer callback.
 */
static void on_timer( pj_timer_heap_t *timer_heap,
                      struct pj_timer_entry *entry)
{
    pjsip_evsub *sub;
    int timer_id;

    PJ_UNUSED_ARG(timer_heap);

    sub = (pjsip_evsub*) entry->user_data;

    pjsip_dlg_inc_lock(sub->dlg);

    /* If this timer entry has just been rescheduled or cancelled
     * while waiting for dialog mutex, just return (see #1885 scenario 1).
     */
    if (pj_timer_entry_running(entry) || entry->id == TIMER_TYPE_NONE) {
        pjsip_dlg_dec_lock(sub->dlg);
        return;
    }

    timer_id = entry->id;
    entry->id = TIMER_TYPE_NONE;

    switch (timer_id) {

    case TIMER_TYPE_UAC_REFRESH:
        /* Time for UAC to refresh subscription */
        if (sub->user.on_client_refresh && sub->call_cb) {
            (*sub->user.on_client_refresh)(sub);
        } else {
            pjsip_tx_data *tdata;
            pj_status_t status;

            PJ_LOG(5,(sub->obj_name, "Refreshing subscription."));
            pj_log_push_indent();
            status = pjsip_evsub_initiate(sub, NULL, 
                                          sub->expires->ivalue,
                                          &tdata);
            if (status == PJ_SUCCESS)
                pjsip_evsub_send_request(sub, tdata);

            pj_log_pop_indent();
        }
        break;

    case TIMER_TYPE_UAS_TIMEOUT:
        /* Refresh from UAC has not been received */
        if (sub->user.on_server_timeout && sub->call_cb) {
            (*sub->user.on_server_timeout)(sub);
        } else {
            pjsip_tx_data *tdata;
            pj_status_t status;

            PJ_LOG(5,(sub->obj_name, "Timeout waiting for refresh. "
                                     "Sending NOTIFY to terminate."));
            pj_log_push_indent();
            status = pjsip_evsub_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, 
                                         NULL, &STR_TIMEOUT, &tdata);
            if (status == PJ_SUCCESS)
                pjsip_evsub_send_request(sub, tdata);

            pj_log_pop_indent();
        }
        break;

    case TIMER_TYPE_UAC_TERMINATE:
        {
            pj_str_t timeout = {"timeout", 7};

            PJ_LOG(5,(sub->obj_name, "Timeout waiting for final NOTIFY. "
                                     "Terminating.."));
            pj_log_push_indent();
            set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, 
                      &timeout);
            pj_log_pop_indent();
        }
        break;

    case TIMER_TYPE_UAC_WAIT_NOTIFY:
        {
            pjsip_tx_data *tdata;
            pj_status_t status;

            PJ_LOG(5,(sub->obj_name, 
                     "Timeout waiting for subsequent NOTIFY (we did "
                     "send non-2xx response for previous NOTIFY). "
                     "Unsubscribing.."));
            pj_log_push_indent();
            status = pjsip_evsub_initiate( sub, NULL, 0, &tdata);
            if (status == PJ_SUCCESS)
                pjsip_evsub_send_request(sub, tdata);

            pj_log_pop_indent();
        }
        break;

    default:
        pj_assert(!"Invalid timer id");
    }

    pjsip_dlg_dec_lock(sub->dlg);
}


/*
 * Create subscription session, used for both client and notifier.
 */
static pj_status_t evsub_create( pjsip_dialog *dlg,
                                 pjsip_role_e role,
                                 const pjsip_evsub_user *user_cb,
                                 const pj_str_t *event,
                                 unsigned option,
                                 pjsip_evsub **p_evsub )
{
    pjsip_evsub *sub;
    struct evpkg *pkg;
    struct dlgsub *dlgsub_head, *dlgsub;
    pj_status_t status;

    /* Make sure there's package register for the event name: */

    pkg = find_pkg(event);
    if (pkg == NULL)
        return PJSIP_SIMPLE_ENOPKG;


    /* Must lock dialog before using pool etc. */
    pjsip_dlg_inc_lock(dlg);

    /* Init attributes: */

    sub = PJ_POOL_ZALLOC_T(dlg->pool, struct pjsip_evsub);
    sub->pool = dlg->pool;
    sub->endpt = dlg->endpt;
    sub->dlg = dlg;
    sub->pkg = pkg;
    sub->role = role;
    sub->call_cb = PJ_TRUE;
    sub->option = option;
    sub->state = PJSIP_EVSUB_STATE_NULL;
    sub->state_str = evsub_state_names[sub->state];
    sub->expires = pjsip_expires_hdr_create(sub->pool, pkg->pkg_expires);
    sub->accept = (pjsip_accept_hdr*) 
                  pjsip_hdr_clone(sub->pool, pkg->pkg_accept);
    pj_list_init(&sub->sub_hdr_list);

    sub->timer.user_data = sub;
    sub->timer.cb = &on_timer;

    /* Set name. */
    pj_ansi_snprintf(sub->obj_name, PJ_ARRAY_SIZE(sub->obj_name),
                     "evsub%p", sub);


    /* Copy callback, if any: */
    if (user_cb)
        pj_memcpy(&sub->user, user_cb, sizeof(pjsip_evsub_user));


    /* Create Event header: */
    sub->event = pjsip_event_hdr_create(sub->pool);
    pj_strdup(sub->pool, &sub->event->event_type, event);


    /* Check if another subscription has been registered to the dialog. In
     * that case, just add ourselves to the subscription list, otherwise
     * create and register a new subscription list.
     */
    if (pjsip_dlg_has_usage(dlg, &mod_evsub.mod)) {
        dlgsub_head = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id];
        dlgsub = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
        dlgsub->sub = sub;
        pj_list_push_back(dlgsub_head, dlgsub);
    } else {
        dlgsub_head = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
        dlgsub = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
        dlgsub->sub = sub;

        pj_list_init(dlgsub_head);
        pj_list_push_back(dlgsub_head, dlgsub);


        /* Register as dialog usage: */

        status = pjsip_dlg_add_usage(dlg, &mod_evsub.mod, dlgsub_head);
        if (status != PJ_SUCCESS) {
            pjsip_dlg_dec_lock(dlg);
            return status;
        }
    }

    PJ_LOG(5,(sub->obj_name, "%s subscription created, using dialog %s",
              (role==PJSIP_ROLE_UAC ? "UAC" : "UAS"),
              dlg->obj_name));

    *p_evsub = sub;
    pjsip_dlg_dec_lock(dlg);

    return PJ_SUCCESS;
}

/*
 * Increment the event subscription's group lock.
 */
PJ_DEF(pj_status_t) pjsip_evsub_add_ref(pjsip_evsub *sub)
{
    return pj_grp_lock_add_ref(sub->grp_lock);
}

/*
 * Decrement the event subscription's group lock.
 */
PJ_DEF(pj_status_t) pjsip_evsub_dec_ref(pjsip_evsub *sub)
{
    return pj_grp_lock_dec_ref(sub->grp_lock);
}

/*
 * Create client subscription session.
 */
PJ_DEF(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg,
                                            const pjsip_evsub_user *user_cb,
                                            const pj_str_t *event,
                                            unsigned option,
                                            pjsip_evsub **p_evsub)
{
    pjsip_evsub *sub;
    pj_status_t status;

    PJ_ASSERT_RETURN(dlg && event && p_evsub, PJ_EINVAL);

    pjsip_dlg_inc_lock(dlg);
    status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, option, &sub);
    if (status != PJ_SUCCESS)
        goto on_return;

    /* Add unique Id to Event header, only when PJSIP_EVSUB_NO_EVENT_ID
     * is not specified.
     */
    if ((option & PJSIP_EVSUB_NO_EVENT_ID) == 0) {
        pj_create_unique_string(sub->pool, &sub->event->id_param);
    }

    /* Increment dlg session. */
    pjsip_dlg_inc_session(sub->dlg, &mod_evsub.mod);

    /* Init group lock */
    status = pj_grp_lock_create(dlg->pool, NULL, &sub->grp_lock);
    if (status != PJ_SUCCESS) {
        pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod);
        goto on_return;
    }

    pj_grp_lock_add_ref(sub->grp_lock);
    pj_grp_lock_add_handler(sub->grp_lock, dlg->pool, sub, &evsub_on_destroy);

    /* Done */
    *p_evsub = sub;

on_return:
    pjsip_dlg_dec_lock(dlg);
    return status;
}


/*
 * Create server subscription session from incoming request.
 */
PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg,
                                            const pjsip_evsub_user *user_cb,
                                            pjsip_rx_data *rdata,
                                            unsigned option,
                                            pjsip_evsub **p_evsub)
{
    pjsip_evsub *sub;
    pjsip_transaction *tsx;
    pjsip_accept_hdr *accept_hdr;
    pjsip_event_hdr *event_hdr;
    pjsip_expires_hdr *expires_hdr;
    pj_status_t status;

    /* Check arguments: */
    PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);

    /* MUST be request message: */
    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
                     PJSIP_ENOTREQUESTMSG);

    /* Transaction MUST have been created (in the dialog) */
    tsx = pjsip_rdata_get_tsx(rdata);
    PJ_ASSERT_RETURN(tsx != NULL, PJSIP_ENOTSX);

    /* No subscription must have been attached to transaction */
    PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] == NULL, 
                     PJSIP_ETYPEEXISTS);

    /* Package MUST implement on_rx_refresh */
    PJ_ASSERT_RETURN(user_cb->on_rx_refresh, PJ_EINVALIDOP);

    /* Request MUST have "Event" header. We need the Event header to get 
     * the package name (don't want to add more arguments in the function).
     */
    event_hdr = (pjsip_event_hdr*) 
                 pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &STR_EVENT,
                                             &STR_EVENT_S, NULL);
    if (event_hdr == NULL) {
        return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
    }

    /* Start locking the mutex: */

    pjsip_dlg_inc_lock(dlg);

    /* Create the session: */

    status = evsub_create(dlg, PJSIP_UAS_ROLE, user_cb, 
                          &event_hdr->event_type, option, &sub);
    if (status != PJ_SUCCESS)
        goto on_return;

    /* Just duplicate Event header from the request */
    sub->event = (pjsip_event_hdr*) pjsip_hdr_clone(sub->pool, event_hdr);

    /* Set the method: */
    pjsip_method_copy(sub->pool, &sub->method, 
                      &rdata->msg_info.msg->line.req.method);

    /* Set expiration time based on client request (in Expires header),
     * or package default expiration time.
     */
    expires_hdr = (pjsip_expires_hdr*)
        pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
    if (expires_hdr) {
        struct evpkg *evpkg;

        evpkg = find_pkg(&event_hdr->event_type);
        if (evpkg) {
            if (expires_hdr->ivalue < evpkg->pkg_expires)
                sub->expires->ivalue = expires_hdr->ivalue;
            else
                sub->expires->ivalue = evpkg->pkg_expires;
        }
    }

    /* Update time. */
    update_expires(sub, sub->expires->ivalue);

    /* Update Accept header: */

    accept_hdr = (pjsip_accept_hdr*)
        pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
    if (accept_hdr)
        sub->accept = (pjsip_accept_hdr*)pjsip_hdr_clone(sub->pool,accept_hdr);

    /* Increment dlg session. */
    pjsip_dlg_inc_session(dlg, &mod_evsub.mod);

    /* Init group lock */
    status = pj_grp_lock_create(dlg->pool, NULL, &sub->grp_lock);
    if (status != PJ_SUCCESS) {
        pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod);
        goto on_return;
    }

    pj_grp_lock_add_ref(sub->grp_lock);
    pj_grp_lock_add_handler(sub->grp_lock, dlg->pool, sub, &evsub_on_destroy);

    /* We can start the session: */

    sub->pending_tsx++;
    tsx->mod_data[mod_evsub.mod.id] = sub;

    /* Done. */
    *p_evsub = sub;


on_return:
    pjsip_dlg_dec_lock(dlg);
    return status;
}


/*
 * Forcefully destroy subscription.
 */
PJ_DEF(pj_status_t) pjsip_evsub_terminate( pjsip_evsub *sub,
                                           pj_bool_t notify )
{
    PJ_ASSERT_RETURN(sub, PJ_EINVAL);

    pjsip_dlg_inc_lock(sub->dlg);

    /* I think it's pretty safe to disable this check.
     
    if (sub->pending_tsx) {
        pj_assert(!"Unable to terminate when there's pending tsx");
        pjsip_dlg_dec_lock(sub->dlg);
        return PJ_EINVALIDOP;
    }
    */

    sub->call_cb = notify;
    set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, NULL);

    pjsip_dlg_dec_lock(sub->dlg);
    return PJ_SUCCESS;
}

/*
 * Get subscription state.
 */
PJ_DEF(pjsip_evsub_state) pjsip_evsub_get_state(const pjsip_evsub *sub)
{
    return sub->state;
}

/*
 * Get state name.
 */
PJ_DEF(const char*) pjsip_evsub_get_state_name(const pjsip_evsub *sub)
{
    return sub->state_str.ptr;
}

/*
 * Get termination reason.
 */
PJ_DEF(const pj_str_t*) pjsip_evsub_get_termination_reason(
                            const pjsip_evsub *sub)
{
    return &sub->term_reason;
}

/**
 * Get subscription expiration time.
 */
PJ_DEF(pj_uint32_t) pjsip_evsub_get_expires(const pjsip_evsub *sub)
{
    return sub->expires->ivalue;
}

/*
 * Initiate client subscription
 */
PJ_DEF(pj_status_t) pjsip_evsub_initiate( pjsip_evsub *sub,
                                          const pjsip_method *method,
                                          pj_uint32_t expires,
                                          pjsip_tx_data **p_tdata)
{
    pjsip_tx_data *tdata;
    pj_status_t status;

    PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL);

    /* Use SUBSCRIBE if method is not specified */
    if (method == NULL)
        method = &pjsip_subscribe_method;

    pjsip_dlg_inc_lock(sub->dlg);

    /* Update method: */
    if (sub->state == PJSIP_EVSUB_STATE_NULL)
        pjsip_method_copy(sub->pool, &sub->method, method);

    status = pjsip_dlg_create_request( sub->dlg, method, -1, &tdata);
    if (status != PJ_SUCCESS)
        goto on_return;


    /* Add Event header: */
    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
                       pjsip_hdr_shallow_clone(tdata->pool, sub->event));

    /* Update and add expires header: */
    if (expires != PJSIP_EXPIRES_NOT_SPECIFIED)
        sub->expires->ivalue = expires;
    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
                       pjsip_hdr_shallow_clone(tdata->pool, sub->expires));

    /* Add Supported header (it's optional in RFC 3265, but some event package
     * RFC may bring this requirement to SHOULD strength - e.g. RFC 5373)
     */
    {
       const pjsip_hdr *hdr = pjsip_endpt_get_capability(sub->endpt,
                                                         PJSIP_H_SUPPORTED,
                                                         NULL);
       if (hdr) {
           pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
                             pjsip_hdr_shallow_clone(tdata->pool, hdr));
       }
    }

    /* Add Accept header: */
    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
                       pjsip_hdr_shallow_clone(tdata->pool, sub->accept));
    

    /* Add Allow-Events header: */
    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
                       pjsip_hdr_shallow_clone(tdata->pool, 
                                               mod_evsub.allow_events_hdr));


    /* Add custom headers */
    {
        const pjsip_hdr *hdr = sub->sub_hdr_list.next;
        while (hdr != &sub->sub_hdr_list) {
            pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
                               pjsip_hdr_shallow_clone(tdata->pool, hdr));
            hdr = hdr->next;
        }
    }


    *p_tdata = tdata;


on_return:

    pjsip_dlg_dec_lock(sub->dlg);
    return status;
}


/*
 * Add custom headers.
 */
PJ_DEF(pj_status_t) pjsip_evsub_add_header( pjsip_evsub *sub,
                                            const pjsip_hdr *hdr_list )
{
    const pjsip_hdr *hdr;

    PJ_ASSERT_RETURN(sub && hdr_list, PJ_EINVAL);

    hdr = hdr_list->next;
    while (hdr != hdr_list) {
        pj_list_push_back(&sub->sub_hdr_list, (pjsip_hdr*)
                          pjsip_hdr_clone(sub->pool, hdr));
        hdr = hdr->next;
    }

    return PJ_SUCCESS;
}


/*
 * Accept incoming subscription request.
 */
PJ_DEF(pj_status_t) pjsip_evsub_accept( pjsip_evsub *sub,
                                        pjsip_rx_data *rdata,
                                        int st_code,
                                        const pjsip_hdr *hdr_list )
{
    pjsip_tx_data *tdata;
    pjsip_transaction *tsx;
    pj_status_t status;

    /* Check arguments */
    PJ_ASSERT_RETURN(sub && rdata, PJ_EINVAL);

    /* Can only be for server subscription: */
    PJ_ASSERT_RETURN(sub->role == PJSIP_ROLE_UAS, PJ_EINVALIDOP);

    /* Only expect 2xx status code (for now) */
    PJ_ASSERT_RETURN(st_code/100 == 2, PJ_EINVALIDOP);

    /* Subscription MUST have been attached to the transaction.
     * Initial subscription request will be attached on evsub_create_uas(),
     * while subsequent requests will be attached in tsx_state()
     */
    tsx = pjsip_rdata_get_tsx(rdata);
    PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] != NULL,
                     PJ_EINVALIDOP);

    /* Lock dialog */
    pjsip_dlg_inc_lock(sub->dlg);

    /* Create response: */
    status = pjsip_dlg_create_response( sub->dlg, rdata, st_code, NULL, 
                                        &tdata);
    if (status != PJ_SUCCESS)
        goto on_return;


    /* Add expires header: */
    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
                       pjsip_hdr_shallow_clone(tdata->pool, sub->expires));

    /* Add additional header, 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;
        }
    }

    /* Send the response: */
    status = pjsip_dlg_send_response( sub->dlg, tsx, tdata );
    if (status != PJ_SUCCESS)
        goto on_return;

    /* Set UAS timeout timer, when status code is 2xx and state is not
     * terminated.
     */
    if (st_code/100 == 2 && sub->state != PJSIP_EVSUB_STATE_TERMINATED) {
        PJ_LOG(5,(sub->obj_name, "UAS timeout in %d seconds",
                  sub->expires->ivalue));
        set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, sub->expires->ivalue);
    }

on_return:

    pjsip_dlg_dec_lock(sub->dlg);
    return status;
}


/*
 * Create Subscription-State header based on current server subscription
 * state.
 */
static pjsip_sub_state_hdr* sub_state_create( pj_pool_t *pool,
                                              pjsip_evsub *sub,
                                              pjsip_evsub_state state,
                                              const pj_str_t *state_str,
                                              const pj_str_t *reason )
{
    pjsip_sub_state_hdr *sub_state;
    pj_time_val now, delay;

    /* Get the remaining time before refresh is required */
    pj_gettimeofday(&now);
    delay = sub->refresh_time;
    PJ_TIME_VAL_SUB(delay, now);

    /* Create the Subscription-State header */
    sub_state = pjsip_sub_state_hdr_create(pool);

    /* Fill up the header */
    switch (state) {
    case PJSIP_EVSUB_STATE_NULL:
    case PJSIP_EVSUB_STATE_SENT:
        pj_assert(!"Invalid state!");
        /* Treat as pending */

    case PJSIP_EVSUB_STATE_ACCEPTED:
    case PJSIP_EVSUB_STATE_PENDING:
        sub_state->sub_state = STR_PENDING;
        sub_state->expires_param = delay.sec;
        break;

    case PJSIP_EVSUB_STATE_ACTIVE:
        sub_state->sub_state = STR_ACTIVE;
        sub_state->expires_param = delay.sec;
        break;

    case PJSIP_EVSUB_STATE_TERMINATED:
        sub_state->sub_state = STR_TERMINATED;
        if (reason != NULL)
            pj_strdup(pool, &sub_state->reason_param, reason);
        break;

    case PJSIP_EVSUB_STATE_UNKNOWN:
        pj_assert(state_str != NULL);
        pj_strdup(pool, &sub_state->sub_state, state_str);
        break;
    }
    
    return sub_state;
}

/*
 * Create and send NOTIFY request.
 */
PJ_DEF(pj_status_t) pjsip_evsub_notify( pjsip_evsub *sub,
                                        pjsip_evsub_state state,
                                        const pj_str_t *state_str,
                                        const pj_str_t *reason,
                                        pjsip_tx_data **p_tdata)
{
    pjsip_tx_data *tdata;
    pjsip_sub_state_hdr *sub_state;
    pj_status_t status;

    /* Check arguments. */
    PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL);

    /* Lock dialog. */
    pjsip_dlg_inc_lock(sub->dlg);

    /* Create NOTIFY request */
    status = pjsip_dlg_create_request( sub->dlg, pjsip_get_notify_method(), 
                                       -1, &tdata);
    if (status != PJ_SUCCESS)
        goto on_return;

    /* Add Event header */
    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
                      pjsip_hdr_shallow_clone(tdata->pool, sub->event));

    /* Add Subscription-State header */
    sub_state = sub_state_create(tdata->pool, sub, state, state_str,
                                 reason);
    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sub_state);

    /* Add Allow-Events header */
    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
                      pjsip_hdr_shallow_clone(tdata->pool, mod_evsub.allow_events_hdr));

    /* Add Authentication headers. */
    pjsip_auth_clt_init_req( &sub->dlg->auth_sess, tdata );

    /* Update reason */
    if (reason)
        pj_strdup(sub->dlg->pool, &sub->term_reason, reason);

    /* Save destination state. */
    sub->dst_state = state;
    if (state_str)
        pj_strdup(sub->pool, &sub->dst_state_str, state_str);
    else
        sub->dst_state_str.slen = 0;


    *p_tdata = tdata;

on_return:
    /* Unlock dialog */
    pjsip_dlg_dec_lock(sub->dlg);
    return status;
}


/*
 * Create NOTIFY to reflect current status.
 */
PJ_DEF(pj_status_t) pjsip_evsub_current_notify( pjsip_evsub *sub,
                                                pjsip_tx_data **p_tdata )
{
    return pjsip_evsub_notify( sub, sub->state, &sub->state_str, 
                               NULL, p_tdata );
}


/*
 * Send request.
 */
PJ_DEF(pj_status_t) pjsip_evsub_send_request( pjsip_evsub *sub,
                                              pjsip_tx_data *tdata)
{
    pj_status_t status = PJ_SUCCESS;

    /* Must be request message. */
    PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
                     PJSIP_ENOTREQUESTMSG);

    /* Lock */
    pjsip_dlg_inc_lock(sub->dlg);


    /* Delay sending NOTIFY if we're inside on_rx_refresh() callback
     * until we have sent the response to the incoming SUBSCRIBE.
     */
    if (sub->calling_on_rx_refresh &&
        pjsip_method_cmp(&tdata->msg->line.req.method, 
                         &pjsip_notify_method)==0)
    {
        sub->pending_notify = tdata;
        goto on_return;
    }

    /* Send the request. */
    status = pjsip_dlg_send_request(sub->dlg, tdata, -1, NULL);
    if (status != PJ_SUCCESS)
        goto on_return;


    /* Special case for NOTIFY:
     * The new state was set in pjsip_evsub_notify(), but we apply the
     * new state now, when the request was actually sent.
     */
    if (pjsip_method_cmp(&tdata->msg->line.req.method, 
                         &pjsip_notify_method)==0) 
    {
        PJ_ASSERT_ON_FAIL(  sub->dst_state!=PJSIP_EVSUB_STATE_NULL,
                            {goto on_return;});

        set_state(sub, sub->dst_state, 
                  (sub->dst_state_str.slen ? &sub->dst_state_str : NULL), 
                  NULL, NULL);

        sub->dst_state = PJSIP_EVSUB_STATE_NULL;
        sub->dst_state_str.slen = 0;

    }


on_return:
    pjsip_dlg_dec_lock(sub->dlg);
    return status;
}


/* Callback to be called to terminate transaction. */
static void terminate_timer_cb(pj_timer_heap_t *timer_heap,
                               struct pj_timer_entry *entry)
{
    pj_str_t *key;
    pjsip_transaction *tsx;

    PJ_UNUSED_ARG(timer_heap);

    /* Clear timer ID */
    entry->id = 0;

    key = (pj_str_t*)entry->user_data;
    tsx = pjsip_tsx_layer_find_tsx(key, PJ_FALSE);
    /* Chance of race condition here */
    if (tsx) {
        pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_UPDATED);
    }
}


/*
 * Attach subscription session to newly created transaction, if appropriate.
 */
static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx,
                                        pjsip_event *event)
{
    /*
     * Newly created transaction will not have subscription session
     * attached to it. Find the subscription session from the dialog,
     * by matching the Event header.
     */
    pjsip_dialog *dlg;
    pjsip_event_hdr *event_hdr;
    pjsip_msg *msg;
    struct dlgsub *dlgsub_head, *dlgsub;
    pjsip_evsub *sub;
    
    dlg = pjsip_tsx_get_dlg(tsx);
    if (!dlg) {
        pj_assert(!"Transaction should have a dialog instance!");
        return NULL;
    }


    switch (event->body.tsx_state.type) {
    case PJSIP_EVENT_RX_MSG:
        msg = event->body.tsx_state.src.rdata->msg_info.msg;
        break;
    case PJSIP_EVENT_TX_MSG:
        msg = event->body.tsx_state.src.tdata->msg;
        break;
    default:
        if (tsx->role == PJSIP_ROLE_UAC)
            msg = tsx->last_tx->msg;
        else
            msg = NULL;
        break;
    }
    
    if (!msg) {
        //Note:
        // this transaction can be other transaction in the dialog.
        // The assertion below probably only valid for dialog that
        // only has one event subscription usage.
        //pj_assert(!"First transaction event is not TX or RX!");
        return NULL;
    }

    event_hdr = (pjsip_event_hdr*)
                pjsip_msg_find_hdr_by_names(msg, &STR_EVENT, 
                                            &STR_EVENT_S, NULL);
    if (!event_hdr) {
        /* Not subscription related message */
        return NULL;
    }

    /* Find the subscription in the dialog, based on the content
     * of Event header: 
     */

    dlgsub_head = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id];
    if (dlgsub_head == NULL) {
        dlgsub_head = PJ_POOL_ALLOC_T(dlg->pool, struct dlgsub);
        pj_list_init(dlgsub_head);
        dlg->mod_data[mod_evsub.mod.id] = dlgsub_head;
    }
    dlgsub = dlgsub_head->next;

    while (dlgsub != dlgsub_head) {

        if (pj_stricmp(&dlgsub->sub->event->event_type, 
                       &event_hdr->event_type)==0)
        {
            /* Event type matched. 
             * Check if event ID matched too.
             */
            if (pj_strcmp(&dlgsub->sub->event->id_param, 
                          &event_hdr->id_param)==0)
            {
                /* Skip this subscription if it has no event ID and has been
                 * terminated (see ticket #1647).
                 */
                if ((dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID) &&
                    (pjsip_evsub_get_state(dlgsub->sub)==
                                        PJSIP_EVSUB_STATE_TERMINATED))
                {
                    dlgsub = dlgsub->next;
                    continue;
                } else {
                    break;
                }

            }
            /*
             * Otherwise if it is an UAC subscription, AND
             * PJSIP_EVSUB_NO_EVENT_ID flag is set, AND
             * the session's event id is NULL, AND
             * the incoming request is NOTIFY with event ID, then
             * we consider it as a match, and update the
             * session's event id.
             */
            else if (dlgsub->sub->role == PJSIP_ROLE_UAC &&
                     (dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID)!=0 &&
                     dlgsub->sub->event->id_param.slen==0 &&
                     !pjsip_method_cmp(&tsx->method, &pjsip_notify_method))
            {
                /* Update session's event id. */
                pj_strdup(dlgsub->sub->pool, 
                          &dlgsub->sub->event->id_param,
                          &event_hdr->id_param);

                break;
            }
        }
        


        dlgsub = dlgsub->next;
    }

    /* Note: 
     *  the second condition is for https://github.com/pjsip/pjproject/issues/911
     * Take note that it could be us that is trying to send a final message,
     * such as final NOTIFY upon unsubscription.
     */
    if (dlgsub == dlgsub_head ||
        (dlgsub->sub &&
            tsx->role == PJSIP_ROLE_UAS &&
            pjsip_evsub_get_state(dlgsub->sub)==PJSIP_EVSUB_STATE_TERMINATED))
    {
        const char *reason_msg = 
            (dlgsub == dlgsub_head ? "Subscription Does Not Exist" :
             "Subscription already terminated");

        /* This could be incoming request to create new subscription */
        PJ_LOG(4,(THIS_FILE, 
                  "%s for %.*s, event=%.*s;id=%.*s",
                  reason_msg,
                  (int)tsx->method.name.slen,
                  tsx->method.name.ptr,
                  (int)event_hdr->event_type.slen,
                  event_hdr->event_type.ptr,
                  (int)event_hdr->id_param.slen,
                  event_hdr->id_param.ptr));

        /* If this is an incoming NOTIFY, reject with 481 */
        if (tsx->state == PJSIP_TSX_STATE_TRYING &&
            pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0)
        {
            pj_str_t reason;
            pjsip_tx_data *tdata;
            pj_status_t status;

            pj_cstr(&reason, reason_msg);
            status = pjsip_dlg_create_response(dlg, 
                                               event->body.tsx_state.src.rdata, 
                                               481, &reason, 
                                               &tdata);
            if (status == PJ_SUCCESS) {
                status = pjsip_dlg_send_response(dlg, tsx, tdata);
            }
        }
        return NULL;
    }

    /* Found! */
    sub = dlgsub->sub;

    /* Attach session to the transaction */
    tsx->mod_data[mod_evsub.mod.id] = sub;
    sub->pending_tsx++;

    /* Special case for outgoing/UAC SUBSCRIBE/REFER transaction. 
     * We can only have one pending UAC SUBSCRIBE/REFER, so if another 
     * transaction is started while previous one still alive, terminate
     * the older one.
     *
     * Sample scenario:
     *  - subscribe sent to destination that doesn't exist, transaction
     *    is still retransmitting request, then unsubscribe is sent.
     */
    if (tsx->role == PJSIP_ROLE_UAC &&
        tsx->state == PJSIP_TSX_STATE_CALLING &&
        (pjsip_method_cmp(&tsx->method, &sub->method) == 0  ||
         pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0))
    {

        if (sub->pending_sub && 
            sub->pending_sub->state < PJSIP_TSX_STATE_COMPLETED) 
        {
            pj_timer_entry *timer;
            pj_str_t *key;
            pj_time_val timeout = {0, 0};

            PJ_LOG(4,(sub->obj_name, 
                      "Cancelling pending subscription request"));

            /* By convention, we use 490 (Request Updated) status code.
             * When transaction handler (below) see this status code, it
             * will ignore the transaction.
             */
            /* This unfortunately may cause deadlock, because at the moment
             * we are holding dialog's mutex. If a response to this
             * transaction is in progress in another thread, that thread
             * will deadlock when trying to acquire dialog mutex, because
             * it is holding the transaction mutex.
             *
             * So the solution is to register timer to kill this transaction.
             */
            //pjsip_tsx_terminate(sub->pending_sub, PJSIP_SC_REQUEST_UPDATED);
            timer = PJ_POOL_ZALLOC_T(dlg->pool, pj_timer_entry);
            key = PJ_POOL_ALLOC_T(dlg->pool, pj_str_t);
            pj_strdup(dlg->pool, key, &sub->pending_sub->transaction_key);
            timer->cb = &terminate_timer_cb;
            timer->user_data = key;
            timer->id = 1;
            sub->pending_sub_timer = timer;

            pjsip_endpt_schedule_timer(dlg->endpt, timer, &timeout);
        }

        sub->pending_sub = tsx;

    }

    return sub;
}


/*
 * Create response, adding custome headers and msg body.
 */
static pj_status_t create_response( pjsip_evsub *sub,
                                    pjsip_rx_data *rdata,
                                    int st_code,
                                    const pj_str_t *st_text,
                                    const pjsip_hdr *res_hdr,
                                    const pjsip_msg_body *body,
                                    pjsip_tx_data **p_tdata)
{
    pjsip_tx_data *tdata;
    pjsip_hdr *hdr;
    pj_status_t status;

    status = pjsip_dlg_create_response(sub->dlg, rdata,
                                       st_code, st_text, &tdata);
    if (status != PJ_SUCCESS)
        return status;

    *p_tdata = tdata;

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

    /* Add msg body, if any */
    if (body) {
        tdata->msg->body = pjsip_msg_body_clone(tdata->pool, body);
        if (tdata->msg->body == NULL) {

            PJ_LOG(4,(THIS_FILE, "Error: unable to clone msg body"));

            /* Ignore */
            return PJ_SUCCESS;
        }
    }

    return PJ_SUCCESS;
}

/*
 * Get subscription state from the value of Subscription-State header.
 */
static void get_hdr_state( pjsip_sub_state_hdr *sub_state,
                           pjsip_evsub_state *state,
                           pj_str_t **state_str )
{
    if (pj_stricmp(&sub_state->sub_state, &STR_TERMINATED)==0) {

        *state = PJSIP_EVSUB_STATE_TERMINATED;
        *state_str = NULL;

    } else if (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0) {

        *state = PJSIP_EVSUB_STATE_ACTIVE;
        *state_str = NULL;

    } else if (pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0) {

        *state = PJSIP_EVSUB_STATE_PENDING;
        *state_str = NULL;

    } else {

        *state = PJSIP_EVSUB_STATE_UNKNOWN;
        *state_str = &sub_state->sub_state;

    }
}

/*
 * Transaction event processing by UAC, after subscription is sent.
 */
static void on_tsx_state_uac( pjsip_evsub *sub, pjsip_transaction *tsx,
                              pjsip_event *event )
{

    if (pjsip_method_cmp(&tsx->method, &sub->method)==0 || 
        pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method)==0) 
    {

        /* Received response to outgoing request that establishes/refresh
         * subscription. 
         */

        /* First time initial request is sent. */
        if (sub->state == PJSIP_EVSUB_STATE_NULL &&
            tsx->state == PJSIP_TSX_STATE_CALLING)
        {
            set_state(sub, PJSIP_EVSUB_STATE_SENT, NULL, event, NULL);
            return;
        }

        /* Only interested in final response */
        if (tsx->state != PJSIP_TSX_STATE_COMPLETED &&
            tsx->state != PJSIP_TSX_STATE_TERMINATED)
        {
            return;
        }

        /* Clear pending subscription */
        if (tsx == sub->pending_sub) {
            sub->pending_sub = NULL;
        } else if (sub->pending_sub != NULL) {
            /* This SUBSCRIBE transaction has been "renewed" with another
             * SUBSCRIBE, so we can just ignore this. For example, user
             * sent SUBSCRIBE followed immediately with UN-SUBSCRIBE.
             */
            return;
        }

        /* Handle authentication. */
        if (tsx->status_code==401 || tsx->status_code==407) {
            pjsip_tx_data *tdata;
            pj_status_t status;

            if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
                /* Previously failed transaction has terminated */
                return;
            }

            status = pjsip_auth_clt_reinit_req(&sub->dlg->auth_sess,
                                               event->body.tsx_state.src.rdata,
                                               tsx->last_tx, &tdata);
            if (status == PJ_SUCCESS) 
                status = pjsip_dlg_send_request(sub->dlg, tdata, -1, NULL);
            
            if (status != PJ_SUCCESS) {
                /* Authentication failed! */
                set_state(sub, PJSIP_EVSUB_STATE_TERMINATED,
                          NULL, event, &tsx->status_text);
                return;
            }

            return;
        }

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

            /* Successfull SUBSCRIBE request! 
             * This could be:
             *  - response to initial SUBSCRIBE request
             *  - response to subsequent refresh
             *  - response to unsubscription
             */

            if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
                /* Ignore; this transaction has been processed before */
                return;
            }

            /* Update UAC refresh time, if response contains Expires header,
             * only when we're not unsubscribing. 
             */
            if (sub->expires->ivalue != 0) {
                pjsip_msg *msg;
                pjsip_expires_hdr *expires;

                msg = event->body.tsx_state.src.rdata->msg_info.msg;
                expires = (pjsip_expires_hdr*)
                          pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
                if (expires) {
                    sub->expires->ivalue = expires->ivalue;
                }
            }

            /* Update time */
            update_expires(sub, sub->expires->ivalue);

            /* Start UAC refresh timer, only when we're not unsubscribing */
            if (sub->expires->ivalue != 0) {
                unsigned timeout = (sub->expires->ivalue > TIME_UAC_REFRESH) ?
                    sub->expires->ivalue - TIME_UAC_REFRESH : sub->expires->ivalue;

                /* Reduce timeout by about 1 - 10 secs (randomized) */
                if (timeout > 10)
                    timeout += -10 + (pj_rand() % 10);

                PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", 
                          timeout));
                set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout);

            } else {
                /* Otherwise set timer to terminate client subscription when
                 * NOTIFY to end subscription is not received.
                 */
                set_timer(sub, TIMER_TYPE_UAC_TERMINATE, TIME_UAC_TERMINATE);
            }
            
            /* Set state, if necessary */
            pj_assert(sub->state != PJSIP_EVSUB_STATE_NULL);
            if (sub->state == PJSIP_EVSUB_STATE_SENT) {
                set_state(sub, PJSIP_EVSUB_STATE_ACCEPTED, NULL, event, NULL);
            }

        } else {

            /* Failed SUBSCRIBE request! 
             *
             * The RFC 3265 says that if outgoing SUBSCRIBE fails with status
             * other than 481, the subscription is still considered valid for
             * the duration of the last Expires.
             *
             * Since we send refresh about 5 seconds (TIME_UAC_REFRESH) before 
             * expiration, theoritically the expiration is still valid for the 
             * next 5 seconds even when we receive non-481 failed response.
             *
             * Ah, what the heck!
             *
             * Just terminate now!
             *
             */

            if (sub->state == PJSIP_EVSUB_STATE_TERMINATED) {
                /* Ignore, has been handled before */
                return;
            }

            /* Ignore 490 (Request Updated) status. 
             * This happens when application sends SUBSCRIBE/REFER while 
             * another one is still in progress.
             */
            if (tsx->status_code == PJSIP_SC_REQUEST_UPDATED) {
                return;
            }

            /* Set state to TERMINATED */
            set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, 
                      NULL, event, &tsx->status_text);

        }

    } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) {

        /* Incoming NOTIFY. 
         * This can be the result of:
         *  - Initial subscription response
         *  - UAS updating the resource info.
         *  - Unsubscription response.
         */
        int st_code = 200;
        pj_str_t *st_text = NULL;
        pjsip_hdr res_hdr;
        pjsip_msg_body *body = NULL;

        pjsip_rx_data *rdata;
        pjsip_msg *msg;
        pjsip_sub_state_hdr *sub_state;

        pjsip_evsub_state new_state;
        pj_str_t *new_state_str;

        pjsip_tx_data *tdata;
        pj_status_t status;

        /* Only want to handle initial NOTIFY receive event. */
        if (tsx->state != PJSIP_TSX_STATE_TRYING)
            return;


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

        pj_list_init(&res_hdr);

        /* Get subscription state header. */
        sub_state = (pjsip_sub_state_hdr*)
                    pjsip_msg_find_hdr_by_name(msg, &STR_SUB_STATE, NULL);
        if (sub_state == NULL) {

            pjsip_warning_hdr *warn_hdr;
            pj_str_t warn_text = { "Missing Subscription-State header", 33};

            /* Bad request! Add warning header. */
            st_code = PJSIP_SC_BAD_REQUEST;
            warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399,
                                                pjsip_endpt_name(sub->endpt),
                                                &warn_text);
            pj_list_push_back(&res_hdr, warn_hdr);
        }

        /* Call application registered callback to handle incoming NOTIFY,
         * if any.
         */
        if (st_code==200 && sub->user.on_rx_notify && sub->call_cb) {
            (*sub->user.on_rx_notify)(sub, rdata, &st_code, &st_text, 
                                      &res_hdr, &body);

            /* Application MUST specify final response! */
            PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; });

            /* Must be a valid status code */
            PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; });
        }


        /* If non-2xx should be returned, then send the response.
         * No need to update server subscription state.
         */
        if (st_code >= 300) {
            status = create_response(sub, rdata, st_code, st_text, &res_hdr,
                                     body, &tdata);
            if (status == PJ_SUCCESS) {
                status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);
            }

            /* Start timer to terminate subscription, just in case server
             * is not able to generate NOTIFY to our response.
             */
            if (status == PJ_SUCCESS) {
                unsigned timeout = TIME_UAC_WAIT_NOTIFY;
                set_timer(sub, TIMER_TYPE_UAC_WAIT_NOTIFY, timeout);
            } else {
                char errmsg[PJ_ERR_MSG_SIZE];
                pj_str_t reason;

                reason = pj_strerror(status, errmsg, sizeof(errmsg));
                set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, 
                          &reason);
            }

            return;
        }

        /* Update expiration from the value of expires param in
         * Subscription-State header, but ONLY when subscription state 
         * is "active" or "pending", AND the header contains expires param.
         */
        if (sub->expires->ivalue != 0 &&
            sub_state->expires_param != PJSIP_EXPIRES_NOT_SPECIFIED &&
            (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0 ||
             pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0))
        {
            unsigned next_refresh = sub_state->expires_param;
            unsigned timeout;

            update_expires(sub, next_refresh);

            /* Start UAC refresh timer, only when we're not unsubscribing */
            timeout = (next_refresh > TIME_UAC_REFRESH) ?
                        next_refresh - TIME_UAC_REFRESH : next_refresh;

            PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", timeout));
            set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout);
        }
        
        /* Find out the state */
        get_hdr_state(sub_state, &new_state, &new_state_str);

        /* Send response. */
        status = create_response(sub, rdata, st_code, st_text, &res_hdr,
                                 body, &tdata);
        if (status == PJ_SUCCESS)
            status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);

        /* Set the state */
        if (status == PJ_SUCCESS) {
            set_state(sub, new_state, new_state_str, event, 
                      &sub_state->reason_param);
        } else {
            char errmsg[PJ_ERR_MSG_SIZE];
            pj_str_t reason;

            reason = pj_strerror(status, errmsg, sizeof(errmsg));
            set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event, 
                      &reason);
        }


    } else {

        /*
         * Unexpected method!
         */
        PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s",
                 (int)tsx->method.name.slen, tsx->method.name.ptr));
    }
}


/*
 * Transaction event processing by UAS, after subscription is accepted.
 */
static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx,
                              pjsip_event *event)
{

    if (pjsip_method_cmp(&tsx->method, &sub->method) == 0 ||
        pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0) 
    {
        
        /*
         * Incoming request (e.g. SUBSCRIBE or REFER) to refresh subsciption.
         *
         */
        pjsip_rx_data *rdata;
        pjsip_event_hdr *event_hdr;
        pjsip_expires_hdr *expires;
        pjsip_msg *msg;
        pjsip_tx_data *tdata;
        int st_code = 200;
        pj_str_t *st_text = NULL;
        pjsip_hdr res_hdr;
        pjsip_msg_body *body = NULL;
        pjsip_evsub_state old_state;
        pj_str_t old_state_str;
        pj_str_t reason = { NULL, 0 };
        pj_status_t status;


        /* Only wants to handle the first event when the request is 
         * received.
         */
        if (tsx->state != PJSIP_TSX_STATE_TRYING)
            return;

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

        /* Set expiration time based on client request (in Expires header),
         * or package default expiration time.
         */
        event_hdr = (pjsip_event_hdr*)
                    pjsip_msg_find_hdr_by_names(msg, &STR_EVENT, 
                                                &STR_EVENT, NULL);
        expires = (pjsip_expires_hdr*)
                  pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
        if (event_hdr && expires) {
            struct evpkg *evpkg;

            evpkg = find_pkg(&event_hdr->event_type);
            if (evpkg) {
                if (expires->ivalue < evpkg->pkg_expires)
                    sub->expires->ivalue = expires->ivalue;
                else
                    sub->expires->ivalue = evpkg->pkg_expires;
            }
        }
        
        /* Update time (before calling on_rx_refresh, since application
         * will send NOTIFY.
         */
        update_expires(sub, sub->expires->ivalue);


        /* Save old state.
         * If application respond with non-2xx, revert to old state.
         * But if subscriber wants to unsubscribe, there is no
         * turning back.
         */
        old_state = sub->state;
        old_state_str = sub->state_str;

        /* Must set this before calling set_state(), to prevent evsub
         * from being destroyed.
         */
        sub->calling_on_rx_refresh = PJ_TRUE;

        if (sub->expires->ivalue == 0) {
            pj_str_t timeout = { "timeout", 7};

            PJ_LOG(4,(sub->obj_name, "Receiving unsubscription request "
                                     "(Expires=0)."));
            set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event,
                      &timeout);
        } else  if (sub->state == PJSIP_EVSUB_STATE_NULL) {
            sub->state = PJSIP_EVSUB_STATE_ACCEPTED;
            sub->state_str = evsub_state_names[sub->state];
        }

        /* Call application's on_rx_refresh, just in case it wants to send
         * response other than 200 (OK)
         */
        pj_list_init(&res_hdr);

        if (sub->user.on_rx_refresh && sub->call_cb) {
            (*sub->user.on_rx_refresh)(sub, rdata, &st_code, &st_text, 
                                       &res_hdr, &body);
            /* We shouldn't fail an unsubscription request, should we? */
            if (sub->expires->ivalue == 0) st_code = 200;
        }
        sub->calling_on_rx_refresh = PJ_FALSE;

        /* Application MUST specify final response! */
        PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; });

        /* Must be a valid status code */
        PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; });


        /* Create and send response */
        status = create_response(sub, rdata, st_code, st_text, &res_hdr,
                                 body, &tdata);
        if (status == PJ_SUCCESS) {
            /* Add expires header: */
            pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
                               pjsip_hdr_shallow_clone(tdata->pool, 
                                                       sub->expires));

            /* Send */
            pjsip_dlg_send_response(sub->dlg, tsx, tdata);
        }

        /* Update state or revert state */
        if (st_code/100==2) {
            
            if (sub->state == PJSIP_EVSUB_STATE_NULL) {
                set_state(sub, sub->state, NULL, event, &reason);
            }

            /* Set UAS timeout timer, when state is not terminated. */
            if (sub->state != PJSIP_EVSUB_STATE_TERMINATED) {
                PJ_LOG(5,(sub->obj_name, "UAS timeout in %d seconds",
                          sub->expires->ivalue));
                set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, 
                          sub->expires->ivalue);
            }

        }  else {
            sub->state = old_state;
            sub->state_str = old_state_str;
        }

        /* Send the pending NOTIFY sent by app from inside
         * on_rx_refresh() callback.
         */
        //pj_assert(sub->pending_notify);
        /* Make sure that pending_notify is set. */
        if (sub->pending_notify) {
            status = pjsip_evsub_send_request(sub, sub->pending_notify);
            sub->pending_notify = NULL;
        }

    } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0) {

        /* Handle authentication */
        if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
            (tsx->status_code==401 || tsx->status_code==407))
        {
            pjsip_tx_data *tdata;
            pj_status_t status;
            pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;

            /* Handled by other module already (e.g: invite module) */
            if (tsx->last_tx->auth_retry)
                return;

            status = pjsip_auth_clt_reinit_req(&sub->dlg->auth_sess, rdata,
                                               tsx->last_tx, &tdata);
            if (status == PJ_SUCCESS)
                status = pjsip_dlg_send_request(sub->dlg, tdata, -1, NULL);

            if (status != PJ_SUCCESS) {
                /* Can't authenticate. Terminate session (?) */
                set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL,
                          &tsx->status_text);
            }
            return;

        }

        if (sub->state == PJSIP_EVSUB_STATE_TERMINATED)
            return;

        /* NOTIFY failure check */
        if (tsx->status_code/100 != 2) {
            pj_bool_t should_terminate_sub = PJ_FALSE;

            if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
                if (tsx->status_code == 481) {
                    should_terminate_sub = PJ_TRUE;
                } else {
                    pjsip_retry_after_hdr *retry_after;
                    pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
                    pjsip_msg *msg = rdata->msg_info.msg;                   

                    retry_after = (pjsip_retry_after_hdr*)
                       pjsip_msg_find_hdr_by_name(msg, &STR_RETRY_AFTER, NULL);

                    if (!retry_after) {
                        should_terminate_sub = PJ_TRUE;
                    }
                }
            } else if (event->body.tsx_state.type == PJSIP_EVENT_TIMER) {
                if (tsx->status_code == 408) {
                    should_terminate_sub = PJ_TRUE;
                }
            }

            /*
             * Terminate event usage if we receive non 2xx without retry_after
             * parameter, 481, 408 responses.
             */
            if (should_terminate_sub) {
                set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event,
                          &tsx->status_text);
                return;
            }
        }
    } else {

        /*
         * Unexpected method!
         */
        PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s",
                 (int)tsx->method.name.slen, tsx->method.name.ptr));
    
    }
}


/*
 * Notification when transaction state has changed!
 */
static void mod_evsub_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
{
    pjsip_evsub *sub = pjsip_tsx_get_evsub(tsx);

    if (sub == NULL) {
        sub = on_new_transaction(tsx, event);
        if (sub == NULL)
            return;
    }


    /* Call on_tsx_state callback, if any. */
    if (sub->user.on_tsx_state && sub->call_cb)
        (*sub->user.on_tsx_state)(sub, tsx, event);


    /* Process the event: */

    if (sub->role == PJSIP_ROLE_UAC) {
        on_tsx_state_uac(sub, tsx, event);
    } else {
        on_tsx_state_uas(sub, tsx, event);
    }


    /* Check transaction TERMINATE event */
    if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {

        --sub->pending_tsx;

        if (sub->state == PJSIP_EVSUB_STATE_TERMINATED &&
            sub->pending_tsx == 0)
        {
            evsub_destroy(sub);
        }

    }
}


