Index: pjproject/main/pjsip/src/pjsip-simple/event_notify.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-simple/event_notify.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-simple/event_notify.c	(revision 39)
@@ -0,0 +1,1629 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/event_notify.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_misc.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_event.h>
+#include <pj/pool.h>
+#include <pj/timer.h>
+#include <pj/string.h>
+#include <pj/hash.h>
+#include <pj/os.h>
+#include <pj/except.h>
+#include <pj/log.h>
+#include <pj/guid.h>
+
+#define THIS_FILE		"event_sub"
+
+/* String names for state. 
+ * The names here should be compliant with sub_state names in RFC3265.
+ */
+static const pj_str_t state[] = {
+    { "null", 4 }, 
+    { "active", 6 },
+    { "pending", 7 },
+    { "terminated", 10 },
+    { "unknown", 7 }
+};
+
+/* Timer IDs */
+#define TIMER_ID_REFRESH	1
+#define TIMER_ID_UAS_EXPIRY	2
+
+/* Static configuration. */
+#define SECONDS_BEFORE_EXPIRY	10
+#define MGR_POOL_SIZE		512
+#define MGR_POOL_INC		0
+#define SUB_POOL_SIZE		2048
+#define SUB_POOL_INC		0
+#define HASH_TABLE_SIZE		32
+
+/* Static vars. */
+static int mod_id;
+static const pjsip_method SUBSCRIBE = { PJSIP_OTHER_METHOD, {"SUBSCRIBE", 9}};
+static const pjsip_method NOTIFY = { PJSIP_OTHER_METHOD, { "NOTIFY", 6}};
+
+typedef struct package
+{
+    PJ_DECL_LIST_MEMBER(struct package)
+    pj_str_t		    event;
+    int			    accept_cnt;
+    pj_str_t		   *accept;
+    pjsip_event_sub_pkg_cb  cb;
+} package;
+
+/* Event subscription manager singleton instance. */
+static struct pjsip_event_sub_mgr
+{
+    pj_pool_t		    *pool;
+    pj_hash_table_t	    *ht;
+    pjsip_endpoint	    *endpt;
+    pj_mutex_t		    *mutex;
+    pjsip_allow_events_hdr  *allow_events;
+    package		     pkg_list;
+} mgr;
+
+/* Fordward declarations for static functions. */
+static pj_status_t	mod_init(pjsip_endpoint *, pjsip_module *, pj_uint32_t);
+static pj_status_t	mod_deinit(pjsip_module*);
+static void		tsx_handler(pjsip_module*, pjsip_event*);
+static pjsip_event_sub *find_sub(pjsip_rx_data *);
+static void		on_subscribe_request(pjsip_transaction*, pjsip_rx_data*);
+static void		on_subscribe_response(void *, pjsip_event*);
+static void		on_notify_request(pjsip_transaction *, pjsip_rx_data*);
+static void		on_notify_response(void *, pjsip_event *);
+static void		refresh_timer_cb(pj_timer_heap_t*, pj_timer_entry*);
+static void		uas_expire_timer_cb(pj_timer_heap_t*, pj_timer_entry*);
+static pj_status_t	send_sub_refresh( pjsip_event_sub *sub );
+
+/* Module descriptor. */
+static pjsip_module event_sub_module = 
+{
+    {"EventSub", 8},			/* Name.		*/
+    0,					/* Flag			*/
+    128,				/* Priority		*/
+    &mgr,				/* User data.		*/
+    2,					/* Number of methods supported . */
+    { &SUBSCRIBE, &NOTIFY },		/* Array of methods */
+    &mod_init,				/* init_module()	*/
+    NULL,				/* start_module()	*/
+    &mod_deinit,			/* deinit_module()	*/
+    &tsx_handler,			/* tsx_handler()	*/
+};
+
+/*
+ * Module initialization.
+ * This will be called by endpoint when it initializes all modules.
+ */
+static pj_status_t mod_init( pjsip_endpoint *endpt,
+			     struct pjsip_module *mod, pj_uint32_t id )
+{
+    pj_pool_t *pool;
+
+    pool = pjsip_endpt_create_pool(endpt, "esubmgr", MGR_POOL_SIZE, MGR_POOL_INC);
+    if (!pool)
+	return -1;
+
+    /* Manager initialization: create hash table and mutex. */
+    mgr.pool = pool;
+    mgr.endpt = endpt;
+    mgr.ht = pj_hash_create(pool, HASH_TABLE_SIZE);
+    if (!mgr.ht)
+	return -1;
+
+    mgr.mutex = pj_mutex_create(pool, "esubmgr", PJ_MUTEX_SIMPLE);
+    if (!mgr.mutex)
+	return -1;
+
+    /* Attach manager to module. */
+    mod->mod_data = &mgr;
+
+    /* Init package list. */
+    pj_list_init(&mgr.pkg_list);
+
+    /* Init Allow-Events header. */
+    mgr.allow_events = pjsip_allow_events_hdr_create(mgr.pool);
+
+    /* Save the module ID. */
+    mod_id = id;
+
+    pjsip_event_notify_init_parser();
+    return 0;
+}
+
+/*
+ * Module deinitialization.
+ * Called by endpoint.
+ */
+static pj_status_t mod_deinit( struct pjsip_module *mod )
+{
+    pj_mutex_lock(mgr.mutex);
+    pj_mutex_destroy(mgr.mutex);
+    pjsip_endpt_destroy_pool(mgr.endpt, mgr.pool);
+    return 0;
+}
+
+/*
+ * This public function is called by application to register callback.
+ * In exchange, the instance of the module is returned.
+ */
+PJ_DEF(pjsip_module*) pjsip_event_sub_get_module(void)
+{
+    return &event_sub_module;
+}
+
+/*
+ * Register event package.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_register_pkg( const pj_str_t *event,
+						  int accept_cnt,
+						  const pj_str_t accept[],
+						  const pjsip_event_sub_pkg_cb *cb )
+{
+    package *pkg;
+    int i;
+
+    pj_mutex_lock(mgr.mutex);
+
+    /* Create and register new package. */
+    pkg = pj_pool_alloc(mgr.pool, sizeof(*pkg));
+    pj_strdup(mgr.pool, &pkg->event, event);
+    pj_list_insert_before(&mgr.pkg_list, pkg);
+
+    /* Save Accept specification. */
+    pkg->accept_cnt = accept_cnt;
+    pkg->accept = pj_pool_alloc(mgr.pool, accept_cnt*sizeof(pj_str_t));
+    for (i=0; i<accept_cnt; ++i) {
+	pj_strdup(mgr.pool, &pkg->accept[i], &accept[i]);
+    }
+
+    /* Copy callback. */
+    pj_memcpy(&pkg->cb, cb, sizeof(*cb));
+
+    /* Update Allow-Events header. */
+    pj_assert(mgr.allow_events->event_cnt < PJSIP_MAX_ALLOW_EVENTS);
+    mgr.allow_events->events[mgr.allow_events->event_cnt++] = pkg->event;
+
+    pj_mutex_unlock(mgr.mutex);
+    return 0;
+}
+
+/*
+ * Create subscription key (for hash table).
+ */
+static void create_subscriber_key( pj_str_t *key, pj_pool_t *pool,
+				   pjsip_role_e role, 
+				   const pj_str_t *call_id, const pj_str_t *from_tag)
+{
+    char *p;
+
+    p = key->ptr = pj_pool_alloc(pool, call_id->slen + from_tag->slen + 3);
+    *p++ = (role == PJSIP_ROLE_UAS ? 'S' : 'C');
+    *p++ = '$';
+    pj_memcpy(p, call_id->ptr, call_id->slen);
+    p += call_id->slen;
+    *p++ = '$';
+    pj_memcpy(p, from_tag->ptr, from_tag->slen);
+    p += from_tag->slen;
+
+    key->slen = p - key->ptr;
+}
+
+
+/*
+ * Create UAC subscription.
+ */
+PJ_DEF(pjsip_event_sub*) pjsip_event_sub_create( pjsip_endpoint *endpt,
+						 const pj_str_t *from,
+						 const pj_str_t *to,
+						 const pj_str_t *event,
+						 int expires,
+						 int accept_cnt,
+						 const pj_str_t accept[],
+						 void *user_data,
+						 const pjsip_event_sub_cb *cb)
+{
+    pjsip_tx_data *tdata;
+    pj_pool_t *pool;
+    const pjsip_hdr *hdr;
+    pjsip_event_sub *sub;
+    PJ_USE_EXCEPTION;
+
+    PJ_LOG(5,(THIS_FILE, "Creating event subscription %.*s to %.*s",
+			 event->slen, event->ptr, to->slen, to->ptr));
+
+    /* Create pool for the event subscription. */
+    pool = pjsip_endpt_create_pool(endpt, "esub", SUB_POOL_SIZE, SUB_POOL_INC);
+    if (!pool) {
+	return NULL;
+    }
+
+    /* Init subscription. */
+    sub = pj_pool_calloc(pool, 1, sizeof(*sub));
+    sub->pool = pool;
+    sub->endpt = endpt;
+    sub->role = PJSIP_ROLE_UAC;
+    sub->state = PJSIP_EVENT_SUB_STATE_PENDING;
+    sub->state_str = state[sub->state];
+    sub->user_data = user_data;
+    sub->timer.id = 0;
+    sub->default_interval = expires;
+    pj_memcpy(&sub->cb, cb, sizeof(*cb));
+    pj_list_init(&sub->auth_sess);
+    pj_list_init(&sub->route_set);
+    sub->mutex = pj_mutex_create(pool, "esub", PJ_MUTEX_RECURSE);
+    if (!sub->mutex) {
+	pjsip_endpt_destroy_pool(endpt, pool);
+	return NULL;
+    }
+
+    /* The easiest way to parse the parameters is to create a dummy request! */
+    tdata = pjsip_endpt_create_request( endpt, &SUBSCRIBE, to, from, to, from,
+					NULL, -1, NULL);
+    if (!tdata) {
+	pj_mutex_destroy(sub->mutex);
+	pjsip_endpt_destroy_pool(endpt, pool);
+	return NULL;
+    }
+
+    /* 
+     * Duplicate headers in the request to our structure. 
+     */
+    PJ_TRY {
+	int i;
+
+	/* From */
+	hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_FROM, NULL);
+	pj_assert(hdr != NULL);
+	sub->from = pjsip_hdr_clone(pool, hdr);
+        
+	/* To */
+	hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_TO, NULL);
+	pj_assert(hdr != NULL);
+	sub->to = pjsip_hdr_clone(pool, hdr);
+
+	/* Contact. */
+	sub->contact = pjsip_contact_hdr_create(pool);
+	sub->contact->uri = sub->from->uri;
+
+	/* Call-ID */
+	hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CALL_ID, NULL);
+	pj_assert(hdr != NULL);
+	sub->call_id = pjsip_hdr_clone(pool, hdr);
+
+	/* CSeq */
+	sub->cseq = pj_rand() % 0xFFFF;
+
+	/* Event. */
+	sub->event = pjsip_event_hdr_create(sub->pool);
+	pj_strdup(pool, &sub->event->event_type, event);
+
+	/* Expires. */
+	sub->uac_expires = pjsip_expires_hdr_create(pool);
+	sub->uac_expires->ivalue = expires;
+
+	/* Accept. */
+	sub->local_accept = pjsip_accept_hdr_create(pool);
+	for (i=0; i<accept_cnt && i < PJSIP_MAX_ACCEPT_COUNT; ++i) {
+	    sub->local_accept->count++;
+	    pj_strdup(sub->pool, &sub->local_accept->values[i], &accept[i]);
+	}
+
+	/* Register to hash table. */
+	create_subscriber_key( &sub->key, pool, PJSIP_ROLE_UAC, 
+			       &sub->call_id->id, &sub->from->tag);
+	pj_mutex_lock( mgr.mutex );
+	pj_hash_set( pool, mgr.ht, sub->key.ptr, sub->key.slen, sub);
+	pj_mutex_unlock( mgr.mutex );
+
+    }
+    PJ_DEFAULT {
+	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): caught exception %d during init", 
+			     sub, state[sub->state].ptr, PJ_GET_EXCEPTION()));
+
+	pjsip_tx_data_dec_ref(tdata);
+	pj_mutex_destroy(sub->mutex);
+	pjsip_endpt_destroy_pool(endpt, sub->pool);
+	return NULL;
+    }
+    PJ_END;
+
+    /* All set, delete temporary transmit data as we don't need it. */
+    pjsip_tx_data_dec_ref(tdata);
+
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): client created, target=%.*s, event=%.*s",
+			 sub, state[sub->state].ptr,
+			 to->slen, to->ptr, event->slen, event->ptr));
+
+    return sub;
+}
+
+/*
+ * Set credentials.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_set_credentials( pjsip_event_sub *sub,
+						     int count,
+						     const pjsip_cred_info cred[])
+{
+    pj_mutex_lock(sub->mutex);
+    if (count > 0) {
+	sub->cred_info = pj_pool_alloc(sub->pool, count*sizeof(pjsip_cred_info));
+	pj_memcpy( sub->cred_info, cred, count*sizeof(pjsip_cred_info));
+    }
+    sub->cred_cnt = count;
+    pj_mutex_unlock(sub->mutex);
+    return 0;
+}
+
+/*
+ * Set route-set.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_set_route_set( pjsip_event_sub *sub,
+						   const pjsip_route_hdr *route_set )
+{
+    const pjsip_route_hdr *hdr;
+
+    pj_mutex_lock(sub->mutex);
+
+    /* Clear existing route set. */
+    pj_list_init(&sub->route_set);
+
+    /* Duplicate route headers. */
+    hdr = route_set->next;
+    while (hdr != route_set) {
+	pjsip_route_hdr *new_hdr = pjsip_hdr_clone(sub->pool, hdr);
+	pj_list_insert_before(&sub->route_set, new_hdr);
+	hdr = hdr->next;
+    }
+
+    pj_mutex_unlock(sub->mutex);
+
+    return 0;
+}
+
+/*
+ * Send subscribe request.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_subscribe( pjsip_event_sub *sub )
+{
+    pj_status_t status;
+
+    pj_mutex_lock(sub->mutex);
+    status = send_sub_refresh(sub);
+    pj_mutex_unlock(sub->mutex);
+
+    return status;
+}
+
+/*
+ * Destroy subscription.
+ * If there are pending transactions, then this will just set the flag.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_destroy(pjsip_event_sub *sub)
+{
+    pj_assert(sub != NULL);
+    if (sub == NULL)
+	return -1;
+
+    /* Application must terminate the subscription first. */
+    pj_assert(sub->state == PJSIP_EVENT_SUB_STATE_NULL ||
+	      sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED);
+
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): about to be destroyed", 
+			 sub, state[sub->state].ptr));
+
+    pj_mutex_lock(mgr.mutex);
+    pj_mutex_lock(sub->mutex);
+
+    /* Set delete flag. */
+    sub->delete_flag = 1;
+
+    /* Unregister timer, if any. */
+    if (sub->timer.id != 0) {
+	pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+	sub->timer.id = 0;
+    }
+
+    if (sub->pending_tsx > 0) {
+	pj_mutex_unlock(sub->mutex);
+	pj_mutex_unlock(mgr.mutex);
+	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): has %d pending, will destroy later",
+			     sub, state[sub->state].ptr,
+			     sub->pending_tsx));
+	return 1;
+    }
+
+    /* Unregister from hash table. */
+    pj_hash_set(sub->pool, mgr.ht, sub->key.ptr, sub->key.slen, NULL);
+
+    /* Destroy. */
+    pj_mutex_destroy(sub->mutex);
+    pjsip_endpt_destroy_pool(sub->endpt, sub->pool);
+
+    pj_mutex_unlock(mgr.mutex);
+
+    PJ_LOG(4,(THIS_FILE, "event_sub%p: destroyed", sub));
+    return 0;
+}
+
+/* Change state. */
+static void sub_set_state( pjsip_event_sub *sub, int new_state)
+{
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): changed state to %s",
+	      sub, state[sub->state].ptr, state[new_state].ptr));
+    sub->state = new_state;
+    sub->state_str = state[new_state];
+}
+
+/*
+ * Refresh subscription.
+ */
+static pj_status_t send_sub_refresh( pjsip_event_sub *sub )
+{
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+    const pjsip_route_hdr *route;
+
+    pj_assert(sub->role == PJSIP_ROLE_UAC);
+    pj_assert(sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED);
+    if (sub->role != PJSIP_ROLE_UAC || 
+	sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED)
+    {
+	return -1;
+    }
+
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): refreshing subscription", 
+			 sub, state[sub->state].ptr));
+
+    /* Create request. */
+    tdata = pjsip_endpt_create_request_from_hdr( sub->endpt, 
+						 &SUBSCRIBE,
+						 sub->to->uri,
+						 sub->from, sub->to, 
+						 sub->contact, sub->call_id,
+						 sub->cseq++,
+						 NULL);
+
+    if (!tdata) {
+	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): refresh: unable to create tx data!",
+			     sub, state[sub->state].ptr));
+	return -1;
+    }
+
+    pjsip_msg_add_hdr( tdata->msg, 
+		       pjsip_hdr_shallow_clone(tdata->pool, sub->event));
+    pjsip_msg_add_hdr( tdata->msg, 
+		       pjsip_hdr_shallow_clone(tdata->pool, sub->uac_expires));
+    pjsip_msg_add_hdr( tdata->msg, 
+		       pjsip_hdr_shallow_clone(tdata->pool, sub->local_accept));
+    pjsip_msg_add_hdr( tdata->msg, 
+		       pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events));
+
+    /* Authentication */
+    pjsip_auth_init_req( sub->pool, tdata, &sub->auth_sess,
+			 sub->cred_cnt, sub->cred_info);
+
+    /* Route set. */
+    route = sub->route_set.next;
+    while (route != &sub->route_set) {
+	pj_list_insert_before( &tdata->msg->hdr,
+			       pjsip_hdr_shallow_clone(tdata->pool, route));
+	route = route->next;
+    }
+
+    /* Send */
+    status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub, 
+				       &on_subscribe_response);
+    if (status == 0) {
+	sub->pending_tsx++;
+    } else {
+	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): FAILED to refresh subscription!", 
+			     sub, state[sub->state].ptr));
+    }
+
+    return status;
+}
+
+/*
+ * Stop subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_unsubscribe( pjsip_event_sub *sub )
+{
+    pjsip_tx_data *tdata;
+    const pjsip_route_hdr *route;
+    pj_status_t status;
+
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): unsubscribing...", 
+			 sub, state[sub->state].ptr));
+
+    /* Lock subscription. */
+    pj_mutex_lock(sub->mutex);
+
+    pj_assert(sub->role == PJSIP_ROLE_UAC);
+
+    /* Kill refresh timer, if any. */
+    if (sub->timer.id != 0) {
+	sub->timer.id = 0;
+	pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+    }
+
+    /* Create request. */
+    tdata = pjsip_endpt_create_request_from_hdr( sub->endpt, 
+						 &SUBSCRIBE,
+						 sub->to->uri,
+						 sub->from, sub->to, 
+						 sub->contact, sub->call_id,
+						 sub->cseq++,
+						 NULL);
+
+    if (!tdata) {
+	pj_mutex_unlock(sub->mutex);
+	return -1;
+    }
+
+    /* Add headers to request. */
+    pjsip_msg_add_hdr( tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, sub->event));
+    sub->uac_expires->ivalue = 0;
+    pjsip_msg_add_hdr( tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, sub->uac_expires));
+
+    /* Add authentication. */
+    pjsip_auth_init_req( sub->pool, tdata, &sub->auth_sess,
+			 sub->cred_cnt, sub->cred_info);
+
+
+    /* Route set. */
+    route = sub->route_set.next;
+    while (route != &sub->route_set) {
+	pj_list_insert_before( &tdata->msg->hdr,
+			       pjsip_hdr_shallow_clone(tdata->pool, route));
+	route = route->next;
+    }
+
+    /* Prevent timer from refreshing itself. */
+    sub->default_interval = 0;
+
+    /* Set state. */
+    sub_set_state( sub, PJSIP_EVENT_SUB_STATE_TERMINATED );
+
+    /* Send the request. */
+    status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub, 
+				       &on_subscribe_response);
+    if (status == 0) {
+	sub->pending_tsx++;
+    }
+
+    pj_mutex_unlock(sub->mutex);
+
+    if (status != 0) {
+	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): FAILED to unsubscribe!", 
+			     sub, state[sub->state].ptr));
+    }
+
+    return status;
+}
+
+/*
+ * Send notify.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_notify(pjsip_event_sub *sub,
+					   pjsip_event_sub_state new_state,
+					   const pj_str_t *reason,
+					   pjsip_msg_body *body)
+{
+    pjsip_tx_data *tdata;
+    pjsip_sub_state_hdr *ss_hdr;
+    const pjsip_route_hdr *route;
+    pj_time_val now;
+    pj_status_t status;
+    pjsip_event_sub_state old_state = sub->state;
+
+    pj_gettimeofday(&now);
+
+    pj_assert(sub->role == PJSIP_ROLE_UAS);
+    if (sub->role != PJSIP_ROLE_UAS)
+	return -1;
+
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): sending NOTIFY", 
+			 sub, state[new_state].ptr));
+
+    /* Lock subscription. */
+    pj_mutex_lock(sub->mutex);
+
+    /* Can not send NOTIFY if current state is NULL. We can accept TERMINATED. */
+    if (sub->state==PJSIP_EVENT_SUB_STATE_NULL) {
+	pj_assert(0);
+	pj_mutex_unlock(sub->mutex);
+	return -1;
+    }
+
+    /* Update state no matter what. */
+    sub_set_state(sub, new_state);
+
+    /* Create transmit data. */
+    tdata = pjsip_endpt_create_request_from_hdr( sub->endpt,
+						 &NOTIFY,
+						 sub->to->uri,
+						 sub->from, sub->to,
+						 sub->contact, sub->call_id,
+						 sub->cseq++,
+						 NULL);
+    if (!tdata) {
+	pj_mutex_unlock(sub->mutex);
+	return -1;
+    }
+
+    /* Add Event header. */
+    pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, sub->event));
+
+    /* Add Subscription-State header. */
+    ss_hdr = pjsip_sub_state_hdr_create(tdata->pool);
+    ss_hdr->sub_state = state[new_state];
+    ss_hdr->expires_param = sub->expiry_time.sec - now.sec;
+    if (ss_hdr->expires_param < 0)
+	ss_hdr->expires_param = 0;
+    if (reason)
+	pj_strdup(tdata->pool, &ss_hdr->reason_param, reason);
+    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)ss_hdr);
+
+    /* Add Allow-Events header. */
+    pjsip_msg_add_hdr( tdata->msg, 
+		       pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events));
+
+    /* Add authentication */
+    pjsip_auth_init_req( sub->pool, tdata, &sub->auth_sess,
+			 sub->cred_cnt, sub->cred_info);
+
+    /* Route set. */
+    route = sub->route_set.next;
+    while (route != &sub->route_set) {
+	pj_list_insert_before( &tdata->msg->hdr,
+			       pjsip_hdr_shallow_clone(tdata->pool, route));
+	route = route->next;
+    }
+
+    /* Attach body. */
+    tdata->msg->body = body;
+
+    /* That's it, send! */
+    status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub, &on_notify_response);
+    if (status == 0)
+	sub->pending_tsx++;
+
+    /* If terminated notify application. */
+    if (new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) {
+	if (sub->cb.on_sub_terminated) {
+	    sub->pending_tsx++;
+	    (*sub->cb.on_sub_terminated)(sub, reason);
+	    sub->pending_tsx--;
+	}
+    }
+
+    /* Unlock subscription. */
+    pj_mutex_unlock(sub->mutex);
+
+    if (status != 0) {
+	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): failed to send NOTIFY", 
+			     sub, state[sub->state].ptr));
+    }
+
+    if (sub->delete_flag && sub->pending_tsx <= 0) {
+	pjsip_event_sub_destroy(sub);
+    }
+    return status;
+}
+
+
+/* If this timer callback is called, it means subscriber hasn't refreshed its
+ * subscription on-time. Set the state to terminated. This will also send
+ * NOTIFY with Subscription-State set to terminated.
+ */
+static void uas_expire_timer_cb( pj_timer_heap_t *timer_heap, pj_timer_entry *entry)
+{
+    pjsip_event_sub *sub = entry->user_data;
+    pj_str_t reason = { "timeout", 7 };
+
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): UAS subscription expired!", 
+			 sub, state[sub->state].ptr));
+
+    pj_mutex_lock(sub->mutex);
+    sub->timer.id = 0;
+
+    if (sub->cb.on_sub_terminated && sub->state!=PJSIP_EVENT_SUB_STATE_TERMINATED) {
+	/* Notify application, but prevent app from destroying the sub. */
+	++sub->pending_tsx;
+	(*sub->cb.on_sub_terminated)(sub, &reason);
+	--sub->pending_tsx;
+    }
+    //pjsip_event_sub_notify( sub, PJSIP_EVENT_SUB_STATE_TERMINATED, 
+    //			    &reason, NULL);
+    pj_mutex_unlock(sub->mutex);
+
+}
+
+/* Schedule notifier expiration. */
+static void sub_schedule_uas_expire( pjsip_event_sub *sub, int sec_delay)
+{
+    pj_time_val delay = { 0, 0 };
+    pj_parsed_time pt;
+
+    if (sub->timer.id != 0)
+	pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+
+    pj_gettimeofday(&sub->expiry_time);
+    sub->expiry_time.sec += sec_delay;
+
+    sub->timer.id = TIMER_ID_UAS_EXPIRY;
+    sub->timer.user_data = sub;
+    sub->timer.cb = &uas_expire_timer_cb;
+    delay.sec = sec_delay;
+    pjsip_endpt_schedule_timer( sub->endpt, &sub->timer, &delay);
+
+    pj_time_decode(&sub->expiry_time, &pt);
+    PJ_LOG(4,(THIS_FILE, 
+	      "event_sub%p (%s)(UAS): will expire at %02d:%02d:%02d (in %d secs)",
+	      sub, state[sub->state].ptr, pt.hour, pt.min, pt.sec, sec_delay));
+}
+
+/* This timer is called for UAC to refresh the subscription. */
+static void refresh_timer_cb( pj_timer_heap_t *timer_heap, pj_timer_entry *entry)
+{
+    pjsip_event_sub *sub = entry->user_data;
+
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): refresh subscription timer", 
+			 sub, state[sub->state].ptr));
+
+    pj_mutex_lock(sub->mutex);
+    sub->timer.id = 0;
+    send_sub_refresh(sub);
+    pj_mutex_unlock(sub->mutex);
+}
+
+
+/* This will update the UAC's refresh schedule. */
+static void update_next_refresh(pjsip_event_sub *sub, int interval)
+{
+    pj_time_val delay = {0, 0};
+    pj_parsed_time pt;
+
+    if (interval < SECONDS_BEFORE_EXPIRY) {
+	PJ_LOG(4,(THIS_FILE, 
+		  "event_sub%p (%s): expiration delay too short (%d sec)! updated.",
+		  sub, state[sub->state].ptr, interval));
+	interval = SECONDS_BEFORE_EXPIRY;
+    }
+
+    if (sub->timer.id != 0)
+	pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+
+    sub->timer.id = TIMER_ID_REFRESH;
+    sub->timer.user_data = sub;
+    sub->timer.cb = &refresh_timer_cb;
+    pj_gettimeofday(&sub->expiry_time);
+    delay.sec = interval - SECONDS_BEFORE_EXPIRY;
+    sub->expiry_time.sec += delay.sec;
+
+    pj_time_decode(&sub->expiry_time, &pt);
+    PJ_LOG(4,(THIS_FILE, 
+	      "event_sub%p (%s): will send SUBSCRIBE at %02d:%02d:%02d (in %d secs)",
+	      sub, state[sub->state].ptr, 
+	      pt.hour, pt.min, pt.sec,
+	      delay.sec));
+
+    pjsip_endpt_schedule_timer( sub->endpt, &sub->timer, &delay );
+}
+
+
+/* Find subscription in the hash table. 
+ * If found, lock the subscription before returning to caller.
+ */
+static pjsip_event_sub *find_sub(pjsip_rx_data *rdata)
+{
+    pj_str_t key;
+    pjsip_role_e role;
+    pjsip_event_sub *sub;
+    pjsip_method *method = &rdata->msg->line.req.method;
+    pj_str_t *tag;
+
+    if (rdata->msg->type == PJSIP_REQUEST_MSG) {
+	if (pjsip_method_cmp(method, &SUBSCRIBE)==0) {
+	    role = PJSIP_ROLE_UAS;
+	    tag = &rdata->to_tag;
+	} else {
+	    pj_assert(pjsip_method_cmp(method, &NOTIFY) == 0);
+	    role = PJSIP_ROLE_UAC;
+	    tag = &rdata->to_tag;
+	}
+    } else {
+	if (pjsip_method_cmp(&rdata->cseq->method, &SUBSCRIBE)==0) {
+	    role = PJSIP_ROLE_UAC;
+	    tag = &rdata->from_tag;
+	} else {
+	    pj_assert(pjsip_method_cmp(method, &NOTIFY) == 0);
+	    role = PJSIP_ROLE_UAS;
+	    tag = &rdata->from_tag;
+	}
+    }
+    create_subscriber_key( &key, rdata->pool, role, &rdata->call_id, tag);
+
+    pj_mutex_lock(mgr.mutex);
+    sub = pj_hash_get(mgr.ht, key.ptr, key.slen);
+    if (sub)
+	pj_mutex_lock(sub->mutex);
+    pj_mutex_unlock(mgr.mutex);
+
+    return sub;
+}
+
+
+/* This function is called when we receive SUBSCRIBE request message 
+ * to refresh existing subscription.
+ */
+static void on_received_sub_refresh( pjsip_event_sub *sub, 
+				     pjsip_transaction *tsx, pjsip_rx_data *rdata)
+{
+    pjsip_event_hdr *e;
+    pjsip_expires_hdr *expires;
+    pj_str_t hname;
+    int status = 200;
+    pj_str_t reason_phrase = { NULL, 0 };
+    int new_state = sub->state;
+    int old_state = sub->state;
+    int new_interval = 0;
+    pjsip_tx_data *tdata;
+
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): received target refresh", 
+			 sub, state[sub->state].ptr));
+
+    /* Check that the event matches. */
+    hname = pj_str("Event");
+    e = pjsip_msg_find_hdr_by_name( rdata->msg, &hname, NULL);
+    if (!e) {
+	status = 400;
+	reason_phrase = pj_str("Missing Event header");
+	goto send_response;
+    }
+    if (pj_stricmp(&e->event_type, &sub->event->event_type) != 0 ||
+	pj_stricmp(&e->id_param, &sub->event->id_param) != 0)
+    {
+	status = 481;
+	reason_phrase = pj_str("Subscription does not exist");
+	goto send_response;
+    }
+
+    /* Check server state. */
+    if (sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED) {
+	status = 481;
+	reason_phrase = pj_str("Subscription does not exist");
+	goto send_response;
+    }
+
+    /* Check expires header. */
+    expires = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_EXPIRES, NULL);
+    if (!expires) {
+	/*
+	status = 400;
+	reason_phrase = pj_str("Missing Expires header");
+	goto send_response;
+	*/
+	new_interval = sub->default_interval;
+    } else {
+	/* Check that interval is not too short. 
+	 * Note that expires time may be zero (for unsubscription).
+	 */
+	new_interval = expires->ivalue;
+	if (new_interval != 0 && new_interval < SECONDS_BEFORE_EXPIRY) {
+	    status = PJSIP_SC_INTERVAL_TOO_BRIEF;
+	    goto send_response;
+	}
+    }
+
+    /* Update interval. */
+    sub->default_interval = new_interval;
+    pj_gettimeofday(&sub->expiry_time);
+    sub->expiry_time.sec += new_interval;
+
+    /* Update timer only if this is not unsubscription. */
+    if (new_interval > 0) {
+	sub->default_interval = new_interval;
+	sub_schedule_uas_expire( sub, new_interval );
+
+	/* Call callback. */
+	if (sub->cb.on_received_refresh) {
+	    sub->pending_tsx++;
+	    (*sub->cb.on_received_refresh)(sub, rdata);
+	    sub->pending_tsx--;
+	}
+    }
+
+send_response:
+    tdata = pjsip_endpt_create_response( sub->endpt, rdata, status);
+    if (tdata) {
+	if (reason_phrase.slen)
+	    tdata->msg->line.status.reason = reason_phrase;
+
+	/* Add Expires header. */
+	expires = pjsip_expires_hdr_create(tdata->pool);
+	expires->ivalue = sub->default_interval;
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)expires);
+
+	if (PJSIP_IS_STATUS_IN_CLASS(status,200)) {
+	    pjsip_msg_add_hdr(tdata->msg, 
+			      pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events));
+	}
+	/* Send down to transaction. */
+	pjsip_tsx_on_tx_msg(tsx, tdata);
+    }
+
+    if (sub->default_interval==0 || !PJSIP_IS_STATUS_IN_CLASS(status,200)) {
+	/* Notify application if sub is terminated. */
+	new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
+	sub_set_state(sub, new_state);
+	if (new_state!=old_state && sub->cb.on_sub_terminated) {
+	    pj_str_t reason = {"", 0};
+	    if (reason_phrase.slen) reason = reason_phrase;
+	    else reason = *pjsip_get_status_text(status);
+
+	    sub->pending_tsx++;
+	    (*sub->cb.on_sub_terminated)(sub, &reason);
+	    sub->pending_tsx--;
+	}
+    }
+
+    pj_mutex_unlock(sub->mutex);
+
+    /* Prefer to call log when we're not holding the mutex. */
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): sent refresh response %s, status=%d", 
+			 sub, state[sub->state].ptr,
+			 (tdata ? tdata->obj_name : "null"), status));
+
+    /* Check if application has requested deletion. */
+    if (sub->delete_flag && sub->pending_tsx <= 0) {
+	pjsip_event_sub_destroy(sub);
+    }
+
+}
+
+
+/* This function is called when we receive SUBSCRIBE request message for 
+ * a new subscription.
+ */
+static void on_new_subscription( pjsip_transaction *tsx, pjsip_rx_data *rdata )
+{
+    package *pkg;
+    pj_pool_t *pool;
+    pjsip_event_sub *sub = NULL;
+    pj_str_t hname;
+    int status = 200;
+    pj_str_t reason = { NULL, 0 };
+    pjsip_tx_data *tdata;
+    pjsip_expires_hdr *expires;
+    pjsip_accept_hdr *accept;
+    pjsip_event_hdr *evhdr;
+
+    /* Get the Event header. */
+    hname = pj_str("Event");
+    evhdr = pjsip_msg_find_hdr_by_name(rdata->msg, &hname, NULL);
+    if (!evhdr) {
+	status = 400;
+	reason = pj_str("No Event header in request");
+	goto send_response;
+    }
+
+    /* Find corresponding package. 
+     * We don't lock the manager's mutex since we assume the package list
+     * won't change once the application is running!
+     */
+    pkg = mgr.pkg_list.next;
+    while (pkg != &mgr.pkg_list) {
+	if (pj_stricmp(&pkg->event, &evhdr->event_type) == 0)
+	    break;
+	pkg = pkg->next;
+    }
+
+    if (pkg == &mgr.pkg_list) {
+	/* Event type is not supported by any packages! */
+	status = 489;
+	reason = pj_str("Bad Event");
+	goto send_response;
+    }
+
+    /* First check that the Accept specification matches the 
+     * package's Accept types.
+     */
+    accept = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_ACCEPT, NULL);
+    if (accept) {
+	unsigned i;
+	pj_str_t *content_type = NULL;
+
+	for (i=0; i<accept->count && !content_type; ++i) {
+	    int j;
+	    for (j=0; j<pkg->accept_cnt; ++j) {
+		if (pj_stricmp(&accept->values[i], &pkg->accept[j])==0) {
+		    content_type = &pkg->accept[j];
+		    break;
+		}
+	    }
+	}
+
+	if (!content_type) {
+	    status = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+	    goto send_response;
+	}
+    }
+
+    /* Check whether the package wants to accept the subscription. */
+    pj_assert(pkg->cb.on_query_subscribe != NULL);
+    (*pkg->cb.on_query_subscribe)(rdata, &status);
+    if (!PJSIP_IS_STATUS_IN_CLASS(status,200))
+	goto send_response;
+
+    /* Create new subscription record. */
+    pool = pjsip_endpt_create_pool(tsx->endpt, "esub", 
+				   SUB_POOL_SIZE, SUB_POOL_INC);
+    if (!pool) {
+	status = 500;
+	goto send_response;
+    }
+    sub = pj_pool_calloc(pool, 1, sizeof(*sub));
+    sub->pool = pool;
+    sub->mutex = pj_mutex_create(pool, "esub", PJ_MUTEX_RECURSE);
+    if (!sub->mutex) {
+	status = 500;
+	goto send_response;
+    }
+
+    PJ_LOG(4,(THIS_FILE, "event_sub%p: notifier is created.", sub));
+
+    /* Start locking mutex. */
+    pj_mutex_lock(sub->mutex);
+
+    /* Init UAS subscription */
+    sub->endpt = tsx->endpt;
+    sub->role = PJSIP_ROLE_UAS;
+    sub->state = PJSIP_EVENT_SUB_STATE_PENDING;
+    sub->state_str = state[sub->state];
+    pj_list_init(&sub->auth_sess);
+    pj_list_init(&sub->route_set);
+    sub->from = pjsip_hdr_clone(pool, rdata->to);
+    pjsip_fromto_set_from(sub->from);
+    if (sub->from->tag.slen == 0) {
+	pj_create_unique_string(pool, &sub->from->tag);
+	rdata->to->tag = sub->from->tag;
+    }
+    sub->to = pjsip_hdr_clone(pool, rdata->from);
+    pjsip_fromto_set_to(sub->to);
+    sub->contact = pjsip_contact_hdr_create(pool);
+    sub->contact->uri = sub->from->uri;
+    sub->call_id = pjsip_cid_hdr_create(pool);
+    pj_strdup(pool, &sub->call_id->id, &rdata->call_id);
+    sub->cseq = pj_rand() % 0xFFFF;
+    
+    expires = pjsip_msg_find_hdr( rdata->msg, PJSIP_H_EXPIRES, NULL);
+    if (expires) {
+	sub->default_interval = expires->ivalue;
+	if (sub->default_interval > 0 && 
+	    sub->default_interval < SECONDS_BEFORE_EXPIRY) 
+	{
+	    status = 423; /* Interval too short. */
+	    goto send_response;
+	}
+    } else {
+	sub->default_interval = 600;
+    }
+
+    /* Clone Event header. */
+    sub->event = pjsip_hdr_clone(pool, evhdr);
+
+    /* Register to hash table. */
+    create_subscriber_key(&sub->key, pool, PJSIP_ROLE_UAS, &sub->call_id->id,
+			  &sub->from->tag);
+    pj_mutex_lock(mgr.mutex);
+    pj_hash_set(pool, mgr.ht, sub->key.ptr, sub->key.slen, sub);
+    pj_mutex_unlock(mgr.mutex);
+
+    /* Set timer where subscription will expire only when expires<>0. 
+     * Subscriber may send new subscription with expires==0.
+     */
+    if (sub->default_interval != 0) {
+	sub_schedule_uas_expire( sub, sub->default_interval-SECONDS_BEFORE_EXPIRY);
+    }
+
+    /* Notify application. */
+    if (pkg->cb.on_subscribe) {
+	pjsip_event_sub_cb *cb = NULL;
+	sub->pending_tsx++;
+	(*pkg->cb.on_subscribe)(sub, rdata, &cb, &sub->default_interval);
+	sub->pending_tsx--;
+	if (cb == NULL)
+	    pj_memset(&sub->cb, 0, sizeof(*cb));
+	else
+	    pj_memcpy(&sub->cb, cb, sizeof(*cb));
+    }
+
+
+send_response:
+    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s)(UAS): status=%d", 
+			  sub, state[sub->state].ptr, status));
+
+    tdata = pjsip_endpt_create_response( tsx->endpt, rdata, status);
+    if (tdata) {
+	if (reason.slen) {
+	    /* Customize reason text. */
+	    tdata->msg->line.status.reason = reason;
+	}
+	if (PJSIP_IS_STATUS_IN_CLASS(status,200)) {
+	    /* Add Expires header. */
+	    pjsip_expires_hdr *hdr;
+
+	    hdr = pjsip_expires_hdr_create(tdata->pool);
+	    hdr->ivalue = sub->default_interval;
+	    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hdr );
+	}
+	if (status == 423) {
+	    /* Add Min-Expires header. */
+	    pjsip_min_expires_hdr *hdr;
+
+	    hdr = pjsip_min_expires_hdr_create(tdata->pool);
+	    hdr->ivalue = SECONDS_BEFORE_EXPIRY;
+	    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hdr);
+	}
+	if (status == 489 || 
+	    status==PJSIP_SC_NOT_ACCEPTABLE_HERE ||
+	    PJSIP_IS_STATUS_IN_CLASS(status,200)) 
+	{
+	    /* Add Allow-Events header. */
+	    pjsip_hdr *hdr;
+	    hdr = pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events);
+	    pjsip_msg_add_hdr(tdata->msg, hdr);
+
+	    /* Should add Accept header?. */
+	}
+
+	pjsip_tsx_on_tx_msg(tsx, tdata);
+    }
+
+    /* If received new subscription with expires=0, terminate. */
+    if (sub && sub->default_interval == 0) {
+	pj_assert(sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED);
+	if (sub->cb.on_sub_terminated) {
+	    pj_str_t reason = { "timeout", 7 };
+	    (*sub->cb.on_sub_terminated)(sub, &reason);
+	}
+    }
+
+    if (!PJSIP_IS_STATUS_IN_CLASS(status,200) || (sub && sub->delete_flag)) {
+	if (sub && sub->mutex) {
+	    pjsip_event_sub_destroy(sub);
+	} else if (sub) {
+	    pjsip_endpt_destroy_pool(tsx->endpt, sub->pool);
+	}
+    } else {
+	pj_assert(status >= 200);
+	pj_mutex_unlock(sub->mutex);
+    }
+}
+
+/* This is the main callback when SUBSCRIBE request is received. */
+static void on_subscribe_request(pjsip_transaction *tsx, pjsip_rx_data *rdata)
+{
+    pjsip_event_sub *sub = find_sub(rdata);
+
+    if (sub)
+	on_received_sub_refresh(sub, tsx, rdata);
+    else
+	on_new_subscription(tsx, rdata);
+}
+
+
+/* This callback is called when response to SUBSCRIBE is received. */
+static void on_subscribe_response(void *token, pjsip_event *event)
+{
+    pjsip_event_sub *sub = token;
+    pjsip_transaction *tsx = event->obj.tsx;
+    int new_state, old_state = sub->state;
+
+    pj_assert(tsx->status_code >= 200);
+    if (tsx->status_code < 200)
+	return;
+
+    pj_assert(sub->role == PJSIP_ROLE_UAC);
+
+    /* Lock mutex. */
+    pj_mutex_lock(sub->mutex);
+
+    /* If request failed with 401/407 error, silently retry the request. */
+    if (tsx->status_code==401 || tsx->status_code==407) {
+	pjsip_tx_data *tdata;
+	tdata = pjsip_auth_reinit_req(sub->endpt,
+				      sub->pool, &sub->auth_sess,
+				      sub->cred_cnt, sub->cred_info,
+				      tsx->last_tx, event->src.rdata );
+	if (tdata) {
+	    int status;
+	    pjsip_cseq_hdr *cseq;
+	    cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
+	    cseq->cseq = sub->cseq++;
+	    status = pjsip_endpt_send_request( sub->endpt, tdata, 
+					       -1, sub, 
+					       &on_subscribe_response);
+	    if (status == 0) {
+		pj_mutex_unlock(sub->mutex);
+		return;
+	    }
+	}
+    }
+
+    if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code,200)) {
+	/* Update To tag. */
+	if (sub->to->tag.slen == 0)
+	    pj_strdup(sub->pool, &sub->to->tag, &event->src.rdata->to_tag);
+
+	new_state = sub->state;
+
+    } else if (tsx->status_code == 481) {
+	new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
+
+    } else if (tsx->status_code >= 300) {
+	/* RFC 3265 Section 3.1.4.2:
+         * If a SUBSCRIBE request to refresh a subscription fails 
+	 * with a non-481 response, the original subscription is still 
+	 * considered valid for the duration of original exires.
+	 *
+	 * Note:
+	 * Since we normally send SUBSCRIBE for refreshing the subscription,
+	 * it means the subscription already expired anyway. So we terminate
+	 * the subscription now.
+	 */
+	if (sub->state != PJSIP_EVENT_SUB_STATE_ACTIVE) {
+	    new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
+	} else {
+	    /* Use this to be compliant with Section 3.1.4.2
+	      new_state = sub->state;
+	     */
+	    new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
+	}
+    } else {
+	pj_assert(0);
+	new_state = sub->state;
+    }
+
+    if (new_state != sub->state && sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED) {
+	sub_set_state(sub, new_state);
+    }
+
+    if (sub->state == PJSIP_EVENT_SUB_STATE_ACTIVE ||
+	sub->state == PJSIP_EVENT_SUB_STATE_PENDING)
+    {
+	/*
+	 * Register timer for next subscription refresh, but only when
+	 * we're not unsubscribing. Also update default_interval and Expires
+	 * header.
+	 */
+	if (sub->default_interval > 0 && !sub->delete_flag) {
+	    pjsip_expires_hdr *exp = NULL;
+	    
+	    /* Could be transaction timeout. */
+	    if (event->src_type == PJSIP_EVENT_RX_MSG) {
+		exp = pjsip_msg_find_hdr(event->src.rdata->msg,
+					 PJSIP_H_EXPIRES, NULL);
+	    }
+
+	    if (exp) {
+		int delay = exp->ivalue;
+		if (delay > 0) {
+		    pj_time_val new_expiry;
+		    pj_gettimeofday(&new_expiry);
+		    new_expiry.sec += delay;
+		    if (sub->timer.id==0 || 
+			new_expiry.sec < sub->expiry_time.sec-SECONDS_BEFORE_EXPIRY/2) 
+		    {
+		    //if (delay > 0 && delay < sub->default_interval) {
+			sub->default_interval = delay;
+			sub->uac_expires->ivalue = delay;
+			update_next_refresh(sub, delay);
+		    }
+		}
+	    }
+	}
+    }
+
+    /* Call callback. */
+    if (!sub->delete_flag) {
+	if (sub->cb.on_received_sub_response) {
+	    (*sub->cb.on_received_sub_response)(sub, event);
+	}
+    }
+
+    /* Notify application if we're terminated. */
+    if (new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) {
+	if (sub->cb.on_sub_terminated) {
+	    pj_str_t reason;
+	    if (event->src_type == PJSIP_EVENT_RX_MSG)
+		reason = event->src.rdata->msg->line.status.reason;
+	    else
+		reason = *pjsip_get_status_text(tsx->status_code);
+
+	    (*sub->cb.on_sub_terminated)(sub, &reason);
+	}
+    }
+
+    /* Decrement pending tsx count. */
+    --sub->pending_tsx;
+    pj_assert(sub->pending_tsx >= 0);
+
+    if (sub->delete_flag && sub->pending_tsx <= 0) {
+	pjsip_event_sub_destroy(sub);
+    } else {
+	pj_mutex_unlock(sub->mutex);
+    }
+
+    /* DO NOT ACCESS sub FROM NOW ON! IT MIGHT HAVE BEEN DELETED */
+}
+
+/*
+ * This callback called when we receive incoming NOTIFY request.
+ */
+static void on_notify_request(pjsip_transaction *tsx, pjsip_rx_data *rdata)
+{
+    pjsip_event_sub *sub;
+    pjsip_tx_data *tdata;
+    int status = 200;
+    int old_state;
+    pj_str_t reason = { NULL, 0 };
+    pj_str_t reason_phrase = { NULL, 0 };
+    int new_state = PJSIP_EVENT_SUB_STATE_NULL;
+
+    /* Find subscription based on Call-ID and From tag. 
+     * This will also automatically lock the subscription, if it's found.
+     */
+    sub = find_sub(rdata);
+    if (!sub) {
+	/* RFC 3265: Section 3.2 Description of NOTIFY Behavior:
+	 * Answer with 481 Subscription does not exist.
+	 */
+	PJ_LOG(4,(THIS_FILE, "Unable to find subscription for incoming NOTIFY!"));
+	status = 481;
+	reason_phrase = pj_str("Subscription does not exist");
+
+    } else {
+	pj_assert(sub->role == PJSIP_ROLE_UAC);
+	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): received NOTIFY", 
+			     sub, state[sub->state].ptr));
+
+    }
+
+    new_state = old_state = sub->state;
+
+    /* RFC 3265: Section 3.2.1
+     * Check that the Event header match the subscription. 
+     */
+    if (status == 200) {
+	pjsip_event_hdr *hdr;
+	pj_str_t hname = { "Event", 5 };
+
+	hdr = pjsip_msg_find_hdr_by_name(rdata->msg, &hname, NULL);
+	if (!hdr) {
+	    status = PJSIP_SC_BAD_REQUEST;
+	    reason_phrase = pj_str("No Event header found");
+	} else if (pj_stricmp(&hdr->event_type, &sub->event->event_type) != 0 ||
+		   pj_stricmp(&hdr->id_param, &sub->event->id_param) != 0) 
+	{
+	    status = 481;
+	    reason_phrase = pj_str("Subscription does not exist");
+	}
+    }
+
+    /* Update subscription state and timer. */
+    if (status == 200) {
+	pjsip_sub_state_hdr *hdr;
+	const pj_str_t hname = { "Subscription-State", 18 };
+	const pj_str_t state_active = { "active", 6 },
+		       state_pending = { "pending", 7},
+		       state_terminated = { "terminated", 10 };
+
+	hdr = pjsip_msg_find_hdr_by_name( rdata->msg, &hname, NULL);
+	if (!hdr) {
+	    status = PJSIP_SC_BAD_REQUEST;
+	    reason_phrase = pj_str("No Subscription-State header found");
+	    goto process;
+	} 
+
+	/*
+	 * Update subscription state.
+	 */
+	if (pj_stricmp(&hdr->sub_state, &state_active) == 0) {
+	    if (sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED)
+		new_state = PJSIP_EVENT_SUB_STATE_ACTIVE;
+	} else if (pj_stricmp(&hdr->sub_state, &state_pending) == 0) {
+	    if (sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED)
+		new_state = PJSIP_EVENT_SUB_STATE_PENDING;
+	} else if (pj_stricmp(&hdr->sub_state, &state_terminated) == 0) {
+	    new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
+	} else {
+	    new_state = PJSIP_EVENT_SUB_STATE_UNKNOWN;
+	}
+
+	reason = hdr->reason_param;
+
+	if (new_state != sub->state && new_state != PJSIP_EVENT_SUB_STATE_NULL &&
+	    sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED) 
+	{
+	    sub_set_state(sub, new_state);
+	    if (new_state == PJSIP_EVENT_SUB_STATE_UNKNOWN) {
+		pj_strdup_with_null(sub->pool, &sub->state_str, &hdr->sub_state);
+	    } else {
+		sub->state_str = state[new_state];
+	    }
+	}
+
+	/*
+	 * Update timeout timer in required, just in case notifier changed the 
+         * expiration to shorter time.
+	 * Section 3.2.2: the expires param can only shorten the interval.
+	 */
+	if ((sub->state==PJSIP_EVENT_SUB_STATE_ACTIVE || 
+	     sub->state==PJSIP_EVENT_SUB_STATE_PENDING) && hdr->expires_param > 0) 
+	{
+	    pj_time_val now, new_expiry;
+
+	    pj_gettimeofday(&now);
+	    new_expiry.sec = now.sec + hdr->expires_param;
+	    if (sub->timer.id==0 || 
+		new_expiry.sec < sub->expiry_time.sec-SECONDS_BEFORE_EXPIRY/2) 
+	    {
+		update_next_refresh(sub, hdr->expires_param);
+	    }
+	}
+    }
+
+process:
+    /* Note: here we sub MAY BE NULL! */
+
+    /* Send response to NOTIFY */
+    tdata = pjsip_endpt_create_response( tsx->endpt, rdata, status );
+    if (tdata) {
+	if (reason_phrase.slen)
+	    tdata->msg->line.status.reason = reason_phrase;
+
+	if (PJSIP_IS_STATUS_IN_CLASS(status,200)) {
+	    pjsip_hdr *hdr;
+	    hdr = pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events);
+	    pjsip_msg_add_hdr( tdata->msg, hdr);
+	}
+
+	pjsip_tsx_on_tx_msg(tsx, tdata);
+    }
+
+    /* Call NOTIFY callback, if any. */
+    if (sub && PJSIP_IS_STATUS_IN_CLASS(status,200) && sub->cb.on_received_notify) {
+	sub->pending_tsx++;
+	(*sub->cb.on_received_notify)(sub, rdata);
+	sub->pending_tsx--;
+    }
+
+    /* Check if subscription is terminated and call callback. */
+    if (sub && new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) {
+	if (sub->cb.on_sub_terminated) {
+	    sub->pending_tsx++;
+	    (*sub->cb.on_sub_terminated)(sub, &reason);
+	    sub->pending_tsx--;
+	}
+    }
+
+    /* Check if application has requested deletion. */
+    if (sub && sub->delete_flag && sub->pending_tsx <= 0) {
+	pjsip_event_sub_destroy(sub);
+    } else if (sub) {
+	pj_mutex_unlock(sub->mutex);
+    }
+}
+
+/* This callback is called when we received NOTIFY response. */
+static void on_notify_response(void *token, pjsip_event *event)
+{
+    pjsip_event_sub *sub = token;
+    pjsip_event_sub_state old_state = sub->state;
+    pjsip_transaction *tsx = event->obj.tsx;
+
+    /* Lock the subscription. */
+    pj_mutex_lock(sub->mutex);
+
+    pj_assert(sub->role == PJSIP_ROLE_UAS);
+
+    /* If request failed with authorization failure, silently retry. */
+    if (tsx->status_code==401 || tsx->status_code==407) {
+	pjsip_tx_data *tdata;
+	tdata = pjsip_auth_reinit_req(sub->endpt,
+				      sub->pool, &sub->auth_sess,
+				      sub->cred_cnt, sub->cred_info,
+				      tsx->last_tx, event->src.rdata );
+	if (tdata) {
+	    int status;
+	    pjsip_cseq_hdr *cseq;
+	    cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
+	    cseq->cseq = sub->cseq++;
+	    status = pjsip_endpt_send_request( sub->endpt, tdata, 
+					       -1, sub, 
+					       &on_notify_response);
+	    if (status == 0) {
+		pj_mutex_unlock(sub->mutex);
+		return;
+	    }
+	}
+    }
+
+    /* Notify application. */
+    if (sub->cb.on_received_notify_response)
+	(*sub->cb.on_received_notify_response)(sub, event);
+
+    /* Check for response 481. */
+    if (event->obj.tsx->status_code == 481) {
+	/* Remote says that the subscription does not exist! 
+	 * Terminate subscription!
+	 */
+	sub_set_state(sub, PJSIP_EVENT_SUB_STATE_TERMINATED);
+	if (sub->timer.id) {
+	    pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+	    sub->timer.id = 0;
+	}
+
+	PJ_LOG(4, (THIS_FILE, 
+		   "event_sub%p (%s): got 481 response to NOTIFY. Terminating...",
+		   sub, state[sub->state].ptr));
+
+	/* Notify app. */
+	if (sub->state!=old_state && sub->cb.on_sub_terminated) 
+	    (*sub->cb.on_sub_terminated)(sub, &event->src.rdata->msg->line.status.reason);
+    }
+
+    /* Decrement pending transaction count. */
+    --sub->pending_tsx;
+    pj_assert(sub->pending_tsx >= 0);
+
+    /* Check that the subscription is marked for deletion. */
+    if (sub->delete_flag && sub->pending_tsx <= 0) {
+	pjsip_event_sub_destroy(sub);
+    } else {
+	pj_mutex_unlock(sub->mutex);
+    }
+
+    /* DO NOT ACCESS sub, IT MIGHT HAVE BEEN DESTROYED! */
+}
+
+
+/* This is the transaction handler for incoming SUBSCRIBE and NOTIFY 
+ * requests. 
+ */
+static void tsx_handler( struct pjsip_module *mod, pjsip_event *event )
+{
+    pjsip_msg *msg;
+    pjsip_rx_data *rdata;
+
+    /* Only want incoming message events. */
+    if (event->src_type != PJSIP_EVENT_RX_MSG)
+	return;
+
+    rdata = event->src.rdata;
+    msg = rdata->msg;
+
+    /* Only want to process request messages. */
+    if (msg->type != PJSIP_REQUEST_MSG)
+	return;
+
+    /* Only want the first notification. */
+    if (event->obj.tsx && event->obj.tsx->status_code >= 100)
+	return;
+
+    if (pjsip_method_cmp(&msg->line.req.method, &SUBSCRIBE)==0) {
+	/* Process incoming SUBSCRIBE request. */
+	on_subscribe_request( event->obj.tsx, rdata );
+    } else if (pjsip_method_cmp(&msg->line.req.method, &NOTIFY)==0) {
+	/* Process incoming NOTIFY request. */
+	on_notify_request( event->obj.tsx, rdata );
+    }
+}
+
Index: pjproject/main/pjsip/src/pjsip-simple/event_notify_msg.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-simple/event_notify_msg.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-simple/event_notify_msg.c	(revision 39)
@@ -0,0 +1,307 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/event_notify_msg.h>
+#include <pjsip/print.h>
+#include <pjsip/sip_parser.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/except.h>
+
+static int pjsip_event_hdr_print( pjsip_event_hdr *hdr, 
+				  char *buf, pj_size_t size);
+static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool, 
+					       const pjsip_event_hdr *hdr);
+static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
+						       const pjsip_event_hdr*);
+
+static pjsip_hdr_vptr event_hdr_vptr = 
+{
+    (pjsip_hdr_clone_fptr) &pjsip_event_hdr_clone,
+    (pjsip_hdr_clone_fptr) &pjsip_event_hdr_shallow_clone,
+    (pjsip_hdr_print_fptr) &pjsip_event_hdr_print,
+};
+
+
+PJ_DEF(pjsip_event_hdr*) pjsip_event_hdr_create(pj_pool_t *pool)
+{
+    pj_str_t event = { "Event", 5 };
+    pjsip_event_hdr *hdr = pj_pool_calloc(pool, 1, sizeof(*hdr));
+    hdr->type = PJSIP_H_OTHER;
+    hdr->name = hdr->sname = event;
+    hdr->vptr = &event_hdr_vptr;
+    pj_list_init(hdr);
+    return hdr;
+}
+
+static int pjsip_event_hdr_print( pjsip_event_hdr *hdr, 
+				  char *buf, pj_size_t size)
+{
+    char *p = buf;
+    char *endbuf = buf+size;
+    int printed;
+
+    copy_advance(p, hdr->name);
+    *p++ = ':';
+    *p++ = ' ';
+
+    copy_advance(p, hdr->event_type);
+    copy_advance_pair(p, ";id=", 4, hdr->id_param);
+    if (hdr->other_param.slen)
+	copy_advance(p, hdr->other_param);
+    return p - buf;
+}
+
+static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool, 
+					       const pjsip_event_hdr *rhs)
+{
+    pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool);
+    pj_strdup(pool, &hdr->event_type, &rhs->event_type);
+    pj_strdup(pool, &hdr->id_param, &rhs->id_param);
+    pj_strdup(pool, &hdr->other_param, &rhs->other_param);
+    return hdr;
+}
+
+static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
+						       const pjsip_event_hdr *rhs )
+{
+    pjsip_event_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr));
+    pj_memcpy(hdr, rhs, sizeof(*hdr));
+    return hdr;
+}
+
+
+static int pjsip_allow_events_hdr_print(pjsip_allow_events_hdr *hdr, 
+					char *buf, pj_size_t size);
+static pjsip_allow_events_hdr* 
+pjsip_allow_events_hdr_clone(pj_pool_t *pool, 
+			     const pjsip_allow_events_hdr *hdr);
+static pjsip_allow_events_hdr* 
+pjsip_allow_events_hdr_shallow_clone(pj_pool_t *pool,
+				     const pjsip_allow_events_hdr*);
+
+static pjsip_hdr_vptr allow_event_hdr_vptr = 
+{
+    (pjsip_hdr_clone_fptr) &pjsip_allow_events_hdr_clone,
+    (pjsip_hdr_clone_fptr) &pjsip_allow_events_hdr_shallow_clone,
+    (pjsip_hdr_print_fptr) &pjsip_allow_events_hdr_print,
+};
+
+
+PJ_DEF(pjsip_allow_events_hdr*) pjsip_allow_events_hdr_create(pj_pool_t *pool)
+{
+    pj_str_t allow_events = { "Allow-Events", 12 };
+    pjsip_allow_events_hdr *hdr = pj_pool_calloc(pool, 1, sizeof(*hdr));
+    hdr->type = PJSIP_H_OTHER;
+    hdr->name = hdr->sname = allow_events;
+    hdr->vptr = &allow_event_hdr_vptr;
+    pj_list_init(hdr);
+    return hdr;
+}
+
+static int pjsip_allow_events_hdr_print(pjsip_allow_events_hdr *hdr, 
+					char *buf, pj_size_t size)
+{
+    char *p = buf;
+    char *endbuf = buf+size;
+    int printed;
+
+    copy_advance(p, hdr->name);
+    *p++ = ':';
+    *p++ = ' ';
+
+    if (hdr->event_cnt > 0) {
+	int i;
+	copy_advance(p, hdr->events[0]);
+	for (i=1; i<hdr->event_cnt; ++i) {
+	    copy_advance_pair(p, ",", 1, hdr->events[i]);
+	}
+    }
+
+    return p - buf;
+}
+
+static pjsip_allow_events_hdr* 
+pjsip_allow_events_hdr_clone(pj_pool_t *pool, 
+			     const pjsip_allow_events_hdr *rhs)
+{
+    int i;
+
+    pjsip_allow_events_hdr *hdr = pjsip_allow_events_hdr_create(pool);
+    hdr->event_cnt = rhs->event_cnt;
+    for (i=0; i<rhs->event_cnt; ++i) {
+	pj_strdup(pool, &hdr->events[i], &rhs->events[i]);
+    }
+    return hdr;
+}
+
+static pjsip_allow_events_hdr* 
+pjsip_allow_events_hdr_shallow_clone(pj_pool_t *pool,
+				     const pjsip_allow_events_hdr *rhs)
+{
+    pjsip_allow_events_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr));
+    pj_memcpy(hdr, rhs, sizeof(*hdr));
+    return hdr;
+}
+
+
+static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr, 
+				     char *buf, pj_size_t size);
+static pjsip_sub_state_hdr* 
+pjsip_sub_state_hdr_clone(pj_pool_t *pool, 
+			  const pjsip_sub_state_hdr *hdr);
+static pjsip_sub_state_hdr* 
+pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
+				  const pjsip_sub_state_hdr*);
+
+static pjsip_hdr_vptr sub_state_hdr_vptr = 
+{
+    (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_clone,
+    (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_shallow_clone,
+    (pjsip_hdr_print_fptr) &pjsip_sub_state_hdr_print,
+};
+
+
+PJ_DEF(pjsip_sub_state_hdr*) pjsip_sub_state_hdr_create(pj_pool_t *pool)
+{
+    pj_str_t sub_state = { "Subscription-State", 18 };
+    pjsip_sub_state_hdr *hdr = pj_pool_calloc(pool, 1, sizeof(*hdr));
+    hdr->type = PJSIP_H_OTHER;
+    hdr->name = hdr->sname = sub_state;
+    hdr->vptr = &sub_state_hdr_vptr;
+    hdr->expires_param = -1;
+    hdr->retry_after = -1;
+    pj_list_init(hdr);
+    return hdr;
+}
+
+static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr, 
+				     char *buf, pj_size_t size)
+{
+    char *p = buf;
+    char *endbuf = buf+size;
+    int printed;
+
+    copy_advance(p, hdr->name);
+    *p++ = ':';
+    *p++ = ' ';
+
+    copy_advance(p, hdr->sub_state);
+    copy_advance_pair(p, ";reason=", 8, hdr->reason_param);
+    if (hdr->expires_param >= 0) {
+	pj_memcpy(p, ";expires=", 9);
+	p += 9;
+	printed = pj_utoa(hdr->expires_param, p);
+	p += printed;
+    }
+    if (hdr->retry_after >= 0) {
+	pj_memcpy(p, ";retry-after=", 13);
+	p += 9;
+	printed = pj_utoa(hdr->retry_after, p);
+	p += printed;
+    }
+    if (hdr->other_param.slen)
+	copy_advance(p, hdr->other_param);
+
+    return p - buf;
+}
+
+static pjsip_sub_state_hdr* 
+pjsip_sub_state_hdr_clone(pj_pool_t *pool, 
+			  const pjsip_sub_state_hdr *rhs)
+{
+    pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool);
+    pj_strdup(pool, &hdr->sub_state, &rhs->sub_state);
+    pj_strdup(pool, &hdr->reason_param, &rhs->reason_param);
+    hdr->retry_after = rhs->retry_after;
+    hdr->expires_param = rhs->expires_param;
+    pj_strdup(pool, &hdr->other_param, &rhs->other_param);
+    return hdr;
+}
+
+static pjsip_sub_state_hdr* 
+pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
+				  const pjsip_sub_state_hdr *rhs)
+{
+    pjsip_sub_state_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr));
+    pj_memcpy(hdr, rhs, sizeof(*hdr));
+    return hdr;
+}
+
+static pjsip_event_hdr *parse_hdr_event(pj_scanner *scanner, 
+					pj_pool_t *pool)
+{
+    pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool);
+    const pj_str_t id_param = { "id", 2 };
+
+    pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->event_type);
+
+    while (*scanner->current == ';') {
+	pj_str_t pname, pvalue;
+	pj_scan_get_char(scanner);
+	pjsip_parse_param_imp(scanner, &pname, &pvalue, 0);
+	if (pj_stricmp(&pname, &id_param)==0) {
+	    hdr->id_param = pvalue;
+	} else {
+	    pjsip_concat_param_imp(&hdr->other_param, pool, &pname, &pvalue, ';');
+	}
+    }
+    pjsip_parse_end_hdr_imp( scanner );
+    return hdr;
+}
+
+static pjsip_allow_events_hdr *parse_hdr_allow_events(pj_scanner *scanner, 
+						      pj_pool_t *pool)
+{
+    pjsip_allow_events_hdr *hdr = pjsip_allow_events_hdr_create(pool);
+
+    pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->events[0]);
+    hdr->event_cnt = 1;
+
+    while (*scanner->current == ',') {
+	pj_scan_get_char(scanner);
+	pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->events[hdr->event_cnt++]);
+	if (hdr->event_cnt == PJSIP_MAX_ALLOW_EVENTS) {
+	    PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+	}
+    }
+
+    pjsip_parse_end_hdr_imp( scanner );
+    return hdr;
+}
+
+static pjsip_sub_state_hdr *parse_hdr_sub_state(pj_scanner *scanner, 
+						pj_pool_t *pool)
+{
+    pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool);
+    const pj_str_t reason = { "reason", 6 },
+		   expires = { "expires", 7 },
+		   retry_after = { "retry-after", 11 };
+    pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->sub_state);
+
+    while (*scanner->current == ';') {
+	pj_str_t pname, pvalue;
+
+	pj_scan_get_char(scanner);
+	pjsip_parse_param_imp(scanner, &pname, &pvalue, 0);
+	if (pj_stricmp(&pname, &reason) == 0) {
+	    hdr->reason_param = pvalue;
+	} else if (pj_stricmp(&pname, &expires) == 0) {
+	    hdr->expires_param = pj_strtoul(&pvalue);
+	} else if (pj_stricmp(&pname, &retry_after) == 0) {
+	    hdr->retry_after = pj_strtoul(&pvalue);
+	} else {
+	    pjsip_concat_param_imp(&hdr->other_param, pool, &pname, &pvalue, ';');
+	}
+    }
+
+    pjsip_parse_end_hdr_imp( scanner );
+    return hdr;
+}
+
+PJ_DEF(void) pjsip_event_notify_init_parser(void)
+{
+    pjsip_register_hdr_parser( "Event", NULL, (pjsip_parse_hdr_func*) &parse_hdr_event);
+    pjsip_register_hdr_parser( "Allow-Events", NULL, (pjsip_parse_hdr_func*) &parse_hdr_allow_events);
+    pjsip_register_hdr_parser( "Subscription-State", NULL, (pjsip_parse_hdr_func*) &parse_hdr_sub_state);
+}
Index: pjproject/main/pjsip/src/pjsip-simple/messaging.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-simple/messaging.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-simple/messaging.c	(revision 39)
@@ -0,0 +1,337 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/messaging.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_misc.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/guid.h>
+#include <pj/string.h>
+#include <pj/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define THIS_FILE   "messaging"
+
+struct messaging_data
+{
+    void		     *token;
+    pjsip_messaging_cb	     cb;
+};
+
+struct pjsip_messaging_session
+{
+    pj_pool_t		*pool;
+    pjsip_endpoint	*endpt;
+    pjsip_from_hdr	*from;
+    pjsip_to_hdr	*to;
+    pjsip_cid_hdr	*call_id;
+    pjsip_cseq_hdr	*cseq;
+};
+
+static int module_id;
+static pjsip_on_new_msg_cb incoming_cb;
+static pjsip_method message_method;
+
+
+/*
+ * Set global callback to receive incoming message.
+ */
+PJ_DEF(pjsip_on_new_msg_cb) 
+pjsip_messaging_set_incoming_callback(pjsip_on_new_msg_cb cb)
+{
+    pjsip_on_new_msg_cb prev_cb = incoming_cb;
+    incoming_cb = cb;
+    return prev_cb;
+}
+
+
+/*
+ * Create an independent message (ie. not associated with a session).
+ */
+PJ_DEF(pjsip_tx_data*) 
+pjsip_messaging_create_msg_from_hdr(pjsip_endpoint *endpt, 
+				    const pjsip_uri *target,
+				    const pjsip_from_hdr *param_from,
+				    const pjsip_to_hdr *param_to, 
+				    const pjsip_cid_hdr *param_call_id,
+				    int param_cseq, 
+				    const pj_str_t *param_text)
+{
+    return pjsip_endpt_create_request_from_hdr( endpt, &message_method, 
+						target,
+						param_from, param_to,
+						NULL, param_call_id,
+						param_cseq, param_text );
+}
+
+/*
+ * Create independent message from string (instead of from header).
+ */
+PJ_DEF(pjsip_tx_data*) 
+pjsip_messaging_create_msg( pjsip_endpoint *endpt, 
+			    const pj_str_t *target,
+			    const pj_str_t *param_from,
+			    const pj_str_t *param_to, 
+			    const pj_str_t *param_call_id,
+			    int param_cseq, 
+			    const pj_str_t *param_text)
+{
+    return pjsip_endpt_create_request( endpt, &message_method, target, 
+				       param_from, param_to, NULL, param_call_id,
+				       param_cseq, param_text);
+}
+
+/*
+ * Initiate transaction to send outgoing message.
+ */
+PJ_DEF(pj_status_t) 
+pjsip_messaging_send_msg( pjsip_endpoint *endpt, pjsip_tx_data *tdata, 
+			  void *token, pjsip_messaging_cb cb )
+{
+    pjsip_transaction *tsx;
+    struct messaging_data *msg_data;
+
+    /* Create transaction. */
+    tsx = pjsip_endpt_create_tsx(endpt);
+    if (!tsx) {
+	pjsip_tx_data_dec_ref(tdata);
+	return -1;
+    }
+
+    /* Save parameters to messaging data and attach to tsx. */
+    msg_data = pj_pool_calloc(tsx->pool, 1, sizeof(struct messaging_data));
+    msg_data->cb = cb;
+    msg_data->token = token;
+
+    /* Init transaction. */
+    tsx->module_data[module_id] = msg_data;
+    if (pjsip_tsx_init_uac(tsx, tdata) != 0) {
+	pjsip_tx_data_dec_ref(tdata);
+	pjsip_endpt_destroy_tsx(endpt, tsx);
+	return -1;
+    }
+
+    pjsip_endpt_register_tsx(endpt, tsx);
+
+    /* 
+     * Instruct transaction to send message.
+     * Further events will be received via transaction's event.
+     */
+    pjsip_tsx_on_tx_msg(tsx, tdata);
+
+    /* Decrement reference counter. */
+    pjsip_tx_data_dec_ref(tdata);
+    return 0;
+}
+
+
+/*
+ * Create 'IM session'.
+ */
+PJ_DEF(pjsip_messaging_session*) 
+pjsip_messaging_create_session( pjsip_endpoint *endpt, const pj_str_t *param_from,
+			        const pj_str_t *param_to )
+{
+    pj_pool_t *pool;
+    pjsip_messaging_session *ses;
+    pj_str_t tmp, to;
+
+    pool = pjsip_endpt_create_pool(endpt, "imsess", 1024, 1024);
+    if (!pool)
+	return NULL;
+
+    ses = pj_pool_calloc(pool, 1, sizeof(pjsip_messaging_session));
+    ses->pool = pool;
+    ses->endpt = endpt;
+
+    ses->call_id = pjsip_cid_hdr_create(pool);
+    pj_create_unique_string(pool, &ses->call_id->id);
+
+    ses->cseq = pjsip_cseq_hdr_create(pool);
+    ses->cseq->cseq = pj_rand();
+    ses->cseq->method = message_method;
+
+    ses->from = pjsip_from_hdr_create(pool);
+    pj_strdup_with_null(pool, &tmp, param_from);
+    ses->from->uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR);
+    if (ses->from->uri == NULL) {
+	pjsip_endpt_destroy_pool(endpt, pool);
+	return NULL;
+    }
+    pj_create_unique_string(pool, &ses->from->tag);
+
+    ses->to = pjsip_to_hdr_create(pool);
+    pj_strdup_with_null(pool, &to, param_from);
+    ses->to->uri = pjsip_parse_uri(pool, to.ptr, to.slen, PJSIP_PARSE_URI_AS_NAMEADDR);
+    if (ses->to->uri == NULL) {
+	pjsip_endpt_destroy_pool(endpt, pool);
+	return NULL;
+    }
+
+    PJ_LOG(4,(THIS_FILE, "IM session created: recipient=%s", to.ptr));
+    return ses;
+}
+
+
+/*
+ * Send IM message using identification from 'IM session'.
+ */
+PJ_DEF(pjsip_tx_data*)
+pjsip_messaging_session_create_msg( pjsip_messaging_session *ses, const pj_str_t *text )
+{
+    return pjsip_endpt_create_request_from_hdr( ses->endpt,
+						&message_method,
+						ses->to->uri,
+						ses->from,
+						ses->to,
+						NULL,
+						ses->call_id,
+						ses->cseq->cseq++,
+						text);
+}
+
+
+/*
+ * Destroy 'IM session'.
+ */
+PJ_DEF(pj_status_t)
+pjsip_messaging_destroy_session( pjsip_messaging_session *ses )
+{
+    /*
+     * NOTE ABOUT POSSIBLE BUG HERE...
+     *
+     * We don't check number of pending transaction before destroying IM
+     * session. As the result, the headers in the txdata of pending transaction
+     * wil be INVALID once the IM session is deleted (because we only
+     * shallo_clone()-ed them).
+     *
+     * This normally should be okay, because once the message is
+     * submitted to transaction, the transaction (or rather the transport)
+     * will 'print' the message to a buffer, and once it is printed, it
+     * won't try to access the original message again. So even when the 
+     * original message has a dangling pointer, we should be safe.
+     *
+     * However, it will cause a problem if:
+     *	- resolving completes asynchronously and with a substantial delay,
+     *	  and before the resolver/transport finished its job the user
+     *	  destroy the IM session.
+     *	- if the transmit data is invalidated after the IM session is
+     *	  destroyed.
+     */
+
+    pjsip_endpt_destroy_pool(ses->endpt, ses->pool);
+    return 0;
+}
+
+
+static pj_status_t messaging_init( pjsip_endpoint *endpt,
+				   struct pjsip_module *mod, pj_uint32_t id )
+{
+    PJ_UNUSED_ARG(endpt)
+    PJ_UNUSED_ARG(mod)
+
+    module_id = id;
+    return 0;
+}
+
+static pj_status_t messaging_start( struct pjsip_module *mod )
+{
+    PJ_UNUSED_ARG(mod)
+    return 0;
+}
+
+static pj_status_t messaging_deinit( struct pjsip_module *mod )
+{
+    PJ_UNUSED_ARG(mod)
+    return 0;
+}
+
+static void messaging_tsx_handler( struct pjsip_module *mod, pjsip_event *event )
+{
+    pjsip_transaction *tsx = event->obj.tsx;
+    struct messaging_data *mod_data;
+
+    PJ_UNUSED_ARG(mod)
+
+    /* Ignore non transaction event */
+    if (event->type != PJSIP_EVENT_TSX_STATE_CHANGED || tsx == NULL)
+	return;
+
+    /* If this is an incoming message, inform application. */
+    if (tsx->role == PJSIP_ROLE_UAS) {
+	int status = 100;
+	pjsip_tx_data *tdata;
+
+	/* Check if we already answered this request. */
+	if (tsx->status_code >= 200)
+	    return;
+
+	/* Only handle MESSAGE requests!. */
+	if (pjsip_method_cmp(&tsx->method, &message_method) != 0)
+	    return;
+
+	/* Call application callback. */
+	if (incoming_cb)
+	    status = (*incoming_cb)(event->src.rdata);
+
+	if (status < 200 || status >= 700)
+	    status = PJSIP_SC_INTERNAL_SERVER_ERROR;
+
+	/* Respond request. */
+	tdata = pjsip_endpt_create_response(tsx->endpt, event->src.rdata, status );
+	if (tdata)
+	    pjsip_tsx_on_tx_msg(tsx, tdata);
+
+	return;
+    }
+
+    /* Ignore if it's not something that came from messaging module. */
+    mod_data = tsx->module_data[ module_id ];
+    if (mod_data == NULL)
+	return;
+
+    /* Ignore non final response. */
+    if (tsx->status_code < 200)
+	return;
+
+    /* Don't want to call the callback more than once. */
+    tsx->module_data[ module_id ] = NULL;
+
+    /* Now call the callback. */
+    if (mod_data->cb) {
+	(*mod_data->cb)(mod_data->token, tsx->status_code);
+    }
+}
+
+static pjsip_module messaging_module = 
+{
+    { "Messaging", 9},	    /* Name.		*/
+    0,			    /* Flag		*/
+    128,		    /* Priority		*/
+    NULL,		    /* User agent instance, initialized by APP.	*/
+    0,			    /* Number of methods supported (will be initialized later). */
+    { 0 },		    /* Array of methods (will be initialized later) */
+    &messaging_init,	    /* init_module()	*/
+    &messaging_start,	    /* start_module()	*/
+    &messaging_deinit,	    /* deinit_module()	*/
+    &messaging_tsx_handler, /* tsx_handler()	*/
+};
+
+PJ_DEF(pjsip_module*) pjsip_messaging_get_module()
+{
+    static pj_str_t method_str = { "MESSAGE", 7 };
+
+    pjsip_method_init_np( &message_method, &method_str);
+
+    messaging_module.method_cnt = 1;
+    messaging_module.methods[0] = &message_method;
+
+    return &messaging_module;
+}
+
Index: pjproject/main/pjsip/src/pjsip-simple/pidf.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-simple/pidf.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-simple/pidf.c	(revision 39)
@@ -0,0 +1,335 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/pidf.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+
+struct pjpidf_op_desc pjpidf_op = 
+{
+    {
+	&pjpidf_pres_construct,
+	&pjpidf_pres_add_tuple,
+	&pjpidf_pres_get_first_tuple,
+	&pjpidf_pres_get_next_tuple,
+	&pjpidf_pres_find_tuple,
+	&pjpidf_pres_remove_tuple,
+	&pjpidf_pres_add_note,
+	&pjpidf_pres_get_first_note,
+	&pjpidf_pres_get_next_note
+    },
+    {
+	&pjpidf_tuple_construct,
+	&pjpidf_tuple_get_id,
+	&pjpidf_tuple_set_id,
+	&pjpidf_tuple_get_status,
+	&pjpidf_tuple_get_contact,
+	&pjpidf_tuple_set_contact,
+	&pjpidf_tuple_set_contact_prio,
+	&pjpidf_tuple_get_contact_prio,
+	&pjpidf_tuple_add_note,
+	&pjpidf_tuple_get_first_note,
+	&pjpidf_tuple_get_next_note,
+	&pjpidf_tuple_get_timestamp,
+	&pjpidf_tuple_set_timestamp,
+	&pjpidf_tuple_set_timestamp_np
+    },
+    {
+	&pjpidf_status_construct,
+	&pjpidf_status_is_basic_open,
+	&pjpidf_status_set_basic_open
+    }
+};
+
+static pj_str_t PRESENCE = { "presence", 8 };
+static pj_str_t ENTITY = { "entity", 6};
+static pj_str_t	TUPLE = { "tuple", 5 };
+static pj_str_t ID = { "id", 2 };
+static pj_str_t NOTE = { "note", 4 };
+static pj_str_t STATUS = { "status", 6 };
+static pj_str_t CONTACT = { "contact", 7 };
+static pj_str_t PRIORITY = { "priority", 8 };
+static pj_str_t TIMESTAMP = { "timestamp", 9 };
+static pj_str_t BASIC = { "basic", 5 };
+static pj_str_t OPEN = { "open", 4 };
+static pj_str_t CLOSED = { "closed", 6 };
+static pj_str_t EMPTY_STRING = { NULL, 0 };
+
+static void xml_init_node(pj_pool_t *pool, pj_xml_node *node,
+			  pj_str_t *name, const pj_str_t *value)
+{
+    pj_list_init(&node->attr_head);
+    pj_list_init(&node->node_head);
+    node->name = *name;
+    if (value) pj_strdup(pool, &node->content, value);
+    else node->content.ptr=NULL, node->content.slen=0;
+}
+
+static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name,
+				    const pj_str_t *value)
+{
+    pj_xml_attr *attr = pj_pool_alloc(pool, sizeof(*attr));
+    attr->name = *name;
+    pj_strdup(pool, &attr->value, value);
+    return attr;
+}
+
+/* Presence */
+PJ_DEF(void) pjpidf_pres_construct(pj_pool_t *pool, pjpidf_pres *pres,
+				   const pj_str_t *entity)
+{
+    pj_xml_attr *attr;
+
+    xml_init_node(pool, pres, &PRESENCE, NULL);
+    attr = xml_create_attr(pool, &ENTITY, entity);
+    pj_xml_add_attr(pres, attr);
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_add_tuple(pj_pool_t *pool, pjpidf_pres *pres,
+					    const pj_str_t *id)
+{
+    pjpidf_tuple *t = pj_pool_alloc(pool, sizeof(*t));
+    pjpidf_tuple_construct(pool, t, id);
+    pj_xml_add_node(pres, t);
+    return t;
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_get_first_tuple(pjpidf_pres *pres)
+{
+    return pj_xml_find_node(pres, &TUPLE);
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_get_next_tuple(pjpidf_pres *pres, 
+						 pjpidf_tuple *tuple)
+{
+    return pj_xml_find_next_node(pres, tuple, &TUPLE);
+}
+
+static pj_bool_t find_tuple_by_id(pj_xml_node *node, const void *id)
+{
+    return pj_xml_find_attr(node, &ID, id) != NULL;
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_find_tuple(pjpidf_pres *pres, const pj_str_t *id)
+{
+    return pj_xml_find(pres, &TUPLE, id, &find_tuple_by_id);
+}
+
+PJ_DEF(void) pjpidf_pres_remove_tuple(pjpidf_pres *pres, pjpidf_tuple *t)
+{
+    PJ_UNUSED_ARG(pres)
+    pj_list_erase(t);
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_pres_add_note(pj_pool_t *pool, pjpidf_pres *pres, 
+					  const pj_str_t *text)
+{
+    pjpidf_note *note = pj_pool_alloc(pool, sizeof(*note));
+    xml_init_node(pool, note, &NOTE, text);
+    pj_xml_add_node(pres, note);
+    return note;
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_pres_get_first_note(pjpidf_pres *pres)
+{
+    return pj_xml_find_node( pres, &NOTE);
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_pres_get_next_note(pjpidf_pres *t, pjpidf_note *note)
+{
+    return pj_xml_find_next_node(t, note, &NOTE);
+}
+
+
+/* Tuple */
+PJ_DEF(void) pjpidf_tuple_construct(pj_pool_t *pool, pjpidf_tuple *t,
+				    const pj_str_t *id)
+{
+    pj_xml_attr *attr;
+    pjpidf_status *st;
+
+    xml_init_node(pool, t, &TUPLE, NULL);
+    attr = xml_create_attr(pool, &ID, id);
+    pj_xml_add_attr(t, attr);
+    st = pj_pool_alloc(pool, sizeof(*st));
+    pjpidf_status_construct(pool, st);
+    pj_xml_add_node(t, st);
+}
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_id(const pjpidf_tuple *t)
+{
+    const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)t, &ID, NULL);
+    pj_assert(attr);
+    return &attr->value;
+}
+
+PJ_DEF(void) pjpidf_tuple_set_id(pj_pool_t *pool, pjpidf_tuple *t, const pj_str_t *id)
+{
+    pj_xml_attr *attr = pj_xml_find_attr(t, &ID, NULL);
+    pj_assert(attr);
+    pj_strdup(pool, &attr->value, id);
+}
+
+
+PJ_DEF(pjpidf_status*) pjpidf_tuple_get_status(pjpidf_tuple *t)
+{
+    pjpidf_status *st = (pjpidf_status*)pj_xml_find_node(t, &STATUS);
+    pj_assert(st);
+    return st;
+}
+
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_contact(const pjpidf_tuple *t)
+{
+    pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &CONTACT);
+    if (!node)
+	return &EMPTY_STRING;
+    return &node->content;
+}
+
+PJ_DEF(void) pjpidf_tuple_set_contact(pj_pool_t *pool, pjpidf_tuple *t, 
+				      const pj_str_t *contact)
+{
+    pj_xml_node *node = pj_xml_find_node(t, &CONTACT);
+    if (!node) {
+	node = pj_pool_alloc(pool, sizeof(*node));
+	xml_init_node(pool, node, &CONTACT, contact);
+	pj_xml_add_node(t, node);
+    } else {
+	pj_strdup(pool, &node->content, contact);
+    }
+}
+
+PJ_DEF(void) pjpidf_tuple_set_contact_prio(pj_pool_t *pool, pjpidf_tuple *t, 
+					   const pj_str_t *prio)
+{
+    pj_xml_node *node = pj_xml_find_node(t, &CONTACT);
+    pj_xml_attr *attr;
+
+    if (!node) {
+	node = pj_pool_alloc(pool, sizeof(*node));
+	xml_init_node(pool, node, &CONTACT, NULL);
+	pj_xml_add_node(t, node);
+    }
+    attr = pj_xml_find_attr(node, &PRIORITY, NULL);
+    if (!attr) {
+	attr = xml_create_attr(pool, &PRIORITY, prio);
+	pj_xml_add_attr(node, attr);
+    } else {
+	pj_strdup(pool, &attr->value, prio);
+    }
+}
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_contact_prio(const pjpidf_tuple *t)
+{
+    pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &CONTACT);
+    pj_xml_attr *attr;
+
+    if (!node)
+	return &EMPTY_STRING;
+    attr = pj_xml_find_attr(node, &PRIORITY, NULL);
+    if (!attr)
+	return &EMPTY_STRING;
+    return &attr->value;
+}
+
+
+PJ_DEF(pjpidf_note*) pjpidf_tuple_add_note(pj_pool_t *pool, pjpidf_tuple *t,
+					   const pj_str_t *text)
+{
+    pjpidf_note *note = pj_pool_alloc(pool, sizeof(*note));
+    xml_init_node(pool, note, &NOTE, text);
+    pj_xml_add_node(t, note);
+    return note;
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_tuple_get_first_note(pjpidf_tuple *t)
+{
+    return pj_xml_find_node(t, &NOTE);
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_tuple_get_next_note(pjpidf_tuple *t, pjpidf_note *n)
+{
+    return pj_xml_find_next_node(t, n, &NOTE);
+}
+
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_timestamp(const pjpidf_tuple *t)
+{
+    pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &TIMESTAMP);
+    return node ? &node->content : &EMPTY_STRING;
+}
+
+PJ_DEF(void) pjpidf_tuple_set_timestamp(pj_pool_t *pool, pjpidf_tuple *t,
+					const pj_str_t *ts)
+{
+    pj_xml_node *node = pj_xml_find_node(t, &TIMESTAMP);
+    if (!node) {
+	node = pj_pool_alloc(pool, sizeof(*node));
+	xml_init_node(pool, node, &TIMESTAMP, ts);
+    } else {
+	pj_strdup(pool, &node->content, ts);
+    }
+}
+
+
+PJ_DEF(void) pjpidf_tuple_set_timestamp_np(pj_pool_t *pool, pjpidf_tuple *t, 
+					   pj_str_t *ts)
+{
+    pj_xml_node *node = pj_xml_find_node(t, &TIMESTAMP);
+    if (!node) {
+	node = pj_pool_alloc(pool, sizeof(*node));
+	xml_init_node(pool, node, &TIMESTAMP, ts);
+    } else {
+	node->content = *ts;
+    }
+}
+
+
+/* Status */
+PJ_DEF(void) pjpidf_status_construct(pj_pool_t *pool, pjpidf_status *st)
+{
+    pj_xml_node *node;
+
+    xml_init_node(pool, st, &STATUS, NULL);
+    node = pj_pool_alloc(pool, sizeof(*node));
+    xml_init_node(pool, node, &BASIC, &CLOSED);
+    pj_xml_add_node(st, node);
+}
+
+PJ_DEF(pj_bool_t) pjpidf_status_is_basic_open(const pjpidf_status *st)
+{
+    pj_xml_node *node = pj_xml_find_node((pj_xml_node*)st, &BASIC);
+    pj_assert(node != NULL);
+    return pj_stricmp(&node->content, &OPEN)==0;
+}
+
+PJ_DEF(void) pjpidf_status_set_basic_open(pjpidf_status *st, pj_bool_t open)
+{
+    pj_xml_node *node = pj_xml_find_node(st, &BASIC);
+    pj_assert(node != NULL);
+    node->content = open ? OPEN : CLOSED;
+}
+
+PJ_DEF(pjpidf_pres*) pjpidf_create(pj_pool_t *pool, const pj_str_t *entity)
+{
+    pjpidf_pres *pres = pj_pool_alloc(pool, sizeof(*pres));
+    pjpidf_pres_construct(pool, pres, entity);
+    return pres;
+}
+
+PJ_DEF(pjpidf_pres*) pjpidf_parse(pj_pool_t *pool, char *text, int len)
+{
+    pjpidf_pres *pres = pj_xml_parse(pool, text, len);
+    if (pres) {
+	if (pj_stricmp(&pres->name, &PRESENCE) != 0)
+	    return NULL;
+    }
+    return pres;
+}
+
+PJ_DEF(int) pjpidf_print(const pjpidf_pres* pres, char *buf, int len)
+{
+    return pj_xml_print(pres, buf, len, PJ_TRUE);
+}
+
Index: pjproject/main/pjsip/src/pjsip-simple/presence.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-simple/presence.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-simple/presence.c	(revision 39)
@@ -0,0 +1,384 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/presence.h>
+#include <pjsip/sip_transport.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/guid.h>
+#include <pj/os.h>
+#include <stdio.h>
+
+/* Forward declarations. */
+static void on_query_subscribe(pjsip_rx_data *rdata, int *status);
+static void on_subscribe(pjsip_event_sub *sub, pjsip_rx_data *rdata,
+			 pjsip_event_sub_cb **cb, int *expires);
+static void on_sub_terminated(pjsip_event_sub *sub, const pj_str_t *reason);
+static void on_sub_received_refresh(pjsip_event_sub *sub, pjsip_rx_data *rdata);
+static void on_received_notify(pjsip_event_sub *sub, pjsip_rx_data *rdata);
+
+/* Some string constants. */
+static pj_str_t PRESENCE_EVENT = { "presence", 8 };
+
+/* Accept types. */
+static pj_str_t accept_names[] = {
+    { "application/pidf+xml", 20 },
+    { "application/xpidf+xml", 21 }
+};
+static pjsip_media_type accept_types[] = {
+    {
+	{ "application", 11 },
+	{ "pidf+xml", 8 }
+    },
+    {
+	{ "application", 11 },
+	{ "xpidf+xml", 9 }
+    }
+};
+
+/* Callback that is registered by application. */
+static pjsip_presence_cb cb;
+
+/* Package callback to be register to event_notify */
+static pjsip_event_sub_pkg_cb pkg_cb = { &on_query_subscribe,
+					 &on_subscribe };
+
+/* Global/static callback to be registered to event_notify */
+static pjsip_event_sub_cb sub_cb = { &on_sub_terminated,
+				     &on_sub_received_refresh,
+				     NULL,
+				     &on_received_notify,
+				     NULL };
+
+/*
+ * Initialize presence module.
+ * This will register event package "presence" to event framework.
+ */
+PJ_DEF(void) pjsip_presence_init(const pjsip_presence_cb *pcb)
+{
+    pj_memcpy(&cb, pcb, sizeof(*pcb));
+    pjsip_event_sub_register_pkg( &PRESENCE_EVENT, 
+				  sizeof(accept_names)/sizeof(accept_names[0]),
+				  accept_names,
+				  &pkg_cb);
+}
+
+/*
+ * Create presence subscription.
+ */
+PJ_DEF(pjsip_presentity*) pjsip_presence_create( pjsip_endpoint *endpt,
+						 const pj_str_t *local_url,
+						 const pj_str_t *remote_url,
+						 int expires,
+						 void *user_data )
+{
+    pjsip_event_sub *sub;
+    pjsip_presentity *pres;
+
+    if (expires < 0)
+	expires = 300;
+
+    /* Create event subscription */
+    sub = pjsip_event_sub_create(endpt, local_url, remote_url, &PRESENCE_EVENT, 
+				 expires, 
+				 sizeof(accept_names)/sizeof(accept_names[0]),
+				 accept_names,
+				 NULL, &sub_cb);
+    if (!sub)
+	return NULL;
+
+    /* Allocate presence descriptor. */
+    pres = pj_pool_calloc(sub->pool, 1, sizeof(*pres));
+    pres->sub = sub;
+    pres->user_data = user_data;
+    sub->user_data = pres;
+
+    return pres;
+}
+
+/*
+ * Send SUBSCRIBE.
+ */
+PJ_DEF(pj_status_t) pjsip_presence_subscribe( pjsip_presentity *pres )
+{
+    return pjsip_event_sub_subscribe( pres->sub );
+}
+
+/*
+ * Set credentials to be used for outgoing requests.
+ */
+PJ_DEF(pj_status_t) pjsip_presence_set_credentials( pjsip_presentity *pres,
+						    int count,
+						    const pjsip_cred_info cred[])
+{
+    return pjsip_event_sub_set_credentials(pres->sub, count, cred);
+}
+
+/*
+ * Set route-set.
+ */
+PJ_DEF(pj_status_t) pjsip_presence_set_route_set( pjsip_presentity *pres,
+						  const pjsip_route_hdr *hdr )
+{
+    return pjsip_event_sub_set_route_set( pres->sub, hdr );
+}
+
+/*
+ * Unsubscribe.
+ */
+PJ_DEF(pj_status_t) pjsip_presence_unsubscribe( pjsip_presentity *pres )
+{
+    return pjsip_event_sub_unsubscribe(pres->sub);
+}
+
+/*
+ * This is the pjsip_msg_body callback to print XML body.
+ */
+static int print_xml(pjsip_msg_body *body, char *buf, pj_size_t size)
+{
+    return pj_xml_print( body->data, buf, size, PJ_TRUE );
+}
+
+/*
+ * Create and initialize PIDF document and msg body (notifier only).
+ */
+static pj_status_t init_presence_info( pjsip_presentity *pres )
+{
+    pj_str_t uri;
+    pj_pool_t *pool = pres->sub->pool;
+    char tmp[PJSIP_MAX_URL_SIZE];
+    pjpidf_tuple *tuple;
+    const pjsip_media_type *content_type = NULL;
+
+    pj_assert(pres->uas_body == NULL);
+
+    /* Make entity_id */
+    uri.ptr = tmp;
+    uri.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, pres->sub->from->uri, 
+			      tmp, sizeof(tmp));
+    if (uri.slen < 0)
+	return -1;
+
+    if (pres->pres_type == PJSIP_PRES_TYPE_PIDF) {
+	pj_str_t s;
+
+	/* Create <presence>. */
+	pres->uas_data.pidf = pjpidf_create(pool, &s);
+
+	/* Create <tuple> */
+	pj_create_unique_string(pool, &s);
+	tuple = pjpidf_pres_add_tuple(pool, pres->uas_data.pidf, &s);
+
+	/* Set <contact> */
+	s.ptr = tmp;
+	s.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, pres->sub->contact->uri, tmp, sizeof(tmp));
+	if (s.slen < 0)
+	    return -1;
+	pjpidf_tuple_set_contact(pool, tuple, &s);
+
+	/* Content-Type */
+	content_type = &accept_types[PJSIP_PRES_TYPE_PIDF];
+
+    } else if (pres->pres_type == PJSIP_PRES_TYPE_XPIDF) {
+
+	/* Create XPIDF */
+	pres->uas_data.xpidf = pjxpidf_create(pool, &uri);
+
+	/* Content-Type. */
+	content_type = &accept_types[PJSIP_PRES_TYPE_XPIDF];
+    }
+
+    /* Create message body */
+    pres->uas_body = pj_pool_alloc(pool, sizeof(pjsip_msg_body));
+    pres->uas_body->content_type = *content_type;
+    pres->uas_body->data = pres->uas_data.pidf;
+    pres->uas_body->len = 0;
+    pres->uas_body->print_body = &print_xml;
+
+    return 0;
+}
+
+/*
+ * Send NOTIFY and set subscription state.
+ */
+PJ_DEF(pj_status_t) pjsip_presence_notify( pjsip_presentity *pres,
+					   pjsip_event_sub_state state,
+					   pj_bool_t is_online )
+{
+    pj_str_t reason = { "", 0 };
+
+    if (pres->uas_data.pidf == NULL) {
+	if (init_presence_info(pres) != 0)
+	    return -1;
+    }
+
+    /* Update basic status in PIDF/XPIDF document. */
+    if (pres->pres_type == PJSIP_PRES_TYPE_PIDF) {
+	pjpidf_tuple *first;
+	pjpidf_status *status;
+	pj_time_val now;
+	pj_parsed_time pnow;
+
+	first = pjpidf_op.pres.get_first_tuple(pres->uas_data.pidf);
+	pj_assert(first);
+	status = pjpidf_op.tuple.get_status(first);
+	pj_assert(status);
+	pjpidf_op.status.set_basic_open(status, is_online);
+
+	/* Update timestamp. */
+	if (pres->timestamp.ptr == 0) {
+	    pres->timestamp.ptr = pj_pool_alloc(pres->sub->pool, 24);
+	}
+	pj_gettimeofday(&now);
+	pj_time_decode(&now, &pnow);
+	pres->timestamp.slen = sprintf(pres->timestamp.ptr,
+				       "%04d-%02d-%02dT%02d:%02d:%02dZ",
+				       pnow.year, pnow.mon, pnow.day,
+				       pnow.hour, pnow.min, pnow.sec);
+	pjpidf_op.tuple.set_timestamp_np(pres->sub->pool, first, &pres->timestamp);
+
+    } else if (pres->pres_type == PJSIP_PRES_TYPE_XPIDF) {
+	pjxpidf_set_status( pres->uas_data.xpidf, is_online );
+
+    } else {
+	pj_assert(0);
+    }
+
+    /* Send notify. */
+    return pjsip_event_sub_notify( pres->sub, state, &reason, pres->uas_body);
+}
+
+/*
+ * Destroy subscription (can be called for both subscriber and notifier).
+ */
+PJ_DEF(pj_status_t) pjsip_presence_destroy( pjsip_presentity *pres )
+{
+    return pjsip_event_sub_destroy(pres->sub);
+}
+
+/*
+ * This callback is called by event framework to query whether we want to
+ * accept an incoming subscription.
+ */
+static void on_query_subscribe(pjsip_rx_data *rdata, int *status)
+{
+    if (cb.accept_presence) {
+	(*cb.accept_presence)(rdata, status);
+    }
+}
+
+/*
+ * This callback is called by event framework after we accept the incoming
+ * subscription, to notify about the new subscription instance.
+ */
+static void on_subscribe(pjsip_event_sub *sub, pjsip_rx_data *rdata,
+			 pjsip_event_sub_cb **set_sub_cb, int *expires)
+{
+    pjsip_presentity *pres;
+    pjsip_accept_hdr *accept;
+
+    pres = pj_pool_calloc(sub->pool, 1, sizeof(*pres));
+    pres->sub = sub;
+    pres->pres_type = PJSIP_PRES_TYPE_PIDF;
+    sub->user_data = pres;
+    *set_sub_cb = &sub_cb;
+
+    accept = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_ACCEPT, NULL);
+    if (accept) {
+	unsigned i;
+	int found = 0;
+	for (i=0; i<accept->count && !found; ++i) {
+	    int j;
+	    for (j=0; j<sizeof(accept_names)/sizeof(accept_names[0]); ++j) {
+		if (!pj_stricmp(&accept->values[i], &accept_names[j])) {
+		    pres->pres_type = j;
+		    found = 1;
+		    break;
+		}
+	    }
+	}
+	pj_assert(found );
+    }
+
+    (*cb.on_received_request)(pres, rdata, expires);
+}
+
+/*
+ * This callback is called by event framework when the subscription is
+ * terminated.
+ */
+static void on_sub_terminated(pjsip_event_sub *sub, const pj_str_t *reason)
+{
+    pjsip_presentity *pres = sub->user_data;
+    if (cb.on_terminated)
+	(*cb.on_terminated)(pres, reason);
+}
+
+/*
+ * This callback is called by event framework when it receives incoming
+ * SUBSCRIBE request to refresh the subscription.
+ */
+static void on_sub_received_refresh(pjsip_event_sub *sub, pjsip_rx_data *rdata)
+{
+    pjsip_presentity *pres = sub->user_data;
+    if (cb.on_received_refresh)
+	(*cb.on_received_refresh)(pres, rdata);
+}
+
+/*
+ * This callback is called by event framework when it receives incoming
+ * NOTIFY request.
+ */
+static void on_received_notify(pjsip_event_sub *sub, pjsip_rx_data *rdata)
+{
+    pjsip_presentity *pres = sub->user_data;
+
+    if (cb.on_received_update) {
+	pj_status_t is_open;
+	pjsip_msg_body *body;
+	int i;
+
+	body = rdata->msg->body;
+	if (!body)
+	    return;
+
+	for (i=0; i<sizeof(accept_types)/sizeof(accept_types[0]); ++i) {
+	    if (!pj_stricmp(&body->content_type.type, &accept_types[i].type) &&
+		!pj_stricmp(&body->content_type.subtype, &accept_types[i].subtype))
+	    {
+		break;
+	    }
+	}
+
+	if (i==PJSIP_PRES_TYPE_PIDF) {
+	    pjpidf_pres *pres;
+	    pjpidf_tuple *tuple;
+	    pjpidf_status *status;
+
+	    pres = pjpidf_parse(rdata->pool, body->data, body->len);
+	    if (!pres)
+		return;
+	    tuple = pjpidf_pres_get_first_tuple(pres);
+	    if (!tuple)
+		return;
+	    status = pjpidf_tuple_get_status(tuple);
+	    if (!status)
+		return;
+	    is_open = pjpidf_status_is_basic_open(status);
+
+	} else if (i==PJSIP_PRES_TYPE_XPIDF) {
+	    pjxpidf_pres *pres;
+
+	    pres = pjxpidf_parse(rdata->pool, body->data, body->len);
+	    if (!pres)
+		return;
+	    is_open = pjxpidf_get_status(pres);
+
+	} else {
+	    return;
+	}
+
+	(*cb.on_received_update)(pres, is_open);
+    }
+}
+
Index: pjproject/main/pjsip/src/pjsip-simple/xpidf.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-simple/xpidf.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-simple/xpidf.c	(revision 39)
@@ -0,0 +1,279 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/xpidf.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/guid.h>
+
+static pj_str_t PRESENCE = { "presence", 8 };
+static pj_str_t STATUS = { "status", 6 };
+static pj_str_t OPEN = { "open", 4 };
+static pj_str_t CLOSED = { "closed", 6 };
+static pj_str_t URI = { "uri", 3 };
+static pj_str_t ATOM = { "atom", 4 };
+static pj_str_t ATOMID = { "atomid", 6 };
+static pj_str_t ADDRESS = { "address", 7 };
+static pj_str_t SUBSCRIBE_PARAM = { ";method=SUBSCRIBE", 17 };
+static pj_str_t PRESENTITY = { "presentity", 10 };
+static pj_str_t EMPTY_STRING = { NULL, 0 };
+
+static pj_xml_node* xml_create_node(pj_pool_t *pool, 
+				    pj_str_t *name, const pj_str_t *value)
+{
+    pj_xml_node *node;
+
+    node = pj_pool_alloc(pool, sizeof(pj_xml_node));
+    pj_list_init(&node->attr_head);
+    pj_list_init(&node->node_head);
+    node->name = *name;
+    if (value) pj_strdup(pool, &node->content, value);
+    else node->content.ptr=NULL, node->content.slen=0;
+
+    return node;
+}
+
+static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name,
+				    const pj_str_t *value)
+{
+    pj_xml_attr *attr = pj_pool_alloc(pool, sizeof(*attr));
+    attr->name = *name;
+    pj_strdup(pool, &attr->value, value);
+    return attr;
+}
+
+
+PJ_DEF(pjxpidf_pres*) pjxpidf_create(pj_pool_t *pool, const pj_str_t *uri_cstr)
+{
+    pjxpidf_pres *pres;
+    pj_xml_node *presentity;
+    pj_xml_node *atom;
+    pj_xml_node *addr;
+    pj_xml_node *status;
+    pj_xml_attr *attr;
+    pj_str_t uri;
+    pj_str_t tmp;
+
+    /* <presence> */
+    pres = xml_create_node(pool, &PRESENCE, NULL);
+
+    /* <presentity> */
+    presentity = xml_create_node(pool, &PRESENTITY, NULL);
+    pj_xml_add_node(pres, presentity);
+
+    /* uri attribute */
+    uri.ptr = pj_pool_alloc(pool, uri_cstr->slen + SUBSCRIBE_PARAM.slen);
+    pj_strcpy( &uri, uri_cstr);
+    pj_strcat( &uri, &SUBSCRIBE_PARAM);
+    attr = xml_create_attr(pool, &URI, &uri);
+    pj_xml_add_attr(presentity, attr);
+
+    /* <atom> */
+    atom = xml_create_node(pool, &ATOM, NULL);
+    pj_xml_add_node(pres, atom);
+
+    /* atom id */
+    pj_create_unique_string(pool, &tmp);
+    attr = xml_create_attr(pool, &ATOMID, &tmp);
+    pj_xml_add_attr(atom, attr);
+
+    /* address */
+    addr = xml_create_node(pool, &ADDRESS, NULL);
+    pj_xml_add_node(atom, addr);
+
+    /* address'es uri */
+    attr = xml_create_attr(pool, &URI, uri_cstr);
+    pj_xml_add_attr(addr, attr);
+
+    /* status */
+    status = xml_create_node(pool, &STATUS, NULL);
+    pj_xml_add_node(addr, status);
+
+    /* status attr */
+    attr = xml_create_attr(pool, &STATUS, &OPEN);
+    pj_xml_add_attr(status, attr);
+
+    return pres;
+}   
+
+
+
+PJ_DEF(pjxpidf_pres*) pjxpidf_parse(pj_pool_t *pool, char *text, pj_size_t len)
+{
+    pjxpidf_pres *pres;
+    pj_xml_node *node;
+
+    pres = pj_xml_parse(pool, text, len);
+    if (!pres)
+	return NULL;
+
+    /* Validate <presence> */
+    if (pj_stricmp(&pres->name, &PRESENCE) != 0)
+	return NULL;
+    if (pj_xml_find_attr(pres, &URI, NULL) == NULL)
+	return NULL;
+
+    /* Validate <presentity> */
+    node = pj_xml_find_node(pres, &PRESENTITY);
+    if (node == NULL)
+	return NULL;
+
+    /* Validate <atom> */
+    node = pj_xml_find_node(pres, &ATOM);
+    if (node == NULL)
+	return NULL;
+    if (pj_xml_find_attr(node, &ATOMID, NULL) == NULL)
+	return NULL;
+
+    /* Address */
+    node = pj_xml_find_node(node, &ADDRESS);
+    if (node == NULL)
+	return NULL;
+    if (pj_xml_find_attr(node, &URI, NULL) == NULL)
+	return NULL;
+
+
+    /* Status */
+    node = pj_xml_find_node(node, &STATUS);
+    if (node == NULL)
+	return NULL;
+    if (pj_xml_find_attr(node, &STATUS, NULL) == NULL)
+	return NULL;
+
+    return pres;
+}
+
+
+PJ_DEF(int) pjxpidf_print( pjxpidf_pres *pres, char *text, pj_size_t len)
+{
+    return pj_xml_print(pres, text, len, PJ_TRUE);
+}
+
+
+PJ_DEF(pj_str_t*) pjxpidf_get_uri(pjxpidf_pres *pres)
+{
+    pj_xml_node *presentity;
+    pj_xml_attr *attr;
+
+    presentity = pj_xml_find_node(pres, &PRESENTITY);
+    if (!presentity)
+	return &EMPTY_STRING;
+
+    attr = pj_xml_find_attr(presentity, &URI, NULL);
+    if (!attr)
+	return &EMPTY_STRING;
+
+    return &attr->value;
+}
+
+
+PJ_DEF(pj_status_t) pjxpidf_set_uri(pj_pool_t *pool, pjxpidf_pres *pres, 
+				    const pj_str_t *uri)
+{
+    pj_xml_node *presentity;
+    pj_xml_node *atom;
+    pj_xml_node *addr;
+    pj_xml_attr *attr;
+    pj_str_t dup_uri;
+
+    presentity = pj_xml_find_node(pres, &PRESENTITY);
+    if (!presentity) {
+	pj_assert(0);
+	return -1;
+    }
+    atom = pj_xml_find_node(pres, &ATOM);
+    if (!atom) {
+	pj_assert(0);
+	return -1;
+    }
+    addr = pj_xml_find_node(atom, &ADDRESS);
+    if (!addr) {
+	pj_assert(0);
+	return -1;
+    }
+
+    /* Set uri in presentity */
+    attr = pj_xml_find_attr(presentity, &URI, NULL);
+    if (!attr) {
+	pj_assert(0);
+	return -1;
+    }
+    pj_strdup(pool, &dup_uri, uri);
+    attr->value = dup_uri;
+
+    /* Set uri in address. */
+    attr = pj_xml_find_attr(addr, &URI, NULL);
+    if (!attr) {
+	pj_assert(0);
+	return -1;
+    }
+    attr->value = dup_uri;
+
+    return 0;
+}
+
+
+PJ_DEF(pj_bool_t) pjxpidf_get_status(pjxpidf_pres *pres)
+{
+    pj_xml_node *atom;
+    pj_xml_node *addr;
+    pj_xml_node *status;
+    pj_xml_attr *attr;
+
+    atom = pj_xml_find_node(pres, &ATOM);
+    if (!atom) {
+	pj_assert(0);
+	return PJ_FALSE;
+    }
+    addr = pj_xml_find_node(atom, &ADDRESS);
+    if (!addr) {
+	pj_assert(0);
+	return PJ_FALSE;
+    }
+    status = pj_xml_find_node(atom, &STATUS);
+    if (!status) {
+	pj_assert(0);
+	return PJ_FALSE;
+    }
+    attr = pj_xml_find_attr(status, &STATUS, NULL);
+    if (!attr) {
+	pj_assert(0);
+	return PJ_FALSE;
+    }
+
+    return pj_stricmp(&attr->value, &OPEN) ? PJ_TRUE : PJ_FALSE;
+}
+
+
+PJ_DEF(pj_status_t) pjxpidf_set_status(pjxpidf_pres *pres, pj_bool_t online_status)
+{
+    pj_xml_node *atom;
+    pj_xml_node *addr;
+    pj_xml_node *status;
+    pj_xml_attr *attr;
+
+    atom = pj_xml_find_node(pres, &ATOM);
+    if (!atom) {
+	pj_assert(0);
+	return -1;
+    }
+    addr = pj_xml_find_node(atom, &ADDRESS);
+    if (!addr) {
+	pj_assert(0);
+	return -1;
+    }
+    status = pj_xml_find_node(addr, &STATUS);
+    if (!status) {
+	pj_assert(0);
+	return -1;
+    }
+    attr = pj_xml_find_attr(status, &STATUS, NULL);
+    if (!attr) {
+	pj_assert(0);
+	return -1;
+    }
+
+    attr->value = ( online_status ? OPEN : CLOSED );
+    return 0;
+}
+
Index: pjproject/main/pjsip/src/pjsip-tests/pjsip_core/main.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-tests/pjsip_core/main.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-tests/pjsip_core/main.c	(revision 39)
@@ -0,0 +1,17 @@
+/* $Id$
+ *
+ */
+#include "test.h"
+#include <stdio.h>
+
+int main()
+{
+    test_uri();
+    test_msg();
+
+#if !IS_PROFILING
+    puts("Press <ENTER> to quit.");
+    fgets( s, sizeof(s), stdin);
+#endif
+    return 0;
+}
Index: pjproject/main/pjsip/src/pjsip-tests/pjsip_core/test.h
===================================================================
--- pjproject/main/pjsip/src/pjsip-tests/pjsip_core/test.h	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-tests/pjsip_core/test.h	(revision 39)
@@ -0,0 +1,11 @@
+/* $Id$
+ *
+ */
+#include <pj/types.h>
+
+pj_status_t test_uri(void);
+pj_status_t test_msg(void);
+
+#define SILENT		1
+#define IS_PROFILING	1
+#define LOOP		2000
Index: pjproject/main/pjsip/src/pjsip-tests/pjsip_core/test_msg.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-tests/pjsip_core/test_msg.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-tests/pjsip_core/test_msg.c	(revision 39)
@@ -0,0 +1,425 @@
+/* $Id$
+ *
+ */
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_parser.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "test.h"
+
+#define ERR_SYNTAX_ERR	(-2)
+#define ERR_NOT_EQUAL	(-3)
+#define ERR_SYSTEM	(-4)
+
+
+static pjsip_msg *create_msg0(pj_pool_t *pool);
+
+struct test_msg
+{
+    char	 msg[1024];
+    pjsip_msg *(*creator)(pj_pool_t *pool);
+    pj_size_t	 len;
+} test_array[] = 
+{
+    {
+	/* 'Normal' message with all headers. */
+	"INVITE sip:user@foo SIP/2.0\n"
+	"From: Hi I'm Joe <sip:joe.user@bar.otherdomain.com>;tag=1234578901234567890\r"
+	"To: Fellow User <sip:user@foo.bar.domain.com>\r\n"
+	"Call-ID: 12345678901234567890@bar\r\n"
+	"Content-Length: 0\r\n"
+	"CSeq: 123456 INVITE\n"
+	"Contact: <sip:joe@bar> ; q=0.5;expires=3600,sip:user@host;q=0.500\r"
+	"  ,sip:user2@host2\n"
+	"Content-Type: text/html ; charset=ISO-8859-4\r"
+	"Route: <sip:bigbox3.site3.atlanta.com;lr>,\r\n"
+	"  <sip:server10.biloxi.com;lr>\r"
+	"Record-Route: <sip:server10.biloxi.com>,\r\n"
+	"  <sip:bigbox3.site3.atlanta.com;lr>\n"
+	"Via: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1\n"
+	"Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n"
+	" ;received=192.0.2.1\r\n"
+	"Via: SIP/2.0/UDP 10.2.1.1, SIP/2.0/TCP 192.168.1.1\n"
+	"Organization: \r"
+	"Max-Forwards: 70\n"
+	"X-Header: \r\n"
+	"\r",
+	&create_msg0
+    }
+};
+
+static pj_caching_pool cp;
+static pj_pool_factory *pf = &cp.factory;
+static pj_uint32_t parse_len, parse_time, print_time;
+
+static void pool_error(pj_pool_t *pool, pj_size_t sz)
+{
+    PJ_UNUSED_ARG(pool)
+    PJ_UNUSED_ARG(sz)
+
+    pj_assert(0);
+    exit(1);
+}
+
+static const char *STATUS_STR(pj_status_t status)
+{
+    switch (status) {
+    case 0: return "OK";
+    case ERR_SYNTAX_ERR: return "Syntax Error";
+    case ERR_NOT_EQUAL: return "Not Equal";
+    case ERR_SYSTEM: return "System Error";
+    }
+    return "???";
+}
+
+static pj_status_t test_entry( struct test_msg *entry )
+{
+    pjsip_msg *parsed_msg, *ref_msg;
+    pj_pool_t *pool;
+    pj_status_t status = PJ_SUCCESS;
+    int len;
+    pj_str_t str1, str2;
+    pjsip_hdr *hdr1, *hdr2;
+    pj_hr_timestamp t1, t2;
+    char *msgbuf;
+
+    enum { BUFLEN = 512 };
+    
+    pool = pj_pool_create( pf, "", 
+			   PJSIP_POOL_LEN_RDATA*2, PJSIP_POOL_INC_RDATA, 
+			   &pool_error);
+
+    if (entry->len == 0) {
+	entry->len = strlen(entry->msg);
+    }
+
+    /* Parse message. */
+    parse_len += entry->len;
+    pj_hr_gettimestamp(&t1);
+    parsed_msg = pjsip_parse_msg(pool, entry->msg, entry->len, NULL);
+    if (parsed_msg == NULL) {
+	status = ERR_SYNTAX_ERR;
+	goto on_return;
+    }
+    pj_hr_gettimestamp(&t2);
+    parse_time += t2.u32.lo - t1.u32.lo;
+
+#if IS_PROFILING
+    goto print_msg;
+#endif
+
+    /* Create reference message. */
+    ref_msg = entry->creator(pool);
+
+    /* Create buffer for comparison. */
+    str1.ptr = pj_pool_alloc(pool, BUFLEN);
+    str2.ptr = pj_pool_alloc(pool, BUFLEN);
+
+    /* Compare message type. */
+    if (parsed_msg->type != ref_msg->type) {
+	status = ERR_NOT_EQUAL;
+	goto on_return;
+    }
+
+    /* Compare request or status line. */
+    if (parsed_msg->type == PJSIP_REQUEST_MSG) {
+	pjsip_method *m1 = &parsed_msg->line.req.method;
+	pjsip_method *m2 = &ref_msg->line.req.method;
+
+	if (m1->id != m2->id || pj_strcmp(&m1->name, &m2->name)) {
+	    status = ERR_NOT_EQUAL;
+	    goto on_return;
+	}
+    } else {
+
+    }
+
+    /* Compare headers. */
+    hdr1 = parsed_msg->hdr.next;
+    hdr2 = ref_msg->hdr.next;
+
+    while (hdr1 != &parsed_msg->hdr && hdr2 != &ref_msg->hdr) {
+	len = hdr1->vptr->print_on(hdr1, str1.ptr, BUFLEN);
+	if (len < 1) {
+	    status = ERR_SYSTEM;
+	    goto on_return;
+	}
+	str1.slen = len;
+
+	len = hdr2->vptr->print_on(hdr2, str2.ptr, BUFLEN);
+	if (len < 1) {
+	    status = ERR_SYSTEM;
+	    goto on_return;
+	}
+	str2.slen = len;
+
+	if (!SILENT) {
+	    printf("hdr1='%.*s'\n"
+		   "hdr2='%.*s'\n\n",
+		   str1.slen, str1.ptr,
+		   str2.slen, str2.ptr);
+	}
+	if (pj_strcmp(&str1, &str2) != 0) {
+	    status = ERR_NOT_EQUAL;
+	    goto on_return;
+	}
+
+	hdr1 = hdr1->next;
+	hdr2 = hdr2->next;
+    }
+
+    if (hdr1 != &parsed_msg->hdr || hdr2 != &ref_msg->hdr) {
+	status = ERR_NOT_EQUAL;
+	goto on_return;
+    }
+
+    /* Print message. */
+#if IS_PROFILING
+print_msg:
+#endif
+    msgbuf = pj_pool_alloc(pool, PJSIP_MAX_PKT_LEN);
+    if (msgbuf == NULL) {
+	status = ERR_SYSTEM;
+	goto on_return;
+    }
+    pj_hr_gettimestamp(&t1);
+    len = pjsip_msg_print(parsed_msg, msgbuf, PJSIP_MAX_PKT_LEN);
+    if (len < 1) {
+	status = ERR_SYSTEM;
+	goto on_return;
+    }
+    pj_hr_gettimestamp(&t2);
+    print_time += t2.u32.lo - t1.u32.lo;
+    status = PJ_SUCCESS;
+
+on_return:
+    pj_pool_release(pool);
+    return status;
+}
+
+static void warm_up()
+{
+    pj_pool_t *pool;
+    pool = pj_pool_create( pf, "", 
+			   PJSIP_POOL_LEN_RDATA*2, PJSIP_POOL_INC_RDATA, 
+			   &pool_error);
+    pj_pool_release(pool);
+}
+
+
+pj_status_t test_msg(void)
+{
+    pj_status_t status;
+    unsigned i;
+
+    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+    warm_up();
+
+    for (i=0; i<LOOP; ++i) {
+	status = test_entry( &test_array[0] );
+    }
+    printf("%s\n", STATUS_STR(status));
+
+    printf("Total bytes: %u, parse time=%f/char, print time=%f/char\n", 
+	   parse_len, 
+	   parse_time*1.0/parse_len,
+	   print_time*1.0/parse_len);
+    return PJ_SUCCESS;
+}
+
+/*****************************************************************************/
+
+static pjsip_msg *create_msg0(pj_pool_t *pool)
+{
+
+    pjsip_msg *msg;
+    pjsip_name_addr *name_addr;
+    pjsip_url *url;
+    pjsip_fromto_hdr *fromto;
+    pjsip_cid_hdr *cid;
+    pjsip_clen_hdr *clen;
+    pjsip_cseq_hdr *cseq;
+    pjsip_contact_hdr *contact;
+    pjsip_ctype_hdr *ctype;
+    pjsip_routing_hdr *routing;
+    pjsip_via_hdr *via;
+    pjsip_generic_string_hdr *generic;
+    pj_str_t str;
+
+    msg = pjsip_msg_create(pool, PJSIP_REQUEST_MSG);
+
+    /* "INVITE sip:user@foo SIP/2.0\n" */
+    pjsip_method_set(&msg->line.req.method, PJSIP_INVITE_METHOD);
+    url = pjsip_url_create(pool, 0);
+    msg->line.req.uri = (pjsip_uri*)url;
+    pj_strdup2(pool, &url->user, "user");
+    pj_strdup2(pool, &url->host, "foo");
+
+    /* "From: Hi I'm Joe <sip:joe.user@bar.otherdomain.com>;tag=1234578901234567890\r" */
+    fromto = pjsip_from_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto);
+    pj_strdup2(pool, &fromto->tag, "1234578901234567890");
+    name_addr = pjsip_name_addr_create(pool);
+    fromto->uri = (pjsip_uri*)name_addr;
+    pj_strdup2(pool, &name_addr->display, "Hi I'm Joe");
+    url = pjsip_url_create(pool, 0);
+    name_addr->uri = (pjsip_uri*)url;
+    pj_strdup2(pool, &url->user, "joe.user");
+    pj_strdup2(pool, &url->host, "bar.otherdomain.com");
+
+    /* "To: Fellow User <sip:user@foo.bar.domain.com>\r\n" */
+    fromto = pjsip_to_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto);
+    name_addr = pjsip_name_addr_create(pool);
+    fromto->uri = (pjsip_uri*)name_addr;
+    pj_strdup2(pool, &name_addr->display, "Fellow User");
+    url = pjsip_url_create(pool, 0);
+    name_addr->uri = (pjsip_uri*)url;
+    pj_strdup2(pool, &url->user, "user");
+    pj_strdup2(pool, &url->host, "foo.bar.domain.com");
+
+    /* "Call-ID: 12345678901234567890@bar\r\n" */
+    cid = pjsip_cid_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)cid);
+    pj_strdup2(pool, &cid->id, "12345678901234567890@bar");
+
+    /* "Content-Length: 0\r\n" */
+    clen = pjsip_clen_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)clen);
+    clen->len = 0;
+
+    /* "CSeq: 123456 INVITE\n" */
+    cseq = pjsip_cseq_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)cseq);
+    cseq->cseq = 123456;
+    pjsip_method_set(&cseq->method, PJSIP_INVITE_METHOD);
+
+    /* "Contact: <sip:joe@bar>;q=0.5;expires=3600*/
+    contact = pjsip_contact_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
+    contact->q1000 = 500;
+    contact->expires = 3600;
+    name_addr = pjsip_name_addr_create(pool);
+    contact->uri = (pjsip_uri*)name_addr;
+    url = pjsip_url_create(pool, 0);
+    name_addr->uri = (pjsip_uri*)url;
+    pj_strdup2(pool, &url->user, "joe");
+    pj_strdup2(pool, &url->host, "bar");
+
+    /*, sip:user@host;q=0.500\r" */
+    contact = pjsip_contact_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
+    contact->q1000 = 500;
+    url = pjsip_url_create(pool, 0);
+    contact->uri = (pjsip_uri*)url;
+    pj_strdup2(pool, &url->user, "user");
+    pj_strdup2(pool, &url->host, "host");
+
+    /* "  ,sip:user2@host2\n" */
+    contact = pjsip_contact_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
+    url = pjsip_url_create(pool, 0);
+    contact->uri = (pjsip_uri*)url;
+    pj_strdup2(pool, &url->user, "user2");
+    pj_strdup2(pool, &url->host, "host2");
+
+    /* "Content-Type: text/html; charset=ISO-8859-4\r" */
+    ctype = pjsip_ctype_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)ctype);
+    pj_strdup2(pool, &ctype->media.type, "text");
+    pj_strdup2(pool, &ctype->media.subtype, "html");
+    pj_strdup2(pool, &ctype->media.param, ";charset=ISO-8859-4");
+
+    /* "Route: <sip:bigbox3.site3.atlanta.com;lr>,\r\n" */
+    routing = pjsip_route_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
+    url = pjsip_url_create(pool, 0);
+    routing->name_addr.uri = (pjsip_uri*)url;
+    pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com");
+    url->lr_param = 1;
+
+    /* "  <sip:server10.biloxi.com;lr>\r" */
+    routing = pjsip_route_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
+    url = pjsip_url_create(pool, 0);
+    routing->name_addr.uri = (pjsip_uri*)url;
+    pj_strdup2(pool, &url->host, "server10.biloxi.com");
+    url->lr_param = 1;
+
+    /* "Record-Route: <sip:server10.biloxi.com>,\r\n" */
+    routing = pjsip_rr_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
+    url = pjsip_url_create(pool, 0);
+    routing->name_addr.uri = (pjsip_uri*)url;
+    pj_strdup2(pool, &url->host, "server10.biloxi.com");
+    url->lr_param = 0;
+
+    /* "  <sip:bigbox3.site3.atlanta.com;lr>\n" */
+    routing = pjsip_rr_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
+    url = pjsip_url_create(pool, 0);
+    routing->name_addr.uri = (pjsip_uri*)url;
+    pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com");
+    url->lr_param = 1;
+
+    /* "Via: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1\n" */
+    via = pjsip_via_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+    pj_strdup2(pool, &via->transport, "SCTP");
+    pj_strdup2(pool, &via->sent_by.host, "bigbox3.site3.atlanta.com");
+    pj_strdup2(pool, &via->branch_param, "z9hG4bK77ef4c2312983.1");
+
+    /* "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n"
+	" ;received=192.0.2.1\r\n" */
+    via = pjsip_via_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+    pj_strdup2(pool, &via->transport, "UDP");
+    pj_strdup2(pool, &via->sent_by.host, "pc33.atlanta.com");
+    pj_strdup2(pool, &via->branch_param, "z9hG4bKnashds8");
+    pj_strdup2(pool, &via->recvd_param, "192.0.2.1");
+
+
+    /* "Via: SIP/2.0/UDP 10.2.1.1, */ 
+    via = pjsip_via_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+    pj_strdup2(pool, &via->transport, "UDP");
+    pj_strdup2(pool, &via->sent_by.host, "10.2.1.1");
+    
+    
+    /*SIP/2.0/TCP 192.168.1.1\n" */
+    via = pjsip_via_hdr_create(pool);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
+    pj_strdup2(pool, &via->transport, "TCP");
+    pj_strdup2(pool, &via->sent_by.host, "192.168.1.1");
+
+    /* "Organization: \r" */
+    str.ptr = "Organization";
+    str.slen = 12;
+    generic = pjsip_generic_string_hdr_create(pool, &str);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
+    generic->hvalue.ptr = NULL;
+    generic->hvalue.slen = 0;
+
+    /* "Max-Forwards: 70\n" */
+    str.ptr = "Max-Forwards";
+    str.slen = 12;
+    generic = pjsip_generic_string_hdr_create(pool, &str);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
+    str.ptr = "70";
+    str.slen = 2;
+    generic->hvalue = str;
+
+    /* "X-Header: \r\n" */
+    str.ptr = "X-Header";
+    str.slen = 8;
+    generic = pjsip_generic_string_hdr_create(pool, &str);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
+    str.ptr = NULL;
+    str.slen = 0;
+    generic->hvalue = str;
+
+    return msg;
+}
Index: pjproject/main/pjsip/src/pjsip-tests/pjsip_core/test_uri.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-tests/pjsip_core/test_uri.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-tests/pjsip_core/test_uri.c	(revision 39)
@@ -0,0 +1,645 @@
+/* $Id$
+ *
+ */
+#include <pjsip/sip_parser.h>
+#include <pjsip/sip_uri.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "test.h"
+
+#define ERR_SYNTAX_ERR	(-2)
+#define ERR_NOT_EQUAL	(-3)
+
+#define ALPHANUM    "abcdefghijklmnopqrstuvwxyz" \
+		    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+		    "0123456789"
+#define MARK	    "-_.!~*'()"
+#define USER	    "&=+$,;?/%"
+#define PASS	    "&=+$,%"
+#define PARAM_CHAR  "[]/:&+$" MARK "%"
+
+#define POOL_SIZE	4096
+
+static const char *STATUS_STR(pj_status_t status)
+{
+    switch (status) {
+    case 0: return "OK";
+    case ERR_SYNTAX_ERR: return "Syntax Error";
+    case ERR_NOT_EQUAL: return "Not Equal";
+    }
+    return "???";
+}
+
+static pj_uint32_t parse_len, parse_time, print_time;
+static pj_caching_pool cp;
+
+
+/* URI creator functions. */
+static pjsip_uri *create_uri1( pj_pool_t *pool );
+static pjsip_uri *create_uri2( pj_pool_t *pool );
+static pjsip_uri *create_uri3( pj_pool_t *pool );
+static pjsip_uri *create_uri4( pj_pool_t *pool );
+static pjsip_uri *create_uri5( pj_pool_t *pool );
+static pjsip_uri *create_uri6( pj_pool_t *pool );
+static pjsip_uri *create_uri7( pj_pool_t *pool );
+static pjsip_uri *create_uri8( pj_pool_t *pool );
+static pjsip_uri *create_uri9( pj_pool_t *pool );
+static pjsip_uri *create_uri10( pj_pool_t *pool );
+static pjsip_uri *create_uri11( pj_pool_t *pool );
+static pjsip_uri *create_uri12( pj_pool_t *pool );
+static pjsip_uri *create_uri13( pj_pool_t *pool );
+static pjsip_uri *create_uri14( pj_pool_t *pool );
+static pjsip_uri *create_uri15( pj_pool_t *pool );
+static pjsip_uri *create_uri16( pj_pool_t *pool );
+static pjsip_uri *create_uri17( pj_pool_t *pool );
+static pjsip_uri *create_uri18( pj_pool_t *pool );
+static pjsip_uri *create_uri19( pj_pool_t *pool );
+static pjsip_uri *create_dummy( pj_pool_t *pool );
+
+struct uri_test
+{
+    pj_status_t	     status;
+    char	     str[PJSIP_MAX_URL_SIZE];
+    pjsip_uri *(*creator)(pj_pool_t *pool);
+    pj_size_t	     len;
+} uri_test_array[] = 
+{
+    {
+	PJ_SUCCESS,
+	"sip:localhost",
+	&create_uri1
+    },
+    {
+	PJ_SUCCESS,
+	"sip:user@localhost",
+	&create_uri2
+    },
+    {
+	PJ_SUCCESS,
+	"sip:user:password@localhost:5060",
+	&create_uri3,
+    },
+    {
+	/* Port is specified should not match unspecified port. */
+	ERR_NOT_EQUAL,
+	"sip:localhost:5060",
+	&create_uri4
+    },
+    {
+	/* All recognized parameters. */
+	PJ_SUCCESS,
+	"sip:localhost;transport=tcp;user=ip;ttl=255;lr;maddr=127.0.0.1;method=ACK",
+	&create_uri5
+    },
+    {
+	/* Params mixed with other params and header params. */
+	PJ_SUCCESS,
+	"sip:localhost;pickup=hurry;user=phone;message=I%20am%20sorry"
+	"?Subject=Hello%20There&Server=SIP%20Server",
+	&create_uri6
+    },
+    {
+	/* SIPS. */
+	PJ_SUCCESS,
+	"sips:localhost",
+	&create_uri7,
+    },
+    {
+	/* Name address */
+	PJ_SUCCESS,
+	"<sip:localhost>",
+	&create_uri8
+    },
+    {
+	/* Name address with display name and SIPS scheme with some redundant
+	 * whitespaced.
+	 */
+	PJ_SUCCESS,
+	"  Power Administrator  <sips:localhost>",
+	&create_uri9
+    },
+    {
+	/* Name address. */
+	PJ_SUCCESS,
+	" \"User\" <sip:user@localhost:5071>",
+	&create_uri10
+    },
+    {
+	/* Escaped sequence in display name (display=Strange User\"\\\"). */
+	PJ_SUCCESS,
+	" \"Strange User\\\"\\\\\\\"\" <sip:localhost>",
+	&create_uri11,
+    },
+    {
+	/* Errorneous escaping in display name. */
+	ERR_SYNTAX_ERR,
+	" \"Rogue User\\\" <sip:localhost>",
+	&create_uri12,
+    },
+    {
+	/* Dangling quote in display name, but that should be OK. */
+	PJ_SUCCESS,
+	"Strange User\" <sip:localhost>",
+	&create_uri13,
+    },
+    {
+	/* Special characters in parameter value must be quoted. */
+	PJ_SUCCESS,
+	"sip:localhost;pvalue=\"hello world\"",
+	&create_uri14,
+    },
+    {
+	/* Excercise strange character sets allowed in display, user, password,
+	 * host, and port. 
+	 */
+	PJ_SUCCESS,
+	"This is -. !% *_+`'~ me <sip:a19A&=+$,;?/%2c:%09a&Zz=+$,@"
+	"my_proxy09.MY-domain.com:9801>",
+	&create_uri15,
+    },
+    {
+	/* Another excercise to the allowed character sets to the hostname. */
+	PJ_SUCCESS,
+	"sip:" ALPHANUM "-_.com",
+	&create_uri16,
+    },
+    {
+	/* Another excercise to the allowed character sets to the username 
+	 * and password.
+	 */
+	PJ_SUCCESS,
+	"sip:" ALPHANUM USER ":" ALPHANUM PASS "@host",
+	&create_uri17,
+    },
+    {
+	/* Excercise to the pname and pvalue, and mixup of other-param
+	 * between 'recognized' params.
+	 */
+	PJ_SUCCESS,
+	"sip:host;user=ip;" ALPHANUM PARAM_CHAR "=" ALPHANUM PARAM_CHAR 
+	";lr;other=1;transport=sctp;other2",
+	&create_uri18,
+    },
+    {
+	/* This should trigger syntax error. */
+	ERR_SYNTAX_ERR,
+	"sip:",
+	&create_dummy,
+    },
+    {
+	/* Syntax error: whitespace after scheme. */
+	ERR_SYNTAX_ERR,
+	"sip :host",
+	&create_dummy,
+    },
+    {
+	/* Syntax error: whitespace before hostname. */
+	ERR_SYNTAX_ERR,
+	"sip: host",
+	&create_dummy,
+    },
+    {
+	/* Syntax error: invalid port. */
+	ERR_SYNTAX_ERR,
+	"sip:user:password",
+	&create_dummy,
+    },
+    {
+	/* Syntax error: no host. */
+	ERR_SYNTAX_ERR,
+	"sip:user@",
+	&create_dummy,
+    },
+    {
+	/* Syntax error: no user/host. */
+	ERR_SYNTAX_ERR,
+	"sip:@",
+	&create_dummy,
+    },
+    {
+	/* Syntax error: empty string. */
+	ERR_SYNTAX_ERR,
+	"",
+	&create_dummy,
+    },
+    {
+	PJ_SUCCESS,
+	"",
+	NULL,
+    },
+};
+
+static pjsip_uri *create_uri1(pj_pool_t *pool)
+{
+    /* "sip:localhost" */
+    pjsip_url *url = pjsip_url_create(pool, 0);
+
+    pj_strdup2(pool, &url->host, "localhost");
+    return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri2(pj_pool_t *pool)
+{
+    /* "sip:user@localhost" */
+    pjsip_url *url = pjsip_url_create(pool, 0);
+
+    pj_strdup2( pool, &url->user, "user");
+    pj_strdup2( pool, &url->host, "localhost");
+
+    return (pjsip_uri*) url;
+}
+
+static pjsip_uri *create_uri3(pj_pool_t *pool)
+{
+    /* "sip:user:password@localhost:5060" */
+    pjsip_url *url = pjsip_url_create(pool, 0);
+
+    pj_strdup2( pool, &url->user, "user");
+    pj_strdup2( pool, &url->passwd, "password");
+    pj_strdup2( pool, &url->host, "localhost");
+    url->port = 5060;
+
+    return (pjsip_uri*) url;
+}
+
+static pjsip_uri *create_uri4(pj_pool_t *pool)
+{
+    /* Like: "sip:localhost:5060", but without the port. */
+    pjsip_url *url = pjsip_url_create(pool, 0);
+
+    pj_strdup2(pool, &url->host, "localhost");
+    return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri5(pj_pool_t *pool)
+{
+    /* "sip:localhost;transport=tcp;user=ip;ttl=255;lr;maddr=127.0.0.1;method=ACK" */
+    pjsip_url *url = pjsip_url_create(pool, 0);
+
+    pj_strdup2(pool, &url->host, "localhost");
+    pj_strdup2(pool, &url->transport_param, "tcp");
+    pj_strdup2(pool, &url->user_param, "ip");
+    url->ttl_param = 255;
+    url->lr_param = 1;
+    pj_strdup2(pool, &url->maddr_param, "127.0.0.1");
+    pj_strdup2(pool, &url->method_param, "ACK");
+
+    return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri6(pj_pool_t *pool)
+{
+    /* "sip:localhost;pickup=hurry;user=phone;message=I%20am%20sorry"
+       "?Subject=Hello%20There&Server=SIP%20Server" 
+     */
+    pjsip_url *url = pjsip_url_create(pool, 0);
+
+    pj_strdup2(pool, &url->host, "localhost");
+    pj_strdup2(pool, &url->user_param, "phone");
+    pj_strdup2(pool, &url->other_param, ";pickup=hurry;message=I%20am%20sorry");
+    pj_strdup2(pool, &url->header_param, "?Subject=Hello%20There&Server=SIP%20Server");
+    return (pjsip_uri*)url;
+
+}
+
+static pjsip_uri *create_uri7(pj_pool_t *pool)
+{
+    /* "sips:localhost" */
+    pjsip_url *url = pjsip_url_create(pool, 1);
+
+    pj_strdup2(pool, &url->host, "localhost");
+    return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri8(pj_pool_t *pool)
+{
+    /* "<sip:localhost>" */
+    pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+    pjsip_url *url;
+
+    url = pjsip_url_create(pool, 0);
+    name_addr->uri = (pjsip_uri*) url;
+
+    pj_strdup2(pool, &url->host, "localhost");
+    return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri9(pj_pool_t *pool)
+{
+    /* "  Power Administrator <sips:localhost>" */
+    pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+    pjsip_url *url;
+
+    url = pjsip_url_create(pool, 1);
+    name_addr->uri = (pjsip_uri*) url;
+
+    pj_strdup2(pool, &name_addr->display, "Power Administrator");
+    pj_strdup2(pool, &url->host, "localhost");
+    return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri10(pj_pool_t *pool)
+{
+    /* " \"User\" <sip:user@localhost:5071>" */
+    pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+    pjsip_url *url;
+
+    url = pjsip_url_create(pool, 0);
+    name_addr->uri = (pjsip_uri*) url;
+
+    pj_strdup2(pool, &name_addr->display, "\"User\"");
+    pj_strdup2(pool, &url->user, "user");
+    pj_strdup2(pool, &url->host, "localhost");
+    url->port = 5071;
+    return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri11(pj_pool_t *pool)
+{
+    /* " \"Strange User\\\"\\\\\\\"\" <sip:localhost>" */
+    pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+    pjsip_url *url;
+
+    url = pjsip_url_create(pool, 0);
+    name_addr->uri = (pjsip_uri*) url;
+
+    pj_strdup2(pool, &name_addr->display, "\"Strange User\\\"\\\\\\\"\"");
+    pj_strdup2(pool, &url->host, "localhost");
+    return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri12(pj_pool_t *pool)
+{
+    /* " \"Rogue User\\\" <sip:localhost>" */
+    pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+    pjsip_url *url;
+
+    url = pjsip_url_create(pool, 0);
+    name_addr->uri = (pjsip_uri*) url;
+
+    pj_strdup2(pool, &name_addr->display, "\"Rogue User\\\"");
+    pj_strdup2(pool, &url->host, "localhost");
+    return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri13(pj_pool_t *pool)
+{
+    /* "Strange User\" <sip:localhost>" */
+    pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+    pjsip_url *url;
+
+    url = pjsip_url_create(pool, 0);
+    name_addr->uri = (pjsip_uri*) url;
+
+    pj_strdup2(pool, &name_addr->display, "Strange User\"");
+    pj_strdup2(pool, &url->host, "localhost");
+    return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri14(pj_pool_t *pool)
+{
+    /* "sip:localhost;pvalue=\"hello world\"" */
+    pjsip_url *url;
+    url = pjsip_url_create(pool, 0);
+    pj_strdup2(pool, &url->host, "localhost");
+    pj_strdup2(pool, &url->other_param, ";pvalue=\"hello world\"");
+    return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri15(pj_pool_t *pool)
+{
+    /* "This is -. !% *_+`'~ me <sip:a19A&=+$,;?/%2c:%09a&Zz=+$,@my_proxy09.my-domain.com:9801>" */
+    pjsip_name_addr *name_addr = pjsip_name_addr_create(pool);
+    pjsip_url *url;
+
+    url = pjsip_url_create(pool, 0);
+    name_addr->uri = (pjsip_uri*) url;
+
+    pj_strdup2(pool, &name_addr->display, "This is -. !% *_+`'~ me");
+    pj_strdup2(pool, &url->user, "a19A&=+$,;?/%2c");
+    pj_strdup2(pool, &url->passwd, "%09a&Zz=+$,");
+    pj_strdup2(pool, &url->host, "my_proxy09.MY-domain.com");
+    url->port = 9801;
+    return (pjsip_uri*)name_addr;
+}
+
+static pjsip_uri *create_uri16(pj_pool_t *pool)
+{
+    /* "sip:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.com" */
+    pjsip_url *url;
+    url = pjsip_url_create(pool, 0);
+    pj_strdup2(pool, &url->host, ALPHANUM "-_.com");
+    return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri17(pj_pool_t *pool)
+{
+    /* "sip:" ALPHANUM USER ":" ALPHANUM PASS "@host" */
+    pjsip_url *url;
+    url = pjsip_url_create(pool, 0);
+    pj_strdup2(pool, &url->user, ALPHANUM USER);
+    pj_strdup2(pool, &url->passwd, ALPHANUM PASS);
+    pj_strdup2(pool, &url->host, "host");
+    return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_uri18(pj_pool_t *pool)
+{
+    /* "sip:host;user=ip;" ALPHANUM PARAM_CHAR "=" ALPHANUM PARAM_CHAR ";lr;other=1;transport=sctp;other2" */
+    pjsip_url *url;
+    url = pjsip_url_create(pool, 0);
+    pj_strdup2(pool, &url->host, "host");
+    pj_strdup2(pool, &url->user_param, "ip");
+    pj_strdup2(pool, &url->transport_param, "sctp");
+    pj_strdup2(pool, &url->other_param, ";" ALPHANUM PARAM_CHAR "=" ALPHANUM PARAM_CHAR ";other=1;other2");    
+    url->lr_param = 1;
+    return (pjsip_uri*)url;
+}
+
+static pjsip_uri *create_dummy(pj_pool_t *pool)
+{
+    PJ_UNUSED_ARG(pool)
+    return NULL;
+}
+
+/*****************************************************************************/
+
+static void pool_error(pj_pool_t *pool, pj_size_t sz)
+{
+    PJ_UNUSED_ARG(pool)
+    PJ_UNUSED_ARG(sz)
+
+    pj_assert(0);
+    exit(1);
+}
+
+/*
+ * Test one test entry.
+ */
+static pj_status_t test_entry(struct uri_test *entry)
+{
+    pj_status_t status;
+    pj_pool_t *pool;
+    int len;
+    pjsip_uri *parsed_uri, *ref_uri;
+    pj_str_t s1 = {NULL, 0}, s2 = {NULL, 0};
+    pj_hr_timestamp t1, t2;
+
+    pool = (*cp.factory.create_pool)( &cp.factory, "", POOL_SIZE, 0, &pool_error);
+
+    /* Parse URI text. */
+    pj_hr_gettimestamp(&t1);
+    parse_len += entry->len;
+    parsed_uri = pjsip_parse_uri(pool, entry->str, entry->len, 0);
+    if (!parsed_uri) {
+	/* Parsing failed. If the entry says that this is expected, then
+	 * return OK.
+	 */
+	status = entry->status==ERR_SYNTAX_ERR ? PJ_SUCCESS : ERR_SYNTAX_ERR;
+	goto on_return;
+    }
+    pj_hr_gettimestamp(&t2);
+    parse_time += t2.u32.lo - t1.u32.lo;
+
+    /* Create the reference URI. */
+    ref_uri = entry->creator(pool);
+
+    /* Print both URI. */
+    s1.ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+    s2.ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+
+    pj_hr_gettimestamp(&t1);
+    len = pjsip_uri_print( PJSIP_URI_IN_OTHER, parsed_uri, s1.ptr, PJSIP_MAX_URL_SIZE);
+    if (len < 1) {
+	status = -1;
+	goto on_return;
+    }
+    s1.slen = len;
+
+    len = pjsip_uri_print( PJSIP_URI_IN_OTHER, ref_uri, s2.ptr, PJSIP_MAX_URL_SIZE);
+    if (len < 1) {
+	status = -1;
+	goto on_return;
+    }
+    s2.slen = len;
+    pj_hr_gettimestamp(&t2);
+    print_time += t2.u32.lo - t1.u32.lo;
+
+    /* Full comparison of parsed URI with reference URI. */
+    if (pjsip_uri_cmp(PJSIP_URI_IN_OTHER, parsed_uri, ref_uri) != 0) {
+	/* Not equal. See if this is the expected status. */
+	status = entry->status==ERR_NOT_EQUAL ? PJ_SUCCESS : ERR_NOT_EQUAL;
+	goto on_return;
+
+    } else {
+	/* Equal. See if this is the expected status. */
+	status = entry->status==PJ_SUCCESS ? PJ_SUCCESS : -1;
+	if (status != PJ_SUCCESS) {
+	    goto on_return;
+	}
+    }
+
+    /* Compare text. */
+    if (pj_strcmp(&s1, &s2) != 0) {
+	/* Not equal. */
+	status = ERR_NOT_EQUAL;
+    }
+
+on_return:
+    if (!SILENT) {
+	printf("%.2d %s (expected status=%s)\n"
+	       "   str=%s\n"
+	       "   uri=%.*s\n"
+	       "   ref=%.*s\n\n", 
+	       entry-uri_test_array, 
+	       STATUS_STR(status), 
+	       STATUS_STR(entry->status), 
+	       entry->str, 
+	       (int)s1.slen, s1.ptr, (int)s2.slen, s2.ptr);
+    }
+
+    pj_pool_release(pool);
+    return status;
+}
+
+static void warm_up(pj_pool_factory *pf)
+{
+    pj_pool_t *pool;
+    struct uri_test *entry;
+
+    pool = pj_pool_create(pf, "", POOL_SIZE, 0, &pool_error);
+    pjsip_parse_uri(pool, "sip:host", 8, 0);
+    entry = &uri_test_array[0];
+    while (entry->creator) {
+	entry->len = strlen(entry->str);
+	++entry;
+    }
+    pj_pool_release(pool);
+}
+
+//#if !IS_PROFILING
+#if 1
+pj_status_t test_uri()
+{
+    struct uri_test *entry;
+    int i=0, err=0;
+    pj_status_t status;
+    pj_hr_timestamp t1, t2;
+    pj_uint32_t total_time;
+
+    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+    warm_up(&cp.factory);
+
+    pj_hr_gettimestamp(&t1);
+    for (i=0; i<LOOP; ++i) {
+	entry = &uri_test_array[0];
+	while (entry->creator) {
+	    status = test_entry(entry);
+	    if (status != PJ_SUCCESS) {
+		++err;
+	    }
+	    ++entry;
+	}
+    }
+    pj_hr_gettimestamp(&t2);
+    total_time = t2.u32.lo - t1.u32.lo;
+
+    printf("Error=%d\n", err);
+    printf("Total parse len:  %u bytes\n", parse_len);
+    printf("Total parse time: %u (%f/char), print time: %u (%f/char)\n", 
+	    parse_time, parse_time*1.0/parse_len,
+	    print_time, print_time*1.0/parse_len);
+    printf("Total time: %u (%f/char)\n", total_time, total_time*1.0/parse_len);
+    return err;
+}
+
+#else
+
+pj_status_t test_uri()
+{
+    struct uri_test *entry;
+    unsigned i;
+
+    warm_up();
+    pj_caching_pool_init(&cp, 1024*1024);
+
+    for (i=0; i<LOOP; ++i) {
+	entry = &uri_test_array[0];
+	while (entry->creator) {
+	    pj_pool_t *pool;
+	    pjsip_uri *uri1, *uri2;
+
+	    pool = pj_pool_create( &cp.factory, "", POOL_SIZE, 0, &pool_error);
+	    uri1 = pjsip_parse_uri(pool, entry->str, strlen(entry->str));
+	    pj_pool_release(pool);
+	    ++entry;
+	}
+    }
+
+    return 0;
+}
+
+#endif
Index: pjproject/main/pjsip/src/pjsip-ua/sip_dialog.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-ua/sip_dialog.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-ua/sip_dialog.c	(revision 39)
@@ -0,0 +1,1786 @@
+/* $Id$
+ *
+ */
+#include <pjsip_mod_ua/sip_dialog.h>
+#include <pjsip_mod_ua/sip_ua.h>
+#include <pjsip_mod_ua/sip_ua_private.h>
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_types.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_uri.h>
+#include <pjsip/sip_misc.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_parser.h>
+#include <pj/string.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/guid.h>
+#include <pj/except.h>
+#include <pj/pool.h>
+
+/* TLS to keep dialog lock record (initialized by UA) */
+int pjsip_dlg_lock_tls_id;
+
+struct dialog_lock_data
+{
+    struct dialog_lock_data *prev;
+    pjsip_dlg	    *dlg;
+    int			     is_alive;
+};
+
+/*
+ * Static function prototypes.
+ */
+static void dlg_create_request_throw( pjsip_tx_data **p_tdata,
+				      pjsip_dlg *dlg,
+				      const pjsip_method *method,
+				      int cseq );
+static int  dlg_on_all_state_pre(  pjsip_dlg *dlg, 
+				   pjsip_transaction *tsx,
+				   pjsip_event *event);
+static int  dlg_on_all_state_post( pjsip_dlg *dlg, 
+				   pjsip_transaction *tsx,
+				   pjsip_event *event);
+static int  dlg_on_state_null( pjsip_dlg *dlg, 
+			       pjsip_transaction *tsx,
+			       pjsip_event *event);
+static int  dlg_on_state_incoming( pjsip_dlg *dlg, 
+				   pjsip_transaction *tsx,
+				   pjsip_event *event);
+static int  dlg_on_state_calling( pjsip_dlg *dlg, 
+				  pjsip_transaction *tsx,
+				  pjsip_event *event);
+static int  dlg_on_state_proceeding( pjsip_dlg *dlg, 
+				     pjsip_transaction *tsx,
+				     pjsip_event *event);
+static int  dlg_on_state_proceeding_caller( pjsip_dlg *dlg, 
+					    pjsip_transaction *tsx,
+					    pjsip_event *event);
+static int  dlg_on_state_proceeding_callee( pjsip_dlg *dlg, 
+					    pjsip_transaction *tsx,
+					    pjsip_event *event);
+static int  dlg_on_state_connecting( pjsip_dlg *dlg, 
+				     pjsip_transaction *tsx,
+				     pjsip_event *event);
+static int  dlg_on_state_established( pjsip_dlg *dlg, 
+				      pjsip_transaction *tsx,
+				      pjsip_event *event);
+static int  dlg_on_state_disconnected( pjsip_dlg *dlg, 
+				       pjsip_transaction *tsx,
+				       pjsip_event *event);
+static int  dlg_on_state_terminated( pjsip_dlg *dlg, 
+				     pjsip_transaction *tsx,
+				     pjsip_event *event);
+
+/*
+ * Dialog state handlers.
+ */
+static int  (*dlg_state_handlers[])(struct pjsip_dlg *, pjsip_transaction *,
+				    pjsip_event *) = 
+{
+    &dlg_on_state_null,
+    &dlg_on_state_incoming,
+    &dlg_on_state_calling,
+    &dlg_on_state_proceeding,
+    &dlg_on_state_connecting,
+    &dlg_on_state_established,
+    &dlg_on_state_disconnected,
+    &dlg_on_state_terminated,
+};
+
+/*
+ * Dialog state names.
+ */
+static const char* dlg_state_names[] = 
+{
+    "STATE_NULL",
+    "STATE_INCOMING",
+    "STATE_CALLING",
+    "STATE_PROCEEDING",
+    "STATE_CONNECTING",
+    "STATE_ESTABLISHED",
+    "STATE_DISCONNECTED",
+    "STATE_TERMINATED",
+};
+
+
+/*
+ * Get the dialog string state, normally for logging purpose.
+ */
+const char *pjsip_dlg_state_str(pjsip_dlg_state_e state)
+{
+    return dlg_state_names[state];
+}
+
+/* Lock dialog mutex. */
+static void lock_dialog(pjsip_dlg *dlg, struct dialog_lock_data *lck)
+{
+    struct dialog_lock_data *prev;
+
+    pj_mutex_lock(dlg->mutex);
+    prev = pj_thread_local_get(pjsip_dlg_lock_tls_id);
+    lck->prev = prev;
+    lck->dlg = dlg;
+    lck->is_alive = 1;
+    pj_thread_local_set(pjsip_dlg_lock_tls_id, lck);
+}
+
+/* Carefully unlock dialog mutex, protect against situation when the dialog
+ * has already been destroyed.
+ */
+static pj_status_t unlock_dialog(pjsip_dlg *dlg, struct dialog_lock_data *lck)
+{
+    pj_assert(pj_thread_local_get(pjsip_dlg_lock_tls_id) == lck);
+    pj_assert(dlg == lck->dlg);
+
+    pj_thread_local_set(pjsip_dlg_lock_tls_id, lck->prev);
+    if (lck->is_alive)
+	pj_mutex_unlock(dlg->mutex);
+
+    return lck->is_alive ? 0 : -1;
+}
+
+/*
+ * This is called by dialog's FSM to change dialog's state.
+ */
+static void dlg_set_state( pjsip_dlg *dlg, pjsip_dlg_state_e state,
+			   pjsip_event *event)
+{
+    PJ_UNUSED_ARG(event);
+
+    PJ_LOG(4, (dlg->obj_name, "State %s-->%s (ev=%s, src=%s, data=%p)", 
+	       pjsip_dlg_state_str(dlg->state), pjsip_dlg_state_str(state),
+	       event ? pjsip_event_str(event->type) : "", 
+	       event ? pjsip_event_str(event->src_type) : "",
+	       event ? event->src.data : NULL));
+
+    dlg->state = state;
+    dlg->handle_tsx_event = dlg_state_handlers[state];
+}
+
+/*
+ * Invoke dialog's callback.
+ * This function is called by dialog's FSM, and interpret the event and call
+ * the corresponding callback registered by application.
+ */
+static void dlg_call_dlg_callback( pjsip_dlg *dlg, pjsip_dlg_event_e dlg_event,
+				   pjsip_event *event )
+{
+    pjsip_dlg_callback *cb = dlg->ua->dlg_cb;
+    if (!cb) {
+	PJ_LOG(4,(dlg->obj_name, "Can not call callback (none registered)."));
+	return;
+    }
+
+    /* Low level event: call the all-events callback. */
+    if (cb->on_all_events) {
+	(*cb->on_all_events)(dlg, dlg_event, event);
+    }
+
+    /* Low level event: call the tx/rx callback if this is a tx/rx event. */
+    if (event->type == PJSIP_EVENT_BEFORE_TX && cb->on_before_tx)
+    {
+	(*cb->on_before_tx)(dlg, event->obj.tsx, event->src.tdata, event->data.long_data);
+    }
+    else if (event->type == PJSIP_EVENT_TX_MSG && 
+	event->src_type == PJSIP_EVENT_TX_MSG && cb->on_tx_msg) 
+    {
+	(*cb->on_tx_msg)(dlg, event->obj.tsx, event->src.tdata);
+    } 
+    else if (event->type == PJSIP_EVENT_RX_MSG &&
+	     event->src_type == PJSIP_EVENT_RX_MSG && cb->on_rx_msg) {
+	(*cb->on_rx_msg)(dlg, event->obj.tsx, event->src.rdata);
+    }
+
+    /* Now trigger high level events. 
+     * High level event should only occurs when dialog's state has changed,
+     * except for on_provisional, which may be called multiple times whenever
+     * response message is sent
+     */
+    if (dlg->state == PJSIP_DIALOG_STATE_PROCEEDING &&
+	(event->type== PJSIP_EVENT_TSX_STATE_CHANGED) && 
+	event->obj.tsx == dlg->invite_tsx) 
+    {
+	/* Sent/received provisional responses. */
+	if (cb->on_provisional)
+	    (*cb->on_provisional)(dlg, event->obj.tsx, event);
+    }
+
+    if (dlg_event == PJSIP_DIALOG_EVENT_MID_CALL_REQUEST) {
+	if (cb->on_mid_call_events)
+	    (*cb->on_mid_call_events)(dlg, event);
+	return;
+    }
+
+    if (dlg_event != PJSIP_DIALOG_EVENT_STATE_CHANGED)
+	return;
+
+    if (dlg->state == PJSIP_DIALOG_STATE_INCOMING) {
+
+	/* New incoming dialog. */
+	pj_assert (event->src_type == PJSIP_EVENT_RX_MSG);
+	(*cb->on_incoming)(dlg, event->obj.tsx, event->src.rdata);
+
+    } else if (dlg->state == PJSIP_DIALOG_STATE_CALLING) {
+
+	/* Dialog has just sent the first INVITE. */
+	if (cb->on_calling) {
+	    (*cb->on_calling)(dlg, event->obj.tsx, event->src.tdata);
+	}
+
+    } else if (dlg->state == PJSIP_DIALOG_STATE_DISCONNECTED) {
+
+	if (cb->on_disconnected)
+	    (*cb->on_disconnected)(dlg, event);
+
+    } else if (dlg->state == PJSIP_DIALOG_STATE_TERMINATED) {
+
+	if (cb->on_terminated)
+	    (*cb->on_terminated)(dlg);
+
+	pjsip_ua_destroy_dialog(dlg);
+
+    } else if (dlg->state == PJSIP_DIALOG_STATE_CONNECTING) {
+
+	if (cb->on_connecting)
+	    (*cb->on_connecting)(dlg, event);
+
+    } else if (dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) {
+
+	if (cb->on_established)
+	    (*cb->on_established)(dlg, event);
+    }
+}
+
+/*
+ * This callback receives event from the transaction layer (via User Agent),
+ * or from dialog timer (for 200/INVITE or ACK retransmission).
+ */
+void pjsip_dlg_on_tsx_event( pjsip_dlg *dlg, 
+			     pjsip_transaction *tsx, 
+			     pjsip_event *event)
+{
+    int status = 0;
+    struct dialog_lock_data lck;
+
+    PJ_LOG(4, (dlg->obj_name, "state=%s (evt=%s, src=%s)", 
+			 pjsip_dlg_state_str(dlg->state),
+			 pjsip_event_str(event->type),
+			 pjsip_event_str(event->src_type)));
+
+
+    lock_dialog(dlg, &lck);
+
+    status = dlg_on_all_state_pre( dlg, tsx, event);
+
+    if (status==0) {
+	status = (*dlg->handle_tsx_event)(dlg, tsx, event);
+    }
+    if (status==0) {
+	status = dlg_on_all_state_post( dlg, tsx, event);
+    }
+    
+    unlock_dialog(dlg, &lck);
+}
+
+/*
+ * This function contains common processing in all states, and it is called
+ * before the FSM is invoked.
+ */
+static int  dlg_on_all_state_pre( pjsip_dlg *dlg, 
+				  pjsip_transaction *tsx,
+				  pjsip_event *event)
+{
+    PJ_UNUSED_ARG(event)
+
+    if (event->type != PJSIP_EVENT_TSX_STATE_CHANGED)
+	return 0;
+
+    if (tsx && (tsx->state==PJSIP_TSX_STATE_CALLING || 
+	tsx->state==PJSIP_TSX_STATE_TRYING)) 
+    {
+	++dlg->pending_tsx_count;
+
+    } else if (tsx && tsx->state==PJSIP_TSX_STATE_DESTROYED) 
+    {
+	--dlg->pending_tsx_count;
+	if (tsx == dlg->invite_tsx)
+	    dlg->invite_tsx = NULL;
+    }
+
+    if (tsx->method.id == PJSIP_INVITE_METHOD) {
+	tsx->handle_ack = 1;
+    }
+    return 0;
+}
+
+
+/*
+ * This function contains common processing in all states, and it is called
+ * after the FSM is invoked.
+ */
+static int  dlg_on_all_state_post( pjsip_dlg *dlg, 
+				   pjsip_transaction *tsx,
+				   pjsip_event *event)
+{
+    PJ_UNUSED_ARG(event)
+
+    if (tsx && tsx->state==PJSIP_TSX_STATE_DESTROYED) {
+	if (dlg->pending_tsx_count == 0 &&
+	    dlg->state != PJSIP_DIALOG_STATE_CONNECTING &&
+	    dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED &&
+	    dlg->state != PJSIP_DIALOG_STATE_TERMINATED) 
+	{
+	    dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event);
+	    dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+	    return -1;
+	}
+    }
+
+    return 0;
+}
+
+
+/*
+ * Internal function to initialize dialog, contains common initialization
+ * for both UAS and UAC dialog.
+ */
+static pj_status_t dlg_init( pjsip_dlg *dlg )
+{
+    /* Init mutex. */
+    dlg->mutex = pj_mutex_create(dlg->pool, "mdlg%p", 0);
+    if (!dlg->mutex) {
+	PJ_PERROR((dlg->obj_name, "pj_mutex_create()"));
+	return -1;
+    }
+
+    /* Init route-set (Initially empty) */
+    pj_list_init(&dlg->route_set);
+
+    /* Init auth credential list. */
+    pj_list_init(&dlg->auth_sess);
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * This one is called just before dialog is destroyed.
+ * It is called while mutex is held.
+ */
+PJ_DEF(void) pjsip_on_dialog_destroyed( pjsip_dlg *dlg )
+{
+    struct dialog_lock_data *lck;
+
+    //PJ_TODO(CHECK_THIS);
+    pj_assert(dlg->pending_tsx_count == 0);
+
+    /* Mark dialog as dead. */
+    lck = pj_thread_local_get(pjsip_dlg_lock_tls_id);
+    while (lck) {
+	if (lck->dlg == dlg)
+	    lck->is_alive = 0;
+	lck = lck->prev;
+    }
+}
+
+/*
+ * Initialize dialog from the received request.
+ * This is an internal function which is called by the User Agent (sip_ua.c), 
+ * and it will initialize most of dialog's properties with values from the
+ * received message.
+ */
+pj_status_t pjsip_dlg_init_from_rdata( pjsip_dlg *dlg, pjsip_rx_data *rdata )
+{
+    pjsip_msg *msg = rdata->msg;
+    pjsip_to_hdr *to;
+    pjsip_contact_hdr *contact;
+    pjsip_name_addr *name_addr;
+    pjsip_url *url;
+    unsigned flag;
+    pjsip_event event;
+
+    pj_assert(dlg && rdata);
+
+    PJ_LOG(5, (dlg->obj_name, "init_from_rdata(%p)", rdata));
+
+    /* Must be an INVITE request. */
+    pj_assert(msg->type == PJSIP_REQUEST_MSG && 
+	      msg->line.req.method.id == PJSIP_INVITE_METHOD);
+
+    /* Init general dialog data. */
+    if (dlg_init(dlg) != PJ_SUCCESS) {
+	return -1;
+    }
+
+    /* Get the To header. */
+    to = rdata->to;
+
+    /* Copy URI in the To header as our local URI. */
+    dlg->local.info = pjsip_hdr_clone( dlg->pool, to);
+
+    /* Set tag in the local info. */
+    dlg->local.info->tag = dlg->local.tag;
+
+    /* Create local Contact to be advertised in the response.
+     * At the moment, just copy URI from the local URI as our contact.
+     */
+    dlg->local.contact = pjsip_contact_hdr_create( dlg->pool );
+    dlg->local.contact->star = 0;
+    name_addr = (pjsip_name_addr *)dlg->local.info->uri;
+    dlg->local.contact->uri = (pjsip_uri*) name_addr;
+    url = (pjsip_url*) name_addr->uri;
+    //url->port = rdata->via->sent_by.port;
+    //url->port = pj_sockaddr_get_port( pjsip_transport_get_local_addr(rdata->transport) );
+
+    /* Save remote URI. */
+    dlg->remote.info = pjsip_hdr_clone( dlg->pool, rdata->from );
+    pjsip_fromto_set_to( dlg->remote.info );
+    pj_strdup( dlg->pool, &dlg->remote.tag, &rdata->from->tag );
+
+    /* Save remote Contact. */
+    contact = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL);
+    if (contact) {
+    	dlg->remote.contact = pjsip_hdr_clone( dlg->pool, contact );
+    } else {
+	PJ_LOG(3,(dlg->obj_name, "No Contact header in INVITE from %s", 
+		  pj_sockaddr_get_str_addr(&rdata->addr)));
+   	dlg->remote.contact = pjsip_contact_hdr_create( dlg->pool );
+	dlg->remote.contact->uri = dlg->remote.info->uri;
+    }
+
+    /* Save Call-ID. */
+    dlg->call_id = pjsip_cid_hdr_create(dlg->pool);
+    pj_strdup( dlg->pool, &dlg->call_id->id, &rdata->call_id );
+
+    /* Initialize local CSeq and save remote CSeq.*/
+    dlg->local.cseq = rdata->timestamp.sec & 0xFFFF;
+    dlg->remote.cseq = rdata->cseq->cseq;
+
+    /* Secure? */
+    flag = pjsip_transport_get_flag(rdata->transport);
+    dlg->secure = (flag & PJSIP_TRANSPORT_SECURE) != 0;
+
+    /* Initial state is NULL. */
+    event.type = event.src_type = PJSIP_EVENT_RX_MSG;
+    event.src.rdata = rdata;
+    dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, &event);
+
+    PJ_LOG(5, (dlg->obj_name, "init_from_rdata(%p) complete",  rdata));
+    return PJ_SUCCESS;
+}
+
+/*
+ * Set the contact details.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_contact( pjsip_dlg *dlg,
+					   const pj_str_t *contact )
+{
+    pjsip_uri *local_uri;
+    pj_str_t tmp;
+
+    pj_strdup_with_null(dlg->pool, &tmp, contact);
+    local_uri = pjsip_parse_uri( dlg->pool, tmp.ptr, tmp.slen, 
+				 PJSIP_PARSE_URI_AS_NAMEADDR);
+    if (local_uri == NULL) {
+	PJ_LOG(2, (dlg->obj_name, "set_contact: invalid URI"));
+	return -1;
+    }
+
+    dlg->local.contact->star = 0;
+    dlg->local.contact->uri = local_uri;
+    return 0;
+}
+
+/*
+ * Set route set.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_route_set( pjsip_dlg *dlg,
+					     const pjsip_route_hdr *route_set )
+{
+    pjsip_route_hdr *hdr;
+
+    pj_list_init(&dlg->route_set);
+    hdr = route_set->next;
+    while (hdr != route_set) {
+	pjsip_route_hdr *cloned = pjsip_hdr_clone(dlg->pool, hdr);
+	pj_list_insert_before( &dlg->route_set, cloned);
+	hdr = hdr->next;
+    }
+    return 0;
+}
+
+/*
+ * Set route set without cloning the header.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_route_set_np( pjsip_dlg *dlg,
+						pjsip_route_hdr *route_set)
+{
+    pjsip_route_hdr *hdr;
+
+    pj_list_init(&dlg->route_set);
+    hdr = route_set->next;
+    while (hdr != route_set) {
+	pj_list_insert_before( &dlg->route_set, hdr);
+	hdr = hdr->next;
+    }
+    return 0;
+}
+
+/*
+ * Application calls this function when it wants to initiate an outgoing
+ * dialog (incoming dialogs are created automatically by UA when it receives
+ * INVITE, by calling pjsip_dlg_init_from_rdata()).
+ * This function should initialize most of the dialog's properties.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_init( pjsip_dlg *dlg,
+				    const pj_str_t *c_local_info,
+				    const pj_str_t *c_remote_info,
+				    const pj_str_t *c_target)
+{
+    pj_time_val tv;
+    pjsip_event event;
+    pj_str_t buf;
+
+    if (!dlg || !c_local_info || !c_remote_info) {
+	pj_assert(dlg && c_local_info && c_remote_info);
+	return -1;
+    }
+
+    PJ_LOG(5, (dlg->obj_name, "initializing"));
+
+    /* Init general dialog */
+    if (dlg_init(dlg) != PJ_SUCCESS) {
+	return -1;
+    }
+
+    /* Duplicate local info. */
+    pj_strdup_with_null( dlg->pool, &buf, c_local_info);
+
+    /* Build local URI. */
+    dlg->local.target = pjsip_parse_uri(dlg->pool, buf.ptr, buf.slen, 
+				        PJSIP_PARSE_URI_AS_NAMEADDR);
+    if (dlg->local.target == NULL) {
+	PJ_LOG(2, (dlg->obj_name, 
+		   "pjsip_dlg_init: invalid local URI %s", buf.ptr));
+	return -1;
+    }
+
+    /* Set local URI. */
+    dlg->local.info = pjsip_from_hdr_create(dlg->pool);
+    dlg->local.info->uri = dlg->local.target;
+    dlg->local.info->tag = dlg->local.tag;
+
+    /* Create local Contact to be advertised in the response. */
+    dlg->local.contact = pjsip_contact_hdr_create( dlg->pool );
+    dlg->local.contact->star = 0;
+    dlg->local.contact->uri = dlg->local.target;
+
+    /* Set remote URI. */
+    dlg->remote.info = pjsip_to_hdr_create(dlg->pool);
+
+    /* Duplicate to buffer. */
+    pj_strdup_with_null( dlg->pool, &buf, c_remote_info);
+
+    /* Build remote info. */
+    dlg->remote.info->uri = pjsip_parse_uri( dlg->pool, buf.ptr, buf.slen,
+					     PJSIP_PARSE_URI_AS_NAMEADDR);
+    if (dlg->remote.info->uri == NULL) {
+	PJ_LOG(2, (dlg->obj_name, 
+		   "pjsip_dlg_init: invalid remote URI %s", buf.ptr));
+	return -1;
+    }
+
+    /* Set remote Contact initially equal to the remote URI. */
+    dlg->remote.contact = pjsip_contact_hdr_create(dlg->pool);
+    dlg->remote.contact->star = 0;
+    dlg->remote.contact->uri = dlg->remote.info->uri;
+
+    /* Set initial remote target. */
+    if (c_target != NULL) {
+	pj_strdup_with_null( dlg->pool, &buf, c_target);
+	dlg->remote.target = pjsip_parse_uri( dlg->pool, buf.ptr, buf.slen, 0);
+	if (dlg->remote.target == NULL) {
+	    PJ_LOG(2, (dlg->obj_name, 
+		       "pjsip_dlg_init: invalid remote target %s", buf.ptr));
+	    return -1;
+	}
+    } else {
+	dlg->remote.target = dlg->remote.info->uri;
+    }
+
+    /* Create globally unique Call-ID */
+    dlg->call_id = pjsip_cid_hdr_create(dlg->pool);
+    pj_create_unique_string( dlg->pool, &dlg->call_id->id );
+
+    /* Local and remote CSeq */
+    pj_gettimeofday(&tv);
+    dlg->local.cseq = tv.sec & 0xFFFF;
+    dlg->remote.cseq = 0;
+
+    /* Initial state is NULL. */
+    event.type = event.src_type = PJSIP_EVENT_TX_MSG;
+    event.src.data = NULL;
+    dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, &event);
+
+    /* Done. */
+    PJ_LOG(4, (dlg->obj_name, "%s dialog initialized, From: %.*s, To: %.*s",
+	       pjsip_role_name(dlg->role), 
+	       c_local_info->slen, c_local_info->ptr,
+	       c_remote_info->slen, c_remote_info->ptr));
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Set credentials.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_credentials(  pjsip_dlg *dlg,
+					        int count,
+						const pjsip_cred_info *cred)
+{
+    if (count > 0) {
+	dlg->cred_info = pj_pool_alloc(dlg->pool, count * sizeof(pjsip_cred_info));
+	pj_memcpy(dlg->cred_info, cred, count * sizeof(pjsip_cred_info));
+    }
+    dlg->cred_count = count;
+    return 0;
+}
+
+/*
+ * Create a new request within dialog (i.e. after the dialog session has been
+ * established). The construction of such requests follows the rule in 
+ * RFC3261 section 12.2.1.
+ */
+static void dlg_create_request_throw( pjsip_tx_data **p_tdata,
+				      pjsip_dlg *dlg,
+				      const pjsip_method *method,
+				      int cseq )
+{
+    pjsip_tx_data *tdata;
+    pjsip_contact_hdr *contact;
+    pjsip_route_hdr *route, *end_list;
+
+    /* Contact Header field.
+     * Contact can only be present in requests that establish dialog (in the 
+     * core SIP spec, only INVITE).
+     */
+    if (method->id == PJSIP_INVITE_METHOD)
+	contact = dlg->local.contact;
+    else
+	contact = NULL;
+
+    tdata = pjsip_endpt_create_request_from_hdr( dlg->ua->endpt,
+						 method,
+						 dlg->remote.target,
+						 dlg->local.info,
+						 dlg->remote.info,
+						 contact,
+						 dlg->call_id,
+						 cseq,
+						 NULL);
+    if (!tdata) {
+	PJ_THROW(1);
+	return;
+    }
+
+    /* Just copy dialog route-set to Route header. 
+     * The transaction will do the processing as specified in Section 12.2.1
+     * of RFC 3261 in function tsx_process_route() in sip_transaction.c.
+     */
+    route = dlg->route_set.next;
+    end_list = &dlg->route_set;
+    for (; route != end_list; route = route->next ) {
+	pjsip_route_hdr *r;
+	r = pjsip_hdr_shallow_clone( tdata->pool, route );
+	pjsip_routing_hdr_set_route(r);
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)r);
+    }
+
+    /* Copy authorization headers. */
+    pjsip_auth_init_req( dlg->pool, tdata, &dlg->auth_sess,
+			 dlg->cred_count, dlg->cred_info);
+
+    *p_tdata = tdata;
+}
+
+
+/*
+ * This function is called by application to create new outgoing request
+ * message for this dialog. After the request is created, application can
+ * modify the message (such adding headers), and eventually send the request.
+ */
+PJ_DEF(pjsip_tx_data*) pjsip_dlg_create_request( pjsip_dlg *dlg,
+						 const pjsip_method *method,
+						 int cseq)
+{
+    PJ_USE_EXCEPTION;
+    struct dialog_lock_data lck;
+    pjsip_tx_data *tdata = NULL;
+
+    pj_assert(dlg != NULL && method != NULL);
+    if (!dlg || !method) {
+	return NULL;
+    }
+
+    PJ_LOG(5, (dlg->obj_name, "Creating request"));
+
+    /* Lock dialog. */
+    lock_dialog(dlg, &lck);
+
+    /* Use outgoing CSeq and increment it by one. */
+    if (cseq < 0)
+	cseq = dlg->local.cseq + 1;
+
+    PJ_LOG(5, (dlg->obj_name, "creating request %.*s cseq=%d", 
+			      method->name.slen, method->name.ptr, cseq));
+
+    /* Create the request. */
+    PJ_TRY {
+	dlg_create_request_throw(&tdata, dlg, method, cseq);
+	PJ_LOG(5, (dlg->obj_name, "request data %s created", tdata->obj_name));
+    }
+    PJ_DEFAULT {
+	/* Failed! Delete transmit data. */
+	if (tdata) {
+	    pjsip_tx_data_dec_ref( tdata );
+	    tdata = NULL;
+	}
+    }
+    PJ_END;
+
+    /* Unlock dialog. */
+    unlock_dialog(dlg, &lck);
+
+    return tdata;
+}
+
+/*
+ * Sends request.
+ * Select the transport for the request message
+ */
+static pj_status_t dlg_send_request( pjsip_dlg *dlg, pjsip_tx_data *tdata )
+{
+    pjsip_transaction *tsx;
+    pj_status_t status = PJ_SUCCESS;
+    struct dialog_lock_data lck;
+
+    pj_assert(dlg != NULL && tdata != NULL);
+    if (!dlg || !tdata) {
+	return -1;
+    }
+
+    PJ_LOG(5, (dlg->obj_name, "sending request %s", tdata->obj_name));
+
+    /* Lock dialog. */
+    lock_dialog(dlg, &lck);
+
+    /* Create a new transaction. */
+    tsx = pjsip_endpt_create_tsx( dlg->ua->endpt );
+    if (!tsx) {
+	unlock_dialog(dlg, &lck);
+	return -1;
+    }
+
+    PJ_LOG(4, (dlg->obj_name, "Created new UAC transaction: %s", tsx->obj_name));
+
+    /* Initialize transaction */
+    tsx->module_data[dlg->ua->mod_id] = dlg;
+    status = pjsip_tsx_init_uac( tsx, tdata );
+    if (status != PJ_SUCCESS) {
+	unlock_dialog(dlg, &lck);
+	pjsip_endpt_destroy_tsx( dlg->ua->endpt, tsx );
+	return -1;
+    }
+    pjsip_endpt_register_tsx( dlg->ua->endpt, tsx );
+
+    /* Start the transaction. */
+    pjsip_tsx_on_tx_msg(tsx, tdata);
+
+    /* Unlock dialog. */
+    unlock_dialog(dlg, &lck);
+
+    return status;
+}
+
+/*
+ * This function can be called by application to send ANY outgoing message
+ * to remote party. 
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_send_msg( pjsip_dlg *dlg, pjsip_tx_data *tdata )
+{
+    pj_status_t status;
+    int tsx_status;
+    struct dialog_lock_data lck;
+
+    pj_assert(dlg != NULL && tdata != NULL);
+    if (!dlg || !tdata) {
+	return -1;
+    }
+
+    lock_dialog(dlg, &lck);
+
+    if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+	int request_cseq;
+	pjsip_msg *msg = tdata->msg;
+	pjsip_cseq_hdr *cseq_hdr;
+
+	switch (msg->line.req.method.id) {
+	case PJSIP_CANCEL_METHOD:
+
+	    /* Check the INVITE transaction state. */
+	    tsx_status = dlg->invite_tsx->status_code;
+	    if (tsx_status >= 200) {
+		/* Already terminated. Can't cancel. */
+		status = -1;
+		goto on_return;
+	    }
+
+	    /* If we've got provisional response, then send CANCEL and wait for
+	     * the response to INVITE to arrive. Otherwise just send CANCEL and
+	     * terminate the INVITE.
+	     */
+	    if (tsx_status < 100) {
+		pjsip_tsx_terminate( dlg->invite_tsx, 
+				     PJSIP_SC_REQUEST_TERMINATED);
+		status = 0;
+		goto on_return;
+	    }
+
+	    status = 0;
+	    request_cseq = dlg->invite_tsx->cseq;
+	    break;
+
+	case PJSIP_ACK_METHOD:
+	    /* Sending ACK outside of transaction is not supported at present! */
+	    pj_assert(0);
+	    status = 0;
+	    request_cseq = dlg->local.cseq;
+	    break;
+
+	case PJSIP_INVITE_METHOD:
+	    /* For an initial INVITE, reset dialog state to NULL so we get
+	     * 'normal' UAC notifications such as on_provisional(), etc.
+	     * Initial INVITE is the request that is sent when the dialog has
+	     * not been established yet. It's not necessarily the first INVITE
+	     * sent, as when the Authorization fails, subsequent INVITE are also
+	     * considered as an initial INVITE.
+	     */
+	    if (dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) {
+		/* Set state to NULL. */
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_NULL, NULL);
+
+	    } else {
+		/* This is a re-INVITE */
+	    }
+	    status = 0;
+	    request_cseq = dlg->local.cseq + 1;
+	    break;
+
+	default:
+	    status = 0;
+	    request_cseq = dlg->local.cseq + 1;
+	    break;
+	}
+
+	if (status != 0)
+	    goto on_return;
+
+	/* Update dialog's local CSeq, if necessary. */
+	if (request_cseq != dlg->local.cseq)
+	    dlg->local.cseq = request_cseq;
+
+	/* Update CSeq header in the request. */
+	cseq_hdr = (pjsip_cseq_hdr*) pjsip_msg_find_hdr( tdata->msg,
+							 PJSIP_H_CSEQ, NULL);
+	pj_assert(cseq_hdr != NULL);
+
+	/* Update the CSeq */
+	cseq_hdr->cseq = request_cseq;
+
+	/* Force the whole message to be re-printed. */
+	pjsip_tx_data_invalidate_msg( tdata );
+
+	/* Now send the request. */
+	status = dlg_send_request(dlg, tdata);
+
+    } else {
+	/*
+	 * This is only valid for sending response to INVITE!
+	 */
+	pjsip_cseq_hdr *cseq_hdr;
+
+	if (dlg->invite_tsx == NULL || dlg->invite_tsx->status_code >= 200) {
+	    status = -1;
+	    goto on_return;
+	}
+
+	cseq_hdr = (pjsip_cseq_hdr*) pjsip_msg_find_hdr( tdata->msg,
+							 PJSIP_H_CSEQ, NULL);
+	pj_assert(cseq_hdr);
+
+	if (cseq_hdr->method.id != PJSIP_INVITE_METHOD) {
+	    status = -1;
+	    goto on_return;
+	}
+
+	pj_assert(cseq_hdr->cseq == dlg->invite_tsx->cseq);
+	
+	pjsip_tsx_on_tx_msg(dlg->invite_tsx, tdata);
+	status = 0;
+    }
+
+on_return:
+    /* Unlock dialog. */
+    unlock_dialog(dlg, &lck);
+   
+    /* Whatever happen delete the message. */
+    pjsip_tx_data_dec_ref( tdata );
+
+    return status;
+}
+
+/*
+ * Sends outgoing invitation.
+ */
+PJ_DEF(pjsip_tx_data*) pjsip_dlg_invite( pjsip_dlg *dlg )
+{
+    pjsip_method method;
+    struct dialog_lock_data lck;
+    const pjsip_allow_hdr *allow_hdr;
+    pjsip_tx_data *tdata;
+
+    pj_assert(dlg != NULL);
+    if (!dlg) {
+	return NULL;
+    }
+
+    PJ_LOG(4, (dlg->obj_name, "request to send invitation"));
+
+    /* Lock dialog. */
+    lock_dialog(dlg, &lck);
+
+    /* Create request. */
+    pjsip_method_set( &method, PJSIP_INVITE_METHOD);
+    tdata = pjsip_dlg_create_request( dlg, &method, -1 );
+    if (tdata == NULL) {
+	unlock_dialog(dlg, &lck);
+	return NULL;
+    }
+
+    /* Invite SHOULD contain "Allow" header. */
+    allow_hdr = pjsip_endpt_get_allow_hdr( dlg->ua->endpt );
+    if (allow_hdr) {
+	pjsip_msg_add_hdr( tdata->msg, 
+			   pjsip_hdr_shallow_clone( tdata->pool, allow_hdr));
+    }
+
+    /* Unlock dialog. */
+    unlock_dialog(dlg, &lck);
+
+    return tdata;
+}
+
+/*
+ * Cancel pending outgoing dialog invitation. 
+ */
+PJ_DEF(pjsip_tx_data*) pjsip_dlg_cancel( pjsip_dlg *dlg )
+{
+    pjsip_tx_data *tdata = NULL;
+    struct dialog_lock_data lck;
+
+    pj_assert(dlg != NULL);
+    if (!dlg) {
+	return NULL;
+    }
+
+    PJ_LOG(4, (dlg->obj_name, "request to cancel invitation"));
+
+    lock_dialog(dlg, &lck);
+
+    /* Check the INVITE transaction. */
+    if (dlg->invite_tsx == NULL || dlg->role != PJSIP_ROLE_UAC) {
+	PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_cancel failed: "
+				  "no INVITE transaction found"));
+	goto on_return;
+    }
+
+    /* Construct the CANCEL request. */
+    tdata = pjsip_endpt_create_cancel( dlg->ua->endpt,
+				       dlg->invite_tsx->last_tx );
+    if (tdata == NULL) {
+	PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_cancel failed: "
+				  "unable to construct request"));
+	goto on_return;
+    }
+
+    /* Add reference counter to tdata. */
+    pjsip_tx_data_add_ref(tdata);
+
+on_return:
+    unlock_dialog(dlg, &lck);
+    return tdata;
+}
+
+
+/*
+ * Answer incoming dialog invitation, with either provisional responses
+ * or a final response.
+ */
+PJ_DEF(pjsip_tx_data*) pjsip_dlg_answer( pjsip_dlg *dlg, int code )
+{
+    pjsip_tx_data *tdata = NULL;
+    pjsip_msg *msg;
+    struct dialog_lock_data lck;
+
+    pj_assert(dlg != NULL);
+    if (!dlg) {
+	return NULL;
+    }
+
+    PJ_LOG(4, (dlg->obj_name, "pjsip_dlg_answer: code=%d", code));
+
+    /* Lock dialog. */
+    lock_dialog(dlg, &lck);
+
+    /* Must have pending INVITE. */
+    if (dlg->invite_tsx == NULL) {
+	PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: no INVITE transaction found"));
+	goto on_return;
+    }
+    /* Must be UAS. */
+    if (dlg->role != PJSIP_ROLE_UAS) {
+	PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: not UAS"));
+	goto on_return;
+    }
+    /* Must have not answered with final response before. */
+    if (dlg->invite_tsx->status_code >= 200) {
+	PJ_LOG(2, (dlg->obj_name, "pjsip_dlg_answer: transaction already terminated "
+				  "with status %d", dlg->invite_tsx->status_code));
+	goto on_return;
+    }
+
+    /* Get transmit data and the message. 
+     * We will rewrite the message with a new status code.
+     */
+    tdata = dlg->invite_tsx->last_tx;
+    msg = tdata->msg;
+
+    /* Set status code and reason phrase. */
+    if (code < 100 || code >= 700) code = 500;
+    msg->line.status.code = code;
+    msg->line.status.reason = *pjsip_get_status_text(code);
+
+    /* For 2xx response, Contact and Record-Route must be added. */
+    if (PJSIP_IS_STATUS_IN_CLASS(code,200)) {
+	const pjsip_allow_hdr *allow_hdr;
+
+	if (pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL) == NULL) {
+	    pjsip_contact_hdr *contact;
+	    contact = pjsip_hdr_shallow_clone( tdata->pool, dlg->local.contact);
+	    pjsip_msg_add_hdr( msg, (pjsip_hdr*)contact );
+	}
+
+	/* 2xx response MUST contain "Allow" header. */
+	allow_hdr = pjsip_endpt_get_allow_hdr( dlg->ua->endpt );
+	if (allow_hdr) {
+	    pjsip_msg_add_hdr( msg, pjsip_hdr_shallow_clone( tdata->pool, allow_hdr));
+	}
+    }
+
+    /* for all but 100 responses, To-tag must be set. */
+    if (code != 100) {
+	pjsip_to_hdr *to;
+	to = pjsip_msg_find_hdr( msg, PJSIP_H_TO, NULL);
+	to->tag = dlg->local.tag;
+    }
+
+    /* Reset packet buffer. */
+    pjsip_tx_data_invalidate_msg(tdata);
+
+    /* Add reference counter */
+    pjsip_tx_data_add_ref(tdata);
+
+on_return:
+
+    /* Unlock dialog. */
+    unlock_dialog(dlg, &lck);
+
+    return tdata;
+}
+
+
+/*
+ * Send BYE request to terminate the dialog's session.
+ */
+PJ_DEF(pjsip_tx_data*) pjsip_dlg_bye( pjsip_dlg *dlg )
+{
+    pjsip_method method;
+    struct dialog_lock_data lck;
+    pjsip_tx_data *tdata;
+
+    if (!dlg) {
+	pj_assert(dlg != NULL);
+	return NULL;
+    }
+
+    PJ_LOG(4, (dlg->obj_name, "request to terminate session"));
+
+    lock_dialog(dlg, &lck);
+
+    pjsip_method_set( &method, PJSIP_BYE_METHOD);
+    tdata = pjsip_dlg_create_request( dlg, &method, -1 );
+
+    unlock_dialog(dlg, &lck);
+
+    return tdata;
+}
+
+/*
+ * High level function to disconnect dialog's session. Depending on dialog's 
+ * state, this function will either send CANCEL, final response, or BYE to
+ * trigger the disconnection. A status code must be supplied, which will be
+ * sent if dialog will be transmitting a final response to INVITE.
+ */
+PJ_DEF(pjsip_tx_data*) pjsip_dlg_disconnect( pjsip_dlg *dlg, 
+					     int status_code )
+{
+    pjsip_tx_data *tdata = NULL;
+
+    pj_assert(dlg != NULL);
+    if (!dlg) {
+	return NULL;
+    }
+
+    switch (dlg->state) {
+    case PJSIP_DIALOG_STATE_INCOMING:
+	tdata = pjsip_dlg_answer(dlg, status_code);
+	break;
+
+    case PJSIP_DIALOG_STATE_CALLING:
+	tdata = pjsip_dlg_cancel(dlg);
+	break;
+
+    case PJSIP_DIALOG_STATE_PROCEEDING:
+	if (dlg->role == PJSIP_ROLE_UAC) {
+	    tdata = pjsip_dlg_cancel(dlg);
+	} else {
+	    tdata = pjsip_dlg_answer(dlg, status_code);
+	}
+	break;
+
+    case PJSIP_DIALOG_STATE_ESTABLISHED:
+	tdata = pjsip_dlg_bye(dlg);
+	break;
+
+    default:
+	PJ_LOG(4,(dlg->obj_name, "Invalid state %s in pjsip_dlg_disconnect()", 
+		  dlg_state_names[dlg->state]));
+	break;
+    }
+
+    return tdata;
+}
+
+/*
+ * Handling of the receipt of 2xx/INVITE response.
+ */
+static void dlg_on_recv_2xx_invite( pjsip_dlg *dlg,
+				    pjsip_event *event )
+{
+    pjsip_msg *msg;
+    pjsip_contact_hdr *contact;
+    pjsip_hdr *hdr, *end_hdr;
+    pjsip_method method;
+    pjsip_tx_data *ack_tdata;
+    
+    /* Get the message */
+    msg = event->src.rdata->msg;
+    
+    /* Update remote's tag information. */
+    pj_strdup(dlg->pool, &dlg->remote.info->tag, &event->src.rdata->to_tag);
+
+    /* Copy Contact information in the 2xx/INVITE response to dialog's.
+     * remote contact
+     */
+    contact = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL);
+    if (contact) {
+	dlg->remote.contact = pjsip_hdr_clone( dlg->pool, contact );
+    } else {
+	/* duplicate contact from "From" header (?) */
+	PJ_LOG(4,(dlg->obj_name, "Received 200/OK to INVITE with no Contact!"));
+	dlg->remote.contact = pjsip_contact_hdr_create(dlg->pool);
+	dlg->remote.contact->uri = dlg->remote.info->uri;
+    }
+    
+    /* Copy Record-Route header (in reverse order) as dialog's route-set,
+     * overwriting previous route-set, if any, even if the received route-set
+     * is empty.
+     */
+    pj_list_init(&dlg->route_set);
+    end_hdr = &msg->hdr;
+    for (hdr = msg->hdr.prev; hdr!=end_hdr; hdr = hdr->prev) {
+	if (hdr->type == PJSIP_H_RECORD_ROUTE) {
+	    pjsip_route_hdr *r;
+	    r = pjsip_hdr_clone(dlg->pool, hdr);
+	    pjsip_routing_hdr_set_route(r);
+	    pj_list_insert_before(&dlg->route_set, r);
+	}
+    }
+    
+    /* On receipt of 200/INVITE response, send ACK. 
+     * This ack must be saved and retransmitted whenever we receive
+     * 200/INVITE retransmission, until 64*T1 seconds elapsed.
+     */
+    pjsip_method_set( &method, PJSIP_ACK_METHOD);
+    ack_tdata = pjsip_dlg_create_request( dlg, &method, dlg->invite_tsx->cseq);
+    if (ack_tdata == NULL) {
+	//PJ_TODO(HANDLE_CREATE_ACK_FAILURE)
+	PJ_LOG(2, (dlg->obj_name, "Error sending ACK msg: can't create request"));
+	return;
+    }	
+    
+    /* Send with the transaction. */
+    pjsip_tsx_on_tx_ack( dlg->invite_tsx, ack_tdata);
+	
+    /* Decrement reference counter because pjsip_dlg_create_request 
+     * automatically increments the request.
+     */
+    pjsip_tx_data_dec_ref( ack_tdata );
+}
+
+/*
+ * State NULL, before any events have been received.
+ */
+static int  dlg_on_state_null( pjsip_dlg *dlg, 
+			       pjsip_transaction *tsx,
+			       pjsip_event *event)
+{
+    if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
+	event->src_type == PJSIP_EVENT_RX_MSG) 
+    {
+	pjsip_hdr *hdr, *hdr_list;
+
+	pj_assert(tsx->method.id == PJSIP_INVITE_METHOD);
+
+	/* Save the INVITE transaction. */
+	dlg->invite_tsx = tsx;
+
+	/* Change state to INCOMING */
+	dlg_set_state(dlg, PJSIP_DIALOG_STATE_INCOMING, event);
+
+	/* Create response buffer. */
+	tsx->last_tx = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, 100);
+	pjsip_tx_data_add_ref(tsx->last_tx);
+
+	/* Copy the Record-Route headers into dialog's route_set, maintaining
+	 * the order.
+	 */
+	pj_list_init(&dlg->route_set);
+	hdr_list = &event->src.rdata->msg->hdr;
+	hdr = hdr_list->next;
+	while (hdr != hdr_list) {
+	    if (hdr->type == PJSIP_H_RECORD_ROUTE) {
+		pjsip_route_hdr *route;
+		route = pjsip_hdr_clone(dlg->pool, hdr);
+		pjsip_routing_hdr_set_route(route);
+		pj_list_insert_before(&dlg->route_set, route);
+	    }
+	    hdr = hdr->next;
+	}
+
+	/* Notify application. */
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+
+    } else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
+	       event->src_type == PJSIP_EVENT_TX_MSG) 
+    {
+	pj_assert(tsx->method.id == PJSIP_INVITE_METHOD);
+
+	/* Save the INVITE transaction. */
+	dlg->invite_tsx = tsx;
+
+	/* Change state to CALLING. */
+	dlg_set_state(dlg, PJSIP_DIALOG_STATE_CALLING, event);
+
+	/* Notify application. */
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+
+    } else {
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
+    }
+
+    return 0;
+}
+
+/*
+ * State INCOMING is after the (callee) dialog has been initialized with
+ * the incoming request, but before any responses is sent by the dialog.
+ */
+static int  dlg_on_state_incoming( pjsip_dlg *dlg, 
+				   pjsip_transaction *tsx,
+				   pjsip_event *event)
+{
+    return dlg_on_state_proceeding_callee( dlg, tsx, event );
+}
+
+/*
+ * State CALLING is after the (caller) dialog has sent outgoing invitation
+ * but before any responses are received.
+ */
+static int  dlg_on_state_calling( pjsip_dlg *dlg, 
+				  pjsip_transaction *tsx,
+				  pjsip_event *event)
+{
+    if (tsx == dlg->invite_tsx) {
+	return dlg_on_state_proceeding_caller( dlg, tsx, event );
+    }
+    return 0;
+}
+
+/*
+ * State PROCEEDING is after provisional response is received.
+ * Since the processing is similar to state CALLING, this function is also
+ * called for CALLING state.
+ */
+static int  dlg_on_state_proceeding_caller( pjsip_dlg *dlg, 
+					    pjsip_transaction *tsx,
+					    pjsip_event *event)
+{
+    int dlg_is_terminated = 0;
+
+    /* We only care about our INVITE transaction. 
+     * Ignore other transaction progression (such as CANCEL).
+     */
+    if (tsx != dlg->invite_tsx) {
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
+	return 0;
+    }
+
+    if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED) {
+	switch (tsx->state) {
+	case PJSIP_TSX_STATE_PROCEEDING:
+	    if (dlg->state != PJSIP_DIALOG_STATE_PROCEEDING) {
+		/* Change state to PROCEEDING */
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_PROCEEDING, event);
+
+		/* Notify application. */
+		dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+	    } else {
+		/* Also notify application. */
+		dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
+	    }
+	    break;
+
+	case PJSIP_TSX_STATE_COMPLETED:
+	    /* Change dialog state. */
+	    if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
+		/* Update remote target, take it from the contact hdr. */
+		pjsip_contact_hdr *contact;
+		contact = pjsip_msg_find_hdr(event->src.rdata->msg, 
+					     PJSIP_H_CONTACT, NULL);
+		if (contact) {
+		    dlg->remote.target = pjsip_uri_clone(dlg->pool, contact->uri);
+		} else {
+		    PJ_LOG(4,(dlg->obj_name, 
+			      "Warning: found no Contact hdr in 200/OK"));
+		}
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_CONNECTING, event);
+	    } else if (tsx->status_code==401 || tsx->status_code==407) {
+		/* Handle Authentication challenge. */
+		pjsip_tx_data *tdata;
+		tdata = pjsip_auth_reinit_req( dlg->ua->endpt,
+					       dlg->pool, &dlg->auth_sess,
+					       dlg->cred_count, dlg->cred_info,
+					       tsx->last_tx, event->src.rdata);
+		if (tdata) {
+		    /* Re-use original request, with a new transaction. 
+		     * Need not to worry about CSeq, dialog will take care.
+		     */
+		    pjsip_dlg_send_msg(dlg, tdata);
+		    return 0;
+		} else {
+		    dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
+		}
+	    } else {
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
+	    }
+
+	    /* Notify application. */
+	    dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+
+	    /* Send ACK when dialog is connected. */
+	    if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
+		pj_assert(event->src_type == PJSIP_EVENT_RX_MSG);
+		dlg_on_recv_2xx_invite(dlg, event);
+	    }
+	    break;
+
+	case PJSIP_TSX_STATE_TERMINATED:
+	    /*
+	     * Transaction is terminated because of timeout or transport error.
+	     * To let the application go to normal state progression, call the
+	     * callback twice. First is to emulate disconnection, and then call
+	     * again (with state TERMINATED) to destroy the dialog.
+	     */
+	    dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
+	    dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+
+	    /* The INVITE transaction will be destroyed, so release reference 
+	     * to it. 
+	     */
+	    dlg->invite_tsx = NULL;
+
+	    /* We should terminate the dialog now.
+	     * But it's possible that we have other pending transactions (for 
+	     * example, outgoing CANCEL is in progress).
+	     * So destroy the dialog only if there's no other transaction.
+	     */
+	    if (dlg->pending_tsx_count == 0) {
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event);
+		dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+		dlg_is_terminated = 1;
+	    }
+	    break;
+
+	default:
+	    pj_assert(0);
+	    break;
+	}
+    } else {
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
+    }
+    return dlg_is_terminated ? -1 : 0;
+}
+
+/*
+ * State PROCEEDING for UAS is after the callee send provisional response.
+ * This function is also called for INCOMING state.
+ */
+static int  dlg_on_state_proceeding_callee( pjsip_dlg *dlg, 
+					    pjsip_transaction *tsx,
+					    pjsip_event *event)
+{
+    int dlg_is_terminated = 0;
+
+    pj_assert( dlg->invite_tsx != NULL );
+
+    if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
+	event->src_type == PJSIP_EVENT_TX_MSG && 
+	tsx == dlg->invite_tsx) 
+    {
+	switch (tsx->state) {
+	case PJSIP_TSX_STATE_PROCEEDING:
+	    /* Change state to PROCEEDING */
+	    dlg_set_state(dlg, PJSIP_DIALOG_STATE_PROCEEDING, event);
+
+	    /* Notify application. */
+	    dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+	    break;
+
+	case PJSIP_TSX_STATE_COMPLETED:
+	case PJSIP_TSX_STATE_TERMINATED:
+	    /* Change dialog state. */
+	    if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_CONNECTING, event);
+	    } else {
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
+	    }
+
+	    /* Notify application. */
+	    dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+
+	    /* If transaction is terminated in non-2xx situation, 
+	     * terminate dialog as well. This happens when something unexpected
+	     * occurs, such as transport error.
+	     */
+	    if (tsx->state == PJSIP_TSX_STATE_TERMINATED && 
+		!PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) 
+	    {
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event);
+		dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+		dlg_is_terminated = 1;
+	    }
+	    break;
+
+	default:
+	    pj_assert(0);
+	    break;
+	}
+
+    } else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
+	       event->src_type == PJSIP_EVENT_RX_MSG && 
+	       tsx->method.id == PJSIP_CANCEL_METHOD) 
+    {
+	pjsip_tx_data *tdata;
+
+	/* Check if sequence number matches the pending INVITE. */
+	if (dlg->invite_tsx==NULL ||
+	    pj_strcmp(&tsx->branch, &dlg->invite_tsx->branch) != 0) 
+	{
+	    PJ_LOG(4, (dlg->obj_name, "Received CANCEL with no matching INVITE"));
+
+	    /* No matching INVITE transaction found. */
+	    tdata = pjsip_endpt_create_response(dlg->ua->endpt,
+						event->src.rdata,
+						PJSIP_SC_CALL_TSX_DOES_NOT_EXIST );
+	    pjsip_tsx_on_tx_msg(tsx, tdata);
+	    return 0;
+	}
+
+	/* Always respond the CANCEL with 200/CANCEL no matter what. */
+	tdata = pjsip_endpt_create_response(dlg->ua->endpt,
+					    event->src.rdata,
+					    200 );
+	pjsip_tsx_on_tx_msg( tsx, tdata );
+
+	/* Respond the INVITE transaction with 487, only if transaction has
+	 * not completed. 
+	 */
+	if (dlg->invite_tsx->last_tx) {
+	    if (dlg->invite_tsx->status_code < 200) {
+		tdata = dlg->invite_tsx->last_tx;
+		tdata->msg->line.status.code = 487;
+		tdata->msg->line.status.reason = *pjsip_get_status_text(487);
+		/* Reset packet buffer. */
+		pjsip_tx_data_invalidate_msg(tdata);
+		pjsip_tsx_on_tx_msg( dlg->invite_tsx, tdata );
+	    } else {
+		PJ_LOG(4, (dlg->obj_name, "Received CANCEL with no effect, "
+					  "Transaction already terminated "
+					  "with status %d",
+					  dlg->invite_tsx->status_code));
+	    }
+	} else {
+	    tdata = pjsip_endpt_create_response(dlg->ua->endpt,
+						event->src.rdata,
+						487);
+	    pjsip_tsx_on_tx_msg( dlg->invite_tsx, tdata );
+	}
+    } else {
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
+    }
+
+    return dlg_is_terminated ? -1 : 0;
+}
+
+static int  dlg_on_state_proceeding( pjsip_dlg *dlg, 
+				     pjsip_transaction *tsx,
+				     pjsip_event *event)
+{
+    if (dlg->role == PJSIP_ROLE_UAC) {
+	return dlg_on_state_proceeding_caller( dlg, tsx, event );
+    } else {
+	return dlg_on_state_proceeding_callee( dlg, tsx, event );
+    }
+}
+
+static int  dlg_on_state_connecting( pjsip_dlg *dlg, 
+				     pjsip_transaction *tsx,
+				     pjsip_event *event)
+{
+    if (tsx == dlg->invite_tsx) {
+	if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
+	    (tsx->state == PJSIP_TSX_STATE_TERMINATED ||
+	    tsx->state == PJSIP_TSX_STATE_COMPLETED ||
+	    tsx->state == PJSIP_TSX_STATE_CONFIRMED)) 
+	{
+	    if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_ESTABLISHED, event);
+	    } else {
+		/* Probably because we never get the ACK, or transport error
+		* when sending ACK.
+		*/
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
+	    }
+	    dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+	} else {
+	    dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
+	}
+    } else {
+	/* Handle case when transaction is started when dialog is connecting
+	* (e.g. BYE requests cross wire.
+	*/
+	if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
+	    event->src_type == PJSIP_EVENT_RX_MSG &&
+	    tsx->role == PJSIP_ROLE_UAS)
+	{
+	    pjsip_tx_data *response;
+
+	    if (tsx->status_code >= 200)
+		return 0;
+
+	    if (tsx->method.id == PJSIP_BYE_METHOD) {
+		/* Set state to DISCONNECTED. */
+		dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
+
+		/* Notify application. */
+		dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+
+		response = pjsip_endpt_create_response(	dlg->ua->endpt, 
+							event->src.rdata, 200);
+	    } else {
+		response = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata,
+							PJSIP_SC_INTERNAL_SERVER_ERROR);
+	    }
+
+	    if (response)
+		pjsip_tsx_on_tx_msg(tsx, response);
+
+	    return 0;
+	}
+    }
+    return 0;
+}
+
+static int  dlg_on_state_established( pjsip_dlg *dlg, 
+				      pjsip_transaction *tsx,
+				      pjsip_event *event)
+{
+    PJ_UNUSED_ARG(tsx)
+
+    if (tsx && tsx->method.id == PJSIP_BYE_METHOD) {
+	/* Set state to DISCONNECTED. */
+	dlg_set_state(dlg, PJSIP_DIALOG_STATE_DISCONNECTED, event);
+
+	/* Notify application. */
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+
+	/* Answer with 200/BYE. */
+	if (event->src_type == PJSIP_EVENT_RX_MSG) {
+	    pjsip_tx_data *tdata;
+	    tdata = pjsip_endpt_create_response(dlg->ua->endpt,
+						event->src.rdata,
+						200 );
+	    if (tdata)
+		pjsip_tsx_on_tx_msg( tsx, tdata );
+	}
+    } else if (tsx && event->src_type == PJSIP_EVENT_RX_MSG) {
+	pjsip_method_e method = event->src.rdata->cseq->method.id;
+
+	PJ_TODO(PROPERLY_HANDLE_REINVITATION)
+
+	/* Reinvitation. The message may be INVITE or an ACK. */
+	if (method == PJSIP_INVITE_METHOD) {
+	    if (dlg->invite_tsx && dlg->invite_tsx->status_code < 200) {
+		/* Section 14.2: A UAS that receives a second INVITE before it 
+		* sends the final response to a first INVITE with a lower
+		* CSeq sequence number on the same dialog MUST return a 500 
+		* (Server Internal Error) response to the second INVITE and 
+		* MUST include a Retry-After header field with a randomly 
+		* chosen value of between 0 and 10 seconds.
+		*/
+		pjsip_retry_after_hdr *hdr;
+		pjsip_tx_data *tdata = 
+		    pjsip_endpt_create_response(dlg->ua->endpt, 
+						event->src.rdata, 500);
+
+		if (!tdata)
+		    return 0;
+
+		/* Add Retry-After. */
+		hdr = pjsip_retry_after_hdr_create(tdata->pool);
+		hdr->ivalue = 9;
+		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+
+		/* Send. */
+		pjsip_tsx_on_tx_msg(tsx, tdata);
+
+		return 0;
+	    }
+
+	    /* Keep this as our current INVITE transaction. */
+	    dlg->invite_tsx = tsx;
+
+	    /* Create response buffer. */
+	    tsx->last_tx = pjsip_endpt_create_response( dlg->ua->endpt, 
+							event->src.rdata, 100);
+	    pjsip_tx_data_add_ref(tsx->last_tx);
+
+	}
+
+	/* Notify application. */
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_MID_CALL_REQUEST, event);
+
+    }  else {
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
+    }
+
+    return 0;
+}
+
+static int  dlg_on_state_disconnected( pjsip_dlg *dlg, 
+				       pjsip_transaction *tsx,
+				       pjsip_event *event)
+{
+    PJ_UNUSED_ARG(tsx)
+
+    /* Handle case when transaction is started when dialog is disconnected
+     * (e.g. BYE requests cross wire.
+     */
+    if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
+	event->src_type == PJSIP_EVENT_RX_MSG &&
+	tsx->role == PJSIP_ROLE_UAS)
+    {
+	pjsip_tx_data *response = NULL;
+
+	if (tsx->status_code >= 200)
+	    return 0;
+
+	if (tsx->method.id == PJSIP_BYE_METHOD) {
+	    response = pjsip_endpt_create_response( dlg->ua->endpt, 
+						    event->src.rdata, 200);
+	} else {
+	    response = pjsip_endpt_create_response( dlg->ua->endpt, event->src.rdata, 
+						    PJSIP_SC_INTERNAL_SERVER_ERROR);
+	}
+	if (response)
+	    pjsip_tsx_on_tx_msg(tsx, response);
+
+	return 0;
+    } 
+    /* Handle case when outgoing BYE was rejected with 401/407 */
+    else if (event->type == PJSIP_EVENT_TSX_STATE_CHANGED &&
+	     event->src_type == PJSIP_EVENT_RX_MSG &&
+	     tsx->role == PJSIP_ROLE_UAC)
+    {
+	if (tsx->status_code==401 || tsx->status_code==407) {
+	    pjsip_tx_data *tdata;
+	    tdata = pjsip_auth_reinit_req( dlg->ua->endpt, dlg->pool,
+					   &dlg->auth_sess,
+					   dlg->cred_count, dlg->cred_info,
+					   tsx->last_tx, event->src.rdata);
+	    if (tdata) {
+		pjsip_dlg_send_msg(dlg, tdata);
+	    }
+	}
+    }
+	    
+
+    if (dlg->pending_tsx_count == 0) {
+	/* Set state to TERMINATED. */
+	dlg_set_state(dlg, PJSIP_DIALOG_STATE_TERMINATED, event);
+
+	/* Notify application. */
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_STATE_CHANGED, event);
+
+	return -1;
+    } else {
+	dlg_call_dlg_callback(dlg, PJSIP_DIALOG_EVENT_OTHER, event);
+    }
+
+    return 0;
+}
+
+static int  dlg_on_state_terminated( pjsip_dlg *dlg, 
+				     pjsip_transaction *tsx,
+				     pjsip_event *event)
+{
+    PJ_UNUSED_ARG(dlg)
+    PJ_UNUSED_ARG(tsx)
+    PJ_UNUSED_ARG(event)
+
+    return -1;
+}
+
Index: pjproject/main/pjsip/src/pjsip-ua/sip_reg.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-ua/sip_reg.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-ua/sip_reg.c	(revision 39)
@@ -0,0 +1,496 @@
+/* $Id$
+ *
+ */
+#include <pjsip_mod_ua/sip_reg.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_misc.h>
+#include <pjsip/sip_auth_msg.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+
+#define REFRESH_TIMER		1
+#define DELAY_BEFORE_REFRESH	5
+#define THIS_FILE		"sip_regc.c"
+
+/**
+ * SIP client registration structure.
+ */
+struct pjsip_regc
+{
+    pj_pool_t	        *pool;
+    pjsip_endpoint	*endpt;
+    pj_bool_t		 _delete_flag;
+    int			pending_tsx;
+
+    void		*token;
+    pjsip_regc_cb	*cb;
+
+    pj_str_t		str_srv_url;
+    pjsip_uri		*srv_url;
+    pjsip_cid_hdr	*cid_hdr;
+    pjsip_cseq_hdr	*cseq_hdr;
+    pjsip_from_hdr	*from_hdr;
+    pjsip_to_hdr	*to_hdr;
+    char		*contact_buf;
+    pjsip_generic_string_hdr	*contact_hdr;
+    pjsip_expires_hdr	*expires_hdr;
+    pjsip_contact_hdr	*unreg_contact_hdr;
+    pjsip_expires_hdr	*unreg_expires_hdr;
+    pj_uint32_t		 expires;
+
+    /* Credentials. */
+    int			 cred_count;
+    pjsip_cred_info     *cred_info;
+    
+    /* Authorization sessions. */
+    pjsip_auth_session	 auth_sess_list;
+
+    /* Auto refresh registration. */
+    pj_bool_t		 auto_reg;
+    pj_timer_entry	 timer;
+};
+
+
+
+PJ_DEF(pjsip_regc*) pjsip_regc_create( pjsip_endpoint *endpt, void *token,
+				       pjsip_regc_cb *cb)
+{
+    pj_pool_t *pool;
+    pjsip_regc *regc;
+
+    if (cb == NULL)
+	return NULL;
+
+    pool = pjsip_endpt_create_pool(endpt, "regc%p", 1024, 1024);
+    regc = pj_pool_calloc(pool, 1, sizeof(struct pjsip_regc));
+
+    regc->pool = pool;
+    regc->endpt = endpt;
+    regc->token = token;
+    regc->cb = cb;
+    regc->contact_buf = pj_pool_alloc(pool, PJSIP_REGC_CONTACT_BUF_SIZE);
+    regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED;
+
+    pj_list_init(&regc->auth_sess_list);
+
+    return regc;
+}
+
+
+PJ_DEF(void) pjsip_regc_destroy(pjsip_regc *regc)
+{
+    if (regc->pending_tsx) {
+	regc->_delete_flag = 1;
+	regc->cb = NULL;
+    } else {
+	pjsip_endpt_destroy_pool(regc->endpt, regc->pool);
+    }
+}
+
+
+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);
+	regc->expires_hdr->ivalue = expires;
+    } else {
+	regc->expires_hdr = NULL;
+    }
+}
+
+
+static pj_status_t set_contact( pjsip_regc *regc,
+			        int contact_cnt,
+				const pj_str_t contact[] )
+{
+    int i;
+    char *s;
+    const pj_str_t contact_STR = { "Contact", 7};
+
+    /* Concatenate contacts. */
+    for (i=0, s=regc->contact_buf; i<contact_cnt; ++i) {
+	if ((s-regc->contact_buf) + contact[i].slen + 2 > PJSIP_REGC_CONTACT_BUF_SIZE) {
+	    return -1;
+	}
+	pj_memcpy(s, contact[i].ptr, contact[i].slen);
+	s += contact[i].slen;
+
+	if (i != contact_cnt - 1) {
+	    *s++ = ',';
+	    *s++ = ' ';
+	}
+    }
+
+    /* Set "Contact" header. */
+    regc->contact_hdr = pjsip_generic_string_hdr_create( regc->pool, &contact_STR);
+    regc->contact_hdr->hvalue.ptr = regc->contact_buf;
+    regc->contact_hdr->hvalue.slen = (s - regc->contact_buf);
+
+    return 0;
+}
+
+
+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;
+
+    /* 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 -1;
+    }
+
+    /* Set "From" header. */
+    pj_strdup_with_null(regc->pool, &tmp, from_url);
+    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", from_url->slen, from_url->ptr));
+	return -1;
+    }
+
+    /* 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", to_url->slen, to_url->ptr));
+	return -1;
+    }
+
+
+    /* Set "Contact" header. */
+    if (set_contact( regc, contact_cnt, contact) != 0)
+	return -1;
+
+    /* Set "Expires" header, if required. */
+    set_expires( regc, expires);
+
+    /* 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 = 0;
+    pjsip_method_set( &regc->cseq_hdr->method, PJSIP_REGISTER_METHOD);
+
+    /* Create "Contact" header used in unregistration. */
+    regc->unreg_contact_hdr = pjsip_contact_hdr_create(regc->pool);
+    regc->unreg_contact_hdr->star = 1;
+
+    /* Create "Expires" header used in unregistration. */
+    regc->unreg_expires_hdr = pjsip_expires_hdr_create( regc->pool);
+    regc->unreg_expires_hdr->ivalue = 0;
+
+    /* Done. */
+    return 0;
+}
+
+PJ_DEF(pj_status_t) pjsip_regc_set_credentials( pjsip_regc *regc,
+						int count,
+						const pjsip_cred_info cred[] )
+{
+    if (count > 0) {
+	regc->cred_info = pj_pool_alloc(regc->pool, count * sizeof(pjsip_cred_info));
+	pj_memcpy(regc->cred_info, cred, count * sizeof(pjsip_cred_info));
+    }
+    regc->cred_count = count;
+    return 0;
+}
+
+static pjsip_tx_data *create_request(pjsip_regc *regc)
+{
+    pjsip_tx_data *tdata;
+    pjsip_msg *msg;
+
+    /* Create transmit data. */
+    tdata = pjsip_endpt_create_tdata(regc->endpt);
+    if (!tdata) {
+	return NULL;
+    }
+
+    /* Create request message. */
+    msg = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG);
+    tdata->msg = msg;
+
+    /* Initialize request line. */
+    pjsip_method_set(&msg->line.req.method, PJSIP_REGISTER_METHOD);
+    msg->line.req.uri = regc->srv_url;
+
+    /* Add headers. */
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->from_hdr);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->to_hdr);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->cid_hdr);
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->cseq_hdr);
+    
+    /* Add cached authorization headers. */
+    pjsip_auth_init_req( regc->pool, tdata, &regc->auth_sess_list,
+			 regc->cred_count, regc->cred_info );
+
+    /* Add reference counter to transmit data. */
+    pjsip_tx_data_add_ref(tdata);
+
+    return tdata;
+}
+
+
+PJ_DEF(pjsip_tx_data*) pjsip_regc_register(pjsip_regc *regc, pj_bool_t autoreg)
+{
+    pjsip_msg *msg;
+    pjsip_tx_data *tdata;
+
+    tdata = create_request(regc);
+    if (!tdata)
+	return NULL;
+
+    msg = tdata->msg;
+    pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->contact_hdr);
+    if (regc->expires_hdr)
+	pjsip_msg_add_hdr(msg, (pjsip_hdr*) regc->expires_hdr);
+
+    if (regc->timer.id != 0) {
+	pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
+	regc->timer.id = 0;
+    }
+
+    regc->auto_reg = autoreg;
+
+    return tdata;
+}
+
+
+PJ_DEF(pjsip_tx_data*) pjsip_regc_unregister(pjsip_regc *regc)
+{
+    pjsip_tx_data *tdata;
+    pjsip_msg *msg;
+
+    if (regc->timer.id != 0) {
+	pjsip_endpt_cancel_timer(regc->endpt, &regc->timer);
+	regc->timer.id = 0;
+    }
+
+    tdata = create_request(regc);
+    if (!tdata)
+	return NULL;
+
+    msg = tdata->msg;
+    pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_contact_hdr);
+    pjsip_msg_add_hdr( msg, (pjsip_hdr*)regc->unreg_expires_hdr);
+
+    return tdata;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_regc_update_contact(  pjsip_regc *regc,
+					        int contact_cnt,
+						const pj_str_t contact[] )
+{
+    return set_contact( regc, contact_cnt, contact );
+}
+
+
+PJ_DEF(pj_status_t) pjsip_regc_update_expires(  pjsip_regc *regc,
+					        pj_uint32_t expires )
+{
+    set_expires( regc, expires );
+    return 0;
+}
+
+
+static void call_callback(pjsip_regc *regc, int status, const pj_str_t *reason,
+			  pjsip_rx_data *rdata, pj_int32_t expiration,
+			  int contact_cnt, pjsip_contact_hdr *contact[])
+{
+    struct pjsip_regc_cbparam cbparam;
+
+
+    cbparam.regc = regc;
+    cbparam.token = regc->token;
+    cbparam.code = status;
+    cbparam.reason = *reason;
+    cbparam.rdata = rdata;
+    cbparam.contact_cnt = contact_cnt;
+    cbparam.expiration = expiration;
+    if (contact_cnt) {
+	pj_memcpy( cbparam.contact, contact, 
+		   contact_cnt*sizeof(pjsip_contact_hdr*));
+    }
+
+    (*regc->cb)(&cbparam);
+}
+
+static void regc_refresh_timer_cb( pj_timer_heap_t *timer_heap,
+				   struct pj_timer_entry *entry)
+{
+    pjsip_regc *regc = entry->user_data;
+    pjsip_tx_data *tdata;
+    
+    PJ_UNUSED_ARG(timer_heap)
+
+    entry->id = 0;
+    tdata = pjsip_regc_register(regc, 1);
+    if (tdata) {
+	pjsip_regc_send(regc, tdata);
+    } else {
+	pj_str_t reason = pj_str("Unable to create txdata");
+	call_callback(regc, -1, &reason, NULL, -1, 0, NULL);
+    }
+}
+
+static void tsx_callback(void *token, pjsip_event *event)
+{
+    pjsip_regc *regc = token;
+    pjsip_transaction *tsx = event->obj.tsx;
+    
+    /* If registration data has been deleted by user then remove registration 
+     * data from transaction's callback, and don't call callback.
+     */
+    if (regc->_delete_flag) {
+	--regc->pending_tsx;
+
+    } else if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED ||
+	       tsx->status_code == PJSIP_SC_UNAUTHORIZED)
+    {
+	pjsip_rx_data *rdata = event->src.rdata;
+	pjsip_tx_data *tdata;
+
+	tdata = pjsip_auth_reinit_req( regc->endpt,
+				       regc->pool, &regc->auth_sess_list,
+				       regc->cred_count, regc->cred_info,
+				       tsx->last_tx, event->src.rdata );
+
+	if (tdata) {
+	    --regc->pending_tsx;
+	    pjsip_regc_send(regc, tdata);
+	    return;
+	} else {
+	    call_callback(regc, tsx->status_code, &rdata->msg->line.status.reason,
+			  rdata, -1, 0, NULL);
+	    --regc->pending_tsx;
+	}
+    } else {
+	int contact_cnt = 0;
+	pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT];
+	pjsip_rx_data *rdata;
+	pj_int32_t expiration = 0xFFFF;
+
+	if (tsx->status_code/100 == 2) {
+	    int i;
+	    pjsip_contact_hdr *hdr;
+	    pjsip_msg *msg;
+	    pjsip_expires_hdr *expires;
+
+	    rdata = event->src.rdata;
+	    msg = rdata->msg;
+	    hdr = pjsip_msg_find_hdr( msg, PJSIP_H_CONTACT, NULL);
+	    while (hdr) {
+		contact[contact_cnt++] = hdr;
+		hdr = hdr->next;
+		if (hdr == (void*)&msg->hdr)
+		    break;
+		hdr = pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, hdr);
+	    }
+
+	    expires = pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+
+	    if (expires)
+		expiration = expires->ivalue;
+	    
+	    for (i=0; i<contact_cnt; ++i) {
+		hdr = contact[i];
+		if (hdr->expires >= 0 && hdr->expires < expiration)
+		    expiration = contact[i]->expires;
+	    }
+
+	    if (regc->auto_reg && expiration != 0 && expiration != 0xFFFF) {
+		pj_time_val delay = { 0, 0};
+
+		delay.sec = expiration - 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) 
+		    delay.sec = DELAY_BEFORE_REFRESH;
+		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);
+	    }
+
+	} else {
+	    rdata = (event->src_type==PJSIP_EVENT_RX_MSG) ? event->src.rdata : NULL;
+	}
+
+
+	/* Call callback. */
+	if (expiration == 0xFFFF) expiration = -1;
+	call_callback(regc, tsx->status_code, 
+		      (rdata ? &rdata->msg->line.status.reason 
+			: pjsip_get_status_text(tsx->status_code)),
+		      rdata, expiration, 
+		      contact_cnt, contact);
+
+	--regc->pending_tsx;
+    }
+
+    /* Delete the record if user destroy regc during the callback. */
+    if (regc->_delete_flag && regc->pending_tsx==0) {
+	pjsip_regc_destroy(regc);
+    }
+}
+
+PJ_DEF(void) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata)
+{
+    int status;
+
+    /* Make sure we don't have pending transaction. */
+    if (regc->pending_tsx) {
+	pj_str_t reason = pj_str("Transaction in progress");
+	call_callback(regc, -1, &reason, NULL, -1, 0, NULL);
+	pjsip_tx_data_dec_ref( tdata );
+	return;
+    }
+
+    /* Invalidate message buffer. */
+    pjsip_tx_data_invalidate_msg(tdata);
+
+    /* Increment CSeq */
+    regc->cseq_hdr->cseq++;
+
+    /* Send. */
+    status = pjsip_endpt_send_request(regc->endpt, tdata, -1, regc, &tsx_callback);
+    if (status==0)
+	++regc->pending_tsx;
+    else {
+	pj_str_t reason = pj_str("Unable to send request.");
+	call_callback(regc, status, &reason, NULL, -1, 0, NULL);
+    }
+}
+
+
Index: pjproject/main/pjsip/src/pjsip-ua/sip_ua.c
===================================================================
--- pjproject/main/pjsip/src/pjsip-ua/sip_ua.c	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-ua/sip_ua.c	(revision 39)
@@ -0,0 +1,491 @@
+/* $Id$
+ *
+ */
+#include <pjsip_mod_ua/sip_ua.h>
+#include <pjsip_mod_ua/sip_dialog.h>
+#include <pjsip_mod_ua/sip_ua_private.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_misc.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_transaction.h>
+#include <pj/list.h>
+#include <pj/log.h>
+#include <pj/string.h>
+#include <pj/guid.h>
+#include <pj/os.h>
+#include <pj/hash.h>
+#include <pj/pool.h>
+
+#define PJSIP_POOL_LEN_USER_AGENT   1024
+#define PJSIP_POOL_INC_USER_AGENT   0
+
+
+#define LOG_THIS    "useragent.."
+
+/*
+ * Static prototypes.
+ */
+static pj_status_t ua_init( pjsip_endpoint *endpt,
+			    struct pjsip_module *mod, pj_uint32_t id );
+static pj_status_t ua_start( struct pjsip_module *mod );
+static pj_status_t ua_deinit( struct pjsip_module *mod );
+static void ua_tsx_handler( struct pjsip_module *mod, pjsip_event *evt );
+static pjsip_dlg *find_dialog( pjsip_user_agent *ua,
+			       pjsip_rx_data *rdata );
+static pj_status_t ua_register_dialog( pjsip_user_agent *ua, pjsip_dlg *dlg,
+				       pj_str_t *key );
+PJ_DECL(void) pjsip_on_dialog_destroyed( pjsip_dlg *dlg );
+
+/*
+ * Default UA instance.
+ */
+static pjsip_user_agent ua_instance;
+
+/*
+ * Module interface.
+ */
+static struct pjsip_module mod_ua = 
+{
+    { "User-Agent", 10 },   /* Name.		*/
+    0,			    /* Flag		*/
+    128,		    /* Priority		*/
+    NULL,		    /* User agent instance, initialized by APP.	*/
+    0,			    /* Number of methods supported (will be initialized later). */
+    { 0 },		    /* Array of methods (will be initialized later) */
+    &ua_init,		    /* init_module()	*/
+    &ua_start,		    /* start_module()	*/
+    &ua_deinit,		    /* deinit_module()	*/
+    &ua_tsx_handler,	    /* tsx_handler()	*/
+};
+
+/*
+ * Initialize user agent instance.
+ */
+static pj_status_t ua_init( pjsip_endpoint *endpt,
+			    struct pjsip_module *mod, pj_uint32_t id )
+{
+    static pjsip_method m_invite, m_ack, m_cancel, m_bye;
+    pjsip_user_agent *ua = mod->mod_data;
+    extern int pjsip_dlg_lock_tls_id;	/* defined in sip_dialog.c */
+
+    pjsip_method_set( &m_invite, PJSIP_INVITE_METHOD );
+    pjsip_method_set( &m_ack, PJSIP_ACK_METHOD );
+    pjsip_method_set( &m_cancel, PJSIP_CANCEL_METHOD );
+    pjsip_method_set( &m_bye, PJSIP_BYE_METHOD );
+
+    mod->method_cnt = 4;
+    mod->methods[0] = &m_invite;
+    mod->methods[1] = &m_ack;
+    mod->methods[2] = &m_cancel;
+    mod->methods[3] = &m_bye;
+
+    /* Initialize the user agent. */
+    ua->endpt = endpt;
+    ua->pool = pjsip_endpt_create_pool(endpt, "pua%p", PJSIP_POOL_LEN_UA, 
+				       PJSIP_POOL_INC_UA);
+    if (!ua->pool) {
+	return -1;
+    }
+    ua->mod_id = id;
+    ua->mutex = pj_mutex_create(ua->pool, " ua%p", 0);
+    if (!ua->mutex) {
+	return -1;
+    }
+    ua->dlg_table = pj_hash_create(ua->pool, PJSIP_MAX_DIALOG_COUNT);
+    if (ua->dlg_table == NULL) {
+	return -1;
+    }
+    pj_list_init(&ua->dlg_list);
+
+    /* Initialize dialog lock. */
+    pjsip_dlg_lock_tls_id = pj_thread_local_alloc();
+    if (pjsip_dlg_lock_tls_id == -1) {
+	return -1;
+    }
+    pj_thread_local_set(pjsip_dlg_lock_tls_id, NULL);
+
+    return 0;
+}
+
+/*
+ * Start user agent instance.
+ */
+static pj_status_t ua_start( struct pjsip_module *mod )
+{
+    PJ_UNUSED_ARG(mod)
+    return 0;
+}
+
+/*
+ * Destroy user agent.
+ */
+static pj_status_t ua_deinit( struct pjsip_module *mod )
+{
+    pjsip_user_agent *ua = mod->mod_data;
+
+    pj_mutex_unlock(ua->mutex);
+
+    /* Release pool */
+    if (ua->pool) {
+	pjsip_endpt_destroy_pool( ua->endpt, ua->pool );
+    }
+    return 0;
+}
+
+/*
+ * Get the module interface for the UA module.
+ */
+PJ_DEF(pjsip_module*) pjsip_ua_get_module(void)
+{
+    mod_ua.mod_data = &ua_instance;
+    return &mod_ua;
+}
+
+/*
+ * Register callback to receive dialog notifications.
+ */
+PJ_DEF(void) pjsip_ua_set_dialog_callback( pjsip_user_agent *ua, 
+					   pjsip_dlg_callback *cb )
+{
+    ua->dlg_cb = cb;
+}
+
+/* 
+ * Find dialog.
+ * This function is called for a new transactions, which a dialog hasn't been
+ * 'attached' to the transaction.
+ */
+static pjsip_dlg *find_dialog( pjsip_user_agent *ua, pjsip_rx_data *rdata )
+{
+    pjsip_dlg *dlg;
+    pj_str_t *tag;
+
+    /* Non-CANCEL requests/response can be found by looking at the tag in the
+     * hash table. CANCEL requests don't have tags, so instead we'll try to
+     * find the UAS INVITE transaction in endpoint's hash table
+     */
+    if (rdata->cseq->method.id == PJSIP_CANCEL_METHOD) {
+
+	/* Create key for the rdata, but this time, use INVITE as the
+	 * method.
+	 */
+	pj_str_t key;
+	pjsip_role_e role;
+	pjsip_method invite_method;
+	pjsip_transaction *invite_tsx;
+
+	if (rdata->msg->type == PJSIP_REQUEST_MSG) {
+	    role = PJSIP_ROLE_UAS;
+	} else {
+	    role = PJSIP_ROLE_UAC;
+	}
+	pjsip_method_set(&invite_method, PJSIP_INVITE_METHOD);
+	pjsip_tsx_create_key(rdata->pool, &key, role, &invite_method, rdata);
+
+	/* Lookup the INVITE transaction */
+	invite_tsx = pjsip_endpt_find_tsx(ua->endpt, &key);
+
+	/* We should find the dialog attached to the INVITE transaction */
+	return invite_tsx ? 
+	    (pjsip_dlg*) invite_tsx->module_data[ua->mod_id] : NULL;
+
+    } else {
+	if (rdata->msg->type == PJSIP_REQUEST_MSG) {
+	    tag = &rdata->to_tag;
+	} else {
+	    tag = &rdata->from_tag;
+	}
+	/* Find the dialog in UA hash table */
+	pj_mutex_lock(ua->mutex);
+	dlg = pj_hash_get( ua->dlg_table, tag->ptr, tag->slen );
+	pj_mutex_unlock(ua->mutex);
+    }
+    
+    return dlg;
+}
+
+/*
+ * This function receives event notification from transactions. It is called by
+ * endpoint.
+ */
+static void ua_tsx_handler( struct pjsip_module *mod, pjsip_event *event )
+{
+    pjsip_user_agent *ua = mod->mod_data;
+    pjsip_dlg *dlg = NULL;
+    pjsip_transaction *tsx = event->obj.tsx;
+
+    PJ_LOG(5, (LOG_THIS, "ua_tsx_handler(tsx=%s, evt=%s, src=%s, data=%p)", 
+	       (tsx ? tsx->obj_name : "NULL"),  pjsip_event_str(event->type), 
+	       pjsip_event_str(event->src_type), event->src.data));
+
+    /* Special case to handle ACK which doesn't match any INVITE transactions. */
+    if (event->type == PJSIP_EVENT_RX_ACK_MSG) {
+	/* Find the dialog based on the "tag". */
+	dlg = find_dialog( ua, event->src.rdata );
+
+	/* We should be able to find it. */
+	if (!dlg) {
+	    PJ_LOG(4,(LOG_THIS, "Unable to find dialog for incoming ACK"));
+	    return;
+	}
+
+	/* Match CSeq with pending INVITE in dialog. */
+	if (dlg->invite_tsx && dlg->invite_tsx->cseq==event->src.rdata->cseq->cseq) {
+	    /* A match found. */
+	    tsx = dlg->invite_tsx;
+
+	    /* Pass the event to transaction if transaction handles ACK. */
+	    if (tsx->handle_ack) {
+		PJ_LOG(4,(LOG_THIS, "Re-routing strandled ACK to transaction"));
+		pjsip_tsx_on_rx_msg(tsx, event->src.rdata);
+		return;
+	    }
+	} else {
+	    tsx = NULL;
+	    PJ_LOG(4,(LOG_THIS, "Unable to find INVITE tsx for incoming ACK"));
+	    return;
+	}
+    }
+
+    /* For discard event, transaction is NULL. */
+    if (tsx == NULL) {
+	return;
+    }
+
+    /* Try to pickup the dlg from the transaction. */
+    dlg = (pjsip_dlg*) tsx->module_data[ua->mod_id];
+
+    if (dlg != NULL) {
+
+	/* Nothing to do now. */
+
+    } else if (event->src_type == PJSIP_EVENT_RX_MSG)  {
+
+	/* This must be a new UAS transaction. */
+
+	/* Finds dlg that can handle this transaction. */
+	dlg = find_dialog( ua, event->src.rdata);
+
+	/* Create a new dlg if there's no existing dlg that can handle 
+	   the request, ONLY if the incoming message is an INVITE request.
+	 */
+	if (dlg==NULL && event->src.rdata->msg->type == PJSIP_REQUEST_MSG) {
+
+	    if (event->src.rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD) {
+		/* Create new dialog. */
+		dlg = pjsip_ua_create_dialog( ua, PJSIP_ROLE_UAS );
+
+		if (dlg == NULL ||
+		    pjsip_dlg_init_from_rdata( dlg, event->src.rdata) != 0) 
+		{
+		    pjsip_tx_data *tdata;
+
+		    /* Dialog initialization has failed. Respond request with 500 */
+		    if (dlg) {
+			pjsip_ua_destroy_dialog(dlg);
+		    }
+		    tdata = pjsip_endpt_create_response(ua->endpt, event->src.rdata, 
+							PJSIP_SC_INTERNAL_SERVER_ERROR);
+		    if (tdata) {
+			pjsip_tsx_on_tx_msg( event->obj.tsx, tdata );
+		    }
+		    return;
+		}
+
+	    } else {
+		pjsip_tx_data *tdata;
+
+		/* Check the method */
+		switch (tsx->method.id) {
+		case PJSIP_INVITE_METHOD:
+		case PJSIP_ACK_METHOD:
+		case PJSIP_BYE_METHOD:
+		case PJSIP_CANCEL_METHOD:
+		    /* Stale non-INVITE request.
+		     * For now, respond all stale requests with 481 (?).
+		     */
+		    tdata = pjsip_endpt_create_response(ua->endpt, event->src.rdata, 
+							PJSIP_SC_CALL_TSX_DOES_NOT_EXIST);
+		    if (tdata) {
+			pjsip_tsx_on_tx_msg( event->obj.tsx, tdata );
+		    }
+		    break;
+		}
+
+		return;
+	    }
+	} else {
+	    /* Check the method */
+	    switch (tsx->method.id) {
+	    case PJSIP_INVITE_METHOD:
+	    case PJSIP_ACK_METHOD:
+	    case PJSIP_BYE_METHOD:
+	    case PJSIP_CANCEL_METHOD:
+		/* These methods belongs to dialog.
+		 * If we receive these methods while no dialog is found, 
+		 * then it must be a stale responses.
+		 */
+		break;
+	    default:
+		return;
+	    }
+
+	}
+	
+	if (dlg == NULL) {
+	    PJ_LOG(3, (LOG_THIS, "Receives spurious rdata %p from %s:%d",
+		       event->src.rdata, 
+		       pj_sockaddr_get_str_addr(&event->src.rdata->addr),
+		       pj_sockaddr_get_port(&event->src.rdata->addr)));
+	}
+
+	/* Set the dlg in the transaction (dlg can be NULL). */
+	tsx->module_data[ua->mod_id] = dlg;
+
+    } else {
+	/* This CAN happen with event->src_type == PJSIP_EVENT_TX_MSG
+	 * if UAS is responding to a transaction which does not exist. 
+	 * Just ignore.
+	 */
+	return;
+    }
+
+    /* Pass the event to the dlg. */
+    if (dlg) {
+	pjsip_dlg_on_tsx_event(dlg, tsx, event);
+    }
+}
+
+/*
+ * Register dialog to UA.
+ */
+static pj_status_t ua_register_dialog( pjsip_user_agent *ua, pjsip_dlg *dlg,
+				       pj_str_t *key )
+{
+    /* Assure that no entry with similar key exists in the hash table. */
+    pj_assert( pj_hash_get( ua->dlg_table, key->ptr, key->slen) == 0);
+
+    /* Insert entry to hash table. */
+    pj_hash_set( dlg->pool, ua->dlg_table, 
+		 key->ptr, key->slen, dlg);
+
+    /* Insert to the list. */
+    pj_list_insert_before(&ua->dlg_list, dlg);
+    return PJ_SUCCESS;
+}
+
+/*
+ * Create a new dialog.
+ */
+PJ_DEF(pjsip_dlg*) pjsip_ua_create_dialog( pjsip_user_agent *ua,
+					      pjsip_role_e role )
+{
+    pj_pool_t *pool;
+    pjsip_dlg *dlg;
+
+    PJ_UNUSED_ARG(ua)
+
+    /* Create pool for the dialog. */
+    pool = pjsip_endpt_create_pool( ua->endpt, "pdlg%p",
+				    PJSIP_POOL_LEN_DIALOG,
+				    PJSIP_POOL_INC_DIALOG);
+
+    /* Create the dialog. */
+    dlg = pj_pool_calloc(pool, 1, sizeof(pjsip_dlg));
+    dlg->pool = pool;
+    dlg->ua = ua;
+    dlg->role = role;
+    sprintf(dlg->obj_name, "dlg%p", dlg);
+
+    /* Create mutex for the dialog. */
+    dlg->mutex = pj_mutex_create(dlg->pool, "mdlg%p", 0);
+    if (!dlg->mutex) {
+	pjsip_endpt_destroy_pool(ua->endpt, pool);
+	return NULL;
+    }
+
+    /* Create unique tag for the dialog. */
+    pj_create_unique_string( pool, &dlg->local.tag );
+
+    /* Register dialog. */
+    pj_mutex_lock(ua->mutex);
+    if (ua_register_dialog(ua, dlg, &dlg->local.tag) != PJ_SUCCESS) {
+	pj_mutex_unlock(ua->mutex);
+	pj_mutex_destroy(dlg->mutex);
+	pjsip_endpt_destroy_pool( ua->endpt, pool );
+	return NULL;
+    }
+    pj_mutex_unlock(ua->mutex);
+
+    PJ_LOG(4, (dlg->obj_name, "new %s dialog created", pjsip_role_name(role)));
+    return dlg;
+}
+
+/*
+ * Destroy dialog.
+ */
+PJ_DEF(void) pjsip_ua_destroy_dialog( pjsip_dlg *dlg )
+{
+    PJ_LOG(5, (dlg->obj_name, "destroying.."));
+
+    /* Lock dialog's mutex. 
+     * Check the mutex validity first since this function can be called
+     * on dialog initialization failure (which might be because mutex could not
+     * be allocated in the first place).
+     */
+    if (dlg->mutex) {
+	pj_mutex_lock(dlg->mutex);
+    }
+
+    /* This must be called while holding dialog's mutex, if any. */
+    pjsip_on_dialog_destroyed(dlg);
+
+    /* Lock UA. */
+    pj_mutex_lock(dlg->ua->mutex);
+
+    /* Erase from hash table. */
+    pj_hash_set( dlg->pool, dlg->ua->dlg_table,
+		 dlg->local.tag.ptr, dlg->local.tag.slen, NULL);
+
+    /* Erase from the list. */
+    pj_list_erase(dlg);
+
+    /* Unlock UA. */
+    pj_mutex_unlock(dlg->ua->mutex);
+
+    /* Unlock mutex. */
+    if (dlg->mutex) {
+	pj_mutex_unlock(dlg->mutex);
+    }
+
+    /* Destroy the pool. */
+    pjsip_endpt_destroy_pool( dlg->ua->endpt, dlg->pool);
+}
+
+/*
+ * Dump user agent state to log file.
+ */
+PJ_DEF(void) pjsip_ua_dump(pjsip_user_agent *ua)
+{
+#if PJ_LOG_MAX_LEVEL >= 3
+    PJ_LOG(3,(LOG_THIS, "Dumping user agent"));
+    PJ_LOG(3,(LOG_THIS, "  Pool capacity=%u, used=%u", 
+			pj_pool_get_capacity(ua->pool),
+			pj_pool_get_used_size(ua->pool)));
+    PJ_LOG(3,(LOG_THIS, "  Number of dialogs=%u", pj_hash_count(ua->dlg_table)));
+
+    if (pj_hash_count(ua->dlg_table)) {
+	pjsip_dlg *dlg;
+
+	PJ_LOG(3,(LOG_THIS, "  Dumping dialog list:"));
+	dlg = ua->dlg_list.next;
+	while (dlg != (pjsip_dlg*) &ua->dlg_list) {
+	    PJ_LOG(3, (LOG_THIS, "    %s %s", dlg->obj_name, 
+		       pjsip_dlg_state_str(dlg->state)));
+	    dlg = dlg->next;
+	}
+    }
+#endif
+}
+
Index: pjproject/main/pjsip/src/pjsip-ua/sip_ua_private.h
===================================================================
--- pjproject/main/pjsip/src/pjsip-ua/sip_ua_private.h	(revision 39)
+++ pjproject/main/pjsip/src/pjsip-ua/sip_ua_private.h	(revision 39)
@@ -0,0 +1,22 @@
+/* $Id$
+ *
+ */
+
+#ifndef __PJSIP_UA_PRIVATE_H__
+#define __PJSIP_UA_PRIVATE_H__
+
+
+/*
+ * Internal dialog functions.
+ */
+pj_status_t pjsip_dlg_init_from_rdata( pjsip_dlg *dlg,
+				       pjsip_rx_data *rdata );
+
+
+void pjsip_dlg_on_tsx_event( pjsip_dlg *dlg, 
+			     pjsip_transaction *tsx, 
+			     pjsip_event *event);
+
+
+#endif	/* __PJSIP_UA_PRIVATE_H__ */
+
Index: pjproject/main/pjsip/src/pjsip_auth.h
===================================================================
--- pjproject/main/pjsip/src/pjsip_auth.h	(revision 38)
+++ 	(revision )
@@ -1,22 +1,0 @@
-/* $Id$
- *
- */
-#ifndef __PJSIP_AUTH_H__
-#define __PJSIP_AUTH_H__
-
-/**
- * @defgroup PJSIP_AUTH SIP Authorization module
- */
-
-/**
- * @file pjsip_auth.h
- * @brief SIP Authorization Module.
- */
-
-
-#include <pjsip_auth/sip_auth.h>
-#include <pjsip_auth/sip_auth_msg.h>
-#include <pjsip_auth/sip_auth_parser.h>
-
-#endif	/* __PJSIP_AUTH_H__ */
-
Index: pjproject/main/pjsip/src/pjsip_core.h
===================================================================
--- pjproject/main/pjsip/src/pjsip_core.h	(revision 38)
+++ 	(revision )
@@ -1,21 +1,0 @@
-/* $Id$
- *
- */
-#ifndef __PJSIP_CORE_H__
-#define __PJSIP_CORE_H__
-
-#include <pjsip/sip_types.h>
-#include <pjsip/sip_endpoint.h>
-#include <pjsip/sip_event.h>
-#include <pjsip/sip_misc.h>
-#include <pjsip/sip_module.h>
-#include <pjsip/sip_msg.h>
-#include <pjsip/sip_parser.h>
-#include <pjsip/sip_resolve.h>
-#include <pjsip/sip_transaction.h>
-#include <pjsip/sip_transport.h>
-#include <pjsip/sip_uri.h>
-#include <pjsip/sip_auth.h>
-
-#endif	/* __PJSIP_CORE_H__ */
-
Index: pjproject/main/pjsip/src/pjsip_simple.h
===================================================================
--- pjproject/main/pjsip/src/pjsip_simple.h	(revision 38)
+++ 	(revision )
@@ -1,26 +1,0 @@
-/* $Id$
- *
- */
-
-/**
- * @defgroup PJSIP_SIMPLE SIP Event, Instant Messaging and Presence Extension (SIMPLE)
- */
-
-/**
- * @file pjsip_simple.h
- * @brief SIP SIMPLE Extension
- */
-
-/*
- * Include this header file to get all functionalities for SIMPLE extension
- * (SIP for Instant Messaging and Presence Leveraging Extension).
- */
-#ifndef __PJSIP_SIMPLE_H__
-#define __PJSIP_SIMPLE_H__
-
-#include <pjsip_simple/messaging.h>
-#include <pjsip_simple/event_notify.h>
-#include <pjsip_simple/pidf.h>
-#include <pjsip_simple/presence.h>
-
-#endif	/* __PJSIP_SIMPLE_H__ */
Index: pjproject/main/pjsip/src/pjsip_ua.h
===================================================================
--- pjproject/main/pjsip/src/pjsip_ua.h	(revision 38)
+++ 	(revision )
@@ -1,13 +1,0 @@
-/* $Id$
- *
- */
-
-#ifndef __PJSIP_UA_H__
-#define __PJSIP_UA_H__
-
-#include <pjsip_mod_ua/sip_dialog.h>
-#include <pjsip_mod_ua/sip_reg.h>
-#include <pjsip_mod_ua/sip_ua.h>
-
-#endif	/* __PJSIP_UA_H__ */
-
