Initial SIMPLE implementation

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@197 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/src/pjsip-simple/presence.c b/pjsip/src/pjsip-simple/presence.c
index a9cc610..ca033f5 100644
--- a/pjsip/src/pjsip-simple/presence.c
+++ b/pjsip/src/pjsip-simple/presence.c
@@ -16,384 +16,919 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
-#include <pjsip_simple/presence.h>
-#include <pjsip/sip_transport.h>
+#include <pjsip-simple/presence.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_dialog.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/os.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 };
+#define THIS_FILE		    "presence.c"
+#define PRES_DEFAULT_EXPIRES	    600
 
 /*
- * Initialize presence module.
- * This will register event package "presence" to event framework.
+ * Presence module (mod-presence)
  */
-PJ_DEF(void) pjsip_presence_init(const pjsip_presence_cb *pcb)
+static struct pjsip_module mod_presence = 
 {
-    pj_memcpy(&cb, pcb, sizeof(*pcb));
-    pjsip_event_sub_register_pkg( &PRESENCE_EVENT, 
-				  sizeof(accept_names)/sizeof(accept_names[0]),
-				  accept_names,
-				  &pkg_cb);
+    NULL, NULL,			    /* prev, next.			*/
+    { "mod-presence", 12 },	    /* Name.				*/
+    -1,				    /* Id				*/
+    PJSIP_MOD_PRIORITY_APPLICATION-1,	/* Priority			*/
+    NULL,			    /* User data.			*/
+    NULL,			    /* load()				*/
+    NULL,			    /* start()				*/
+    NULL,			    /* stop()				*/
+    NULL,			    /* unload()				*/
+    NULL,			    /* on_rx_request()			*/
+    NULL,			    /* on_rx_response()			*/
+    NULL,			    /* on_tx_request.			*/
+    NULL,			    /* on_tx_response()			*/
+    NULL,			    /* on_tsx_state()			*/
+};
+
+
+/*
+ * Presence message body type.
+ */
+typedef enum content_type
+{
+    CONTENT_TYPE_NONE,
+    CONTENT_TYPE_PIDF,
+    CONTENT_TYPE_XPIDF,
+} content_type;
+
+/*
+ * This structure describe a presentity, for both subscriber and notifier.
+ */
+struct pjsip_pres
+{
+    pjsip_evsub		*sub;		/**< Event subscribtion record.	    */
+    pjsip_dialog	*dlg;		/**< The dialog.		    */
+    content_type	 content_type;	/**< Content-Type.		    */
+    pjsip_pres_status	 status;	/**< Presence status.		    */
+    pjsip_pres_status	 tmp_status;	/**< Temp, before NOTIFY is answred.*/
+    pjsip_evsub_user	 user_cb;	/**< The user callback.		    */
+};
+
+
+typedef struct pjsip_pres pjsip_pres;
+
+
+/*
+ * Forward decl for evsub callback.
+ */
+static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
+static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+				     pjsip_event *event);
+static void pres_on_evsub_rx_refresh( pjsip_evsub *sub, 
+				      pjsip_rx_data *rdata,
+				      int *p_st_code,
+				      pj_str_t **p_st_text,
+				      pjsip_hdr *res_hdr,
+				      pjsip_msg_body **p_body);
+static void pres_on_evsub_rx_notify( pjsip_evsub *sub, 
+				     pjsip_rx_data *rdata,
+				     int *p_st_code,
+				     pj_str_t **p_st_text,
+				     pjsip_hdr *res_hdr,
+				     pjsip_msg_body **p_body);
+static void pres_on_evsub_client_refresh(pjsip_evsub *sub);
+static void pres_on_evsub_server_timeout(pjsip_evsub *sub);
+
+
+/*
+ * Event subscription callback for presence.
+ */
+static pjsip_evsub_user pres_user = 
+{
+    &pres_on_evsub_state,
+    &pres_on_evsub_tsx_state,
+    &pres_on_evsub_rx_refresh,
+    &pres_on_evsub_rx_notify,
+    &pres_on_evsub_client_refresh,
+    &pres_on_evsub_server_timeout,
+};
+
+
+/*
+ * Some static constants.
+ */
+const pj_str_t STR_EVENT	    = { "Event", 5 };
+const pj_str_t STR_PRESENCE	    = { "presence", 8 };
+const pj_str_t STR_APPLICATION	    = { "application", 11 };
+const pj_str_t STR_PIDF_XML	    = { "pidf+xml", 8};
+const pj_str_t STR_XPIDF_XML	    = { "xpidf+xml", 9};
+const pj_str_t STR_APP_PIDF_XML	    = { "application/pidf+xml", 20 };
+const pj_str_t STR_APP_XPIDF_XML    = { "application/xpidf+xml", 21 };
+
+
+/*
+ * Init presence module.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_init_module( pjsip_endpoint *endpt,
+					    pjsip_module *mod_evsub)
+{
+    pj_status_t status;
+    pj_str_t accept[2];
+
+    /* Check arguments. */
+    PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL);
+
+    /* Must have not been registered */
+    PJ_ASSERT_RETURN(mod_presence.id == -1, PJ_EINVALIDOP);
+
+    /* Register to endpoint */
+    status = pjsip_endpt_register_module(endpt, &mod_presence);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    accept[0] = STR_APP_PIDF_XML;
+    accept[1] = STR_APP_XPIDF_XML;
+
+    /* Register event package to event module. */
+    status = pjsip_evsub_register_pkg( &mod_presence, &STR_PRESENCE, 
+				       PRES_DEFAULT_EXPIRES, 
+				       PJ_ARRAY_SIZE(accept), accept);
+    if (status != PJ_SUCCESS) {
+	pjsip_endpt_unregister_module(endpt, &mod_presence);
+	return status;
+    }
+
+    return PJ_SUCCESS;
 }
 
-/*
- * 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;
+/*
+ * Get presence module instance.
+ */
+PJ_DEF(pjsip_module*) pjsip_pres_instance(void)
+{
+    return &mod_presence;
+}
+
+
+/*
+ * Create client subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg,
+					   const pjsip_evsub_user *user_cb,
+					   pjsip_evsub **p_evsub )
+{
+    pj_status_t status;
+    pjsip_pres *pres;
+    pjsip_evsub *sub;
+
+    PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
+
+    pjsip_dlg_inc_lock(dlg);
 
     /* 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;
+    status = pjsip_evsub_create_uac( dlg,  &pres_user, &STR_PRESENCE, &sub);
+    if (status != PJ_SUCCESS)
+	goto on_return;
 
-    /* Allocate presence descriptor. */
-    pres = pj_pool_calloc(sub->pool, 1, sizeof(*pres));
-    pres->sub = sub;
-    pres->user_data = user_data;
-    sub->user_data = pres;
+    /* Create presence */
+    pres = pj_pool_zalloc(dlg->pool, sizeof(pjsip_pres));
+    pres->dlg = dlg;
+    if (user_cb)
+	pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user));
 
-    return pres;
+    /* Attach to evsub */
+    pjsip_evsub_set_mod_data(sub, mod_presence.id, pres);
+
+    *p_evsub = sub;
+
+on_return:
+    pjsip_dlg_dec_lock(dlg);
+    return status;
 }
 
-/*
- * 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.
+ * Create server subscription.
  */
-PJ_DEF(pj_status_t) pjsip_presence_set_credentials( pjsip_presentity *pres,
-						    int count,
-						    const pjsip_cred_info cred[])
+PJ_DEF(pj_status_t) pjsip_pres_create_uas( pjsip_dialog *dlg,
+					   const pjsip_evsub_user *user_cb,
+					   pjsip_rx_data *rdata,
+					   pjsip_evsub **p_evsub )
 {
-    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;
+    pjsip_event_hdr *event;
+    pjsip_expires_hdr *expires_hdr;
+    unsigned expires;
+    content_type content_type = CONTENT_TYPE_NONE;
+    pjsip_evsub *sub;
+    pjsip_pres *pres;
+    pj_status_t status;
 
-    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;
+    /* Check arguments */
+    PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
 
-    accept = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_ACCEPT, NULL);
+    /* Must be request message */
+    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+		     PJSIP_ENOTREQUESTMSG);
+
+    /* Check that request is SUBSCRIBE */
+    PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
+				      &pjsip_subscribe_method)==0,
+		     PJSIP_SIMPLE_ENOTSUBSCRIBE);
+
+    /* Check that Event header contains "presence" */
+    event = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL);
+    if (!event) {
+	return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
+    }
+    if (pj_stricmp(&event->event_type, &STR_PRESENCE) != 0) {
+	return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT);
+    }
+
+    /* Check that request contains compatible Accept header. */
+    accept = pjsip_msg_find_hdr(rdata->msg_info.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))
-	    {
+	for (i=0; i<accept->count; ++i) {
+	    if (pj_stricmp(&accept->values[i], &STR_APP_PIDF_XML)==0) {
+		content_type = CONTENT_TYPE_PIDF;
+		break;
+	    } else
+	    if (pj_stricmp(&accept->values[i], &STR_APP_XPIDF_XML)==0) {
+		content_type = CONTENT_TYPE_XPIDF;
 		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;
+	if (i==accept->count) {
+	    /* Nothing is acceptable */
+	    return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
 	}
 
-	(*cb.on_received_update)(pres, is_open);
+    } else {
+	/* No Accept header.
+	 * Treat as "application/pidf+xml"
+	 */
+	content_type = CONTENT_TYPE_PIDF;
+    }
+
+    /* Check that expires is not too short. */
+    expires_hdr=pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
+    if (expires_hdr) {
+	if (expires_hdr->ivalue < 5) {
+	    return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_INTERVAL_TOO_BRIEF);
+	}
+
+	expires = expires_hdr->ivalue;
+	if (expires > PRES_DEFAULT_EXPIRES)
+	    expires = PRES_DEFAULT_EXPIRES;
+
+    } else {
+	expires = PRES_DEFAULT_EXPIRES;
+    }
+    
+    /* Lock dialog */
+    pjsip_dlg_inc_lock(dlg);
+
+
+    /* Create server subscription */
+    status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, &sub);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Create server presence subscription */
+    pres = pj_pool_zalloc(dlg->pool, sizeof(pjsip_pres));
+    pres->dlg = dlg;
+    pres->sub = sub;
+    pres->content_type = content_type;
+    if (user_cb)
+	pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user));
+
+    /* Attach to evsub */
+    pjsip_evsub_set_mod_data(sub, mod_presence.id, pres);
+
+    /* Done: */
+    *p_evsub = sub;
+
+on_return:
+    pjsip_dlg_dec_lock(dlg);
+    return status;
+}
+
+
+/*
+ * Create SUBSCRIBE
+ */
+PJ_DEF(pj_status_t) pjsip_pres_initiate( pjsip_evsub *sub,
+					 pj_int32_t expires,
+					 pjsip_tx_data **p_tdata)
+{
+    return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires, 
+				p_tdata);
+}
+
+
+/*
+ * Accept incoming subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_accept( pjsip_evsub *sub,
+				       pjsip_rx_data *rdata,
+				       int st_code,
+				       const pjsip_hdr *hdr_list )
+{
+    return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
+}
+
+
+/*
+ * Get presence status.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_get_status( pjsip_evsub *sub,
+					   pjsip_pres_status *status )
+{
+    pjsip_pres *pres;
+
+    PJ_ASSERT_RETURN(sub && status, PJ_EINVAL);
+
+    pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+    PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
+
+    if (pres->tmp_status._is_valid)
+	pj_memcpy(status, &pres->tmp_status, sizeof(pjsip_pres_status));
+    else
+	pj_memcpy(status, &pres->status, sizeof(pjsip_pres_status));
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Set presence status.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_set_status( pjsip_evsub *sub,
+					   const pjsip_pres_status *status )
+{
+    unsigned i;
+    pjsip_pres *pres;
+
+    PJ_ASSERT_RETURN(sub && status, PJ_EINVAL);
+
+    pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+    PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
+
+    for (i=0; i<status->info_cnt; ++i) {
+	pres->status.info[i].basic_open = status->info[i].basic_open;
+	if (status->info[i].id.slen == 0) {
+	    pj_create_unique_string(pres->dlg->pool, 
+	    			    &pres->status.info[i].id);
+	} else {
+	    pj_strdup(pres->dlg->pool, 
+		      &pres->status.info[i].id,
+		      &status->info[i].id);
+	}
+	pj_strdup(pres->dlg->pool, 
+		  &pres->status.info[i].contact,
+		  &status->info[i].contact);
+    }
+
+    pres->status.info_cnt = status->info_cnt;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Create PIDF document based on the presence info.
+ */
+static pjpidf_pres* pres_create_pidf( pj_pool_t *pool,
+				      pjsip_pres *pres )
+{
+    pjpidf_pres *pidf;
+    unsigned i;
+    pj_str_t entity;
+
+    /* Get publisher URI */
+    entity.ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+    entity.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
+				  pres->dlg->local.info->uri,
+				  entity.ptr, PJSIP_MAX_URL_SIZE);
+    if (entity.slen < 1)
+	return NULL;
+
+    /* Create <presence>. */
+    pidf = pjpidf_create(pool, &entity);
+
+    /* Create <tuple> */
+    for (i=0; i<pres->status.info_cnt; ++i) {
+
+	pjpidf_tuple *pidf_tuple;
+	pjpidf_status *pidf_status;
+
+	/* Add tuple id. */
+	pidf_tuple = pjpidf_pres_add_tuple(pool, pidf, 
+					   &pres->status.info[i].id);
+
+	/* Set <contact> */
+	if (pres->status.info[i].contact.slen)
+	    pjpidf_tuple_set_contact(pool, pidf_tuple, 
+				     &pres->status.info[i].contact);
+
+
+	/* Set basic status */
+	pidf_status = pjpidf_tuple_get_status(pidf_tuple);
+	pjpidf_status_set_basic_open(pidf_status, 
+				     pres->status.info[i].basic_open);
+    }
+
+    return pidf;
+}
+
+
+/*
+ * Create XPIDF document based on the presence info.
+ */
+static pjxpidf_pres* pres_create_xpidf( pj_pool_t *pool,
+				        pjsip_pres *pres )
+{
+    /* Note: PJSIP implementation of XPIDF is not complete!
+     */
+    pjxpidf_pres *xpidf;
+    pj_str_t publisher_uri;
+
+    PJ_LOG(4,(THIS_FILE, "Warning: XPIDF format is not fully supported "
+			 "by PJSIP"));
+
+    publisher_uri.ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE);
+    publisher_uri.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
+					 pres->dlg->local.info->uri,
+					 publisher_uri.ptr,
+					 PJSIP_MAX_URL_SIZE);
+    if (publisher_uri.slen < 1)
+	return NULL;
+
+    /* Create XPIDF document. */
+    xpidf = pjxpidf_create(pool, &publisher_uri);
+
+    /* Set basic status. */
+    if (pres->status.info_cnt > 0)
+	pjxpidf_set_status( xpidf, pres->status.info[0].basic_open);
+    else
+	pjxpidf_set_status( xpidf, PJ_FALSE);
+
+    return xpidf;
+}
+
+
+/*
+ * Function to print XML message body.
+ */
+static int pres_print_body(struct pjsip_msg_body *msg_body, 
+			   char *buf, pj_size_t size)
+{
+    return pj_xml_print(msg_body->data, buf, size, PJ_TRUE);
+}
+
+
+/*
+ * Function to clone XML document.
+ */
+static void* xml_clone_data(pj_pool_t *pool, const void *data, unsigned len)
+{
+    PJ_UNUSED_ARG(len);
+    return pj_xml_clone( pool, data);
+}
+
+
+/*
+ * Create message body.
+ */
+static pj_status_t pres_create_msg_body( pjsip_pres *pres, 
+					 pjsip_tx_data *tdata)
+{
+    pjsip_msg_body *body;
+
+    body = pj_pool_zalloc(tdata->pool, sizeof(pjsip_msg_body));
+    
+    if (pres->content_type == CONTENT_TYPE_PIDF) {
+
+	body->data = pres_create_pidf(tdata->pool, pres);
+	body->content_type.type = pj_str("application");
+	body->content_type.subtype = pj_str("pidf+xml");
+
+    } else if (pres->content_type == CONTENT_TYPE_XPIDF) {
+
+	body->data = pres_create_xpidf(tdata->pool, pres);
+	body->content_type.type = pj_str("application");
+	body->content_type.subtype = pj_str("xpidf+xml");
+
+    } else {
+	return PJSIP_SIMPLE_EBADCONTENT;
+    }
+
+
+    body->print_body = &pres_print_body;
+    body->clone_data = &xml_clone_data;
+
+    tdata->msg->body = body;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Create NOTIFY
+ */
+PJ_DEF(pj_status_t) pjsip_pres_notify( pjsip_evsub *sub,
+				       pjsip_evsub_state state,
+				       const pj_str_t *state_str,
+				       const pj_str_t *reason,
+				       pjsip_tx_data **p_tdata)
+{
+    pjsip_pres *pres;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+    
+    /* Check arguments. */
+    PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+    /* Get the presence object. */
+    pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+    PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE);
+
+    /* Must have at least one presence info. */
+    PJ_ASSERT_RETURN(pres->status.info_cnt > 0, PJSIP_SIMPLE_ENOPRESENCEINFO);
+
+
+    /* Lock object. */
+    pjsip_dlg_inc_lock(pres->dlg);
+
+    /* Create the NOTIFY request. */
+    status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+
+    /* Create message body to reflect the presence status. */
+    status = pres_create_msg_body( pres, tdata );
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+
+    /* Done. */
+    *p_tdata = tdata;
+
+
+on_return:
+    pjsip_dlg_dec_lock(pres->dlg);
+    return status;
+}
+
+
+/*
+ * Create NOTIFY that reflect current state.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_current_notify( pjsip_evsub *sub,
+					       pjsip_tx_data **p_tdata )
+{
+    pjsip_pres *pres;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+    
+    /* Check arguments. */
+    PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+    /* Get the presence object. */
+    pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+    PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE);
+
+    /* Must have at least one presence info. */
+    PJ_ASSERT_RETURN(pres->status.info_cnt > 0, PJSIP_SIMPLE_ENOPRESENCEINFO);
+
+
+    /* Lock object. */
+    pjsip_dlg_inc_lock(pres->dlg);
+
+    /* Create the NOTIFY request. */
+    status = pjsip_evsub_current_notify( sub, &tdata);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+
+    /* Create message body to reflect the presence status. */
+    status = pres_create_msg_body( pres, tdata );
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+
+    /* Done. */
+    *p_tdata = tdata;
+
+
+on_return:
+    pjsip_dlg_dec_lock(pres->dlg);
+    return status;
+}
+
+
+/*
+ * Send request.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_send_request( pjsip_evsub *sub,
+					     pjsip_tx_data *tdata )
+{
+    return pjsip_evsub_send_request(sub, tdata);
+}
+
+
+/*
+ * This callback is called by event subscription when subscription
+ * state has changed.
+ */
+static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
+{
+    pjsip_pres *pres;
+
+    pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+    PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+    if (pres->user_cb.on_evsub_state)
+	(*pres->user_cb.on_evsub_state)(sub, event);
+}
+
+/*
+ * Called when transaction state has changed.
+ */
+static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+				     pjsip_event *event)
+{
+    pjsip_pres *pres;
+
+    pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+    PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+    if (pres->user_cb.on_tsx_state)
+	(*pres->user_cb.on_tsx_state)(sub, tsx, event);
+}
+
+
+/*
+ * Called when SUBSCRIBE is received.
+ */
+static void pres_on_evsub_rx_refresh( pjsip_evsub *sub, 
+				      pjsip_rx_data *rdata,
+				      int *p_st_code,
+				      pj_str_t **p_st_text,
+				      pjsip_hdr *res_hdr,
+				      pjsip_msg_body **p_body)
+{
+    pjsip_pres *pres;
+
+    pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+    PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+    if (pres->user_cb.on_rx_refresh) {
+	(*pres->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
+				       res_hdr, p_body);
+
+    } else {
+	/* Implementors MUST send NOTIFY if it implements on_rx_refresh */
+	pjsip_tx_data *tdata;
+	pj_str_t timeout = { "timeout", 7};
+	pj_status_t status;
+
+	if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
+	    status = pjsip_pres_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
+					NULL, &timeout, &tdata);
+	} else {
+	    status = pjsip_pres_current_notify(sub, &tdata);
+	}
+
+	if (status == PJ_SUCCESS)
+	    pjsip_pres_send_request(sub, tdata);
+    }
+}
+
+/*
+ * Parse PIDF to info.
+ */
+static pj_status_t pres_parse_pidf( pjsip_pres *pres,
+				    pjsip_rx_data *rdata,
+				    pjsip_pres_status *pres_status)
+{
+    pjpidf_pres *pidf;
+    pjpidf_tuple *pidf_tuple;
+
+    pidf = pjpidf_parse(rdata->tp_info.pool, 
+			rdata->msg_info.msg->body->data,
+			rdata->msg_info.msg->body->len);
+    if (pidf == NULL)
+	return PJSIP_SIMPLE_EBADPIDF;
+
+    pres_status->info_cnt = 0;
+
+    pidf_tuple = pjpidf_pres_get_first_tuple(pidf);
+    while (pidf_tuple) {
+	pjpidf_status *pidf_status;
+
+	pj_strdup(pres->dlg->pool, 
+		  &pres_status->info[pres_status->info_cnt].id,
+		  pjpidf_tuple_get_id(pidf_tuple));
+
+	pj_strdup(pres->dlg->pool, 
+		  &pres_status->info[pres_status->info_cnt].contact,
+		  pjpidf_tuple_get_contact(pidf_tuple));
+
+	pidf_status = pjpidf_tuple_get_status(pidf_tuple);
+	if (pidf_status) {
+	    pres_status->info[pres_status->info_cnt].basic_open = 
+		pjpidf_status_is_basic_open(pidf_status);
+	} else {
+	    pres_status->info[pres_status->info_cnt].basic_open = PJ_FALSE;
+	}
+
+	pidf_tuple = pjpidf_pres_get_next_tuple( pidf, pidf_tuple );
+	pres_status->info_cnt++;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Parse XPIDF info.
+ */
+static pj_status_t pres_parse_xpidf( pjsip_pres *pres,
+				     pjsip_rx_data *rdata,
+				     pjsip_pres_status *pres_status)
+{
+    pjxpidf_pres *xpidf;
+
+    xpidf = pjxpidf_parse(rdata->tp_info.pool, 
+			  rdata->msg_info.msg->body->data,
+			  rdata->msg_info.msg->body->len);
+    if (xpidf == NULL)
+	return PJSIP_SIMPLE_EBADXPIDF;
+
+    pres_status->info_cnt = 1;
+    
+    pj_strdup(pres->dlg->pool,
+	      &pres_status->info[0].contact,
+	      pjxpidf_get_uri(xpidf));
+    pres_status->info[0].basic_open = pjxpidf_get_status(xpidf);
+    pres_status->info[0].id.slen = 0;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Called when NOTIFY is received.
+ */
+static void pres_on_evsub_rx_notify( pjsip_evsub *sub, 
+				     pjsip_rx_data *rdata,
+				     int *p_st_code,
+				     pj_str_t **p_st_text,
+				     pjsip_hdr *res_hdr,
+				     pjsip_msg_body **p_body)
+{
+    pjsip_ctype_hdr *ctype_hdr;
+    pjsip_pres *pres;
+    pj_status_t status;
+
+    pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+    PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+    /* Check Content-Type and msg body are present. */
+    ctype_hdr = rdata->msg_info.ctype;
+
+    if (ctype_hdr==NULL || rdata->msg_info.msg->body==NULL) {
+	
+	pjsip_warning_hdr *warn_hdr;
+	pj_str_t warn_text;
+
+	*p_st_code = PJSIP_SC_BAD_REQUEST;
+
+	warn_text = pj_str("Message body is not present");
+	warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399,
+					    pjsip_endpt_name(pres->dlg->endpt),
+					    &warn_text);
+	pj_list_push_back(res_hdr, warn_hdr);
+
+	return;
+    }
+
+    /* Parse content. */
+
+    if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
+	pj_stricmp(&ctype_hdr->media.subtype, &STR_PIDF_XML)==0)
+    {
+	status = pres_parse_pidf( pres, rdata, &pres->tmp_status);
+    }
+    else 
+    if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
+	pj_stricmp(&ctype_hdr->media.subtype, &STR_XPIDF_XML)==0)
+    {
+	status = pres_parse_pidf( pres, rdata, &pres->tmp_status);
+    }
+    else
+    {
+	status = PJSIP_SIMPLE_EBADCONTENT;
+    }
+
+    if (status != PJ_SUCCESS) {
+	/* Unsupported or bad Content-Type */
+	pjsip_accept_hdr *accept_hdr;
+	pjsip_warning_hdr *warn_hdr;
+
+	*p_st_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+
+	/* Add Accept header */
+	accept_hdr = pjsip_accept_hdr_create(rdata->tp_info.pool);
+	accept_hdr->values[accept_hdr->count++] = STR_APP_PIDF_XML;
+	accept_hdr->values[accept_hdr->count++] = STR_APP_XPIDF_XML;
+	pj_list_push_back(res_hdr, accept_hdr);
+
+	/* Add Warning header */
+	warn_hdr = pjsip_warning_hdr_create_from_status(
+				    rdata->tp_info.pool,
+				    pjsip_endpt_name(pres->dlg->endpt),
+				    status);
+	pj_list_push_back(res_hdr, warn_hdr);
+
+	return;
+    }
+
+    /* If application calls pres_get_status(), redirect the call to
+     * retrieve the temporary status.
+     */
+    pres->tmp_status._is_valid = PJ_TRUE;
+
+    /* Notify application. */
+    if (pres->user_cb.on_rx_notify) {
+	(*pres->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text, 
+				      res_hdr, p_body);
+    }
+
+    
+    /* If application responded NOTIFY with 2xx, copy temporary status
+     * to main status, and mark the temporary status as invalid.
+     */
+    if ((*p_st_code)/100 == 2) {
+	pj_memcpy(&pres->status, &pres->tmp_status, sizeof(pjsip_pres_status));
+    }
+
+    pres->tmp_status._is_valid = PJ_FALSE;
+
+    /* Done */
+}
+
+/*
+ * Called when it's time to send SUBSCRIBE.
+ */
+static void pres_on_evsub_client_refresh(pjsip_evsub *sub)
+{
+    pjsip_pres *pres;
+
+    pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+    PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+    if (pres->user_cb.on_client_refresh) {
+	(*pres->user_cb.on_client_refresh)(sub);
+    } else {
+	pj_status_t status;
+	pjsip_tx_data *tdata;
+
+	status = pjsip_pres_initiate(sub, -1, &tdata);
+	if (status == PJ_SUCCESS)
+	    pjsip_pres_send_request(sub, tdata);
+    }
+}
+
+/*
+ * Called when no refresh is received after the interval.
+ */
+static void pres_on_evsub_server_timeout(pjsip_evsub *sub)
+{
+    pjsip_pres *pres;
+
+    pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+    PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+    if (pres->user_cb.on_server_timeout) {
+	(*pres->user_cb.on_server_timeout)(sub);
+    } else {
+	pj_status_t status;
+	pjsip_tx_data *tdata;
+	pj_str_t reason = { "timeout", 7 };
+
+	status = pjsip_pres_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
+				   NULL, &reason, &tdata);
+	if (status == PJ_SUCCESS)
+	    pjsip_pres_send_request(sub, tdata);
     }
 }