initial import

git-svn-id: https://svn.pjsip.org/repos/pjproject/main@1 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/src/pjsip_simple/event_notify.c b/pjsip/src/pjsip_simple/event_notify.c
new file mode 100644
index 0000000..6fd53d9
--- /dev/null
+++ b/pjsip/src/pjsip_simple/event_notify.c
@@ -0,0 +1,1627 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/event_notify.c 11    8/31/05 9:05p Bennylp $ */

+#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 );

+    }

+}

+

diff --git a/pjsip/src/pjsip_simple/event_notify.h b/pjsip/src/pjsip_simple/event_notify.h
new file mode 100644
index 0000000..bdc909d
--- /dev/null
+++ b/pjsip/src/pjsip_simple/event_notify.h
@@ -0,0 +1,313 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/event_notify.h 7     8/31/05 9:05p Bennylp $ */

+#ifndef __PJSIP_SIMPLE_EVENT_NOTIFY_H__

+#define __PJSIP_SIMPLE_EVENT_NOTIFY_H__

+

+/**

+ * @file event_notify.h

+ * @brief SIP Specific Event Notification Extension (RFC 3265)

+ */

+

+#include <pjsip/sip_types.h>

+#include <pjsip/sip_auth.h>

+#include <pjsip_simple/event_notify_msg.h>

+#include <pj/timer.h>

+

+/**

+ * @defgroup PJSIP_EVENT_NOT SIP Event Notification (RFC 3265) Module

+ * @ingroup PJSIP_SIMPLE

+ * @{

+ *

+ * This module provides the implementation of SIP Extension for SIP Specific

+ * Event Notification (RFC 3265). It extends PJSIP by supporting SUBSCRIBE and

+ * NOTIFY methods.

+ *

+ * This module itself is extensible; new event packages can be registered to

+ * this module to handle specific extensions (such as presence).

+ */

+

+PJ_BEGIN_DECL

+

+typedef struct pjsip_event_sub_cb pjsip_event_sub_cb;

+typedef struct pjsip_event_sub pjsip_event_sub;

+

+/**

+ * This enumeration describes subscription state as described in the RFC 3265.

+ * The standard specifies that extensions may define additional states. In the

+ * case where the state is not known, the subscription state will be set to

+ * PJSIP_EVENT_SUB_STATE_UNKNOWN, and the token will be kept in state_str

+ * member of the susbcription structure.

+ */

+typedef enum pjsip_event_sub_state

+{

+    /** State is NULL. */

+    PJSIP_EVENT_SUB_STATE_NULL,

+

+    /** Subscription is active. */

+    PJSIP_EVENT_SUB_STATE_ACTIVE,

+

+    /** Subscription is pending. */

+    PJSIP_EVENT_SUB_STATE_PENDING,

+

+    /** Subscription is terminated. */

+    PJSIP_EVENT_SUB_STATE_TERMINATED,

+

+    /** Subscription state can not be determined. Application can query

+     *  the state information in state_str member.

+     */

+    PJSIP_EVENT_SUB_STATE_UNKNOWN,

+

+} pjsip_event_sub_state;

+

+/**

+ * This structure describes notification to be called when incoming SUBSCRIBE

+ * request is received. The module will call the callback registered by package

+ * that matches the event description in the incoming SUBSCRIBE.

+ */

+typedef struct pjsip_event_sub_pkg_cb

+{

+    /**

+     * This callback is called to first enquery the package whether it wants

+     * to accept incoming SUBSCRIBE request. If it does, then on_subscribe

+     * will be called.

+     *

+     * @param rdata	The incoming request.

+     * @param status	The status code to be returned back to subscriber.

+     */

+    void (*on_query_subscribe)(pjsip_rx_data *rdata, int *status);

+

+    /**

+     * This callback is called when the module receives incoming SUBSCRIBE

+     * request.

+     *

+     * @param sub	The subscription instance.

+     * @param rdata	The received buffer.

+     * @param cb	Callback to be registered to the subscription instance.

+     * @param expires	The expiration to be set.

+     */

+    void (*on_subscribe)(pjsip_event_sub *sub, pjsip_rx_data *rdata,

+			 pjsip_event_sub_cb **cb, int *expires);

+

+} pjsip_event_sub_pkg_cb;

+

+/**

+ * This structure describes callback that is registered by application or

+ * package to receive notifications about a subscription.

+ */

+struct pjsip_event_sub_cb

+{

+    /**

+     * This callback is used by both subscriber and notifier. It is called 

+     * when the subscription has been terminated.

+     *

+     * @param sub	The subscription instance.

+     * @param reason	The termination reason.

+     */

+    void (*on_sub_terminated)(pjsip_event_sub *sub, const pj_str_t *reason);

+

+    /**

+     * This callback is called when we received SUBSCRIBE request to refresh

+     * the subscription.

+     *

+     * @param sub	The subscription instance.

+     * @param rdata	The received SUBSCRIBE request.

+     */

+    void (*on_received_refresh)(pjsip_event_sub *sub, pjsip_rx_data *rdata);

+

+    /**

+     * This callback is called when the module receives final response on

+     * previously sent SUBSCRIBE request.

+     *

+     * @param sub	The subscription instance.

+     * @param event	The event.

+     */

+    void (*on_received_sub_response)(pjsip_event_sub *sub, pjsip_event *event);

+

+    /**

+     * This callback is called when the module receives incoming NOTIFY

+     * request.

+     *

+     * @param sub	The subscription instance.

+     * @param rdata	The received data.

+     */

+    void (*on_received_notify)(pjsip_event_sub *sub, pjsip_rx_data *rdata);

+

+    /**

+     * This callback is called when the module receives final response to

+     * previously sent NOTIFY request.

+     *

+     * @param sub	The subscription instance.

+     * @param event	The event.

+     */

+    void (*on_received_notify_response)(pjsip_event_sub *sub, pjsip_event *event);

+

+};

+

+/**

+ * This structure describes an event subscription record. The structure is used

+ * to represent both subscriber and notifier.

+ */

+struct pjsip_event_sub

+{

+    pj_pool_t		*pool;		    /**< Pool. */

+    pjsip_endpoint	*endpt;		    /**< Endpoint. */

+    pjsip_event_sub_cb	 cb;		    /**< Callback. */

+    pj_mutex_t		*mutex;		    /**< Mutex. */

+    pjsip_role_e	 role;		    /**< Role (UAC=subscriber, UAS=notifier) */

+    pjsip_event_sub_state state;	    /**< Subscription state. */

+    pj_str_t		 state_str;	    /**< String describing the state. */

+    pjsip_from_hdr	*from;		    /**< Cached local info (From) */

+    pjsip_to_hdr	*to;		    /**< Cached remote info (To) */

+    pjsip_contact_hdr	*contact;	    /**< Cached local contact. */

+    pjsip_cid_hdr	*call_id;	    /**< Cached Call-ID */

+    int			 cseq;		    /**< Outgoing CSeq */

+    pjsip_event_hdr	*event;		    /**< Event description. */

+    pjsip_expires_hdr	*uac_expires;	    /**< Cached Expires header (UAC only). */

+    pjsip_accept_hdr	*local_accept;	    /**< Local Accept header. */

+    pjsip_route_hdr	 route_set;	    /**< Route-set. */

+

+    pj_str_t		 key;		    /**< Key in the hash table. */

+    void		*user_data;	    /**< Application data. */

+    int			 default_interval;  /**< Refresh interval. */

+    pj_timer_entry	 timer;		    /**< Internal timer. */

+    pj_time_val		 expiry_time;	    /**< Time when subscription expires. */

+    int			 pending_tsx;	    /**< Number of pending transactions. */

+    pj_bool_t		 delete_flag;	    /**< Pending deletion flag. */

+

+    pjsip_auth_session	 auth_sess;	    /**< Authorization sessions.	*/

+    unsigned		 cred_cnt;	    /**< Number of credentials.		*/

+    pjsip_cred_info	*cred_info;	    /**< Array of credentials.		*/

+};

+

+

+

+

+/**

+ * Initialize the module and get the instance of the module to be registered to

+ * endpoint.

+ *

+ * @return		The module instance.

+ */

+PJ_DECL(pjsip_module*) pjsip_event_sub_get_module(void);

+

+

+/**

+ * Register event package.

+ *

+ * @param event		The event identification for the package.

+ * @param accept_cnt	Number of strings in Accept array.

+ * @param accept	Array of Accept value.

+ * @param cb		Callback to receive incoming SUBSCRIBE for the package.

+ *

+ * @return		Zero on success.

+ */

+PJ_DECL(pj_status_t) pjsip_event_sub_register_pkg( const pj_str_t *event_name,

+						   int accept_cnt,

+						   const pj_str_t accept[],

+						   const pjsip_event_sub_pkg_cb *cb );

+

+

+/**

+ * Create initial subscription instance (client).

+ *

+ * @param endpt		The endpoint.

+ * @param from		URL to put in From header.

+ * @param to		The target resource.

+ * @param event		Event package.

+ * @param expires	Expiration time.

+ * @param accept	Accept specification.

+ * @param user_data	Application data to attach to this subscription.

+ *

+ * @return		New client subscription instance.

+ */

+PJ_DECL(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);

+

+/**

+ * Set credentials to be used for outgoing request messages.

+ *

+ * @param sub		Subscription instance.

+ * @param count		Number of credentials.

+ * @param cred		Array of credential info.

+ *

+ * @return		Zero on success.

+ */

+PJ_DECL(pj_status_t) pjsip_event_sub_set_credentials( pjsip_event_sub *sub,

+						      int count,

+						      const pjsip_cred_info cred[]);

+

+/**

+ * Set route set for outgoing requests.

+ *

+ * @param sub		Subscription instance.

+ * @param route_set	List of route headers.

+ *

+ * @return		Zero on success.

+ */

+PJ_DECL(pj_status_t) pjsip_event_sub_set_route_set( pjsip_event_sub *sub,

+						    const pjsip_route_hdr *route_set );

+

+

+/**

+ * Send SUBSCRIBE request.

+ *

+ * @param sub		Subscription instance.

+ *

+ * @return		Zero on success.

+ */

+PJ_DECL(pj_status_t) pjsip_event_sub_subscribe( pjsip_event_sub *sub );

+

+/**

+ * Terminate subscription (client). This will send unsubscription request to

+ * notifier.

+ *

+ * @param sub		Client subscription instance.

+ *

+ * @return		Zero on success.

+ */

+PJ_DECL(pj_status_t) pjsip_event_sub_unsubscribe( pjsip_event_sub *sub );

+

+

+/**

+ * For notifier, send NOTIFY request to subscriber, and set the state of

+ * the subscription.

+ *

+ * @param sub		The server subscription (notifier) instance.

+ * @param state		New state to set.

+ * @param reason	Specify reason if new state is terminated, otherwise

+ *			put NULL.

+ * @param type		Description of content type.

+ * @param body		Text body to send with the NOTIFY, or NULL if the

+ *			NOTIFY request should not contain any message body.

+ *

+ * @return		Zero on success.

+ */

+PJ_DECL(pj_status_t) pjsip_event_sub_notify( pjsip_event_sub *sub,

+					     pjsip_event_sub_state state,

+					     const pj_str_t *reason,

+					     pjsip_msg_body *body);

+

+/**

+ * Destroy subscription instance.

+ *

+ * @param sub		The client or server subscription instance.

+ *

+ * @return		Zero on success, one if the subscription will be

+ *			deleted automatically later, or -1 on error.

+ */

+PJ_DECL(pj_status_t) pjsip_event_sub_destroy(pjsip_event_sub *sub);

+

+

+PJ_END_DECL

+

+/**

+ * @}

+ */

+

+#endif	/* __PJSIP_SIMPLE_EVENT_NOTIFY_H__ */

diff --git a/pjsip/src/pjsip_simple/event_notify_msg.c b/pjsip/src/pjsip_simple/event_notify_msg.c
new file mode 100644
index 0000000..6e7136d
--- /dev/null
+++ b/pjsip/src/pjsip_simple/event_notify_msg.c
@@ -0,0 +1,305 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/event_notify_msg.c 2     6/21/05 12:37a Bennylp $ */

+#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);

+}

diff --git a/pjsip/src/pjsip_simple/event_notify_msg.h b/pjsip/src/pjsip_simple/event_notify_msg.h
new file mode 100644
index 0000000..c3f7356
--- /dev/null
+++ b/pjsip/src/pjsip_simple/event_notify_msg.h
@@ -0,0 +1,100 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/event_notify_msg.h 4     8/24/05 10:33a Bennylp $ */

+#ifndef __PJSIP_SIMPLE_EVENT_NOTIFY_MSG_H__

+#define __PJSIP_SIMPLE_EVENT_NOTIFY_MSG_H__

+

+/**

+ * @file event_notify_msg.h

+ * @brief SIP Event Notification Headers (RFC 3265)

+ */

+#include <pjsip/sip_msg.h>

+

+/**

+ * @ingroup PJSIP_EVENT_NOT

+ * @{

+ */

+

+PJ_BEGIN_DECL

+

+

+/** Max events in Allow-Events header. */

+#define PJSIP_MAX_ALLOW_EVENTS	16

+

+/**

+ * This structure describes Event header.

+ */

+typedef struct pjsip_event_hdr

+{

+    PJSIP_DECL_HDR_MEMBER(struct pjsip_event_hdr)

+    pj_str_t	    event_type;	    /**< Event name. */

+    pj_str_t	    id_param;	    /**< Optional event ID parameter. */

+    pj_str_t	    other_param;    /**< Other parameter, concatenated together. */

+} pjsip_event_hdr;

+

+/**

+ * Create an Event header.

+ *

+ * @param pool	    The pool.

+ *

+ * @return	    New Event header instance.

+ */

+PJ_DECL(pjsip_event_hdr*) pjsip_event_hdr_create(pj_pool_t *pool);

+

+

+/**

+ * This structure describes Allow-Events header.

+ */

+typedef struct pjsip_allow_events_hdr

+{

+    PJSIP_DECL_HDR_MEMBER(struct pjsip_allow_events_hdr)

+    int		    event_cnt;			    /**< Number of event names. */

+    pj_str_t	    events[PJSIP_MAX_ALLOW_EVENTS]; /**< Event names. */

+} pjsip_allow_events_hdr;

+

+

+/**

+ * Create a new Allow-Events header.

+ *

+ * @param pool.	    The pool.

+ *

+ * @return	    Allow-Events header.

+ */

+PJ_DECL(pjsip_allow_events_hdr*) pjsip_allow_events_hdr_create(pj_pool_t *pool);

+

+

+/**

+ * This structure describes Subscription-State header.

+ */

+typedef struct pjsip_sub_state_hdr

+{

+    PJSIP_DECL_HDR_MEMBER(struct pjsip_sub_state_hdr)

+    pj_str_t	    sub_state;		/**< Subscription state. */

+    pj_str_t	    reason_param;	/**< Optional termination reason. */

+    int		    expires_param;	/**< Expires param, or -1. */

+    int		    retry_after;	/**< Retry after param, or -1. */

+    pj_str_t	    other_param;	/**< Other parameter, concatenated together. */

+} pjsip_sub_state_hdr;

+

+/**

+ * Create new Subscription-State header.

+ *

+ * @param pool	    The pool.

+ *

+ * @return	    Subscription-State header.

+ */

+PJ_DECL(pjsip_sub_state_hdr*) pjsip_sub_state_hdr_create(pj_pool_t *pool);

+

+/**

+ * Initialize parser for event notify module.

+ */

+PJ_DEF(void) pjsip_event_notify_init_parser(void);

+

+

+PJ_END_DECL

+

+

+/**

+ * @}

+ */

+

+#endif	/* __PJSIP_SIMPLE_EVENT_NOTIFY_MSG_H__ */

+

diff --git a/pjsip/src/pjsip_simple/messaging.c b/pjsip/src/pjsip_simple/messaging.c
new file mode 100644
index 0000000..8b271f9
--- /dev/null
+++ b/pjsip/src/pjsip_simple/messaging.c
@@ -0,0 +1,335 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/messaging.c 7     8/31/05 9:05p Bennylp $ */

+#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;

+}

+

diff --git a/pjsip/src/pjsip_simple/messaging.h b/pjsip/src/pjsip_simple/messaging.h
new file mode 100644
index 0000000..f1d26b8
--- /dev/null
+++ b/pjsip/src/pjsip_simple/messaging.h
@@ -0,0 +1,251 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/messaging.h 6     8/31/05 9:05p Bennylp $ */

+#ifndef __PJSIP_SIMPLE_MESSAGING_H__

+#define __PJSIP_SIMPLE_MESSAGING_H__

+

+/**

+ * @file messaging.h

+ * @brief Instant Messaging Extension (RFC 3428)

+ */

+

+#include <pjsip/sip_msg.h>

+

+PJ_BEGIN_DECL

+

+/**

+ * @defgroup PJSIP_MESSAGING SIP Instant Messaging (RFC 3428) Module

+ * @ingroup PJSIP_SIMPLE

+ * @{

+ *

+ * This module provides the implementation of SIP Extension for Instant

+ * Messaging (RFC 3428). It extends PJSIP by supporting MESSAGE method.

+ *

+ * The RFC 3428 doesn't provide any means of dialog for the purpose of sending/

+ * receiving instant messaging. IM with SIP is basicly sessionless, which means

+ * that there is absolutely no correlation between IM messages sent or received

+ * by a host. Any correlation between IM messages is only perceivable by

+ * user, phsychologically.

+ *

+ * However, the RFC doesn't prohibit sending IM within a dialog (presumably

+ * using the same Call-ID and CSeq namespace), although it prohibits creating

+ * a dialog specificly for creating IM session.

+ *

+ * The implementation here is modeled to support both ways of sending IM msgs,

+ * i.e. sending IM message individually and sending IM message within a dialog.

+ * Although IM message can be associated with a dialog, this implementation of

+ * IM module is completely independent of the User Agent library in PJSIP. Yes,

+ * that's what is called modularity, and it demonstrates the clearness 

+ * of PJSIP design (the last sentence is of course marketing crap :)).

+ *

+ * To send an IM message as part of dialog, application would first create the

+ * message using #pjsip_messaging_create_msg, using dialog's Call-ID, CSeq,

+ * From, and To header, then send the message using #pjsip_dlg_send_msg instead

+ * of #pjsip_messaging_send_msg. 

+ *

+ * To send IM messages individually, application has two options. The first is

+ * to create the request with #pjsip_messaging_create_msg then send it with

+ * #pjsip_messaging_send_msg. But this way, application has to pre-construct

+ * From and To header first, which is not too convenient.

+ *

+ * The second option (to send IM messages not associated with a dialog) is to

+ * first create an 'IM session'. An 'IM session' here is not a SIP dialog, as

+ * it doesn't have Contact header etc. An 'IM session' here is just a local

+ * state to cache most of IM headers, for convenience and optimization. Appl 

+ * creates an IM session with #pjsip_messaging_create_session, and destroy

+ * the session with #pjsip_messaging_destroy_session. To create an outgoing

+ * IM message, application would call #pjsip_messaging_session_create_msg,

+ * and to send the message it would use #pjsip_messaging_send_msg.

+ *

+ * Message authorization is handled by application, as usual, by inserting a 

+ * proper WWW-Authenticate or Proxy-Authenticate header before sending the 

+ * message.

+ *

+ * And the last bit, to handle incoing IM messages.

+ *

+ * To handle incoming IM messages, application would register a global callback

+ * to be called when incoming messages arrive, by registering with function

+ * #pjsip_messaging_set_incoming_callback. This will be the global callback

+ * for all incoming IM messages. Although the message was sent as part of

+ * a dialog, it would still come here. And as long as the request has proper

+ * identification (Call-ID, From/To tag), the dialog will be aware of the

+ * request and update it's state (remote CSeq) accordingly.

+ */

+

+

+

+/**

+ * Typedef for callback to be called when outgoing message has been sent

+ * and a final response has been received.

+ */

+typedef void (*pjsip_messaging_cb)(void *token, int status_code);

+

+/**

+ * Typedef for callback to receive incoming message.

+ *

+ * @param rdata	    Incoming message data.

+ *

+ * @return	    The status code to be returned back to original sender.

+ *		    Application must return a final status code upon returning

+ *		    from this function, or otherwise the stack will respond

+ *		    with error.

+ */

+typedef int (*pjsip_on_new_msg_cb)(pjsip_rx_data *rdata);

+

+/**

+ * Opaque data type for instant messaging session.

+ */

+typedef struct pjsip_messaging_session pjsip_messaging_session;

+

+/**

+ * Get the messaging module.

+ *

+ * @return SIP module.

+ */

+PJ_DECL(pjsip_module*) pjsip_messaging_get_module();

+

+/**

+ * Set the global callback to be called when incoming message is received.

+ *

+ * @param cb	    The callback to be called when incoming message is received.

+ *

+ * @return	    The previous callback.

+ */

+PJ_DECL(pjsip_on_new_msg_cb) 

+pjsip_messaging_set_incoming_callback(pjsip_on_new_msg_cb cb);

+

+

+/**

+ * Create an instant message transmit data buffer using the specified arguments.

+ * The returned transmit data buffers will have it's reference counter set

+ * to 1, and when application send the buffer, the send function will decrement

+ * the reference counter. When the reference counter reach zero, the buffer

+ * will be deleted. As long as the function does not increment the buffer's 

+ * reference counter between creating and sending the request,  the buffer 

+ * will always be deleted and no memory leak will occur.

+ *

+ * @param endpt	    Endpoint instance.

+ * @param target    Target URL.

+ * @param from	    The "From" header, which content will be copied to request.

+ *		    If the "From" header doesn't have a tag parameter, the

+ *		    function will generate one.

+ * @param to	    The "To" header, which content will be copied to request.

+ * @param call_id   Optionally specify Call-ID, or put NULL to make this

+ *		    function generate a unique Call-ID automatically.

+ * @param cseq	    Optionally specify CSeq, or put -1 to make this function

+ *		    generate a random CSeq.

+ * @param text	    Optionally specify "text/plain" message body, or put NULL 

+ *		    if application wants to put body other than "text/plain"

+ *		    manually.

+ *

+ * @return	    SIP transmit data buffer, which reference count has been

+ *		    set to 1.

+ */

+PJ_DECL(pjsip_tx_data*) 

+pjsip_messaging_create_msg_from_hdr(pjsip_endpoint *endpt, 

+				    const pjsip_uri *target,

+				    const pjsip_from_hdr *from,

+				    const pjsip_to_hdr *to, 

+				    const pjsip_cid_hdr *call_id,

+				    int cseq, 

+				    const pj_str_t *text);

+

+/**

+ * Create instant message, by specifying URL string for both From and To header.

+ *

+ * @param endpt	    Endpoint instance.

+ * @param target    Target URL.

+ * @param from	    URL of the sender.

+ * @param to	    URL of the recipient.

+ * @param call_id   Optionally specify Call-ID, or put NULL to make this

+ *		    function generate a unique Call-ID automatically.

+ * @param cseq	    Optionally specify CSeq, or put -1 to make this function

+ *		    generate a random CSeq.

+ * @param text	    Optionally specify "text/plain" message body, or put NULL 

+ *		    if application wants to put body other than "text/plain"

+ *		    manually.

+ *

+ * @return	    SIP transmit data buffer, which reference count has been

+ *		    set to 1.

+ */

+PJ_DECL(pjsip_tx_data*) pjsip_messaging_create_msg( pjsip_endpoint *endpt, 

+						    const pj_str_t *target,

+						    const pj_str_t *from,

+						    const pj_str_t *to, 

+						    const pj_str_t *call_id,

+						    int cseq, 

+						    const pj_str_t *text);

+

+/**

+ * Send the instant message transmit buffer and attach a callback to be called

+ * when the request has received a final response. This function will decrement

+ * the transmit buffer's reference counter, and when the reference counter

+ * reach zero, the buffer will be deleted. As long as the function does not

+ * increment the buffer's reference counter between creating the request and

+ * calling this function, the buffer will always be deleted regardless whether

+ * the sending was failed or succeeded.

+ *

+ * @param endpt	    Endpoint instance.

+ * @param tdata	    Transmit data buffer.

+ * @param token	    Token to be associated with the SIP transaction which sends

+ *		    this request.

+ * @param cb	    The callback to be called when the SIP request has received

+ *		    a final response from destination.

+ *

+ * @return	    Zero if the transaction was started successfully. Note that

+ *		    this doesn't mean the request has been received successfully

+ *		    by remote recipient.

+ */

+PJ_DECL(pj_status_t) pjsip_messaging_send_msg( pjsip_endpoint *endpt, 

+					       pjsip_tx_data *tdata, 

+					       void *token, 

+					       pjsip_messaging_cb cb );

+

+/**

+ * Create an instant messaging session, which can conveniently be used to send

+ * multiple instant messages to the same recipient.

+ *

+ * @param endpt	    Endpoint instance.

+ * @param from	    URI of sender. The function will add a unique tag parameter

+ *		    to this URI in the From header.

+ * @param to	    URI of recipient.

+ *

+ * @return	    Messaging session.

+ */

+PJ_DECL(pjsip_messaging_session*) 

+pjsip_messaging_create_session( pjsip_endpoint *endpt, 

+			        const pj_str_t *from,

+			        const pj_str_t *to );

+

+/**

+ * Create an instant message using instant messaging session, and optionally

+ * attach a text message.

+ *

+ * @param ses	    The instant messaging session.

+ * @param text	    Optional "text/plain" message to be attached as the

+ *		    message body. If this parameter is NULL, then no message

+ *		    body will be created, and application can attach any

+ *		    type of message body before the request is sent.

+ *

+ * @return	    SIP transmit data buffer, which reference counter has been

+ *		    set to 1.

+ */

+PJ_DECL(pjsip_tx_data*)

+pjsip_messaging_session_create_msg( pjsip_messaging_session *ses, 

+				    const pj_str_t *text );

+

+/**

+ * Destroy an instant messaging session.

+ *

+ * @param ses	    The instant messaging session.

+ *

+ * @return	    Zero on successfull.

+ */

+PJ_DECL(pj_status_t)

+pjsip_messaging_destroy_session( pjsip_messaging_session *ses );

+

+/**

+ * @}

+ */

+

+PJ_END_DECL

+

+#endif

diff --git a/pjsip/src/pjsip_simple/pidf.c b/pjsip/src/pjsip_simple/pidf.c
new file mode 100644
index 0000000..4db1c14
--- /dev/null
+++ b/pjsip/src/pjsip_simple/pidf.c
@@ -0,0 +1,333 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/pidf.c 3     6/21/05 12:37a Bennylp $ */

+#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);

+}

+

diff --git a/pjsip/src/pjsip_simple/pidf.h b/pjsip/src/pjsip_simple/pidf.h
new file mode 100644
index 0000000..bebe68e
--- /dev/null
+++ b/pjsip/src/pjsip_simple/pidf.h
@@ -0,0 +1,159 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/pidf.h 3     8/24/05 10:33a Bennylp $ */

+#ifndef __PJSIP_SIMPLE_PIDF_H__

+#define __PJSIP_SIMPLE_PIDF_H__

+

+/**

+ * @file pidf.h

+ * @brief PIDF/Presence Information Data Format (RFC 3863)

+ */

+#include <pj/types.h>

+#include <pj/xml.h>

+

+PJ_BEGIN_DECL

+

+

+/**

+ * @defgroup PJSIP_SIMPLE_PIDF PIDF/Presence Information Data Format (RFC 3863)

+ * @ingroup PJSIP_SIMPLE

+ * @{

+ *

+ * This file provides tools for manipulating Presence Information Data 

+ * Format (PIDF) as described in RFC 3863.

+ */

+typedef struct pj_xml_node pjpidf_pres;

+typedef struct pj_xml_node pjpidf_tuple;

+typedef struct pj_xml_node pjpidf_status;

+typedef struct pj_xml_node pjpidf_note;

+

+typedef struct pjpidf_status_op

+{

+    void	    (*construct)(pj_pool_t*, pjpidf_status*);

+    pj_bool_t	    (*is_basic_open)(const pjpidf_status*);

+    void	    (*set_basic_open)(pjpidf_status*, pj_bool_t);

+} pjpidf_status_op;

+

+typedef struct pjpidf_tuple_op

+{

+    void	    (*construct)(pj_pool_t*, pjpidf_tuple*, const pj_str_t*);

+

+    const pj_str_t* (*get_id)(const pjpidf_tuple* );

+    void	    (*set_id)(pj_pool_t*, pjpidf_tuple *, const pj_str_t*);

+

+    pjpidf_status*  (*get_status)(pjpidf_tuple* );

+

+    const pj_str_t* (*get_contact)(const pjpidf_tuple*);

+    void	    (*set_contact)(pj_pool_t*, pjpidf_tuple*, const pj_str_t*);

+    void	    (*set_contact_prio)(pj_pool_t*, pjpidf_tuple*, const pj_str_t*);

+    const pj_str_t* (*get_contact_prio)(const pjpidf_tuple*);

+

+    pjpidf_note*    (*add_note)(pj_pool_t*, pjpidf_tuple*, const pj_str_t*);

+    pjpidf_note*    (*get_first_note)(pjpidf_tuple*);

+    pjpidf_note*    (*get_next_note)(pjpidf_tuple*, pjpidf_note*);

+

+    const pj_str_t* (*get_timestamp)(const pjpidf_tuple*);

+    void	    (*set_timestamp)(pj_pool_t*, pjpidf_tuple*, const pj_str_t*);

+    void	    (*set_timestamp_np)(pj_pool_t*,pjpidf_tuple*, pj_str_t*);

+

+} pjpidf_tuple_op;

+

+typedef struct pjpidf_pres_op

+{

+    void	    (*construct)(pj_pool_t*, pjpidf_pres*, const pj_str_t*);

+

+    pjpidf_tuple*   (*add_tuple)(pj_pool_t*, pjpidf_pres*, const pj_str_t*);

+    pjpidf_tuple*   (*get_first_tuple)(pjpidf_pres*);

+    pjpidf_tuple*   (*get_next_tuple)(pjpidf_pres*, pjpidf_tuple*);

+    pjpidf_tuple*   (*find_tuple)(pjpidf_pres*, const pj_str_t*);

+    void	    (*remove_tuple)(pjpidf_pres*, pjpidf_tuple*);

+

+    pjpidf_note*    (*add_note)(pj_pool_t*, pjpidf_pres*, const pj_str_t*);

+    pjpidf_note*    (*get_first_note)(pjpidf_pres*);

+    pjpidf_note*    (*get_next_note)(pjpidf_pres*, pjpidf_note*);

+

+} pjpidf_pres_op;

+

+

+extern struct pjpidf_op_desc

+{

+    pjpidf_pres_op	pres;

+    pjpidf_tuple_op	tuple;

+    pjpidf_status_op	status;

+} pjpidf_op;

+

+

+/******************************************************************************

+ * Top level API for managing presence document. 

+ *****************************************************************************/

+PJ_DECL(pjpidf_pres*)    pjpidf_create(pj_pool_t *pool, const pj_str_t *entity);

+PJ_DECL(pjpidf_pres*)	 pjpidf_parse(pj_pool_t *pool, char *text, int len);

+PJ_DECL(int)		 pjpidf_print(const pjpidf_pres* pres, char *buf, int len);

+

+

+/******************************************************************************

+ * API for managing Presence node.

+ *****************************************************************************/

+PJ_DECL(void)		 pjpidf_pres_construct(pj_pool_t *pool, pjpidf_pres *pres,

+					       const pj_str_t *entity);

+PJ_DECL(pjpidf_tuple*)	 pjpidf_pres_add_tuple(pj_pool_t *pool, pjpidf_pres *pres, 

+					       const pj_str_t *id);

+PJ_DECL(pjpidf_tuple*)	 pjpidf_pres_get_first_tuple(pjpidf_pres *pres);

+PJ_DECL(pjpidf_tuple*)	 pjpidf_pres_get_next_tuple(pjpidf_pres *pres, 

+						    pjpidf_tuple *t);

+PJ_DECL(pjpidf_tuple*)	 pjpidf_pres_find_tuple(pjpidf_pres *pres, 

+						const pj_str_t *id);

+PJ_DECL(void)		 pjpidf_pres_remove_tuple(pjpidf_pres *pres, 

+						  pjpidf_tuple*);

+

+PJ_DECL(pjpidf_note*)	 pjpidf_pres_add_note(pj_pool_t *pool, pjpidf_pres *pres, 

+					      const pj_str_t *text);

+PJ_DECL(pjpidf_note*)	 pjpidf_pres_get_first_note(pjpidf_pres *pres);

+PJ_DECL(pjpidf_note*)	 pjpidf_pres_get_next_note(pjpidf_pres*, pjpidf_note*);

+

+

+/******************************************************************************

+ * API for managing Tuple node.

+ *****************************************************************************/

+PJ_DECL(void)		 pjpidf_tuple_construct(pj_pool_t *pool, pjpidf_tuple *t,

+						const pj_str_t *id);

+PJ_DECL(const pj_str_t*) pjpidf_tuple_get_id(const pjpidf_tuple *t );

+PJ_DECL(void)		 pjpidf_tuple_set_id(pj_pool_t *pool, pjpidf_tuple *t, 

+					     const pj_str_t *id);

+

+PJ_DECL(pjpidf_status*)  pjpidf_tuple_get_status(pjpidf_tuple *t);

+

+PJ_DECL(const pj_str_t*) pjpidf_tuple_get_contact(const pjpidf_tuple *t);

+PJ_DECL(void)		 pjpidf_tuple_set_contact(pj_pool_t *pool, pjpidf_tuple *t, 

+						  const pj_str_t *contact);

+PJ_DECL(void)		 pjpidf_tuple_set_contact_prio(pj_pool_t *pool, pjpidf_tuple *t, 

+						       const pj_str_t *prio);

+PJ_DECL(const pj_str_t*) pjpidf_tuple_get_contact_prio(const pjpidf_tuple *t);

+

+PJ_DECL(pjpidf_note*)	 pjpidf_tuple_add_note(pj_pool_t *pool, pjpidf_tuple *t,

+					       const pj_str_t *text);

+PJ_DECL(pjpidf_note*)	 pjpidf_tuple_get_first_note(pjpidf_tuple *t);

+PJ_DECL(pjpidf_note*)	 pjpidf_tuple_get_next_note(pjpidf_tuple *t, pjpidf_note *n);

+

+PJ_DECL(const pj_str_t*) pjpidf_tuple_get_timestamp(const pjpidf_tuple *t);

+PJ_DECL(void)		 pjpidf_tuple_set_timestamp(pj_pool_t *pool, pjpidf_tuple *t,

+						    const pj_str_t *ts);

+PJ_DECL(void)		 pjpidf_tuple_set_timestamp_np(	pj_pool_t*, pjpidf_tuple *t,

+							pj_str_t *ts);

+

+

+/******************************************************************************

+ * API for managing Status node.

+ *****************************************************************************/

+PJ_DECL(void)		 pjpidf_status_construct(pj_pool_t*, pjpidf_status*);

+PJ_DECL(pj_bool_t)	 pjpidf_status_is_basic_open(const pjpidf_status*);

+PJ_DECL(void)		 pjpidf_status_set_basic_open(pjpidf_status*, pj_bool_t);

+

+

+/**

+ * @}

+ */

+

+

+PJ_END_DECL

+

+

+#endif	/* __PJSIP_SIMPLE_PIDF_H__ */

diff --git a/pjsip/src/pjsip_simple/presence.c b/pjsip/src/pjsip_simple/presence.c
new file mode 100644
index 0000000..31ab5cd
--- /dev/null
+++ b/pjsip/src/pjsip_simple/presence.c
@@ -0,0 +1,382 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/presence.c 7     8/24/05 10:33a Bennylp $ */

+#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);

+    }

+}

+

diff --git a/pjsip/src/pjsip_simple/presence.h b/pjsip/src/pjsip_simple/presence.h
new file mode 100644
index 0000000..f9cc838
--- /dev/null
+++ b/pjsip/src/pjsip_simple/presence.h
@@ -0,0 +1,212 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/presence.h 6     8/24/05 10:33a Bennylp $ */

+#ifndef __PJSIP_SIMPLE_PRESENCE_H__

+#define __PJSIP_SIMPLE_PRESENCE_H__

+

+/**

+ * @file presence.h

+ * @brief SIP Extension for Presence (RFC 3856)

+ */

+#include <pjsip_simple/event_notify.h>

+#include <pjsip_simple/pidf.h>

+#include <pjsip_simple/xpidf.h>

+

+

+PJ_BEGIN_DECL

+

+

+/**

+ * @defgroup PJSIP_SIMPLE_PRES SIP Extension for Presence (RFC 3856)

+ * @ingroup PJSIP_SIMPLE

+ * @{

+ *

+ * This module contains the implementation of SIP Presence Extension as 

+ * described in RFC 3856. It uses the SIP Event Notification framework

+ * (event_notify.h) and extends the framework by implementing "presence"

+ * event package.

+ */

+

+/**

+ * Presence message body type.

+ */

+typedef enum pjsip_pres_type

+{

+    PJSIP_PRES_TYPE_PIDF,

+    PJSIP_PRES_TYPE_XPIDF,

+} pjsip_pres_type;

+

+/**

+ * This structure describe a presentity, for both subscriber and notifier.

+ */

+typedef struct pjsip_presentity

+{

+    pjsip_event_sub *sub;	    /**< Event subscribtion record.	*/

+    pjsip_pres_type  pres_type;	    /**< Presentity type.		*/

+    pjsip_msg_body  *uas_body;	    /**< Message body (UAS only).	*/

+    union {

+	pjpidf_pres *pidf;

+	pjxpidf_pres *xpidf;

+    }		     uas_data;	    /**< UAS data.			*/

+    pj_str_t	     timestamp;	    /**< Time of last update.		*/

+    void	    *user_data;	    /**< Application data.		*/

+} pjsip_presentity;

+

+

+/**

+ * This structure describe callback that is registered to receive notification

+ * from the presence module.

+ */

+typedef struct pjsip_presence_cb

+{

+    /**

+     * This callback is first called when the module receives incoming 

+     * SUBSCRIBE request to determine whether application wants to accept

+     * the request. If it does, then on_presence_request will be called.

+     *

+     * @param rdata	The received message.

+     * @return		Application should return 2xx to accept the request,

+     *			or failure status (>=300) to reject the request.

+     */

+    void (*accept_presence)(pjsip_rx_data *rdata, int *status);

+

+    /**

+     * This callback is called when the module receive the first presence

+     * subscription request.

+     *

+     * @param pres	The presence descriptor.

+     * @param rdata	The incoming request.

+     * @param timeout	Timeout to be set for incoming request. Otherwise

+     *			app can just leave this and accept the default.

+     */

+    void (*on_received_request)(pjsip_presentity *pres, pjsip_rx_data *rdata,

+				int *timeout);

+

+    /**

+     * This callback is called when the module received subscription refresh

+     * request.

+     *

+     * @param pres	The presence descriptor.

+     * @param rdata	The incoming request.

+     */

+    void (*on_received_refresh)(pjsip_presentity *pres, pjsip_rx_data *rdata);

+

+    /**

+     * This callback is called when the module receives incoming NOTIFY

+     * request.

+     *

+     * @param pres	The presence descriptor.

+     * @param open	The latest status of the presentity.

+     */

+    void (*on_received_update)(pjsip_presentity *pres, pj_bool_t open);

+

+    /**

+     * This callback is called when the subscription has terminated.

+     *

+     * @param sub	The subscription instance.

+     * @param reason	The termination reason.

+     */

+    void (*on_terminated)(pjsip_presentity *pres, const pj_str_t *reason);

+

+} pjsip_presence_cb;

+

+

+/**

+ * Initialize the presence module and register callback.

+ *

+ * @param cb		Callback structure.

+ */

+PJ_DECL(void) pjsip_presence_init(const pjsip_presence_cb *cb);

+

+

+/**

+ * Create to presence subscription of a presentity URL.

+ *

+ * @param endpt		Endpoint instance.

+ * @param local_url	Local URL.

+ * @param remote_url	Remote URL which the presence is being subscribed.

+ * @param expires	The expiration.

+ * @param user_data	User data to attach to presence subscription.

+ *

+ * @return		The presence structure if successfull, or NULL if

+ *			failed.

+ */

+PJ_DECL(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 );

+

+/**

+ * Set credentials to be used by this presentity for outgoing requests.

+ *

+ * @param pres		Presentity instance.

+ * @param count		Number of credentials in the array.

+ * @param cred		Array of credentials.

+ *

+ * @return		Zero on success.

+ */

+PJ_DECL(pj_status_t) pjsip_presence_set_credentials( pjsip_presentity *pres,

+						     int count,

+						     const pjsip_cred_info cred[]);

+

+/**

+ * Set route set for outgoing requests.

+ *

+ * @param pres		Presentity instance.

+ * @param route_set	List of route headers.

+ *

+ * @return		Zero on success.

+ */

+PJ_DECL(pj_status_t) pjsip_presence_set_route_set( pjsip_presentity *pres,

+						   const pjsip_route_hdr *hdr );

+

+/**

+ * Send SUBSCRIBE request for the specified presentity.

+ *

+ * @param pres		The presentity instance.

+ *

+ * @return		Zero on success.

+ */

+PJ_DECL(pj_status_t) pjsip_presence_subscribe( pjsip_presentity *pres );

+

+/**

+ * Ceased the presence subscription.

+ *

+ * @param pres		The presence structure.

+ * 

+ * @return		Zero on success.

+ */

+PJ_DECL(pj_status_t) pjsip_presence_unsubscribe( pjsip_presentity *pres );

+

+/**

+ * Notify subscriber about change in local status.

+ *

+ * @param pres		The presence structure.

+ * @param state		Set the state of the subscription.

+ * @param open		Set the presence status (open or closed).

+ *

+ * @return		Zero if a NOTIFY request can be sent.

+ */

+PJ_DECL(pj_status_t) pjsip_presence_notify( pjsip_presentity *pres,

+					    pjsip_event_sub_state state,

+					    pj_bool_t open );

+

+/**

+ * Destroy presence structure and the underlying subscription.

+ *

+ * @param pres		The presence structure.

+ *

+ * @return		Zero if the subscription was destroyed, or one if

+ *			the subscription can not be destroyed immediately

+ *			and will be destroyed later, or -1 if failed.

+ */

+PJ_DECL(pj_status_t) pjsip_presence_destroy( pjsip_presentity *pres );

+

+

+/**

+ * @}

+ */

+

+PJ_END_DECL

+

+

+#endif	/* __PJSIP_SIMPLE_PRESENCE_H__ */

diff --git a/pjsip/src/pjsip_simple/xpidf.c b/pjsip/src/pjsip_simple/xpidf.c
new file mode 100644
index 0000000..5241d9a
--- /dev/null
+++ b/pjsip/src/pjsip_simple/xpidf.c
@@ -0,0 +1,277 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/xpidf.c 3     6/22/05 11:42p Bennylp $ */

+#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;

+}

+

diff --git a/pjsip/src/pjsip_simple/xpidf.h b/pjsip/src/pjsip_simple/xpidf.h
new file mode 100644
index 0000000..a0a56aa
--- /dev/null
+++ b/pjsip/src/pjsip_simple/xpidf.h
@@ -0,0 +1,116 @@
+/* $Header: /pjproject/pjsip/src/pjsip_simple/xpidf.h 3     8/24/05 10:33a Bennylp $ */

+#ifndef __PJSIP_SIMPLE_XPIDF_H__

+#define __PJSIP_SIMPLE_XPIDF_H__

+

+/**

+ * @file xpidf.h

+ * @brief XPIDF/Presence Information Data Format

+ */

+#include <pj/types.h>

+#include <pj/xml.h>

+

+PJ_BEGIN_DECL

+

+/**

+ * @defgroup PJSIP_SIMPLE_XPIDF XPIDF/Presence Information Data Format

+ * @ingroup PJSIP_SIMPLE

+ * @{

+ *

+ * This is an old presence data format as described in:

+ * draft-rosenberg-impp-pidf-00.txt.

+ *

+ * We won't support this format extensively here, as it seems there's not

+ * too many implementations support this anymore, as it shouldn't.

+ */

+

+/** Type definitions for XPIDF root document. */

+typedef pj_xml_node pjxpidf_pres;

+

+

+/**

+ * Create a new XPIDF document.

+ *

+ * @param pool	    Pool.

+ * @param uri	    URI to set in the XPIDF document.

+ *

+ * @return	    XPIDF document.

+ */

+PJ_DECL(pjxpidf_pres*) pjxpidf_create(pj_pool_t *pool, const pj_str_t *uri);

+

+

+/**

+ * Parse XPIDF document.

+ *

+ * @param pool	    Pool.

+ * @param text	    Input text.

+ * @param len	    Length of input text.

+ *

+ * @return	    XPIDF document.

+ */

+PJ_DECL(pjxpidf_pres*) pjxpidf_parse(pj_pool_t *pool, char *text, pj_size_t len);

+

+

+/**

+ * Print XPIDF document.

+ *

+ * @param pres	    The XPIDF document to print.

+ * @param text	    Buffer to place the output.

+ * @param len	    Length of the buffer.

+ *

+ * @return	    The length printed.

+ */

+PJ_DECL(int) pjxpidf_print( pjxpidf_pres *pres, char *text, pj_size_t len);

+

+

+/**

+ * Get URI in the XPIDF document

+ *

+ * @param pres	    XPIDF document

+ *

+ * @return	    The URI, or an empty string.

+ */

+PJ_DECL(pj_str_t*) pjxpidf_get_uri(pjxpidf_pres *pres);

+

+

+/**

+ * Set the URI of the XPIDF document.

+ *

+ * @param pool	    Pool.

+ * @param pres	    The XPIDF document.

+ * @param uri	    URI to set in the XPIDF document.

+ *

+ * @return	    Zero on success.

+ */

+PJ_DECL(pj_status_t) pjxpidf_set_uri(pj_pool_t *pool, pjxpidf_pres *pres, 

+				     const pj_str_t *uri);

+

+

+/**

+ * Get presence status in the XPIDF document.

+ *

+ * @param pres	    XPIDF document.

+ *

+ * @return	    True to indicate the contact is online.

+ */

+PJ_DECL(pj_bool_t) pjxpidf_get_status(pjxpidf_pres *pres);

+

+

+/**

+ * Set presence status in the XPIDF document.

+ *

+ * @param pres	    XPIDF document.

+ * @param status    Status to set, True for online, False for offline.

+ *

+ * @return	    Zero on success.

+ */

+PJ_DECL(pj_status_t) pjxpidf_set_status(pjxpidf_pres *pres, pj_bool_t status);

+

+

+/**

+ * @}

+ */

+

+PJ_END_DECL

+

+

+#endif	/* __PJSIP_SIMPLE_XPIDF_H__ */