Index: pjproject/trunk/pjsip/build/pjsip.dsw
===================================================================
--- pjproject/trunk/pjsip/build/pjsip.dsw	(revision 196)
+++ pjproject/trunk/pjsip/build/pjsip.dsw	(revision 197)
@@ -94,4 +94,16 @@
 ###############################################################################
 
+Project: "pjsip_simple"=.\pjsip_simple.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
 Project: "pjsip_ua"=.\pjsip_ua.dsp - Package Owner=<4>
 
@@ -135,4 +147,7 @@
     Project_Dep_Name pjlib_util
     End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjsip_simple
+    End Project Dependency
 }}}
 
Index: pjproject/trunk/pjsip/build/pjsip_simple.dsp
===================================================================
--- pjproject/trunk/pjsip/build/pjsip_simple.dsp	(revision 196)
+++ pjproject/trunk/pjsip/build/pjsip_simple.dsp	(revision 197)
@@ -33,14 +33,14 @@
 # PROP BASE Use_MFC 0
 # PROP BASE Use_Debug_Libraries 0
-# PROP BASE Output_Dir "./output/pjsip_simple_Win32_Release"
-# PROP BASE Intermediate_Dir "./output/pjsip_simple_Win32_Release"
+# PROP BASE Output_Dir "./output/pjsip-simple-i386-win32-vc6-release"
+# PROP BASE Intermediate_Dir "./output/pjsip-simple-i386-win32-vc6-release"
 # PROP BASE Target_Dir ""
 # PROP Use_MFC 0
 # PROP Use_Debug_Libraries 0
-# PROP Output_Dir "./output/pjsip_simple_Win32_Release"
-# PROP Intermediate_Dir "./output/pjsip_simple_Win32_Release"
+# PROP Output_Dir "./output/pjsip-simple-i386-win32-vc6-release"
+# PROP Intermediate_Dir "./output/pjsip-simple-i386-win32-vc6-release"
 # PROP Target_Dir ""
 # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
-# ADD CPP /nologo /MD /W3 /GX /O2 /I "../src" /I "../../pjlib/src" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "../include" /I "../../pjlib-util/include" /I "../../pjlib/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /YX /FD /c
 # ADD BASE RSC /l 0x409 /d "NDEBUG"
 # ADD RSC /l 0x409 /d "NDEBUG"
@@ -50,5 +50,5 @@
 LIB32=link.exe -lib
 # ADD BASE LIB32 /nologo
-# ADD LIB32 /nologo /out:"../lib/pjsip_simple_vc6.lib"
+# ADD LIB32 /nologo /out:"../lib/pjsip-simple-i386-win32-vc6-release.lib"
 
 !ELSEIF  "$(CFG)" == "pjsip_simple - Win32 Debug"
@@ -56,14 +56,14 @@
 # PROP BASE Use_MFC 0
 # PROP BASE Use_Debug_Libraries 1
-# PROP BASE Output_Dir "./output/pjsip_simple_Win32_Debug"
-# PROP BASE Intermediate_Dir "./output/pjsip_simple_Win32_Debug"
+# PROP BASE Output_Dir "./output/pjsip-simple-i386-win32-vc6-debug"
+# PROP BASE Intermediate_Dir "./output/pjsip-simple-i386-win32-vc6-debug"
 # PROP BASE Target_Dir ""
 # PROP Use_MFC 0
 # PROP Use_Debug_Libraries 1
-# PROP Output_Dir "./output/pjsip_simple_Win32_Debug"
-# PROP Intermediate_Dir "./output/pjsip_simple_Win32_Debug"
+# PROP Output_Dir "./output/pjsip-simple-i386-win32-vc6-debug"
+# PROP Intermediate_Dir "./output/pjsip-simple-i386-win32-vc6-debug"
 # PROP Target_Dir ""
 # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
-# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../src" /I "../../pjlib/src" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FR /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib-util/include" /I "../../pjlib/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /YX /FD /GZ /c
 # ADD BASE RSC /l 0x409 /d "_DEBUG"
 # ADD RSC /l 0x409 /d "_DEBUG"
@@ -73,5 +73,5 @@
 LIB32=link.exe -lib
 # ADD BASE LIB32 /nologo
-# ADD LIB32 /nologo /out:"../lib/pjsip_simple_vc6d.lib"
+# ADD LIB32 /nologo /out:"../lib/pjsip-simple-i386-win32-vc6-debug.lib"
 
 !ENDIF 
@@ -86,25 +86,21 @@
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\event_notify.c
+SOURCE="..\src\pjsip-simple\evsub.c"
 # End Source File
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\event_notify_msg.c
+SOURCE="..\src\pjsip-simple\evsub_msg.c"
 # End Source File
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\messaging.c
+SOURCE="..\src\pjsip-simple\pidf.c"
 # End Source File
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\pidf.c
+SOURCE="..\src\pjsip-simple\presence.c"
 # End Source File
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\presence.c
-# End Source File
-# Begin Source File
-
-SOURCE=..\src\pjsip_simple\xpidf.c
+SOURCE="..\src\pjsip-simple\xpidf.c"
 # End Source File
 # End Group
@@ -114,29 +110,33 @@
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\event_notify.h
+SOURCE="..\include\pjsip-simple\errno.h"
 # End Source File
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\event_notify_msg.h
+SOURCE="..\include\pjsip-simple\evsub.h"
 # End Source File
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\messaging.h
+SOURCE="..\include\pjsip-simple\evsub_msg.h"
 # End Source File
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\pidf.h
+SOURCE="..\include\pjsip-simple\pidf.h"
 # End Source File
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple.h
+SOURCE=..\include\pjsip_simple.h
 # End Source File
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\presence.h
+SOURCE="..\include\pjsip-simple\presence.h"
 # End Source File
 # Begin Source File
 
-SOURCE=..\src\pjsip_simple\xpidf.h
+SOURCE="..\include\pjsip-simple\types.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjsip-simple\xpidf.h"
 # End Source File
 # End Group
Index: pjproject/trunk/pjsip/build/pjsip_ua.dsp
===================================================================
--- pjproject/trunk/pjsip/build/pjsip_ua.dsp	(revision 196)
+++ pjproject/trunk/pjsip/build/pjsip_ua.dsp	(revision 197)
@@ -33,11 +33,11 @@
 # PROP BASE Use_MFC 0
 # PROP BASE Use_Debug_Libraries 0
-# PROP BASE Output_Dir ".\output\pjsip_ua_vc6_Release"
-# PROP BASE Intermediate_Dir ".\output\pjsip_ua_vc6_Release"
+# PROP BASE Output_Dir ".\output\pjsip-ua-i386-win32-vc6-release"
+# PROP BASE Intermediate_Dir ".\output\pjsip-ua-i386-win32-vc6-release"
 # PROP BASE Target_Dir ""
 # PROP Use_MFC 0
 # PROP Use_Debug_Libraries 0
-# PROP Output_Dir ".\output\pjsip_ua_vc6_Release"
-# PROP Intermediate_Dir ".\output\pjsip_ua_vc6_Release"
+# PROP Output_Dir ".\output\pjsip-ua-i386-win32-vc6-release"
+# PROP Intermediate_Dir ".\output\pjsip-ua-i386-win32-vc6-release"
 # PROP Target_Dir ""
 # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
@@ -51,5 +51,5 @@
 LIB32=link.exe -lib
 # ADD BASE LIB32 /nologo
-# ADD LIB32 /nologo /out:"../lib/pjsip_ua_vc6s.lib"
+# ADD LIB32 /nologo /out:"../lib/pjsip-ua-i386-win32-vc6-release.lib"
 
 !ELSEIF  "$(CFG)" == "pjsip_ua - Win32 Debug"
@@ -57,11 +57,11 @@
 # PROP BASE Use_MFC 0
 # PROP BASE Use_Debug_Libraries 1
-# PROP BASE Output_Dir ".\output\pjsip_ua_vc6_Debug"
-# PROP BASE Intermediate_Dir ".\output\pjsip_ua_vc6_Debug"
+# PROP BASE Output_Dir ".\output\pjsip-ua-i386-win32-vc6-debug"
+# PROP BASE Intermediate_Dir ".\output\pjsip-ua-i386-win32-vc6-debug"
 # PROP BASE Target_Dir ""
 # PROP Use_MFC 0
 # PROP Use_Debug_Libraries 1
-# PROP Output_Dir ".\output\pjsip_ua_vc6_Debug"
-# PROP Intermediate_Dir ".\output\pjsip_ua_vc6_Debug"
+# PROP Output_Dir ".\output\pjsip-ua-i386-win32-vc6-debug"
+# PROP Intermediate_Dir ".\output\pjsip-ua-i386-win32-vc6-debug"
 # PROP Target_Dir ""
 # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
@@ -75,5 +75,5 @@
 LIB32=link.exe -lib
 # ADD BASE LIB32 /nologo
-# ADD LIB32 /nologo /out:"../lib/pjsip_ua_vc6sd.lib"
+# ADD LIB32 /nologo /out:"../lib/pjsip-ua-i386-win32-vc6-debug.lib"
 
 !ENDIF 
@@ -93,11 +93,4 @@
 
 SOURCE="..\src\pjsip-ua\sip_reg.c"
-
-!IF  "$(CFG)" == "pjsip_ua - Win32 Release"
-
-!ELSEIF  "$(CFG)" == "pjsip_ua - Win32 Debug"
-
-!ENDIF 
-
 # End Source File
 # End Group
Index: pjproject/trunk/pjsip/build/pjsua.dsp
===================================================================
--- pjproject/trunk/pjsip/build/pjsua.dsp	(revision 196)
+++ pjproject/trunk/pjsip/build/pjsua.dsp	(revision 197)
@@ -33,11 +33,11 @@
 # PROP BASE Use_MFC 0
 # PROP BASE Use_Debug_Libraries 0
-# PROP BASE Output_Dir ".\output\pjsua_vc6_Release"
-# PROP BASE Intermediate_Dir ".\output\pjsua_vc6_Release"
+# PROP BASE Output_Dir ".\output\pjsua-i386-win32-vc6-release"
+# PROP BASE Intermediate_Dir ".\output\pjsua-i386-win32-vc6-release"
 # PROP BASE Target_Dir ""
 # PROP Use_MFC 0
 # PROP Use_Debug_Libraries 0
-# PROP Output_Dir ".\output\pjsua_vc6_Release"
-# PROP Intermediate_Dir ".\output\pjsua_vc6_Release"
+# PROP Output_Dir ".\output\pjsua-i386-win32-vc6-release"
+# PROP Intermediate_Dir ".\output\pjsua-i386-win32-vc6-release"
 # PROP Ignore_Export_Lib 0
 # PROP Target_Dir ""
@@ -59,11 +59,11 @@
 # PROP BASE Use_MFC 0
 # PROP BASE Use_Debug_Libraries 1
-# PROP BASE Output_Dir ".\output\pjsua_vc6_Debug"
-# PROP BASE Intermediate_Dir ".\output\pjsua_vc6_Debug"
+# PROP BASE Output_Dir ".\output\pjsua-i386-win32-vc6-debug"
+# PROP BASE Intermediate_Dir ".\output\pjsua-i386-win32-vc6-debug"
 # PROP BASE Target_Dir ""
 # PROP Use_MFC 0
 # PROP Use_Debug_Libraries 1
-# PROP Output_Dir ".\output\pjsua_vc6_Debug"
-# PROP Intermediate_Dir ".\output\pjsua_vc6_Debug"
+# PROP Output_Dir ".\output\pjsua-i386-win32-vc6-debug"
+# PROP Intermediate_Dir ".\output\pjsua-i386-win32-vc6-debug"
 # PROP Ignore_Export_Lib 0
 # PROP Target_Dir ""
@@ -111,4 +111,8 @@
 # Begin Source File
 
+SOURCE=..\src\pjsua\pjsua_pres.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\pjsua\pjsua_reg.c
 # End Source File
Index: pjproject/trunk/pjsip/include/pjsip-simple/errno.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-simple/errno.h	(revision 197)
+++ pjproject/trunk/pjsip/include/pjsip-simple/errno.h	(revision 197)
@@ -0,0 +1,71 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef __PJSIP_SIMPLE_ERRNO_H__
+#define __PJSIP_SIMPLE_ERRNO_H__
+
+
+#include <pjsip/sip_errno.h>
+
+/**
+ * @hideinitializer
+ * No event package with the specified name.
+ */
+#define PJSIP_SIMPLE_ENOPKG	    -1
+/**
+ * @hideinitializer
+ * Event package already exists.
+ */
+#define PJSIP_SIMPLE_EPKGEXISTS	    -1
+
+
+/**
+ * @hideinitializer
+ * Expecting SUBSCRIBE request
+ */
+#define PJSIP_SIMPLE_ENOTSUBSCRIBE  -1
+/**
+ * @hideinitializer
+ * No presence associated with subscription
+ */
+#define PJSIP_SIMPLE_ENOPRESENCE    -1
+/**
+ * @hideinitializer
+ * No presence info in server subscription
+ */
+#define PJSIP_SIMPLE_ENOPRESENCEINFO -1
+/**
+ * @hideinitializer
+ * Bad Content-Type
+ */
+#define PJSIP_SIMPLE_EBADCONTENT    -1
+/**
+ * @hideinitializer
+ * Bad PIDF Message
+ */
+#define PJSIP_SIMPLE_EBADPIDF	    -1
+/**
+ * @hideinitializer
+ * Bad XPIDF Message
+ */
+#define PJSIP_SIMPLE_EBADXPIDF	    -1
+
+
+
+#endif	/* __PJSIP_SIMPLE_ERRNO_H__ */
+
Index: pjproject/trunk/pjsip/include/pjsip-simple/event_notify.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-simple/event_notify.h	(revision 196)
+++ 	(revision )
@@ -1,330 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
- */
-#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__ */
Index: pjproject/trunk/pjsip/include/pjsip-simple/event_notify_msg.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-simple/event_notify_msg.h	(revision 196)
+++ 	(revision )
@@ -1,117 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
- */
-#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__ */
-
Index: pjproject/trunk/pjsip/include/pjsip-simple/evsub.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-simple/evsub.h	(revision 197)
+++ pjproject/trunk/pjsip/include/pjsip-simple/evsub.h	(revision 197)
@@ -0,0 +1,421 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef __PJSIP_SIMPLE_EVSUB_H__
+#define __PJSIP_SIMPLE_EVSUB_H__
+
+/**
+ * @file event_notify.h
+ * @brief SIP Specific Event Notification Extension (RFC 3265)
+ */
+
+#include <pjsip-simple/types.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
+
+
+/**
+ * Opaque type for event subscription session.
+ */
+typedef struct pjsip_evsub pjsip_evsub;
+
+
+/**
+ * This enumeration describes basic 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_EVSUB_STATE_UNKNOWN, and the token will be kept
+ * in state_str member of the susbcription structure.
+ */
+enum pjsip_evsub_state
+{
+    PJSIP_EVSUB_STATE_NULL,	 /**< State is NULL.			    */
+    PJSIP_EVSUB_STATE_SENT,	 /**< Client has sent SUBSCRIBE request.    */
+    PJSIP_EVSUB_STATE_ACCEPTED,	 /**< 2xx response to SUBSCRIBE has been 
+				      sent/received.			    */
+    PJSIP_EVSUB_STATE_PENDING,	 /**< Subscription is pending.		    */
+    PJSIP_EVSUB_STATE_ACTIVE,	 /**< Subscription is active.		    */
+    PJSIP_EVSUB_STATE_TERMINATED,/**< Subscription is terminated.	    */
+    PJSIP_EVSUB_STATE_UNKNOWN,	 /**< Subscription state can not be determined.
+				      Application can query the state by 
+				      calling #pjsip_evsub_get_state_name().*/
+};
+
+/**
+ * @see pjsip_evsub_state
+ */
+typedef enum pjsip_evsub_state pjsip_evsub_state;
+
+
+
+/**
+ * This structure describes callback that is registered by application or
+ * package to receive notifications about subscription events.
+ */
+struct pjsip_evsub_user
+{
+    /**
+     * This callback is called when subscription state has changed.
+     * Application MUST be prepared to receive NULL event and events with
+     * type other than PJSIP_EVENT_TSX_STATE
+     *
+     * This callback is OPTIONAL.
+     *
+     * @param sub	The subscription instance.
+     * @param event	The event that has caused the state to change,
+     *			which may be NULL or may have type other than
+     *			PJSIP_EVENT_TSX_STATE.
+     */
+    void (*on_evsub_state)( pjsip_evsub *sub, pjsip_event *event);
+
+    /**
+     * This callback is called when transaction state has changed.
+     *
+     * @param sub	The subscription instance.
+     * @param tsx	Transaction.
+     * @param event	The event.
+     */
+    void (*on_tsx_state)(pjsip_evsub *sub, pjsip_transaction *tsx,
+			 pjsip_event *event);
+
+    /**
+     * This callback is called when incoming SUBSCRIBE (or any method that
+     * establishes the subscription in the first place) is received. It 
+     * allows application to specify what response should be sent to 
+     * remote, along with additional headers and message body to be put 
+     * in the response.
+     *
+     * This callback is OPTIONAL.
+     *
+     * However, implementation MUST send NOTIFY request upon receiving this
+     * callback. The suggested behavior is to call 
+     * #pjsip_evsub_last_notify(), since this function takes care
+     * about unsubscription request and calculates the appropriate expiration
+     * interval.
+     */
+    void (*on_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);
+
+    /**
+     * This callback is called when client/subscriber received incoming
+     * NOTIFY request. It allows the application to specify what response
+     * should be sent to remote, along with additional headers and message
+     * body to be put in the response.
+     *
+     * This callback is OPTIONAL. When it is not implemented, the default
+     * behavior is to respond incoming NOTIFY request with 200 (OK).
+     *
+     * @param sub	The subscription instance.
+     * @param rdata	The received NOTIFY request.
+     * @param p_st_code	Application MUST set the value of this argument with
+     *			final status code (200-699) upon returning from the
+     *			callback.
+     * @param p_st_text	Custom status text, if any.
+     * @param res_hdr	Upon return, application can put additional headers 
+     *			to be sent in the response in this list.
+     * @param p_body	Application MAY specify message body to be sent in
+     *			the response.
+     */
+    void (*on_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);
+
+    /**
+     * This callback is called when it is time for the client to refresh
+     * the subscription.
+     *
+     * This callback is OPTIONAL. When it is not implemented, the default 
+     * behavior is to refresh subscription by sending SUBSCRIBE with the
+     * interval set to current/last interval.
+     *
+     * @param sub	The subscription instance.
+     */
+    void (*on_client_refresh)(pjsip_evsub *sub);
+
+    /**
+     * This callback is called when server doesn't receive subscription 
+     * refresh after the specified subscription interval.
+     *
+     * This callback is OPTIONAL. When it is not implemented, the default
+     * behavior is to send NOTIFY to terminate the subscription.
+     */
+    void (*on_server_timeout)(pjsip_evsub *sub);
+
+};
+
+
+/**
+ * @see pjsip_evsub_user
+ */
+typedef struct pjsip_evsub_user pjsip_evsub_user;
+
+
+/**
+ * SUBSCRIBE method constant.
+ */
+extern const pjsip_method pjsip_subscribe_method;
+
+/**
+ * NOTIFY method constant.
+ */
+extern const pjsip_method pjsip_notify_method;
+
+
+
+/**
+ * Initialize the event subscription module and register the module to the 
+ * specified endpoint.
+ *
+ * @param endpt		The endpoint instance.
+ *
+ * @return		PJ_SUCCESS if module can be created and registered
+ *			successfully.
+ */
+PJ_DECL(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt);
+
+
+/**
+ * Get the event subscription module instance that was previously created 
+ * and registered to endpoint.
+ *
+ * @return		The event subscription module instance.
+ */
+PJ_DECL(pjsip_module*) pjsip_evsub_instance(void);
+
+
+/**
+ * Register event package to the event subscription framework.
+ *
+ * @param pkg_mod	The module that implements the event package being
+ *			registered.
+ * @param event_name	Event package identification.
+ * @param expires	Default subscription expiration time, in seconds.
+ * @param accept_cnt	Number of strings in Accept array.
+ * @param accept	Array of Accept value.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_evsub_register_pkg( pjsip_module *pkg_mod,
+					       const pj_str_t *event_name,
+					       unsigned expires,
+					       unsigned accept_cnt,
+					       const pj_str_t accept[]);
+
+
+/**
+ * Create client subscription session.
+ *
+ * @param dlg		The underlying dialog to use.
+ * @param user_cb	Callback to receive event subscription notifications.
+ * @param event		Event name.
+ * @param p_evsub	Pointer to receive event subscription instance.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg,
+					     const pjsip_evsub_user *user_cb,
+					     const pj_str_t *event,
+					     pjsip_evsub **p_evsub);
+
+/**
+ * Create server subscription session.
+ *
+ * @param dlg		The underlying dialog to use.
+ * @param user_cb	Callback to receive event subscription notifications.
+ * @param rdata		The incoming request that creates the event 
+ *			subscription, such as SUBSCRIBE or REFER.
+ * @param p_evsub	Pointer to receive event subscription instance.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg,
+					     const pjsip_evsub_user *user_cb,
+					     pjsip_rx_data *rdata,
+					     pjsip_evsub **p_evsub);
+
+
+/**
+ * Get subscription state.
+ *
+ * @param sub		Event subscription instance.
+ *
+ * @return		Subscription state.
+ */
+PJ_DECL(pjsip_evsub_state) pjsip_evsub_get_state(pjsip_evsub *sub);
+
+
+/**
+ * Get the string representation of the subscription state.
+ *
+ * @param sub		Event subscription instance.
+ *
+ * @return		NULL terminated string.
+ */
+PJ_DECL(const char*) pjsip_evsub_get_state_name(pjsip_evsub *sub);
+
+
+/**
+ * Call this function to create request to initiate subscription, to 
+ * refresh subcription, or to request subscription termination.
+ *
+ * @param sub		Client subscription instance.
+ * @param method	The method that establishes the subscription, such as
+ *			SUBSCRIBE or REFER. If this argument is NULL, then
+ *			SUBSCRIBE will be used.
+ * @param expires	Subscription expiration. If the value is set to zero,
+ *			this will request unsubscription. If the value is
+ *			negative, default expiration as defined by the package
+ *			will be used.
+ * @param p_tdata	Pointer to receive the request.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_evsub_initiate( pjsip_evsub *sub,
+					   const pjsip_method *method,
+					   pj_int32_t expires,
+					   pjsip_tx_data **p_tdata);
+
+
+/**
+ * Accept the incoming subscription request by sending 2xx response to
+ * incoming SUBSCRIBE request.
+ *
+ * @param sub		Server subscription instance.
+ * @param rdata		The incoming subscription request message.
+ * @param st_code	Status code, which MUST be final response.
+ * @param hdr_list	Optional list of headers to be added in the response.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_evsub_accept( pjsip_evsub *sub,
+					 pjsip_rx_data *rdata,
+				         int st_code,
+					 const pjsip_hdr *hdr_list );
+
+
+/**
+ * For notifier, create 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 state_str	The state string name, if state contains value other
+ *			than active, pending, or terminated. Otherwise this
+ *			argument is ignored.
+ * @param reason	Specify reason if new state is terminated, otherwise
+ *			put NULL.
+ * @param p_tdata	Pointer to receive request message.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_evsub_notify( pjsip_evsub *sub,
+					 pjsip_evsub_state state,
+					 const pj_str_t *state_str,
+					 const pj_str_t *reason,
+					 pjsip_tx_data **p_tdata);
+
+
+/**
+ * For notifier, create a NOTIFY request that reflects current subscription
+ * status.
+ *
+ * @param sub		The server subscription instance.
+ * @param p_tdata	Pointer to receive the request messge.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_evsub_current_notify( pjsip_evsub *sub,
+						 pjsip_tx_data **p_tdata );
+
+
+
+/**
+ * Send request message.
+ *
+ * @param sub		The event subscription object.
+ * @param tdata		Request message to be send.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_evsub_send_request( pjsip_evsub *sub,
+					       pjsip_tx_data *tdata);
+
+
+
+/**
+ * Get the event subscription instance in the transaction.
+ *
+ * @param tsx		The transaction.
+ *
+ * @return		The event subscription instance registered in the
+ *			transaction, if any.
+ */
+PJ_DECL(pjsip_evsub*) pjsip_tsx_get_evsub(pjsip_transaction *tsx);
+
+
+/**
+ * Set event subscription's module data.
+ *
+ * @param sub		The event subscription.
+ * @param index		The module id.
+ * @param data		Arbitrary data.
+ */
+PJ_DECL(void) pjsip_evsub_set_mod_data( pjsip_evsub *sub, unsigned mod_id,
+				        void *data );
+
+
+/**
+ * Get event subscription's module data.
+ *
+ * @param sub		The event subscription.
+ * @param mod_id	The module id.
+ *
+ * @return		Data previously set at the specified id.
+ */
+PJ_DECL(void*) pjsip_evsub_get_mod_data( pjsip_evsub *sub, unsigned mod_id );
+
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+#endif	/* __PJSIP_SIMPLE_EVSUB_H__ */
Index: pjproject/trunk/pjsip/include/pjsip-simple/evsub_msg.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-simple/evsub_msg.h	(revision 197)
+++ pjproject/trunk/pjsip/include/pjsip-simple/evsub_msg.h	(revision 197)
@@ -0,0 +1,112 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#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. */
+    pjsip_param	    other_param;    /**< Other parameter. */
+} 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 pjsip_generic_array_hdr 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. */
+    pjsip_param	    other_param;	/**< Other parameters. */
+} 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_evsub_init_parser(void);
+
+
+PJ_END_DECL
+
+
+/**
+ * @}
+ */
+
+#endif	/* __PJSIP_SIMPLE_EVENT_NOTIFY_MSG_H__ */
+
Index: pjproject/trunk/pjsip/include/pjsip-simple/messaging.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-simple/messaging.h	(revision 196)
+++ 	(revision )
@@ -1,268 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
- */
-#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
Index: pjproject/trunk/pjsip/include/pjsip-simple/pidf.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-simple/pidf.h	(revision 196)
+++ pjproject/trunk/pjsip/include/pjsip-simple/pidf.h	(revision 197)
@@ -24,6 +24,6 @@
  * @brief PIDF/Presence Information Data Format (RFC 3863)
  */
-#include <pj/types.h>
-#include <pj/xml.h>
+#include <pjsip-simple/types.h>
+#include <pjlib-util/xml.h>
 
 PJ_BEGIN_DECL
Index: pjproject/trunk/pjsip/include/pjsip-simple/presence.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-simple/presence.h	(revision 196)
+++ pjproject/trunk/pjsip/include/pjsip-simple/presence.h	(revision 197)
@@ -24,7 +24,7 @@
  * @brief SIP Extension for Presence (RFC 3856)
  */
-#include <pjsip_simple/event_notify.h>
-#include <pjsip_simple/pidf.h>
-#include <pjsip_simple/xpidf.h>
+#include <pjsip-simple/evsub.h>
+#include <pjsip-simple/pidf.h>
+#include <pjsip-simple/xpidf.h>
 
 
@@ -39,183 +39,204 @@
  * 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"
+ * (evsub.h) and extends the framework by implementing "presence"
  * event package.
  */
 
-/**
- * Presence message body type.
- */
-typedef enum pjsip_pres_type
+
+
+/**
+ * Initialize the presence module and register it as endpoint module and
+ * package to the event subscription module.
+ *
+ * @param endpt		The endpoint instance.
+ * @param mod_evsub	The event subscription module instance.
+ *
+ * @return		PJ_SUCCESS if the module is successfully 
+ *			initialized and registered to both endpoint
+ *			and the event subscription module.
+ */
+PJ_DECL(pj_status_t) pjsip_pres_init_module(pjsip_endpoint *endpt,
+					    pjsip_module *mod_evsub);
+
+
+/**
+ * Get the presence module instance.
+ *
+ * @return		The presence module instance.
+ */
+PJ_DECL(pjsip_module*) pjsip_pres_instance(void);
+
+
+#define PJSIP_PRES_STATUS_MAX_INFO  8
+
+/**
+ * This structure describes presence status of a presentity.
+ */
+struct pjsip_pres_status
 {
-    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 );
+    unsigned		info_cnt;	/**< Number of info in the status.  */
+    struct {
+
+	pj_bool_t	basic_open;	/**< Basic status/availability.	    */
+	pj_str_t	id;		/**< Tuple id.			    */
+	pj_str_t	contact;	/**< Optional contact address.	    */
+
+    } info[PJSIP_PRES_STATUS_MAX_INFO];	/**< Array of info.		    */
+
+    pj_bool_t		_is_valid;	/**< Internal flag.		    */
+};
+
+
+/**
+ * @see pjsip_pres_status
+ */
+typedef struct pjsip_pres_status pjsip_pres_status;
+
+
+/**
+ * Create presence client subscription session.
+ *
+ * @param dlg		The underlying dialog to use.
+ * @param user_cb	Pointer to callbacks to receive presence subscription
+ *			events.
+ * @param p_evsub	Pointer to receive the presence subscription
+ *			session.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg,
+					    const pjsip_evsub_user *user_cb,
+					    pjsip_evsub **p_evsub );
+
+
+/**
+ * Create presence server subscription session.
+ *
+ * @param dlg		The underlying dialog to use.
+ * @param user_cb	Pointer to callbacks to receive presence subscription
+ *			events.
+ * @param rdata		The incoming SUBSCRIBE request that creates the event 
+ *			subscription.
+ * @param p_evsub	Pointer to receive the presence subscription
+ *			session.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_pres_create_uas( pjsip_dialog *dlg,
+					    const pjsip_evsub_user *user_cb,
+					    pjsip_rx_data *rdata,
+					    pjsip_evsub **p_evsub );
+
+
+/**
+ * Call this function to create request to initiate presence subscription, to 
+ * refresh subcription, or to request subscription termination.
+ *
+ * @param sub		Client subscription instance.
+ * @param expires	Subscription expiration. If the value is set to zero,
+ *			this will request unsubscription.
+ * @param p_tdata	Pointer to receive the request.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_pres_initiate( pjsip_evsub *sub,
+					  pj_int32_t expires,
+					  pjsip_tx_data **p_tdata);
+
+
+
+/**
+ * Accept the incoming subscription request by sending 2xx response to
+ * incoming SUBSCRIBE request.
+ *
+ * @param sub		Server subscription instance.
+ * @param rdata		The incoming subscription request message.
+ * @param st_code	Status code, which MUST be final response.
+ * @param hdr_list	Optional list of headers to be added in the response.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_pres_accept( pjsip_evsub *sub,
+					pjsip_rx_data *rdata,
+				        int st_code,
+					const pjsip_hdr *hdr_list );
+
+
+
+
+/**
+ * For notifier, create NOTIFY request to subscriber, and set the state 
+ * of the subscription. Application MUST set the presence status to the
+ * appropriate state (by calling #pjsip_pres_set_status()) before calling
+ * this function.
+ *
+ * @param sub		The server subscription (notifier) instance.
+ * @param state		New state to set.
+ * @param state_str	The state string name, if state contains value other
+ *			than active, pending, or terminated. Otherwise this
+ *			argument is ignored.
+ * @param reason	Specify reason if new state is terminated, otherwise
+ *			put NULL.
+ * @param p_tdata	Pointer to receive the request.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(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);
+
+
+/**
+ * Create NOTIFY request to reflect current subscription status.
+ *
+ * @param sub		Server subscription object.
+ * @param p_tdata	Pointer to receive request.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_pres_current_notify( pjsip_evsub *sub,
+					        pjsip_tx_data **p_tdata );
+
+
+
+/**
+ * Send request.
+ *
+ * @param sub		The subscription object.
+ * @param tdata		Request message to be sent.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_pres_send_request( pjsip_evsub *sub,
+					      pjsip_tx_data *tdata );
+
+
+/**
+ * Get the presence status. Client normally would call this function
+ * after receiving NOTIFY request from server.
+ *
+ * @param sub		The client or server subscription.
+ * @param status	The structure to receive presence status.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_pres_get_status( pjsip_evsub *sub,
+					    pjsip_pres_status *status );
+
+
+/**
+ * Set the presence status. This operation is only valid for server
+ * subscription. After calling this function, application would need to
+ * send NOTIFY request to client.
+ *
+ * @param sub		The server subscription.
+ * @param status	Status to be set.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_pres_set_status( pjsip_evsub *sub,
+					    const pjsip_pres_status *status );
 
 
Index: pjproject/trunk/pjsip/include/pjsip-simple/types.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-simple/types.h	(revision 197)
+++ pjproject/trunk/pjsip/include/pjsip-simple/types.h	(revision 197)
@@ -0,0 +1,30 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJSIP_SIMPLE_TYPES_H__
+#define __PJSIP_SIMPLE_TYPES_H__
+
+#include <pjsip/sip_types.h>
+
+
+#define PJSIP_EVSUB_POOL_LEN	4000
+#define PJSIP_EVSUB_POOL_INC	4000
+
+
+#endif	/* __PJSIP_SIMPLE_TYPES_H__ */
+
Index: pjproject/trunk/pjsip/include/pjsip-simple/xpidf.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-simple/xpidf.h	(revision 196)
+++ pjproject/trunk/pjsip/include/pjsip-simple/xpidf.h	(revision 197)
@@ -24,6 +24,6 @@
  * @brief XPIDF/Presence Information Data Format
  */
-#include <pj/types.h>
-#include <pj/xml.h>
+#include <pjsip-simple/types.h>
+#include <pjlib-util/xml.h>
 
 PJ_BEGIN_DECL
Index: pjproject/trunk/pjsip/include/pjsip/sip_config.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip/sip_config.h	(revision 196)
+++ pjproject/trunk/pjsip/include/pjsip/sip_config.h	(revision 197)
@@ -34,6 +34,6 @@
 #define PJSIP_POOL_LEN_TRANSPORT	512
 #define PJSIP_POOL_INC_TRANSPORT	512
-#define PJSIP_POOL_LEN_TDATA		2500
-#define PJSIP_POOL_INC_TDATA		512
+#define PJSIP_POOL_LEN_TDATA		4000
+#define PJSIP_POOL_INC_TDATA		4000
 #define PJSIP_POOL_LEN_UA		(64 + 32*PJSIP_MAX_DIALOG_COUNT)
 #define PJSIP_POOL_INC_UA		0
@@ -80,5 +80,5 @@
 
 /* Module related constants. */
-#define PJSIP_MAX_MODULE		8
+#define PJSIP_MAX_MODULE		16
 
 /* Maximum header types. */
Index: pjproject/trunk/pjsip/include/pjsip/sip_errno.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip/sip_errno.h	(revision 196)
+++ pjproject/trunk/pjsip/include/pjsip/sip_errno.h	(revision 197)
@@ -208,4 +208,9 @@
  */
 #define PJSIP_ETSXDESTROYED     (PJSIP_ERRNO_START_PJSIP + 70)	/* 171070 */
+/**
+ * @hideinitializer
+ * No transaction.
+ */
+#define PJSIP_ENOTSX		(PJSIP_ERRNO_START_PJSIP + 71)	/* 171071 */
 
 
Index: pjproject/trunk/pjsip/include/pjsip/sip_msg.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip/sip_msg.h	(revision 196)
+++ pjproject/trunk/pjsip/include/pjsip/sip_msg.h	(revision 197)
@@ -412,5 +412,5 @@
     PJSIP_SC_REQUEST_TERMINATED = 487,
     PJSIP_SC_NOT_ACCEPTABLE_HERE = 488,
-    PJSIP_SC_UNKNOWN_EVENT = 489,
+    PJSIP_SC_BAD_EVENT = 489,
     PJSIP_SC_REQUEST_UPDATED = 490,
     PJSIP_SC_REQUEST_PENDING = 491,
@@ -499,5 +499,5 @@
  * must fill in all members of this structure. 
  */
-typedef struct pjsip_msg_body
+struct pjsip_msg_body
 {
     /** MIME content type. 
@@ -566,5 +566,5 @@
     void* (*clone_data)(pj_pool_t *pool, const void *data, unsigned len);
 
-} pjsip_msg_body;
+};
 
 /**
Index: pjproject/trunk/pjsip/include/pjsip/sip_types.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip/sip_types.h	(revision 196)
+++ pjproject/trunk/pjsip/include/pjsip/sip_types.h	(revision 197)
@@ -93,4 +93,9 @@
 
 /**
+ * Forward declaration for message body (sip_msg.h).
+ */
+typedef struct pjsip_msg_body pjsip_msg_body;
+
+/**
  * Forward declaration for header field (sip_msg.h).
  */
@@ -144,6 +149,12 @@
 typedef enum pjsip_role_e
 {
-    PJSIP_ROLE_UAC,	/**< Transaction role is UAC. */
-    PJSIP_ROLE_UAS,	/**< Transaction role is UAS. */
+    PJSIP_ROLE_UAC,	/**< Role is UAC. */
+    PJSIP_ROLE_UAS,	/**< Role is UAS. */
+
+    /* Alias: */
+
+    PJSIP_UAC_ROLE = PJSIP_ROLE_UAC,	/**< Role is UAC. */
+    PJSIP_UAS_ROLE = PJSIP_ROLE_UAS,	/**< Role is UAS. */
+
 } pjsip_role_e;
 
Index: pjproject/trunk/pjsip/include/pjsip_simple.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip_simple.h	(revision 196)
+++ pjproject/trunk/pjsip/include/pjsip_simple.h	(revision 197)
@@ -34,8 +34,8 @@
 #define __PJSIP_SIMPLE_H__
 
-#include <pjsip_simple/messaging.h>
-#include <pjsip_simple/event_notify.h>
-#include <pjsip_simple/pidf.h>
-#include <pjsip_simple/presence.h>
+#include <pjsip-simple/evsub.h>
+#include <pjsip-simple/presence.h>
+#include <pjsip-simple/pidf.h>
+#include <pjsip-simple/xpidf.h>
 
 #endif	/* __PJSIP_SIMPLE_H__ */
Index: pjproject/trunk/pjsip/src/pjsip-simple/event_notify.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-simple/event_notify.c	(revision 196)
+++ 	(revision )
@@ -1,1644 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
- */
-#include <pjsip_simple/event_notify.h>
-#include <pjsip/sip_msg.h>
-#include <pjsip/sip_util.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_CATCH_ANY {
-	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): caught exception %d during init", 
-			     sub, state[sub->state].ptr, PJ_GET_EXCEPTION()));
-
-	pjsip_tx_data_dec_ref(tdata);
-	pj_mutex_destroy(sub->mutex);
-	pjsip_endpt_destroy_pool(endpt, sub->pool);
-	return NULL;
-    }
-    PJ_END;
-
-    /* All set, delete temporary transmit data as we don't need it. */
-    pjsip_tx_data_dec_ref(tdata);
-
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): client created, target=%.*s, event=%.*s",
-			 sub, state[sub->state].ptr,
-			 to->slen, to->ptr, event->slen, event->ptr));
-
-    return sub;
-}
-
-/*
- * Set credentials.
- */
-PJ_DEF(pj_status_t) pjsip_event_sub_set_credentials( pjsip_event_sub *sub,
-						     int count,
-						     const pjsip_cred_info cred[])
-{
-    pj_mutex_lock(sub->mutex);
-    if (count > 0) {
-	sub->cred_info = pj_pool_alloc(sub->pool, count*sizeof(pjsip_cred_info));
-	pj_memcpy( sub->cred_info, cred, count*sizeof(pjsip_cred_info));
-    }
-    sub->cred_cnt = count;
-    pj_mutex_unlock(sub->mutex);
-    return 0;
-}
-
-/*
- * Set route-set.
- */
-PJ_DEF(pj_status_t) pjsip_event_sub_set_route_set( pjsip_event_sub *sub,
-						   const pjsip_route_hdr *route_set )
-{
-    const pjsip_route_hdr *hdr;
-
-    pj_mutex_lock(sub->mutex);
-
-    /* Clear existing route set. */
-    pj_list_init(&sub->route_set);
-
-    /* Duplicate route headers. */
-    hdr = route_set->next;
-    while (hdr != route_set) {
-	pjsip_route_hdr *new_hdr = pjsip_hdr_clone(sub->pool, hdr);
-	pj_list_insert_before(&sub->route_set, new_hdr);
-	hdr = hdr->next;
-    }
-
-    pj_mutex_unlock(sub->mutex);
-
-    return 0;
-}
-
-/*
- * Send subscribe request.
- */
-PJ_DEF(pj_status_t) pjsip_event_sub_subscribe( pjsip_event_sub *sub )
-{
-    pj_status_t status;
-
-    pj_mutex_lock(sub->mutex);
-    status = send_sub_refresh(sub);
-    pj_mutex_unlock(sub->mutex);
-
-    return status;
-}
-
-/*
- * Destroy subscription.
- * If there are pending transactions, then this will just set the flag.
- */
-PJ_DEF(pj_status_t) pjsip_event_sub_destroy(pjsip_event_sub *sub)
-{
-    pj_assert(sub != NULL);
-    if (sub == NULL)
-	return -1;
-
-    /* Application must terminate the subscription first. */
-    pj_assert(sub->state == PJSIP_EVENT_SUB_STATE_NULL ||
-	      sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED);
-
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): about to be destroyed", 
-			 sub, state[sub->state].ptr));
-
-    pj_mutex_lock(mgr.mutex);
-    pj_mutex_lock(sub->mutex);
-
-    /* Set delete flag. */
-    sub->delete_flag = 1;
-
-    /* Unregister timer, if any. */
-    if (sub->timer.id != 0) {
-	pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
-	sub->timer.id = 0;
-    }
-
-    if (sub->pending_tsx > 0) {
-	pj_mutex_unlock(sub->mutex);
-	pj_mutex_unlock(mgr.mutex);
-	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): has %d pending, will destroy later",
-			     sub, state[sub->state].ptr,
-			     sub->pending_tsx));
-	return 1;
-    }
-
-    /* Unregister from hash table. */
-    pj_hash_set(sub->pool, mgr.ht, sub->key.ptr, sub->key.slen, NULL);
-
-    /* Destroy. */
-    pj_mutex_destroy(sub->mutex);
-    pjsip_endpt_destroy_pool(sub->endpt, sub->pool);
-
-    pj_mutex_unlock(mgr.mutex);
-
-    PJ_LOG(4,(THIS_FILE, "event_sub%p: destroyed", sub));
-    return 0;
-}
-
-/* Change state. */
-static void sub_set_state( pjsip_event_sub *sub, int new_state)
-{
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): changed state to %s",
-	      sub, state[sub->state].ptr, state[new_state].ptr));
-    sub->state = new_state;
-    sub->state_str = state[new_state];
-}
-
-/*
- * Refresh subscription.
- */
-static pj_status_t send_sub_refresh( pjsip_event_sub *sub )
-{
-    pjsip_tx_data *tdata;
-    pj_status_t status;
-    const pjsip_route_hdr *route;
-
-    pj_assert(sub->role == PJSIP_ROLE_UAC);
-    pj_assert(sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED);
-    if (sub->role != PJSIP_ROLE_UAC || 
-	sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED)
-    {
-	return -1;
-    }
-
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): refreshing subscription", 
-			 sub, state[sub->state].ptr));
-
-    /* Create request. */
-    tdata = pjsip_endpt_create_request_from_hdr( sub->endpt, 
-						 &SUBSCRIBE,
-						 sub->to->uri,
-						 sub->from, sub->to, 
-						 sub->contact, sub->call_id,
-						 sub->cseq++,
-						 NULL);
-
-    if (!tdata) {
-	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): refresh: unable to create tx data!",
-			     sub, state[sub->state].ptr));
-	return -1;
-    }
-
-    pjsip_msg_add_hdr( tdata->msg, 
-		       pjsip_hdr_shallow_clone(tdata->pool, sub->event));
-    pjsip_msg_add_hdr( tdata->msg, 
-		       pjsip_hdr_shallow_clone(tdata->pool, sub->uac_expires));
-    pjsip_msg_add_hdr( tdata->msg, 
-		       pjsip_hdr_shallow_clone(tdata->pool, sub->local_accept));
-    pjsip_msg_add_hdr( tdata->msg, 
-		       pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events));
-
-    /* Authentication */
-    pjsip_auth_init_req( sub->pool, tdata, &sub->auth_sess,
-			 sub->cred_cnt, sub->cred_info);
-
-    /* Route set. */
-    route = sub->route_set.next;
-    while (route != &sub->route_set) {
-	pj_list_insert_before( &tdata->msg->hdr,
-			       pjsip_hdr_shallow_clone(tdata->pool, route));
-	route = route->next;
-    }
-
-    /* Send */
-    status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub, 
-				       &on_subscribe_response);
-    if (status == 0) {
-	sub->pending_tsx++;
-    } else {
-	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): FAILED to refresh subscription!", 
-			     sub, state[sub->state].ptr));
-    }
-
-    return status;
-}
-
-/*
- * Stop subscription.
- */
-PJ_DEF(pj_status_t) pjsip_event_sub_unsubscribe( pjsip_event_sub *sub )
-{
-    pjsip_tx_data *tdata;
-    const pjsip_route_hdr *route;
-    pj_status_t status;
-
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): unsubscribing...", 
-			 sub, state[sub->state].ptr));
-
-    /* Lock subscription. */
-    pj_mutex_lock(sub->mutex);
-
-    pj_assert(sub->role == PJSIP_ROLE_UAC);
-
-    /* Kill refresh timer, if any. */
-    if (sub->timer.id != 0) {
-	sub->timer.id = 0;
-	pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
-    }
-
-    /* Create request. */
-    tdata = pjsip_endpt_create_request_from_hdr( sub->endpt, 
-						 &SUBSCRIBE,
-						 sub->to->uri,
-						 sub->from, sub->to, 
-						 sub->contact, sub->call_id,
-						 sub->cseq++,
-						 NULL);
-
-    if (!tdata) {
-	pj_mutex_unlock(sub->mutex);
-	return -1;
-    }
-
-    /* Add headers to request. */
-    pjsip_msg_add_hdr( tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, sub->event));
-    sub->uac_expires->ivalue = 0;
-    pjsip_msg_add_hdr( tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, sub->uac_expires));
-
-    /* Add authentication. */
-    pjsip_auth_init_req( sub->pool, tdata, &sub->auth_sess,
-			 sub->cred_cnt, sub->cred_info);
-
-
-    /* Route set. */
-    route = sub->route_set.next;
-    while (route != &sub->route_set) {
-	pj_list_insert_before( &tdata->msg->hdr,
-			       pjsip_hdr_shallow_clone(tdata->pool, route));
-	route = route->next;
-    }
-
-    /* Prevent timer from refreshing itself. */
-    sub->default_interval = 0;
-
-    /* Set state. */
-    sub_set_state( sub, PJSIP_EVENT_SUB_STATE_TERMINATED );
-
-    /* Send the request. */
-    status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub, 
-				       &on_subscribe_response);
-    if (status == 0) {
-	sub->pending_tsx++;
-    }
-
-    pj_mutex_unlock(sub->mutex);
-
-    if (status != 0) {
-	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): FAILED to unsubscribe!", 
-			     sub, state[sub->state].ptr));
-    }
-
-    return status;
-}
-
-/*
- * Send notify.
- */
-PJ_DEF(pj_status_t) pjsip_event_sub_notify(pjsip_event_sub *sub,
-					   pjsip_event_sub_state new_state,
-					   const pj_str_t *reason,
-					   pjsip_msg_body *body)
-{
-    pjsip_tx_data *tdata;
-    pjsip_sub_state_hdr *ss_hdr;
-    const pjsip_route_hdr *route;
-    pj_time_val now;
-    pj_status_t status;
-    pjsip_event_sub_state old_state = sub->state;
-
-    pj_gettimeofday(&now);
-
-    pj_assert(sub->role == PJSIP_ROLE_UAS);
-    if (sub->role != PJSIP_ROLE_UAS)
-	return -1;
-
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): sending NOTIFY", 
-			 sub, state[new_state].ptr));
-
-    /* Lock subscription. */
-    pj_mutex_lock(sub->mutex);
-
-    /* Can not send NOTIFY if current state is NULL. We can accept TERMINATED. */
-    if (sub->state==PJSIP_EVENT_SUB_STATE_NULL) {
-	pj_assert(0);
-	pj_mutex_unlock(sub->mutex);
-	return -1;
-    }
-
-    /* Update state no matter what. */
-    sub_set_state(sub, new_state);
-
-    /* Create transmit data. */
-    tdata = pjsip_endpt_create_request_from_hdr( sub->endpt,
-						 &NOTIFY,
-						 sub->to->uri,
-						 sub->from, sub->to,
-						 sub->contact, sub->call_id,
-						 sub->cseq++,
-						 NULL);
-    if (!tdata) {
-	pj_mutex_unlock(sub->mutex);
-	return -1;
-    }
-
-    /* Add Event header. */
-    pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, sub->event));
-
-    /* Add Subscription-State header. */
-    ss_hdr = pjsip_sub_state_hdr_create(tdata->pool);
-    ss_hdr->sub_state = state[new_state];
-    ss_hdr->expires_param = sub->expiry_time.sec - now.sec;
-    if (ss_hdr->expires_param < 0)
-	ss_hdr->expires_param = 0;
-    if (reason)
-	pj_strdup(tdata->pool, &ss_hdr->reason_param, reason);
-    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)ss_hdr);
-
-    /* Add Allow-Events header. */
-    pjsip_msg_add_hdr( tdata->msg, 
-		       pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events));
-
-    /* Add authentication */
-    pjsip_auth_init_req( sub->pool, tdata, &sub->auth_sess,
-			 sub->cred_cnt, sub->cred_info);
-
-    /* Route set. */
-    route = sub->route_set.next;
-    while (route != &sub->route_set) {
-	pj_list_insert_before( &tdata->msg->hdr,
-			       pjsip_hdr_shallow_clone(tdata->pool, route));
-	route = route->next;
-    }
-
-    /* Attach body. */
-    tdata->msg->body = body;
-
-    /* That's it, send! */
-    status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub, &on_notify_response);
-    if (status == 0)
-	sub->pending_tsx++;
-
-    /* If terminated notify application. */
-    if (new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) {
-	if (sub->cb.on_sub_terminated) {
-	    sub->pending_tsx++;
-	    (*sub->cb.on_sub_terminated)(sub, reason);
-	    sub->pending_tsx--;
-	}
-    }
-
-    /* Unlock subscription. */
-    pj_mutex_unlock(sub->mutex);
-
-    if (status != 0) {
-	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): failed to send NOTIFY", 
-			     sub, state[sub->state].ptr));
-    }
-
-    if (sub->delete_flag && sub->pending_tsx <= 0) {
-	pjsip_event_sub_destroy(sub);
-    }
-    return status;
-}
-
-
-/* If this timer callback is called, it means subscriber hasn't refreshed its
- * subscription on-time. Set the state to terminated. This will also send
- * NOTIFY with Subscription-State set to terminated.
- */
-static void uas_expire_timer_cb( pj_timer_heap_t *timer_heap, pj_timer_entry *entry)
-{
-    pjsip_event_sub *sub = entry->user_data;
-    pj_str_t reason = { "timeout", 7 };
-
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): UAS subscription expired!", 
-			 sub, state[sub->state].ptr));
-
-    pj_mutex_lock(sub->mutex);
-    sub->timer.id = 0;
-
-    if (sub->cb.on_sub_terminated && sub->state!=PJSIP_EVENT_SUB_STATE_TERMINATED) {
-	/* Notify application, but prevent app from destroying the sub. */
-	++sub->pending_tsx;
-	(*sub->cb.on_sub_terminated)(sub, &reason);
-	--sub->pending_tsx;
-    }
-    //pjsip_event_sub_notify( sub, PJSIP_EVENT_SUB_STATE_TERMINATED, 
-    //			    &reason, NULL);
-    pj_mutex_unlock(sub->mutex);
-
-}
-
-/* Schedule notifier expiration. */
-static void sub_schedule_uas_expire( pjsip_event_sub *sub, int sec_delay)
-{
-    pj_time_val delay = { 0, 0 };
-    pj_parsed_time pt;
-
-    if (sub->timer.id != 0)
-	pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
-
-    pj_gettimeofday(&sub->expiry_time);
-    sub->expiry_time.sec += sec_delay;
-
-    sub->timer.id = TIMER_ID_UAS_EXPIRY;
-    sub->timer.user_data = sub;
-    sub->timer.cb = &uas_expire_timer_cb;
-    delay.sec = sec_delay;
-    pjsip_endpt_schedule_timer( sub->endpt, &sub->timer, &delay);
-
-    pj_time_decode(&sub->expiry_time, &pt);
-    PJ_LOG(4,(THIS_FILE, 
-	      "event_sub%p (%s)(UAS): will expire at %02d:%02d:%02d (in %d secs)",
-	      sub, state[sub->state].ptr, pt.hour, pt.min, pt.sec, sec_delay));
-}
-
-/* This timer is called for UAC to refresh the subscription. */
-static void refresh_timer_cb( pj_timer_heap_t *timer_heap, pj_timer_entry *entry)
-{
-    pjsip_event_sub *sub = entry->user_data;
-
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): refresh subscription timer", 
-			 sub, state[sub->state].ptr));
-
-    pj_mutex_lock(sub->mutex);
-    sub->timer.id = 0;
-    send_sub_refresh(sub);
-    pj_mutex_unlock(sub->mutex);
-}
-
-
-/* This will update the UAC's refresh schedule. */
-static void update_next_refresh(pjsip_event_sub *sub, int interval)
-{
-    pj_time_val delay = {0, 0};
-    pj_parsed_time pt;
-
-    if (interval < SECONDS_BEFORE_EXPIRY) {
-	PJ_LOG(4,(THIS_FILE, 
-		  "event_sub%p (%s): expiration delay too short (%d sec)! updated.",
-		  sub, state[sub->state].ptr, interval));
-	interval = SECONDS_BEFORE_EXPIRY;
-    }
-
-    if (sub->timer.id != 0)
-	pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
-
-    sub->timer.id = TIMER_ID_REFRESH;
-    sub->timer.user_data = sub;
-    sub->timer.cb = &refresh_timer_cb;
-    pj_gettimeofday(&sub->expiry_time);
-    delay.sec = interval - SECONDS_BEFORE_EXPIRY;
-    sub->expiry_time.sec += delay.sec;
-
-    pj_time_decode(&sub->expiry_time, &pt);
-    PJ_LOG(4,(THIS_FILE, 
-	      "event_sub%p (%s): will send SUBSCRIBE at %02d:%02d:%02d (in %d secs)",
-	      sub, state[sub->state].ptr, 
-	      pt.hour, pt.min, pt.sec,
-	      delay.sec));
-
-    pjsip_endpt_schedule_timer( sub->endpt, &sub->timer, &delay );
-}
-
-
-/* Find subscription in the hash table. 
- * If found, lock the subscription before returning to caller.
- */
-static pjsip_event_sub *find_sub(pjsip_rx_data *rdata)
-{
-    pj_str_t key;
-    pjsip_role_e role;
-    pjsip_event_sub *sub;
-    pjsip_method *method = &rdata->msg->line.req.method;
-    pj_str_t *tag;
-
-    if (rdata->msg->type == PJSIP_REQUEST_MSG) {
-	if (pjsip_method_cmp(method, &SUBSCRIBE)==0) {
-	    role = PJSIP_ROLE_UAS;
-	    tag = &rdata->to_tag;
-	} else {
-	    pj_assert(pjsip_method_cmp(method, &NOTIFY) == 0);
-	    role = PJSIP_ROLE_UAC;
-	    tag = &rdata->to_tag;
-	}
-    } else {
-	if (pjsip_method_cmp(&rdata->cseq->method, &SUBSCRIBE)==0) {
-	    role = PJSIP_ROLE_UAC;
-	    tag = &rdata->from_tag;
-	} else {
-	    pj_assert(pjsip_method_cmp(method, &NOTIFY) == 0);
-	    role = PJSIP_ROLE_UAS;
-	    tag = &rdata->from_tag;
-	}
-    }
-    create_subscriber_key( &key, rdata->pool, role, &rdata->call_id, tag);
-
-    pj_mutex_lock(mgr.mutex);
-    sub = pj_hash_get(mgr.ht, key.ptr, key.slen);
-    if (sub)
-	pj_mutex_lock(sub->mutex);
-    pj_mutex_unlock(mgr.mutex);
-
-    return sub;
-}
-
-
-/* This function is called when we receive SUBSCRIBE request message 
- * to refresh existing subscription.
- */
-static void on_received_sub_refresh( pjsip_event_sub *sub, 
-				     pjsip_transaction *tsx, pjsip_rx_data *rdata)
-{
-    pjsip_event_hdr *e;
-    pjsip_expires_hdr *expires;
-    pj_str_t hname;
-    int status = 200;
-    pj_str_t reason_phrase = { NULL, 0 };
-    int new_state = sub->state;
-    int old_state = sub->state;
-    int new_interval = 0;
-    pjsip_tx_data *tdata;
-
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): received target refresh", 
-			 sub, state[sub->state].ptr));
-
-    /* Check that the event matches. */
-    hname = pj_str("Event");
-    e = pjsip_msg_find_hdr_by_name( rdata->msg, &hname, NULL);
-    if (!e) {
-	status = 400;
-	reason_phrase = pj_str("Missing Event header");
-	goto send_response;
-    }
-    if (pj_stricmp(&e->event_type, &sub->event->event_type) != 0 ||
-	pj_stricmp(&e->id_param, &sub->event->id_param) != 0)
-    {
-	status = 481;
-	reason_phrase = pj_str("Subscription does not exist");
-	goto send_response;
-    }
-
-    /* Check server state. */
-    if (sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED) {
-	status = 481;
-	reason_phrase = pj_str("Subscription does not exist");
-	goto send_response;
-    }
-
-    /* Check expires header. */
-    expires = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_EXPIRES, NULL);
-    if (!expires) {
-	/*
-	status = 400;
-	reason_phrase = pj_str("Missing Expires header");
-	goto send_response;
-	*/
-	new_interval = sub->default_interval;
-    } else {
-	/* Check that interval is not too short. 
-	 * Note that expires time may be zero (for unsubscription).
-	 */
-	new_interval = expires->ivalue;
-	if (new_interval != 0 && new_interval < SECONDS_BEFORE_EXPIRY) {
-	    status = PJSIP_SC_INTERVAL_TOO_BRIEF;
-	    goto send_response;
-	}
-    }
-
-    /* Update interval. */
-    sub->default_interval = new_interval;
-    pj_gettimeofday(&sub->expiry_time);
-    sub->expiry_time.sec += new_interval;
-
-    /* Update timer only if this is not unsubscription. */
-    if (new_interval > 0) {
-	sub->default_interval = new_interval;
-	sub_schedule_uas_expire( sub, new_interval );
-
-	/* Call callback. */
-	if (sub->cb.on_received_refresh) {
-	    sub->pending_tsx++;
-	    (*sub->cb.on_received_refresh)(sub, rdata);
-	    sub->pending_tsx--;
-	}
-    }
-
-send_response:
-    tdata = pjsip_endpt_create_response( sub->endpt, rdata, status);
-    if (tdata) {
-	if (reason_phrase.slen)
-	    tdata->msg->line.status.reason = reason_phrase;
-
-	/* Add Expires header. */
-	expires = pjsip_expires_hdr_create(tdata->pool);
-	expires->ivalue = sub->default_interval;
-	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)expires);
-
-	if (PJSIP_IS_STATUS_IN_CLASS(status,200)) {
-	    pjsip_msg_add_hdr(tdata->msg, 
-			      pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events));
-	}
-	/* Send down to transaction. */
-	pjsip_tsx_on_tx_msg(tsx, tdata);
-    }
-
-    if (sub->default_interval==0 || !PJSIP_IS_STATUS_IN_CLASS(status,200)) {
-	/* Notify application if sub is terminated. */
-	new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
-	sub_set_state(sub, new_state);
-	if (new_state!=old_state && sub->cb.on_sub_terminated) {
-	    pj_str_t reason = {"", 0};
-	    if (reason_phrase.slen) reason = reason_phrase;
-	    else reason = *pjsip_get_status_text(status);
-
-	    sub->pending_tsx++;
-	    (*sub->cb.on_sub_terminated)(sub, &reason);
-	    sub->pending_tsx--;
-	}
-    }
-
-    pj_mutex_unlock(sub->mutex);
-
-    /* Prefer to call log when we're not holding the mutex. */
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): sent refresh response %s, status=%d", 
-			 sub, state[sub->state].ptr,
-			 (tdata ? tdata->obj_name : "null"), status));
-
-    /* Check if application has requested deletion. */
-    if (sub->delete_flag && sub->pending_tsx <= 0) {
-	pjsip_event_sub_destroy(sub);
-    }
-
-}
-
-
-/* This function is called when we receive SUBSCRIBE request message for 
- * a new subscription.
- */
-static void on_new_subscription( pjsip_transaction *tsx, pjsip_rx_data *rdata )
-{
-    package *pkg;
-    pj_pool_t *pool;
-    pjsip_event_sub *sub = NULL;
-    pj_str_t hname;
-    int status = 200;
-    pj_str_t reason = { NULL, 0 };
-    pjsip_tx_data *tdata;
-    pjsip_expires_hdr *expires;
-    pjsip_accept_hdr *accept;
-    pjsip_event_hdr *evhdr;
-
-    /* Get the Event header. */
-    hname = pj_str("Event");
-    evhdr = pjsip_msg_find_hdr_by_name(rdata->msg, &hname, NULL);
-    if (!evhdr) {
-	status = 400;
-	reason = pj_str("No Event header in request");
-	goto send_response;
-    }
-
-    /* Find corresponding package. 
-     * We don't lock the manager's mutex since we assume the package list
-     * won't change once the application is running!
-     */
-    pkg = mgr.pkg_list.next;
-    while (pkg != &mgr.pkg_list) {
-	if (pj_stricmp(&pkg->event, &evhdr->event_type) == 0)
-	    break;
-	pkg = pkg->next;
-    }
-
-    if (pkg == &mgr.pkg_list) {
-	/* Event type is not supported by any packages! */
-	status = 489;
-	reason = pj_str("Bad Event");
-	goto send_response;
-    }
-
-    /* First check that the Accept specification matches the 
-     * package's Accept types.
-     */
-    accept = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_ACCEPT, NULL);
-    if (accept) {
-	unsigned i;
-	pj_str_t *content_type = NULL;
-
-	for (i=0; i<accept->count && !content_type; ++i) {
-	    int j;
-	    for (j=0; j<pkg->accept_cnt; ++j) {
-		if (pj_stricmp(&accept->values[i], &pkg->accept[j])==0) {
-		    content_type = &pkg->accept[j];
-		    break;
-		}
-	    }
-	}
-
-	if (!content_type) {
-	    status = PJSIP_SC_NOT_ACCEPTABLE_HERE;
-	    goto send_response;
-	}
-    }
-
-    /* Check whether the package wants to accept the subscription. */
-    pj_assert(pkg->cb.on_query_subscribe != NULL);
-    (*pkg->cb.on_query_subscribe)(rdata, &status);
-    if (!PJSIP_IS_STATUS_IN_CLASS(status,200))
-	goto send_response;
-
-    /* Create new subscription record. */
-    pool = pjsip_endpt_create_pool(tsx->endpt, "esub", 
-				   SUB_POOL_SIZE, SUB_POOL_INC);
-    if (!pool) {
-	status = 500;
-	goto send_response;
-    }
-    sub = pj_pool_calloc(pool, 1, sizeof(*sub));
-    sub->pool = pool;
-    sub->mutex = pj_mutex_create(pool, "esub", PJ_MUTEX_RECURSE);
-    if (!sub->mutex) {
-	status = 500;
-	goto send_response;
-    }
-
-    PJ_LOG(4,(THIS_FILE, "event_sub%p: notifier is created.", sub));
-
-    /* Start locking mutex. */
-    pj_mutex_lock(sub->mutex);
-
-    /* Init UAS subscription */
-    sub->endpt = tsx->endpt;
-    sub->role = PJSIP_ROLE_UAS;
-    sub->state = PJSIP_EVENT_SUB_STATE_PENDING;
-    sub->state_str = state[sub->state];
-    pj_list_init(&sub->auth_sess);
-    pj_list_init(&sub->route_set);
-    sub->from = pjsip_hdr_clone(pool, rdata->to);
-    pjsip_fromto_set_from(sub->from);
-    if (sub->from->tag.slen == 0) {
-	pj_create_unique_string(pool, &sub->from->tag);
-	rdata->to->tag = sub->from->tag;
-    }
-    sub->to = pjsip_hdr_clone(pool, rdata->from);
-    pjsip_fromto_set_to(sub->to);
-    sub->contact = pjsip_contact_hdr_create(pool);
-    sub->contact->uri = sub->from->uri;
-    sub->call_id = pjsip_cid_hdr_create(pool);
-    pj_strdup(pool, &sub->call_id->id, &rdata->call_id);
-    sub->cseq = pj_rand() % 0xFFFF;
-    
-    expires = pjsip_msg_find_hdr( rdata->msg, PJSIP_H_EXPIRES, NULL);
-    if (expires) {
-	sub->default_interval = expires->ivalue;
-	if (sub->default_interval > 0 && 
-	    sub->default_interval < SECONDS_BEFORE_EXPIRY) 
-	{
-	    status = 423; /* Interval too short. */
-	    goto send_response;
-	}
-    } else {
-	sub->default_interval = 600;
-    }
-
-    /* Clone Event header. */
-    sub->event = pjsip_hdr_clone(pool, evhdr);
-
-    /* Register to hash table. */
-    create_subscriber_key(&sub->key, pool, PJSIP_ROLE_UAS, &sub->call_id->id,
-			  &sub->from->tag);
-    pj_mutex_lock(mgr.mutex);
-    pj_hash_set(pool, mgr.ht, sub->key.ptr, sub->key.slen, sub);
-    pj_mutex_unlock(mgr.mutex);
-
-    /* Set timer where subscription will expire only when expires<>0. 
-     * Subscriber may send new subscription with expires==0.
-     */
-    if (sub->default_interval != 0) {
-	sub_schedule_uas_expire( sub, sub->default_interval-SECONDS_BEFORE_EXPIRY);
-    }
-
-    /* Notify application. */
-    if (pkg->cb.on_subscribe) {
-	pjsip_event_sub_cb *cb = NULL;
-	sub->pending_tsx++;
-	(*pkg->cb.on_subscribe)(sub, rdata, &cb, &sub->default_interval);
-	sub->pending_tsx--;
-	if (cb == NULL)
-	    pj_memset(&sub->cb, 0, sizeof(*cb));
-	else
-	    pj_memcpy(&sub->cb, cb, sizeof(*cb));
-    }
-
-
-send_response:
-    PJ_LOG(4,(THIS_FILE, "event_sub%p (%s)(UAS): status=%d", 
-			  sub, state[sub->state].ptr, status));
-
-    tdata = pjsip_endpt_create_response( tsx->endpt, rdata, status);
-    if (tdata) {
-	if (reason.slen) {
-	    /* Customize reason text. */
-	    tdata->msg->line.status.reason = reason;
-	}
-	if (PJSIP_IS_STATUS_IN_CLASS(status,200)) {
-	    /* Add Expires header. */
-	    pjsip_expires_hdr *hdr;
-
-	    hdr = pjsip_expires_hdr_create(tdata->pool);
-	    hdr->ivalue = sub->default_interval;
-	    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hdr );
-	}
-	if (status == 423) {
-	    /* Add Min-Expires header. */
-	    pjsip_min_expires_hdr *hdr;
-
-	    hdr = pjsip_min_expires_hdr_create(tdata->pool);
-	    hdr->ivalue = SECONDS_BEFORE_EXPIRY;
-	    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hdr);
-	}
-	if (status == 489 || 
-	    status==PJSIP_SC_NOT_ACCEPTABLE_HERE ||
-	    PJSIP_IS_STATUS_IN_CLASS(status,200)) 
-	{
-	    /* Add Allow-Events header. */
-	    pjsip_hdr *hdr;
-	    hdr = pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events);
-	    pjsip_msg_add_hdr(tdata->msg, hdr);
-
-	    /* Should add Accept header?. */
-	}
-
-	pjsip_tsx_on_tx_msg(tsx, tdata);
-    }
-
-    /* If received new subscription with expires=0, terminate. */
-    if (sub && sub->default_interval == 0) {
-	pj_assert(sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED);
-	if (sub->cb.on_sub_terminated) {
-	    pj_str_t reason = { "timeout", 7 };
-	    (*sub->cb.on_sub_terminated)(sub, &reason);
-	}
-    }
-
-    if (!PJSIP_IS_STATUS_IN_CLASS(status,200) || (sub && sub->delete_flag)) {
-	if (sub && sub->mutex) {
-	    pjsip_event_sub_destroy(sub);
-	} else if (sub) {
-	    pjsip_endpt_destroy_pool(tsx->endpt, sub->pool);
-	}
-    } else {
-	pj_assert(status >= 200);
-	pj_mutex_unlock(sub->mutex);
-    }
-}
-
-/* This is the main callback when SUBSCRIBE request is received. */
-static void on_subscribe_request(pjsip_transaction *tsx, pjsip_rx_data *rdata)
-{
-    pjsip_event_sub *sub = find_sub(rdata);
-
-    if (sub)
-	on_received_sub_refresh(sub, tsx, rdata);
-    else
-	on_new_subscription(tsx, rdata);
-}
-
-
-/* This callback is called when response to SUBSCRIBE is received. */
-static void on_subscribe_response(void *token, pjsip_event *event)
-{
-    pjsip_event_sub *sub = token;
-    pjsip_transaction *tsx = event->obj.tsx;
-    int new_state, old_state = sub->state;
-
-    pj_assert(tsx->status_code >= 200);
-    if (tsx->status_code < 200)
-	return;
-
-    pj_assert(sub->role == PJSIP_ROLE_UAC);
-
-    /* Lock mutex. */
-    pj_mutex_lock(sub->mutex);
-
-    /* If request failed with 401/407 error, silently retry the request. */
-    if (tsx->status_code==401 || tsx->status_code==407) {
-	pjsip_tx_data *tdata;
-	tdata = pjsip_auth_reinit_req(sub->endpt,
-				      sub->pool, &sub->auth_sess,
-				      sub->cred_cnt, sub->cred_info,
-				      tsx->last_tx, event->src.rdata );
-	if (tdata) {
-	    int status;
-	    pjsip_cseq_hdr *cseq;
-	    cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
-	    cseq->cseq = sub->cseq++;
-	    status = pjsip_endpt_send_request( sub->endpt, tdata, 
-					       -1, sub, 
-					       &on_subscribe_response);
-	    if (status == 0) {
-		pj_mutex_unlock(sub->mutex);
-		return;
-	    }
-	}
-    }
-
-    if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code,200)) {
-	/* Update To tag. */
-	if (sub->to->tag.slen == 0)
-	    pj_strdup(sub->pool, &sub->to->tag, &event->src.rdata->to_tag);
-
-	new_state = sub->state;
-
-    } else if (tsx->status_code == 481) {
-	new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
-
-    } else if (tsx->status_code >= 300) {
-	/* RFC 3265 Section 3.1.4.2:
-         * If a SUBSCRIBE request to refresh a subscription fails 
-	 * with a non-481 response, the original subscription is still 
-	 * considered valid for the duration of original exires.
-	 *
-	 * Note:
-	 * Since we normally send SUBSCRIBE for refreshing the subscription,
-	 * it means the subscription already expired anyway. So we terminate
-	 * the subscription now.
-	 */
-	if (sub->state != PJSIP_EVENT_SUB_STATE_ACTIVE) {
-	    new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
-	} else {
-	    /* Use this to be compliant with Section 3.1.4.2
-	      new_state = sub->state;
-	     */
-	    new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
-	}
-    } else {
-	pj_assert(0);
-	new_state = sub->state;
-    }
-
-    if (new_state != sub->state && sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED) {
-	sub_set_state(sub, new_state);
-    }
-
-    if (sub->state == PJSIP_EVENT_SUB_STATE_ACTIVE ||
-	sub->state == PJSIP_EVENT_SUB_STATE_PENDING)
-    {
-	/*
-	 * Register timer for next subscription refresh, but only when
-	 * we're not unsubscribing. Also update default_interval and Expires
-	 * header.
-	 */
-	if (sub->default_interval > 0 && !sub->delete_flag) {
-	    pjsip_expires_hdr *exp = NULL;
-	    
-	    /* Could be transaction timeout. */
-	    if (event->src_type == PJSIP_EVENT_RX_MSG) {
-		exp = pjsip_msg_find_hdr(event->src.rdata->msg,
-					 PJSIP_H_EXPIRES, NULL);
-	    }
-
-	    if (exp) {
-		int delay = exp->ivalue;
-		if (delay > 0) {
-		    pj_time_val new_expiry;
-		    pj_gettimeofday(&new_expiry);
-		    new_expiry.sec += delay;
-		    if (sub->timer.id==0 || 
-			new_expiry.sec < sub->expiry_time.sec-SECONDS_BEFORE_EXPIRY/2) 
-		    {
-		    //if (delay > 0 && delay < sub->default_interval) {
-			sub->default_interval = delay;
-			sub->uac_expires->ivalue = delay;
-			update_next_refresh(sub, delay);
-		    }
-		}
-	    }
-	}
-    }
-
-    /* Call callback. */
-    if (!sub->delete_flag) {
-	if (sub->cb.on_received_sub_response) {
-	    (*sub->cb.on_received_sub_response)(sub, event);
-	}
-    }
-
-    /* Notify application if we're terminated. */
-    if (new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) {
-	if (sub->cb.on_sub_terminated) {
-	    pj_str_t reason;
-	    if (event->src_type == PJSIP_EVENT_RX_MSG)
-		reason = event->src.rdata->msg->line.status.reason;
-	    else
-		reason = *pjsip_get_status_text(tsx->status_code);
-
-	    (*sub->cb.on_sub_terminated)(sub, &reason);
-	}
-    }
-
-    /* Decrement pending tsx count. */
-    --sub->pending_tsx;
-    pj_assert(sub->pending_tsx >= 0);
-
-    if (sub->delete_flag && sub->pending_tsx <= 0) {
-	pjsip_event_sub_destroy(sub);
-    } else {
-	pj_mutex_unlock(sub->mutex);
-    }
-
-    /* DO NOT ACCESS sub FROM NOW ON! IT MIGHT HAVE BEEN DELETED */
-}
-
-/*
- * This callback called when we receive incoming NOTIFY request.
- */
-static void on_notify_request(pjsip_transaction *tsx, pjsip_rx_data *rdata)
-{
-    pjsip_event_sub *sub;
-    pjsip_tx_data *tdata;
-    int status = 200;
-    int old_state;
-    pj_str_t reason = { NULL, 0 };
-    pj_str_t reason_phrase = { NULL, 0 };
-    int new_state = PJSIP_EVENT_SUB_STATE_NULL;
-
-    /* Find subscription based on Call-ID and From tag. 
-     * This will also automatically lock the subscription, if it's found.
-     */
-    sub = find_sub(rdata);
-    if (!sub) {
-	/* RFC 3265: Section 3.2 Description of NOTIFY Behavior:
-	 * Answer with 481 Subscription does not exist.
-	 */
-	PJ_LOG(4,(THIS_FILE, "Unable to find subscription for incoming NOTIFY!"));
-	status = 481;
-	reason_phrase = pj_str("Subscription does not exist");
-
-    } else {
-	pj_assert(sub->role == PJSIP_ROLE_UAC);
-	PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): received NOTIFY", 
-			     sub, state[sub->state].ptr));
-
-    }
-
-    new_state = old_state = sub->state;
-
-    /* RFC 3265: Section 3.2.1
-     * Check that the Event header match the subscription. 
-     */
-    if (status == 200) {
-	pjsip_event_hdr *hdr;
-	pj_str_t hname = { "Event", 5 };
-
-	hdr = pjsip_msg_find_hdr_by_name(rdata->msg, &hname, NULL);
-	if (!hdr) {
-	    status = PJSIP_SC_BAD_REQUEST;
-	    reason_phrase = pj_str("No Event header found");
-	} else if (pj_stricmp(&hdr->event_type, &sub->event->event_type) != 0 ||
-		   pj_stricmp(&hdr->id_param, &sub->event->id_param) != 0) 
-	{
-	    status = 481;
-	    reason_phrase = pj_str("Subscription does not exist");
-	}
-    }
-
-    /* Update subscription state and timer. */
-    if (status == 200) {
-	pjsip_sub_state_hdr *hdr;
-	const pj_str_t hname = { "Subscription-State", 18 };
-	const pj_str_t state_active = { "active", 6 },
-		       state_pending = { "pending", 7},
-		       state_terminated = { "terminated", 10 };
-
-	hdr = pjsip_msg_find_hdr_by_name( rdata->msg, &hname, NULL);
-	if (!hdr) {
-	    status = PJSIP_SC_BAD_REQUEST;
-	    reason_phrase = pj_str("No Subscription-State header found");
-	    goto process;
-	} 
-
-	/*
-	 * Update subscription state.
-	 */
-	if (pj_stricmp(&hdr->sub_state, &state_active) == 0) {
-	    if (sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED)
-		new_state = PJSIP_EVENT_SUB_STATE_ACTIVE;
-	} else if (pj_stricmp(&hdr->sub_state, &state_pending) == 0) {
-	    if (sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED)
-		new_state = PJSIP_EVENT_SUB_STATE_PENDING;
-	} else if (pj_stricmp(&hdr->sub_state, &state_terminated) == 0) {
-	    new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
-	} else {
-	    new_state = PJSIP_EVENT_SUB_STATE_UNKNOWN;
-	}
-
-	reason = hdr->reason_param;
-
-	if (new_state != sub->state && new_state != PJSIP_EVENT_SUB_STATE_NULL &&
-	    sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED) 
-	{
-	    sub_set_state(sub, new_state);
-	    if (new_state == PJSIP_EVENT_SUB_STATE_UNKNOWN) {
-		pj_strdup_with_null(sub->pool, &sub->state_str, &hdr->sub_state);
-	    } else {
-		sub->state_str = state[new_state];
-	    }
-	}
-
-	/*
-	 * Update timeout timer in required, just in case notifier changed the 
-         * expiration to shorter time.
-	 * Section 3.2.2: the expires param can only shorten the interval.
-	 */
-	if ((sub->state==PJSIP_EVENT_SUB_STATE_ACTIVE || 
-	     sub->state==PJSIP_EVENT_SUB_STATE_PENDING) && hdr->expires_param > 0) 
-	{
-	    pj_time_val now, new_expiry;
-
-	    pj_gettimeofday(&now);
-	    new_expiry.sec = now.sec + hdr->expires_param;
-	    if (sub->timer.id==0 || 
-		new_expiry.sec < sub->expiry_time.sec-SECONDS_BEFORE_EXPIRY/2) 
-	    {
-		update_next_refresh(sub, hdr->expires_param);
-	    }
-	}
-    }
-
-process:
-    /* Note: here we sub MAY BE NULL! */
-
-    /* Send response to NOTIFY */
-    tdata = pjsip_endpt_create_response( tsx->endpt, rdata, status );
-    if (tdata) {
-	if (reason_phrase.slen)
-	    tdata->msg->line.status.reason = reason_phrase;
-
-	if (PJSIP_IS_STATUS_IN_CLASS(status,200)) {
-	    pjsip_hdr *hdr;
-	    hdr = pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events);
-	    pjsip_msg_add_hdr( tdata->msg, hdr);
-	}
-
-	pjsip_tsx_on_tx_msg(tsx, tdata);
-    }
-
-    /* Call NOTIFY callback, if any. */
-    if (sub && PJSIP_IS_STATUS_IN_CLASS(status,200) && sub->cb.on_received_notify) {
-	sub->pending_tsx++;
-	(*sub->cb.on_received_notify)(sub, rdata);
-	sub->pending_tsx--;
-    }
-
-    /* Check if subscription is terminated and call callback. */
-    if (sub && new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) {
-	if (sub->cb.on_sub_terminated) {
-	    sub->pending_tsx++;
-	    (*sub->cb.on_sub_terminated)(sub, &reason);
-	    sub->pending_tsx--;
-	}
-    }
-
-    /* Check if application has requested deletion. */
-    if (sub && sub->delete_flag && sub->pending_tsx <= 0) {
-	pjsip_event_sub_destroy(sub);
-    } else if (sub) {
-	pj_mutex_unlock(sub->mutex);
-    }
-}
-
-/* This callback is called when we received NOTIFY response. */
-static void on_notify_response(void *token, pjsip_event *event)
-{
-    pjsip_event_sub *sub = token;
-    pjsip_event_sub_state old_state = sub->state;
-    pjsip_transaction *tsx = event->obj.tsx;
-
-    /* Lock the subscription. */
-    pj_mutex_lock(sub->mutex);
-
-    pj_assert(sub->role == PJSIP_ROLE_UAS);
-
-    /* If request failed with authorization failure, silently retry. */
-    if (tsx->status_code==401 || tsx->status_code==407) {
-	pjsip_tx_data *tdata;
-	tdata = pjsip_auth_reinit_req(sub->endpt,
-				      sub->pool, &sub->auth_sess,
-				      sub->cred_cnt, sub->cred_info,
-				      tsx->last_tx, event->src.rdata );
-	if (tdata) {
-	    int status;
-	    pjsip_cseq_hdr *cseq;
-	    cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
-	    cseq->cseq = sub->cseq++;
-	    status = pjsip_endpt_send_request( sub->endpt, tdata, 
-					       -1, sub, 
-					       &on_notify_response);
-	    if (status == 0) {
-		pj_mutex_unlock(sub->mutex);
-		return;
-	    }
-	}
-    }
-
-    /* Notify application. */
-    if (sub->cb.on_received_notify_response)
-	(*sub->cb.on_received_notify_response)(sub, event);
-
-    /* Check for response 481. */
-    if (event->obj.tsx->status_code == 481) {
-	/* Remote says that the subscription does not exist! 
-	 * Terminate subscription!
-	 */
-	sub_set_state(sub, PJSIP_EVENT_SUB_STATE_TERMINATED);
-	if (sub->timer.id) {
-	    pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
-	    sub->timer.id = 0;
-	}
-
-	PJ_LOG(4, (THIS_FILE, 
-		   "event_sub%p (%s): got 481 response to NOTIFY. Terminating...",
-		   sub, state[sub->state].ptr));
-
-	/* Notify app. */
-	if (sub->state!=old_state && sub->cb.on_sub_terminated) 
-	    (*sub->cb.on_sub_terminated)(sub, &event->src.rdata->msg->line.status.reason);
-    }
-
-    /* Decrement pending transaction count. */
-    --sub->pending_tsx;
-    pj_assert(sub->pending_tsx >= 0);
-
-    /* Check that the subscription is marked for deletion. */
-    if (sub->delete_flag && sub->pending_tsx <= 0) {
-	pjsip_event_sub_destroy(sub);
-    } else {
-	pj_mutex_unlock(sub->mutex);
-    }
-
-    /* DO NOT ACCESS sub, IT MIGHT HAVE BEEN DESTROYED! */
-}
-
-
-/* This is the transaction handler for incoming SUBSCRIBE and NOTIFY 
- * requests. 
- */
-static void tsx_handler( struct pjsip_module *mod, pjsip_event *event )
-{
-    pjsip_msg *msg;
-    pjsip_rx_data *rdata;
-
-    /* Only want incoming message events. */
-    if (event->src_type != PJSIP_EVENT_RX_MSG)
-	return;
-
-    rdata = event->src.rdata;
-    msg = rdata->msg;
-
-    /* Only want to process request messages. */
-    if (msg->type != PJSIP_REQUEST_MSG)
-	return;
-
-    /* Only want the first notification. */
-    if (event->obj.tsx && event->obj.tsx->status_code >= 100)
-	return;
-
-    if (pjsip_method_cmp(&msg->line.req.method, &SUBSCRIBE)==0) {
-	/* Process incoming SUBSCRIBE request. */
-	on_subscribe_request( event->obj.tsx, rdata );
-    } else if (pjsip_method_cmp(&msg->line.req.method, &NOTIFY)==0) {
-	/* Process incoming NOTIFY request. */
-	on_notify_request( event->obj.tsx, rdata );
-    }
-}
-
Index: pjproject/trunk/pjsip/src/pjsip-simple/event_notify_msg.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-simple/event_notify_msg.c	(revision 196)
+++ 	(revision )
@@ -1,322 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
- */
-#include <pjsip_simple/event_notify_msg.h>
-#include <pjsip/print.h>
-#include <pjsip/sip_parser.h>
-#include <pj/pool.h>
-#include <pj/string.h>
-#include <pj/except.h>
-
-static int pjsip_event_hdr_print( pjsip_event_hdr *hdr, 
-				  char *buf, pj_size_t size);
-static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool, 
-					       const pjsip_event_hdr *hdr);
-static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
-						       const pjsip_event_hdr*);
-
-static pjsip_hdr_vptr event_hdr_vptr = 
-{
-    (pjsip_hdr_clone_fptr) &pjsip_event_hdr_clone,
-    (pjsip_hdr_clone_fptr) &pjsip_event_hdr_shallow_clone,
-    (pjsip_hdr_print_fptr) &pjsip_event_hdr_print,
-};
-
-
-PJ_DEF(pjsip_event_hdr*) pjsip_event_hdr_create(pj_pool_t *pool)
-{
-    pj_str_t event = { "Event", 5 };
-    pjsip_event_hdr *hdr = pj_pool_calloc(pool, 1, sizeof(*hdr));
-    hdr->type = PJSIP_H_OTHER;
-    hdr->name = hdr->sname = event;
-    hdr->vptr = &event_hdr_vptr;
-    pj_list_init(hdr);
-    return hdr;
-}
-
-static int pjsip_event_hdr_print( pjsip_event_hdr *hdr, 
-				  char *buf, pj_size_t size)
-{
-    char *p = buf;
-    char *endbuf = buf+size;
-    int printed;
-
-    copy_advance(p, hdr->name);
-    *p++ = ':';
-    *p++ = ' ';
-
-    copy_advance(p, hdr->event_type);
-    copy_advance_pair(p, ";id=", 4, hdr->id_param);
-    if (hdr->other_param.slen)
-	copy_advance(p, hdr->other_param);
-    return p - buf;
-}
-
-static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool, 
-					       const pjsip_event_hdr *rhs)
-{
-    pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool);
-    pj_strdup(pool, &hdr->event_type, &rhs->event_type);
-    pj_strdup(pool, &hdr->id_param, &rhs->id_param);
-    pj_strdup(pool, &hdr->other_param, &rhs->other_param);
-    return hdr;
-}
-
-static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
-						       const pjsip_event_hdr *rhs )
-{
-    pjsip_event_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr));
-    pj_memcpy(hdr, rhs, sizeof(*hdr));
-    return hdr;
-}
-
-
-static int pjsip_allow_events_hdr_print(pjsip_allow_events_hdr *hdr, 
-					char *buf, pj_size_t size);
-static pjsip_allow_events_hdr* 
-pjsip_allow_events_hdr_clone(pj_pool_t *pool, 
-			     const pjsip_allow_events_hdr *hdr);
-static pjsip_allow_events_hdr* 
-pjsip_allow_events_hdr_shallow_clone(pj_pool_t *pool,
-				     const pjsip_allow_events_hdr*);
-
-static pjsip_hdr_vptr allow_event_hdr_vptr = 
-{
-    (pjsip_hdr_clone_fptr) &pjsip_allow_events_hdr_clone,
-    (pjsip_hdr_clone_fptr) &pjsip_allow_events_hdr_shallow_clone,
-    (pjsip_hdr_print_fptr) &pjsip_allow_events_hdr_print,
-};
-
-
-PJ_DEF(pjsip_allow_events_hdr*) pjsip_allow_events_hdr_create(pj_pool_t *pool)
-{
-    pj_str_t allow_events = { "Allow-Events", 12 };
-    pjsip_allow_events_hdr *hdr = pj_pool_calloc(pool, 1, sizeof(*hdr));
-    hdr->type = PJSIP_H_OTHER;
-    hdr->name = hdr->sname = allow_events;
-    hdr->vptr = &allow_event_hdr_vptr;
-    pj_list_init(hdr);
-    return hdr;
-}
-
-static int pjsip_allow_events_hdr_print(pjsip_allow_events_hdr *hdr, 
-					char *buf, pj_size_t size)
-{
-    char *p = buf;
-    char *endbuf = buf+size;
-    int printed;
-
-    copy_advance(p, hdr->name);
-    *p++ = ':';
-    *p++ = ' ';
-
-    if (hdr->event_cnt > 0) {
-	int i;
-	copy_advance(p, hdr->events[0]);
-	for (i=1; i<hdr->event_cnt; ++i) {
-	    copy_advance_pair(p, ",", 1, hdr->events[i]);
-	}
-    }
-
-    return p - buf;
-}
-
-static pjsip_allow_events_hdr* 
-pjsip_allow_events_hdr_clone(pj_pool_t *pool, 
-			     const pjsip_allow_events_hdr *rhs)
-{
-    int i;
-
-    pjsip_allow_events_hdr *hdr = pjsip_allow_events_hdr_create(pool);
-    hdr->event_cnt = rhs->event_cnt;
-    for (i=0; i<rhs->event_cnt; ++i) {
-	pj_strdup(pool, &hdr->events[i], &rhs->events[i]);
-    }
-    return hdr;
-}
-
-static pjsip_allow_events_hdr* 
-pjsip_allow_events_hdr_shallow_clone(pj_pool_t *pool,
-				     const pjsip_allow_events_hdr *rhs)
-{
-    pjsip_allow_events_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr));
-    pj_memcpy(hdr, rhs, sizeof(*hdr));
-    return hdr;
-}
-
-
-static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr, 
-				     char *buf, pj_size_t size);
-static pjsip_sub_state_hdr* 
-pjsip_sub_state_hdr_clone(pj_pool_t *pool, 
-			  const pjsip_sub_state_hdr *hdr);
-static pjsip_sub_state_hdr* 
-pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
-				  const pjsip_sub_state_hdr*);
-
-static pjsip_hdr_vptr sub_state_hdr_vptr = 
-{
-    (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_clone,
-    (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_shallow_clone,
-    (pjsip_hdr_print_fptr) &pjsip_sub_state_hdr_print,
-};
-
-
-PJ_DEF(pjsip_sub_state_hdr*) pjsip_sub_state_hdr_create(pj_pool_t *pool)
-{
-    pj_str_t sub_state = { "Subscription-State", 18 };
-    pjsip_sub_state_hdr *hdr = pj_pool_calloc(pool, 1, sizeof(*hdr));
-    hdr->type = PJSIP_H_OTHER;
-    hdr->name = hdr->sname = sub_state;
-    hdr->vptr = &sub_state_hdr_vptr;
-    hdr->expires_param = -1;
-    hdr->retry_after = -1;
-    pj_list_init(hdr);
-    return hdr;
-}
-
-static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr, 
-				     char *buf, pj_size_t size)
-{
-    char *p = buf;
-    char *endbuf = buf+size;
-    int printed;
-
-    copy_advance(p, hdr->name);
-    *p++ = ':';
-    *p++ = ' ';
-
-    copy_advance(p, hdr->sub_state);
-    copy_advance_pair(p, ";reason=", 8, hdr->reason_param);
-    if (hdr->expires_param >= 0) {
-	pj_memcpy(p, ";expires=", 9);
-	p += 9;
-	printed = pj_utoa(hdr->expires_param, p);
-	p += printed;
-    }
-    if (hdr->retry_after >= 0) {
-	pj_memcpy(p, ";retry-after=", 13);
-	p += 9;
-	printed = pj_utoa(hdr->retry_after, p);
-	p += printed;
-    }
-    if (hdr->other_param.slen)
-	copy_advance(p, hdr->other_param);
-
-    return p - buf;
-}
-
-static pjsip_sub_state_hdr* 
-pjsip_sub_state_hdr_clone(pj_pool_t *pool, 
-			  const pjsip_sub_state_hdr *rhs)
-{
-    pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool);
-    pj_strdup(pool, &hdr->sub_state, &rhs->sub_state);
-    pj_strdup(pool, &hdr->reason_param, &rhs->reason_param);
-    hdr->retry_after = rhs->retry_after;
-    hdr->expires_param = rhs->expires_param;
-    pj_strdup(pool, &hdr->other_param, &rhs->other_param);
-    return hdr;
-}
-
-static pjsip_sub_state_hdr* 
-pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
-				  const pjsip_sub_state_hdr *rhs)
-{
-    pjsip_sub_state_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr));
-    pj_memcpy(hdr, rhs, sizeof(*hdr));
-    return hdr;
-}
-
-static pjsip_event_hdr *parse_hdr_event(pj_scanner *scanner, 
-					pj_pool_t *pool)
-{
-    pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool);
-    const pj_str_t id_param = { "id", 2 };
-
-    pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->event_type);
-
-    while (*scanner->current == ';') {
-	pj_str_t pname, pvalue;
-	pj_scan_get_char(scanner);
-	pjsip_parse_param_imp(scanner, &pname, &pvalue, 0);
-	if (pj_stricmp(&pname, &id_param)==0) {
-	    hdr->id_param = pvalue;
-	} else {
-	    pjsip_concat_param_imp(&hdr->other_param, pool, &pname, &pvalue, ';');
-	}
-    }
-    pjsip_parse_end_hdr_imp( scanner );
-    return hdr;
-}
-
-static pjsip_allow_events_hdr *parse_hdr_allow_events(pj_scanner *scanner, 
-						      pj_pool_t *pool)
-{
-    pjsip_allow_events_hdr *hdr = pjsip_allow_events_hdr_create(pool);
-
-    pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->events[0]);
-    hdr->event_cnt = 1;
-
-    while (*scanner->current == ',') {
-	pj_scan_get_char(scanner);
-	pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->events[hdr->event_cnt++]);
-	if (hdr->event_cnt == PJSIP_MAX_ALLOW_EVENTS) {
-	    PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
-	}
-    }
-
-    pjsip_parse_end_hdr_imp( scanner );
-    return hdr;
-}
-
-static pjsip_sub_state_hdr *parse_hdr_sub_state(pj_scanner *scanner, 
-						pj_pool_t *pool)
-{
-    pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool);
-    const pj_str_t reason = { "reason", 6 },
-		   expires = { "expires", 7 },
-		   retry_after = { "retry-after", 11 };
-    pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->sub_state);
-
-    while (*scanner->current == ';') {
-	pj_str_t pname, pvalue;
-
-	pj_scan_get_char(scanner);
-	pjsip_parse_param_imp(scanner, &pname, &pvalue, 0);
-	if (pj_stricmp(&pname, &reason) == 0) {
-	    hdr->reason_param = pvalue;
-	} else if (pj_stricmp(&pname, &expires) == 0) {
-	    hdr->expires_param = pj_strtoul(&pvalue);
-	} else if (pj_stricmp(&pname, &retry_after) == 0) {
-	    hdr->retry_after = pj_strtoul(&pvalue);
-	} else {
-	    pjsip_concat_param_imp(&hdr->other_param, pool, &pname, &pvalue, ';');
-	}
-    }
-
-    pjsip_parse_end_hdr_imp( scanner );
-    return hdr;
-}
-
-PJ_DEF(void) pjsip_event_notify_init_parser(void)
-{
-    pjsip_register_hdr_parser( "Event", NULL, (pjsip_parse_hdr_func*) &parse_hdr_event);
-    pjsip_register_hdr_parser( "Allow-Events", NULL, (pjsip_parse_hdr_func*) &parse_hdr_allow_events);
-    pjsip_register_hdr_parser( "Subscription-State", NULL, (pjsip_parse_hdr_func*) &parse_hdr_sub_state);
-}
Index: pjproject/trunk/pjsip/src/pjsip-simple/evsub.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-simple/evsub.c	(revision 197)
+++ pjproject/trunk/pjsip/src/pjsip-simple/evsub.c	(revision 197)
@@ -0,0 +1,1785 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjsip-simple/evsub.h>
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_dialog.h>
+#include <pjsip/sip_auth.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_event.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE	"evsub.c"
+
+/*
+ * Global constant
+ */
+
+/* Let's define this enum, so that it'll trigger compilation error
+ * when somebody define the same enum in sip_msg.h
+ */
+enum
+{
+    PJSIP_SUBSCRIBE_METHOD = PJSIP_OTHER_METHOD,
+    PJSIP_NOTIFY_METHOD = PJSIP_OTHER_METHOD
+};
+
+const pjsip_method pjsip_subscribe_method = 
+{
+    PJSIP_SUBSCRIBE_METHOD,
+    { "SUBSCRIBE", 9 }
+};
+
+const pjsip_method pjsip_notify_method = 
+{
+    PJSIP_NOTIFY_METHOD,
+    { "NOTIFY", 6 }
+};
+
+/*
+ * Static prototypes.
+ */
+static void	   mod_evsub_on_tsx_state(pjsip_transaction*, pjsip_event*);
+static pj_status_t mod_evsub_unload(void);
+
+
+/*
+ * State names.
+ */
+static pj_str_t evsub_state_names[] = 
+{
+    { "NULL",	    4},
+    { "SENT",	    4},
+    { "ACCEPTED",   8},
+    { "PENDING",    7},
+    { "ACTIVE",	    6},
+    { "TERMINATED", 10},
+    { "UNKNOWN",    7}
+};
+
+/*
+ * Timer constants.
+ */
+
+/* Number of seconds to send SUBSCRIBE before the actual expiration */
+#define TIME_UAC_REFRESH	5
+
+/* Time to wait for the final NOTIFY after sending unsubscription */
+#define TIME_UAC_TERMINATE	5
+
+/* If client responds NOTIFY with non-2xx final response (such as 401),
+ * wait for this seconds for further NOTIFY, otherwise client will
+ * unsubscribe
+ */
+#define TIME_UAC_WAIT_NOTIFY	5
+
+
+/*
+ * Timer id
+ */
+enum timer_id
+{
+    /* No timer. */
+    TIMER_TYPE_NONE,
+
+    /* Time to refresh client subscription. 
+     * The action is to call on_client_refresh() callback.
+     */
+    TIMER_TYPE_UAC_REFRESH,
+
+    /* UAS timeout after to subscription refresh. 
+     * The action is to call on_server_timeout() callback.
+     */
+    TIMER_TYPE_UAS_TIMEOUT,
+
+    /* UAC waiting for final NOTIFY after unsubscribing 
+     * The action is to terminate.
+     */
+    TIMER_TYPE_UAC_TERMINATE,
+
+    /* UAC waiting for further NOTIFY after sending non-2xx response to 
+     * NOTIFY. The action is to unsubscribe.
+     */
+    TIMER_TYPE_UAC_WAIT_NOTIFY,
+
+};
+
+static const char *timer_names[] = 
+{
+    "None",
+    "UAC_REFRESH",
+    "UAS_TIMEOUT"
+    "UAC_TERMINATE",
+    "UAC_WAIT_NOTIFY",
+};
+
+/*
+ * Definition of event package.
+ */
+struct evpkg
+{
+    PJ_DECL_LIST_MEMBER(struct evpkg);
+
+    pj_str_t		 pkg_name;
+    pjsip_module	*pkg_mod;
+    unsigned		 pkg_expires;
+    pjsip_accept_hdr	*pkg_accept;
+};
+
+
+/*
+ * Event subscription module (mod-evsub).
+ */
+static struct mod_evsub
+{
+    pjsip_module	     mod;
+    pj_pool_t		    *pool;
+    pjsip_endpoint	    *endpt;
+    struct evpkg	     pkg_list;
+    pjsip_allow_events_hdr  *allow_events_hdr;
+
+} mod_evsub = 
+{
+    {
+	NULL, NULL,		    /* prev, next.			*/
+	{ "mod-evsub", 9 },	    /* Name.				*/
+	-1,			    /* Id				*/
+	PJSIP_MOD_PRIORITY_APPLICATION-1,	/* Priority		*/
+	NULL,			    /* User data.			*/
+	NULL,			    /* load()				*/
+	NULL,			    /* start()				*/
+	NULL,			    /* stop()				*/
+	&mod_evsub_unload,	    /* unload()				*/
+	NULL,			    /* on_rx_request()			*/
+	NULL,			    /* on_rx_response()			*/
+	NULL,			    /* on_tx_request.			*/
+	NULL,			    /* on_tx_response()			*/
+	&mod_evsub_on_tsx_state,    /* on_tsx_state()			*/
+    }
+};
+
+
+/*
+ * Event subscription session.
+ */
+struct pjsip_evsub
+{
+    char		  obj_name[PJ_MAX_OBJ_NAME]; /**< Name.		    */
+    pj_pool_t		 *pool;		/**< Pool.			    */
+    pjsip_endpoint	 *endpt;	/**< Endpoint instance.		    */
+    pjsip_dialog	 *dlg;		/**< Underlying dialog.		    */
+    struct evpkg	 *pkg;		/**< The event package.		    */
+    pjsip_evsub_user	  user;		/**< Callback.			    */
+    pjsip_role_e	  role;		/**< UAC=subscriber, UAS=notifier   */
+    pjsip_evsub_state	  state;	/**< Subscription state.	    */
+    pj_str_t		  state_str;	/**< String describing the state.   */
+    pjsip_evsub_state	  dst_state;	/**< Pending state to be set.	    */
+    pj_str_t		  dst_state_str;/**< Pending state to be set.	    */
+    pjsip_method	  method;	/**< Method that established subscr.*/
+    pjsip_event_hdr	 *event;	/**< Event description.		    */
+    pjsip_expires_hdr	 *expires;	/**< Expires header		    */
+    pjsip_accept_hdr	 *accept;	/**< Local Accept header.	    */
+
+    pj_time_val		  refresh_time;	/**< Time to refresh.		    */
+    pj_timer_entry	  timer;	/**< Internal timer.		    */
+    int			  pending_tsx;	/**< Number of pending transactions.*/
+
+    void		 *mod_data[PJSIP_MAX_MODULE];	/**< Module data.   */
+};
+
+
+/*
+ * This is the structure that will be "attached" to dialog.
+ * The purpose is to allow multiple subscriptions inside a dialog.
+ */
+struct dlgsub
+{
+    PJ_DECL_LIST_MEMBER(struct dlgsub);
+    pjsip_evsub *sub;
+};
+
+
+/* Static vars. */
+static const pj_str_t STR_EVENT	     = { "Event", 5 };
+static const pj_str_t STR_SUB_STATE  = { "Subscription-State", 18 };
+static const pj_str_t STR_TERMINATED = { "terminated", 10 };
+static const pj_str_t STR_ACTIVE     = { "active", 6 };
+static const pj_str_t STR_PENDING    = { "pending", 7 };
+static const pj_str_t STR_TIMEOUT    = { "timeout", 7};
+
+/*
+ * On unload module.
+ */
+static pj_status_t mod_evsub_unload(void)
+{
+    pjsip_endpt_release_pool(mod_evsub.endpt, mod_evsub.pool);
+    mod_evsub.pool = NULL;
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Init and register module.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt)
+{
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
+    PJ_ASSERT_RETURN(mod_evsub.mod.id == -1, PJ_EINVALIDOP);
+
+    /* Keep endpoint for future reference: */
+    mod_evsub.endpt = endpt;
+
+    /* Init event package list: */
+    pj_list_init(&mod_evsub.pkg_list);
+
+    /* Create pool: */
+    mod_evsub.pool = pjsip_endpt_create_pool(endpt, "evsub", 4000, 4000);
+    if (!mod_evsub.pool)
+	return PJ_ENOMEM;
+
+    /* Register module: */
+    status = pjsip_endpt_register_module(endpt, &mod_evsub.mod);
+    if (status  != PJ_SUCCESS)
+	goto on_error;
+ 
+    /* Create Allow-Events header: */
+    mod_evsub.allow_events_hdr = pjsip_allow_events_hdr_create(mod_evsub.pool);
+
+    /* Register SIP-event specific headers parser: */
+    pjsip_evsub_init_parser();
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (mod_evsub.pool) {
+	pjsip_endpt_release_pool(endpt, mod_evsub.pool);
+	mod_evsub.pool = NULL;
+    }
+    mod_evsub.endpt = NULL;
+    return status;
+}
+
+
+/*
+ * Get the instance of the module.
+ */
+PJ_DEF(pjsip_module*) pjsip_evsub_instance(void)
+{
+    PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, NULL);
+
+    return &mod_evsub.mod;
+}
+
+
+/*
+ * Get the event subscription instance in the transaction.
+ */
+PJ_DEF(pjsip_evsub*) pjsip_tsx_get_evsub(pjsip_transaction *tsx)
+{
+    return tsx->mod_data[mod_evsub.mod.id];
+}
+
+
+/*
+ * Set event subscription's module data.
+ */
+PJ_DEF(void) pjsip_evsub_set_mod_data( pjsip_evsub *sub, unsigned mod_id,
+				       void *data )
+{
+    PJ_ASSERT_ON_FAIL(mod_id < PJSIP_MAX_MODULE, return);
+    sub->mod_data[mod_id] = data;
+}
+
+
+/*
+ * Get event subscription's module data.
+ */
+PJ_DEF(void*) pjsip_evsub_get_mod_data( pjsip_evsub *sub, unsigned mod_id )
+{
+    PJ_ASSERT_RETURN(mod_id < PJSIP_MAX_MODULE, NULL);
+    return sub->mod_data[mod_id];
+}
+
+
+/*
+ * Find registered event package with matching name.
+ */
+static struct evpkg* find_pkg(const pj_str_t *event_name)
+{
+    struct evpkg *pkg;
+
+    pkg = mod_evsub.pkg_list.next;
+    while (pkg != &mod_evsub.pkg_list) {
+
+	if (pj_stricmp(&pkg->pkg_name, event_name) == 0) {
+	    return pkg;
+	}
+
+	pkg = pkg->next;
+    }
+
+    return NULL;
+}
+
+/*
+ * Register an event package
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_register_pkg( pjsip_module *pkg_mod,
+					      const pj_str_t *event_name,
+					      unsigned expires,
+					      unsigned accept_cnt,
+					      const pj_str_t accept[])
+{
+    struct evpkg *pkg;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(pkg_mod && event_name, PJ_EINVAL);
+    PJ_ASSERT_RETURN(accept_cnt < PJ_ARRAY_SIZE(pkg->pkg_accept->values), 
+		     PJ_ETOOMANY);
+
+    /* Make sure no module with the specified name already registered: */
+
+    PJ_ASSERT_RETURN(find_pkg(event_name) == NULL, PJSIP_SIMPLE_EPKGEXISTS);
+
+
+    /* Create new event package: */
+
+    pkg = pj_pool_alloc(mod_evsub.pool, sizeof(struct evpkg));
+    pkg->pkg_mod = pkg_mod;
+    pkg->pkg_expires = expires;
+    pj_strdup(mod_evsub.pool, &pkg->pkg_name, event_name);
+
+    pkg->pkg_accept = pjsip_accept_hdr_create(mod_evsub.pool);
+    pkg->pkg_accept->count = accept_cnt;
+    for (i=0; i<accept_cnt; ++i) {
+	pj_strdup(mod_evsub.pool, &pkg->pkg_accept->values[i], &accept[i]);
+    }
+
+    /* Add to package list: */
+
+    pj_list_push_back(&mod_evsub.pkg_list, pkg);
+
+    /* Add to Allow-Events header: */
+
+    if (mod_evsub.allow_events_hdr->count !=
+	PJ_ARRAY_SIZE(mod_evsub.allow_events_hdr->values))
+    {
+	mod_evsub.allow_events_hdr->values[mod_evsub.allow_events_hdr->count] =
+	    pkg->pkg_name;
+	++mod_evsub.allow_events_hdr->count;
+    }
+    
+
+    /* Done */
+
+    PJ_LOG(5,(THIS_FILE, "Event pkg \"%.*s\" registered by %.*s",
+	      (int)event_name->slen, event_name->ptr,
+	      (int)pkg_mod->name.slen, pkg_mod->name.ptr));
+
+    return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Update expiration time.
+ */
+static void update_expires( pjsip_evsub *sub, pj_uint32_t interval )
+{
+    pj_gettimeofday(&sub->refresh_time);
+    sub->refresh_time.sec += interval;
+}
+
+
+/* 
+ * Schedule timer.
+ */
+static void set_timer( pjsip_evsub *sub, int timer_id,
+		       pj_int32_t seconds)
+{
+    if (sub->timer.id != TIMER_TYPE_NONE) {
+	PJ_LOG(5,(sub->obj_name, "%s %s timer", 
+		  (timer_id==sub->timer.id ? "Updating" : "Cancelling"),
+		  timer_names[sub->timer.id]));
+	pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+	sub->timer.id = TIMER_TYPE_NONE;
+    }
+
+    if (timer_id != TIMER_TYPE_NONE) {
+	pj_time_val timeout;
+
+	PJ_ASSERT_ON_FAIL(seconds > 0, return);
+
+	timeout.sec = seconds;
+	timeout.msec = 0;
+	sub->timer.id = timer_id;
+
+	pjsip_endpt_schedule_timer(sub->endpt, &sub->timer, &timeout);
+
+	PJ_LOG(5,(sub->obj_name, "Timer %s scheduled in %d seconds", 
+		  timer_names[sub->timer.id], timeout.sec));
+    }
+}
+
+
+/*
+ * Destroy session.
+ */
+static void evsub_destroy( pjsip_evsub *sub )
+{
+    struct dlgsub *dlgsub_head, *dlgsub;
+
+    PJ_LOG(4,(sub->obj_name, "Subscription destroyed"));
+
+    /* Kill timer */
+    set_timer(sub, TIMER_TYPE_NONE, 0);
+
+    /* Remote this session from dialog's list of subscription */
+    dlgsub_head = sub->dlg->mod_data[mod_evsub.mod.id];
+    dlgsub = dlgsub_head->next;
+    while (dlgsub != dlgsub_head) {
+	
+	if (dlgsub->sub == sub) {
+	    pj_list_erase(dlgsub);
+	    break;
+	}
+
+	dlgsub = dlgsub->next;
+    }
+
+    /* Decrement dialog's session */
+    pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod);
+}
+
+/*
+ * Set subscription session state.
+ */
+static void set_state( pjsip_evsub *sub, pjsip_evsub_state state,
+		       const pj_str_t *state_str, pjsip_event *event)
+{
+    pj_str_t old_state_str = sub->state_str;
+
+    sub->state = state;
+
+    if (state_str && state_str->slen)
+	pj_strdup_with_null(sub->pool, &sub->state_str, state_str);
+    else
+	sub->state_str = evsub_state_names[state];
+
+    PJ_LOG(4,(sub->obj_name, 
+	      "Subscription state changed %.*s --> %.*s",
+	      (int)old_state_str.slen,
+	      old_state_str.ptr,
+	      (int)sub->state_str.slen,
+	      sub->state_str.ptr));
+
+    if (sub->user.on_evsub_state)
+	(*sub->user.on_evsub_state)(sub, event);
+
+    if (state == PJSIP_EVSUB_STATE_TERMINATED) {
+	
+	if (sub->pending_tsx == 0) {
+	    evsub_destroy(sub);
+	}
+    }
+}
+
+
+/*
+ * Timer callback.
+ */
+static void on_timer( pj_timer_heap_t *timer_heap,
+		      struct pj_timer_entry *entry)
+{
+    pjsip_evsub *sub;
+    int timer_id;
+
+    PJ_UNUSED_ARG(timer_heap);
+
+    sub = entry->user_data;
+
+    pjsip_dlg_inc_lock(sub->dlg);
+
+    timer_id = entry->id;
+    entry->id = TIMER_TYPE_NONE;
+
+    switch (timer_id) {
+
+    case TIMER_TYPE_UAC_REFRESH:
+	/* Time for UAC to refresh subscription */
+	if (sub->user.on_client_refresh) {
+	    (*sub->user.on_client_refresh)(sub);
+	} else {
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+
+	    PJ_LOG(5,(sub->obj_name, "Refreshing subscription."));
+	    status = pjsip_evsub_initiate(sub, &sub->method, 
+					  sub->expires->ivalue,
+					  &tdata);
+	    if (status == PJ_SUCCESS)
+		pjsip_evsub_send_request(sub, tdata);
+	}
+	break;
+
+    case TIMER_TYPE_UAS_TIMEOUT:
+	/* Refresh from UAC has not been received */
+	if (sub->user.on_server_timeout) {
+	    (*sub->user.on_server_timeout)(sub);
+	} else {
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+
+	    PJ_LOG(5,(sub->obj_name, "Timeout waiting for refresh. "
+				     "Sending NOTIFY to terminate."));
+	    status = pjsip_evsub_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, 
+					 NULL, &STR_TIMEOUT, &tdata);
+	    if (status == PJ_SUCCESS)
+		pjsip_evsub_send_request(sub, tdata);
+	}
+	break;
+
+    case TIMER_TYPE_UAC_TERMINATE:
+	{
+	    pjsip_event event;
+	    pj_str_t reason = { "unsubscribing", 13};
+
+	    PJSIP_EVENT_INIT_TIMER(event, entry);
+	    PJ_LOG(5,(sub->obj_name, "Timeout waiting for final NOTIFY. "
+				     "Terminating.."));
+	    set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, &event);
+	}
+	break;
+
+    case TIMER_TYPE_UAC_WAIT_NOTIFY:
+	{
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+
+	    PJ_LOG(5,(sub->obj_name, 
+		     "Timeout waiting for subsequent NOTIFY (we did "
+		     "send non-2xx response for previous NOTIFY). "
+		     "Unsubscribing.."));
+	    status = pjsip_evsub_initiate( sub, &sub->method, 0, &tdata);
+	    if (status == PJ_SUCCESS)
+		pjsip_evsub_send_request(sub, tdata);
+	}
+	break;
+
+    default:
+	pj_assert(!"Invalid timer id");
+    }
+
+    pjsip_dlg_dec_lock(sub->dlg);
+}
+
+
+/*
+ * Create subscription session, used for both client and notifier.
+ */
+static pj_status_t evsub_create( pjsip_dialog *dlg,
+				 pjsip_role_e role,
+				 const pjsip_evsub_user *user_cb,
+				 const pj_str_t *event,
+				 pjsip_evsub **p_evsub )
+{
+    pjsip_evsub *sub;
+    struct evpkg *pkg;
+    struct dlgsub *dlgsub_head, *dlgsub;
+    pj_status_t status;
+
+    /* Make sure there's package register for the event name: */
+
+    pkg = find_pkg(event);
+    if (pkg == NULL)
+	return PJSIP_SIMPLE_ENOPKG;
+
+
+    /* Init attributes: */
+
+    sub = pj_pool_zalloc(dlg->pool, sizeof(struct pjsip_evsub));
+    sub->pool = dlg->pool;
+    sub->endpt = dlg->endpt;
+    sub->dlg = dlg;
+    sub->pkg = pkg;
+    sub->role = role;
+    sub->state = PJSIP_EVSUB_STATE_NULL;
+    sub->state_str = evsub_state_names[sub->state];
+    sub->expires = pjsip_expires_hdr_create(sub->pool, pkg->pkg_expires);
+    sub->accept = pjsip_hdr_clone(sub->pool, pkg->pkg_accept);
+
+    sub->timer.user_data = sub;
+    sub->timer.cb = &on_timer;
+
+    /* Set name. */
+    pj_snprintf(sub->obj_name, PJ_ARRAY_SIZE(sub->obj_name),
+		"evsub%p", sub);
+
+
+    /* Copy callback, if any: */
+    if (user_cb)
+	pj_memcpy(&sub->user, user_cb, sizeof(pjsip_evsub_user));
+
+
+    /* Create Event header: */
+    sub->event = pjsip_event_hdr_create(sub->pool);
+    pj_strdup(sub->pool, &sub->event->event_type, event);
+
+
+    /* Create subcription list: */
+
+    dlgsub_head = pj_pool_alloc(sub->pool, sizeof(struct dlgsub));
+    dlgsub = pj_pool_alloc(sub->pool, sizeof(struct dlgsub));
+    dlgsub->sub = sub;
+
+    pj_list_init(dlgsub_head);
+    pj_list_push_back(dlgsub_head, dlgsub);
+
+
+    /* Register as dialog usage: */
+
+    status = pjsip_dlg_add_usage(dlg, &mod_evsub.mod, dlgsub_head);
+    if (status != PJ_SUCCESS)
+	return status;
+
+
+    PJ_LOG(5,(sub->obj_name, "%s subscription created, using dialog %s",
+	      (role==PJSIP_ROLE_UAC ? "UAC" : "UAS"),
+	      dlg->obj_name));
+
+    *p_evsub = sub;
+
+    return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Create client subscription session.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg,
+					    const pjsip_evsub_user *user_cb,
+					    const pj_str_t *event,
+					    pjsip_evsub **p_evsub)
+{
+    pjsip_evsub *sub;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(dlg && event && p_evsub, PJ_EINVAL);
+
+    pjsip_dlg_inc_lock(dlg);
+    status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, &sub);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Add unique Id to Event header */
+    pj_create_unique_string(sub->pool, &sub->event->id_param);
+
+    /* Increment dlg session. */
+    pjsip_dlg_inc_session(sub->dlg, &mod_evsub.mod);
+
+    /* Done */
+    *p_evsub = sub;
+
+on_return:
+    pjsip_dlg_dec_lock(dlg);
+    return status;
+}
+
+
+/*
+ * Create server subscription session from incoming request.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg,
+					    const pjsip_evsub_user *user_cb,
+					    pjsip_rx_data *rdata,
+					    pjsip_evsub **p_evsub)
+{
+    pjsip_evsub *sub;
+    pjsip_transaction *tsx;
+    pjsip_accept_hdr *accept_hdr;
+    pjsip_event_hdr *event_hdr;
+    pjsip_expires_hdr *expires_hdr;
+    pj_status_t status;
+
+    /* Check arguments: */
+    PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
+
+    /* MUST be request message: */
+    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+		     PJSIP_ENOTREQUESTMSG);
+
+    /* Transaction MUST have been created (in the dialog) */
+    tsx = pjsip_rdata_get_tsx(rdata);
+    PJ_ASSERT_RETURN(tsx != NULL, PJSIP_ENOTSX);
+
+    /* No subscription must have been attached to transaction */
+    PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] == NULL, 
+		     PJSIP_ETYPEEXISTS);
+
+    /* Package MUST implement on_rx_refresh */
+    PJ_ASSERT_RETURN(user_cb->on_rx_refresh, PJ_EINVALIDOP);
+
+    /* Request MUST have "Event" header: */
+
+    event_hdr = (pjsip_event_hdr*) 
+	pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL);
+    if (event_hdr == NULL) {
+	return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
+    }
+
+    /* Start locking the mutex: */
+
+    pjsip_dlg_inc_lock(dlg);
+
+    /* Create the session: */
+
+    status = evsub_create(dlg, PJSIP_UAS_ROLE, user_cb, 
+			  &event_hdr->event_type, &sub);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Just duplicate Event header from the request */
+    sub->event = pjsip_hdr_clone(sub->pool, event_hdr);
+
+    /* Set the method: */
+    pjsip_method_copy(sub->pool, &sub->method, 
+		      &rdata->msg_info.msg->line.req.method);
+
+    /* Update expiration time according to client request: */
+
+    expires_hdr = (pjsip_expires_hdr*)
+	pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL);
+    if (expires_hdr) {
+	sub->expires->ivalue = expires_hdr->ivalue;
+    }
+
+    /* Update time. */
+    update_expires(sub, sub->expires->ivalue);
+
+    /* Update Accept header: */
+
+    accept_hdr = (pjsip_accept_hdr*)
+	pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
+    if (accept_hdr)
+	sub->accept = pjsip_hdr_clone(sub->pool, accept_hdr);
+
+    /* We can start the session: */
+
+    pjsip_dlg_inc_session(dlg, &mod_evsub.mod);
+    sub->pending_tsx++;
+    tsx->mod_data[mod_evsub.mod.id] = sub;
+
+
+    /* Done. */
+    *p_evsub = sub;
+
+
+on_return:
+    pjsip_dlg_dec_lock(dlg);
+    return status;
+}
+
+
+/*
+ * Get subscription state.
+ */
+PJ_DEF(pjsip_evsub_state) pjsip_evsub_get_state(pjsip_evsub *sub)
+{
+    return sub->state;
+}
+
+/*
+ * Get state name.
+ */
+PJ_DEF(const char*) pjsip_evsub_get_state_name(pjsip_evsub *sub)
+{
+    return sub->state_str.ptr;
+}
+
+
+/*
+ * Initiate client subscription
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_initiate( pjsip_evsub *sub,
+					  const pjsip_method *method,
+					  pj_int32_t expires,
+					  pjsip_tx_data **p_tdata)
+{
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL);
+
+    /* Use SUBSCRIBE if method is not specified */
+    if (method == NULL)
+	method = &pjsip_subscribe_method;
+
+    pjsip_dlg_inc_lock(sub->dlg);
+
+    /* Update method: */
+    if (sub->state == PJSIP_EVSUB_STATE_NULL)
+	pjsip_method_copy(sub->pool, &sub->method, method);
+
+    status = pjsip_dlg_create_request( sub->dlg, method, -1, &tdata);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+
+    /* Add Event header: */
+    pjsip_msg_add_hdr( tdata->msg,
+		       pjsip_hdr_shallow_clone(tdata->pool, sub->event));
+
+    /* Update and add expires header: */
+    if (expires >= 0)
+	sub->expires->ivalue = expires;
+    pjsip_msg_add_hdr( tdata->msg,
+		       pjsip_hdr_shallow_clone(tdata->pool, sub->expires));
+
+    /* Add Accept header: */
+    pjsip_msg_add_hdr( tdata->msg,
+		       pjsip_hdr_shallow_clone(tdata->pool, sub->accept));
+    
+
+    /* Add Allow-Events header: */
+    pjsip_msg_add_hdr( tdata->msg,
+		       pjsip_hdr_shallow_clone(tdata->pool, 
+					       mod_evsub.allow_events_hdr));
+
+   
+    *p_tdata = tdata;
+
+
+on_return:
+
+    pjsip_dlg_dec_lock(sub->dlg);
+    return status;
+}
+
+
+/*
+ * Accept incoming subscription request.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_accept( pjsip_evsub *sub,
+					pjsip_rx_data *rdata,
+				        int st_code,
+					const pjsip_hdr *hdr_list )
+{
+    pjsip_tx_data *tdata;
+    pjsip_transaction *tsx;
+    pj_status_t status;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(sub && rdata, PJ_EINVAL);
+
+    /* Can only be for server subscription: */
+    PJ_ASSERT_RETURN(sub->role == PJSIP_ROLE_UAS, PJ_EINVALIDOP);
+
+    /* Only expect 2xx status code (for now) */
+    PJ_ASSERT_RETURN(st_code/100 == 2, PJ_EINVALIDOP);
+
+    /* Subscription MUST have been attached to the transaction.
+     * Initial subscription request will be attached on evsub_create_uas(),
+     * while subsequent requests will be attached in tsx_state()
+     */
+    tsx = pjsip_rdata_get_tsx(rdata);
+    PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] != NULL,
+		     PJ_EINVALIDOP);
+
+    /* Lock dialog */
+    pjsip_dlg_inc_lock(sub->dlg);
+
+    /* Create response: */
+    status = pjsip_dlg_create_response( sub->dlg, rdata, st_code, NULL, 
+					&tdata);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+
+    /* Add expires header: */
+    pjsip_msg_add_hdr( tdata->msg,
+		       pjsip_hdr_shallow_clone(tdata->pool, sub->expires));
+
+
+    /* Send the response: */
+    status = pjsip_dlg_send_response( sub->dlg, tsx, tdata );
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+
+on_return:
+
+    pjsip_dlg_dec_lock(sub->dlg);
+    return status;
+}
+
+
+/*
+ * Create Subscription-State header based on current server subscription
+ * state.
+ */
+static pjsip_sub_state_hdr* sub_state_create( pj_pool_t *pool,
+					      pjsip_evsub *sub,
+					      pjsip_evsub_state state,
+					      const pj_str_t *state_str,
+					      const pj_str_t *reason )
+{
+    pjsip_sub_state_hdr *sub_state;
+    pj_time_val now, delay;
+
+    /* Get the remaining time before refresh is required */
+    pj_gettimeofday(&now);
+    delay = sub->refresh_time;
+    PJ_TIME_VAL_SUB(delay, now);
+
+    /* Create the Subscription-State header */
+    sub_state = pjsip_sub_state_hdr_create(pool);
+
+    /* Fill up the header */
+    switch (state) {
+    case PJSIP_EVSUB_STATE_SENT:
+    case PJSIP_EVSUB_STATE_ACCEPTED:
+	pj_assert(!"Invalid state!");
+	/* Treat as pending */
+
+    case PJSIP_EVSUB_STATE_PENDING:
+	sub_state->sub_state = STR_PENDING;
+	sub_state->expires_param = delay.sec;
+	break;
+
+    case PJSIP_EVSUB_STATE_ACTIVE:
+	sub_state->sub_state = STR_ACTIVE;
+	sub_state->expires_param = delay.sec;
+	break;
+
+    case PJSIP_EVSUB_STATE_TERMINATED:
+	sub_state->sub_state = STR_TERMINATED;
+	if (reason != NULL)
+	    pj_strdup(pool, &sub_state->reason_param, reason);
+	break;
+
+    case PJSIP_EVSUB_STATE_UNKNOWN:
+	pj_assert(state_str != NULL);
+	pj_strdup(pool, &sub_state->sub_state, state_str);
+	break;
+    }
+    
+    return sub_state;
+}
+
+/*
+ * Create and send NOTIFY request.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_notify( pjsip_evsub *sub,
+					pjsip_evsub_state state,
+					const pj_str_t *state_str,
+					const pj_str_t *reason,
+					pjsip_tx_data **p_tdata)
+{
+    pjsip_tx_data *tdata;
+    pjsip_sub_state_hdr *sub_state;
+    pj_status_t status;
+
+    /* Check arguments. */
+    PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL);
+
+    /* Lock dialog. */
+    pjsip_dlg_inc_lock(sub->dlg);
+
+    /* Create NOTIFY request */
+    status = pjsip_dlg_create_request( sub->dlg, &pjsip_notify_method, -1, 
+				       &tdata);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* Add Event header */
+    pjsip_msg_add_hdr(tdata->msg,
+		      pjsip_hdr_shallow_clone(tdata->pool, sub->event));
+
+    /* Add Subscription-State header */
+    sub_state = sub_state_create(tdata->pool, sub, state, state_str,
+				 reason);
+    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sub_state);
+
+    /* Add Allow-Events header */
+    pjsip_msg_add_hdr(tdata->msg,
+		      pjsip_hdr_shallow_clone(tdata->pool, mod_evsub.allow_events_hdr));
+
+    /* Add Authentication headers. */
+    pjsip_auth_clt_init_req( &sub->dlg->auth_sess, tdata );
+
+    
+
+    /* Save destination state. */
+    sub->dst_state = state;
+    if (state_str)
+	pj_strdup(sub->pool, &sub->dst_state_str, state_str);
+    else
+	sub->dst_state_str.slen = 0;
+
+
+    *p_tdata = tdata;
+
+on_return:
+    /* Unlock dialog */
+    pjsip_dlg_dec_lock(sub->dlg);
+    return status;
+}
+
+
+/*
+ * Create NOTIFY to reflect current status.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_current_notify( pjsip_evsub *sub,
+						pjsip_tx_data **p_tdata )
+{
+    return pjsip_evsub_notify( sub, sub->state, &sub->state_str, 
+			       NULL, p_tdata );
+}
+
+
+/*
+ * Send request.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_send_request( pjsip_evsub *sub,
+					      pjsip_tx_data *tdata)
+{
+    pj_status_t status;
+
+    /* Must be request message. */
+    PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
+		     PJSIP_ENOTREQUESTMSG);
+
+    /* Lock */
+    pjsip_dlg_inc_lock(sub->dlg);
+
+    /* Send the request. */
+    status = pjsip_dlg_send_request(sub->dlg, tdata, NULL);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+
+    /* Special case for NOTIFY:
+     * The new state was set in pjsip_evsub_notify(), but we apply the
+     * new state now, when the request was actually sent.
+     */
+    if (pjsip_method_cmp(&tdata->msg->line.req.method, 
+			 &pjsip_notify_method)==0) 
+    {
+	PJ_ASSERT_ON_FAIL(  sub->dst_state!=PJSIP_EVSUB_STATE_NULL,
+			    {goto on_return;});
+
+	set_state(sub, sub->dst_state, 
+		  (sub->dst_state_str.slen ? &sub->dst_state_str : NULL), 
+		  NULL);
+
+	sub->dst_state = PJSIP_EVSUB_STATE_NULL;
+	sub->dst_state_str.slen = 0;
+
+    }
+
+
+on_return:
+    pjsip_dlg_dec_lock(sub->dlg);
+    return status;
+}
+
+
+
+/*
+ * Attach subscription session to newly created transaction, if appropriate.
+ */
+static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx,
+				        pjsip_event *event)
+{
+    /*
+     * Newly created transaction will not have subscription session
+     * attached to it. Find the subscription session from the dialog,
+     * by matching the Event header.
+     */
+    pjsip_dialog *dlg;
+    pjsip_event_hdr *event_hdr;
+    pjsip_msg *msg;
+    struct dlgsub *dlgsub_head, *dlgsub;
+    pjsip_evsub *sub;
+    
+    dlg = pjsip_tsx_get_dlg(tsx);
+    if (!dlg) {
+	pj_assert(!"Transaction should have a dialog instance!");
+	return NULL;
+    }
+
+    switch (event->body.tsx_state.type) {
+    case PJSIP_EVENT_RX_MSG:
+	msg = event->body.tsx_state.src.rdata->msg_info.msg;
+	break;
+    case PJSIP_EVENT_TX_MSG:
+	msg = event->body.tsx_state.src.tdata->msg;
+	break;
+    default:
+	if (tsx->role == PJSIP_ROLE_UAC)
+	    msg = tsx->last_tx->msg;
+	else
+	    msg = NULL;
+	break;
+    }
+    
+    if (!msg) {
+	pj_assert(!"First transaction event is not TX or RX!");
+	return NULL;
+    }
+
+    event_hdr = pjsip_msg_find_hdr_by_name(msg, &STR_EVENT, NULL);
+    if (!event_hdr) {
+	/* Not subscription related message */
+	return NULL;
+    }
+
+    /* Find the subscription in the dialog, based on the content
+     * of Event header: 
+     */
+
+    dlgsub_head = dlg->mod_data[mod_evsub.mod.id];
+    if (dlgsub_head == NULL) {
+	dlgsub_head = pj_pool_alloc(dlg->pool, sizeof(struct dlgsub));
+	pj_list_init(dlgsub_head);
+	dlg->mod_data[mod_evsub.mod.id] = dlgsub_head;
+    }
+    dlgsub = dlgsub_head->next;
+
+    while (dlgsub != dlgsub_head) {
+
+	/* Match event type and Id */
+	if (pj_strcmp(&dlgsub->sub->event->id_param, &event_hdr->id_param)==0 &&
+	    pj_stricmp(&dlgsub->sub->event->event_type, &event_hdr->event_type)==0)
+	{
+	    break;
+	}
+	dlgsub = dlgsub->next;
+    }
+
+    if (dlgsub == dlgsub_head) {
+	/* This could be incoming request to create new subscription */
+	PJ_LOG(4,(THIS_FILE, 
+		  "Subscription not found for %.*s, event=%.*s;id=%.*s",
+		  (int)event_hdr->event_type.slen,
+		  event_hdr->event_type.ptr,
+		  (int)event_hdr->id_param.slen,
+		  event_hdr->id_param.ptr));
+
+	/* If this is an incoming NOTIFY, reject with 481 */
+	if (tsx->state == PJSIP_TSX_STATE_TRYING &&
+	    pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0)
+	{
+	    pj_str_t reason = pj_str("Subscription Does Not Exist");
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+
+	    status = pjsip_dlg_create_response(dlg, 
+					       event->body.tsx_state.src.rdata, 
+					       481, &reason, 
+					       &tdata);
+	    if (status == PJ_SUCCESS) {
+		status = pjsip_dlg_send_response(dlg, tsx, tdata);
+	    }
+	}
+	return NULL;
+    }
+
+    /* Found! */
+    sub = dlgsub->sub;
+
+    /* Attach session to the transaction */
+    tsx->mod_data[mod_evsub.mod.id] = sub;
+    sub->pending_tsx++;
+
+    return sub;
+}
+
+
+/*
+ * Create response, adding custome headers and msg body.
+ */
+static pj_status_t create_response( pjsip_evsub *sub,
+				    pjsip_rx_data *rdata,
+				    int st_code,
+				    const pj_str_t *st_text,
+				    const pjsip_hdr *res_hdr,
+				    const pjsip_msg_body *body,
+				    pjsip_tx_data **p_tdata)
+{
+    pjsip_tx_data *tdata;
+    pjsip_hdr *hdr;
+    pj_status_t status;
+
+    status = pjsip_dlg_create_response(sub->dlg, rdata,
+				       st_code, st_text, &tdata);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    *p_tdata = tdata;
+
+    /* Add response headers. */
+    hdr = res_hdr->next;
+    while (hdr != res_hdr) {
+	pjsip_msg_add_hdr( tdata->msg, 
+			   pjsip_hdr_clone(tdata->pool, hdr));
+	hdr = hdr->next;
+    }
+
+    /* Add msg body, if any */
+    if (body) {
+	tdata->msg->body = pj_pool_zalloc(tdata->pool, 
+					  sizeof(pjsip_msg_body));
+	status = pjsip_msg_body_clone(tdata->pool, 
+				      tdata->msg->body, 
+				      body);
+	if (status != PJ_SUCCESS) {
+	    tdata->msg->body = NULL;
+	    /* Ignore */
+	    return PJ_SUCCESS;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Get subscription state from the value of Subscription-State header.
+ */
+static void get_hdr_state( pjsip_sub_state_hdr *sub_state,
+			   pjsip_evsub_state *state,
+			   pj_str_t **state_str )
+{
+    if (pj_stricmp(&sub_state->sub_state, &STR_TERMINATED)==0) {
+
+	*state = PJSIP_EVSUB_STATE_TERMINATED;
+	*state_str = NULL;
+
+    } else if (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0) {
+
+	*state = PJSIP_EVSUB_STATE_ACTIVE;
+	*state_str = NULL;
+
+    } else if (pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0) {
+
+	*state = PJSIP_EVSUB_STATE_PENDING;
+	*state_str = NULL;
+
+    } else {
+
+	*state = PJSIP_EVSUB_STATE_UNKNOWN;
+	*state_str = &sub_state->sub_state;
+
+    }
+}
+
+/*
+ * Transaction event processing by UAC, after subscription is sent.
+ */
+static void on_tsx_state_uac( pjsip_evsub *sub, pjsip_transaction *tsx,
+			      pjsip_event *event )
+{
+
+    if (pjsip_method_cmp(&tsx->method, &sub->method)==0) {
+
+	/* Received response to outgoing request that establishes/refresh
+	 * subscription. 
+	 */
+
+	/* First time initial request is sent. */
+	if (sub->state == PJSIP_EVSUB_STATE_NULL &&
+	    tsx->state == PJSIP_TSX_STATE_CALLING)
+	{
+	    set_state(sub, PJSIP_EVSUB_STATE_SENT, NULL, event);
+	    return;
+	}
+
+	/* Only interested in final response */
+	if (tsx->state != PJSIP_TSX_STATE_COMPLETED &&
+	    tsx->state != PJSIP_TSX_STATE_TERMINATED)
+	{
+	    return;
+	}
+
+	/* Handle authentication. */
+	if (tsx->status_code==401 || tsx->status_code==407) {
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+
+	    if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+		/* Previously failed transaction has terminated */
+		return;
+	    }
+
+	    status = pjsip_auth_clt_reinit_req(&sub->dlg->auth_sess,
+					       event->body.tsx_state.src.rdata,
+					       tsx->last_tx, &tdata);
+	    if (status == PJ_SUCCESS) 
+		status = pjsip_dlg_send_request(sub->dlg, tdata, NULL);
+	    
+	    if (status != PJ_SUCCESS) {
+		/* Authentication failed! */
+		set_state(sub, PJSIP_EVSUB_STATE_TERMINATED,
+			       NULL,
+			       event);
+		return;
+	    }
+
+	    return;
+	}
+
+	if (tsx->status_code/100 == 2) {
+
+	    /* Successfull SUBSCRIBE request! 
+	     * This could be:
+	     *	- response to initial SUBSCRIBE request
+	     *	- response to subsequent refresh
+	     *	- response to unsubscription
+	     */
+
+	    if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+		/* Ignore; this transaction has been processed before */
+		return;
+	    }
+
+	    /* Update UAC refresh time, if response contains Expires header,
+	     * only when we're not unsubscribing. 
+	     */
+	    if (sub->expires->ivalue != 0) {
+		pjsip_msg *msg;
+		pjsip_expires_hdr *expires;
+
+		msg = event->body.tsx_state.src.rdata->msg_info.msg;
+		expires = pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+		if (expires) {
+		    sub->expires->ivalue = expires->ivalue;
+		}
+	    }
+
+	    /* Update time */
+	    update_expires(sub, sub->expires->ivalue);
+
+	    /* Start UAC refresh timer, only when we're not unsubscribing */
+	    if (sub->expires->ivalue != 0) {
+		unsigned timeout = (sub->expires->ivalue > TIME_UAC_REFRESH) ?
+		    sub->expires->ivalue - TIME_UAC_REFRESH : sub->expires->ivalue;
+
+		PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", 
+			  timeout));
+		set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout);
+
+	    } else {
+		/* Otherwise set timer to terminate client subscription when
+		 * NOTIFY to end subscription is not received.
+		 */
+		set_timer(sub, TIMER_TYPE_UAC_TERMINATE, TIME_UAC_TERMINATE);
+	    }
+	    
+	    /* Set state, if necessary */
+	    pj_assert(sub->state != PJSIP_EVSUB_STATE_NULL);
+	    if (sub->state == PJSIP_EVSUB_STATE_SENT) {
+		set_state(sub, PJSIP_EVSUB_STATE_ACCEPTED, NULL, event);
+	    }
+
+	} else {
+
+	    /* Failed SUBSCRIBE request! 
+	     *
+	     * The RFC 3265 says that if outgoing SUBSCRIBE fails with status
+	     * other than 481, the subscription is still considered valid for
+	     * the duration of the last Expires.
+	     *
+	     * Since we send refresh about 5 seconds (TIME_UAC_REFRESH) before 
+	     * expiration, theoritically the expiration is still valid for the 
+	     * next 5 seconds even when we receive non-481 failed response.
+	     *
+	     * Ah, what the heck!
+	     *
+	     * Just terminate now!
+	     *
+	     */
+
+	    if (sub->state == PJSIP_EVSUB_STATE_TERMINATED) {
+		/* Ignore, has been handled before */
+		return;
+	    }
+
+	    /* Kill any timer. */
+	    set_timer(sub, TIMER_TYPE_NONE, 0);
+
+	    /* Set state to TERMINATED */
+	    set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, 
+		      NULL, event);
+
+	}
+
+    } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) {
+
+	/* Incoming NOTIFY. 
+	 * This can be the result of:
+	 *  - Initial subscription response
+	 *  - UAS updating the resource info.
+	 *  - Unsubscription response.
+	 */
+	int st_code = 200;
+	pj_str_t *st_text = NULL;
+	pjsip_hdr res_hdr;
+	pjsip_msg_body *body = NULL;
+
+	pjsip_rx_data *rdata;
+	pjsip_msg *msg;
+	pjsip_sub_state_hdr *sub_state;
+
+	pjsip_evsub_state new_state;
+	pj_str_t *new_state_str;
+
+	pjsip_tx_data *tdata;
+	pj_status_t status;
+	int next_refresh;
+
+	/* Only want to handle initial NOTIFY receive event. */
+	if (tsx->state != PJSIP_TSX_STATE_TRYING)
+	    return;
+
+
+	rdata = event->body.tsx_state.src.rdata;
+	msg = rdata->msg_info.msg;
+
+	pj_list_init(&res_hdr);
+
+	/* Get subscription state header. */
+	sub_state = pjsip_msg_find_hdr_by_name(msg, &STR_SUB_STATE, NULL);
+	if (sub_state == NULL) {
+
+	    pjsip_warning_hdr *warn_hdr;
+	    pj_str_t warn_text = { "Missing Subscription-State header", 33};
+
+	    /* Bad request! Add warning header. */
+	    st_code = PJSIP_SC_BAD_REQUEST;
+	    warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399,
+					        pjsip_endpt_name(sub->endpt),
+						&warn_text);
+	    pj_list_push_back(&res_hdr, warn_hdr);
+	}
+
+	/* Call application registered callback to handle incoming NOTIFY,
+	 * if any.
+	 */
+	if (st_code==200 && sub->user.on_rx_notify) {
+	    (*sub->user.on_rx_notify)(sub, rdata, &st_code, &st_text, 
+				      &res_hdr, &body);
+
+	    /* Application MUST specify final response! */
+	    PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; });
+
+	    /* Must be a valid status code */
+	    PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; });
+	}
+
+
+	/* If non-2xx should be returned, then send the response.
+	 * No need to update server subscription state.
+	 */
+	if (st_code >= 300) {
+	    status = create_response(sub, rdata, st_code, st_text, &res_hdr,
+				     body, &tdata);
+	    if (status == PJ_SUCCESS) {
+		status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);
+	    }
+
+	    /* Start timer to terminate subscription, just in case server
+	     * is not able to generate NOTIFY to our response.
+	     */
+	    if (status == PJ_SUCCESS) {
+		unsigned timeout = TIME_UAC_WAIT_NOTIFY;
+		set_timer(sub, TIMER_TYPE_UAC_WAIT_NOTIFY, timeout);
+	    } else {
+		set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL);
+	    }
+
+	    return;
+	}
+
+	/* Update expiration from the value of expires param in
+	 * Subscription-State header, but ONLY when subscription state 
+	 * is "active" or "pending", AND the header contains expires param.
+	 */
+	if (sub->expires->ivalue != 0 &&
+	    sub_state->expires_param >= 0 &&
+	    (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0 ||
+	     pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0))
+	{
+	    next_refresh = sub_state->expires_param;
+
+	} else {
+	    next_refresh = sub->expires->ivalue;
+	}
+
+	/* Update time */
+	update_expires(sub, next_refresh);
+
+	/* Start UAC refresh timer, only when we're not unsubscribing */
+	if (sub->expires->ivalue != 0) {
+	    unsigned timeout = (next_refresh > TIME_UAC_REFRESH) ?
+		next_refresh - TIME_UAC_REFRESH : next_refresh;
+
+	    PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", timeout));
+	    set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout);
+	}
+	
+	/* Find out the state */
+	get_hdr_state(sub_state, &new_state, &new_state_str);
+
+	/* Send response. */
+	status = create_response(sub, rdata, st_code, st_text, &res_hdr,
+				 body, &tdata);
+	if (status == PJ_SUCCESS)
+	    status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);
+
+	/* Set the state */
+	if (status == PJ_SUCCESS) {
+	    set_state(sub, new_state, new_state_str, event);
+	} else {
+	    set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event);
+	}
+
+
+    } else {
+
+	/*
+	 * Unexpected method!
+	 */
+	PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s",
+		 (int)tsx->method.name.slen, tsx->method.name.ptr));
+    }
+}
+
+
+/*
+ * Transaction event processing by UAS, after subscription is accepted.
+ */
+static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx,
+			      pjsip_event *event)
+{
+
+    if (pjsip_method_cmp(&tsx->method, &sub->method) == 0) {
+	
+	/*
+	 * Incoming request (e.g. SUBSCRIBE or REFER) to refresh subsciption.
+	 *
+	 */
+	pjsip_rx_data *rdata;
+	pjsip_event_hdr *event_hdr;
+	pjsip_expires_hdr *expires;
+	pjsip_msg *msg;
+	pjsip_tx_data *tdata;
+	int st_code = 200;
+	pj_str_t *st_text = NULL;
+	pjsip_hdr res_hdr;
+	pjsip_msg_body *body = NULL;
+	pjsip_evsub_state old_state;
+	pj_str_t old_state_str;
+	pj_status_t status;
+
+
+	/* Only wants to handle the first event when the request is 
+	 * received.
+	 */
+	if (tsx->state != PJSIP_TSX_STATE_TRYING)
+	    return;
+
+	rdata = event->body.tsx_state.src.rdata;
+	msg = rdata->msg_info.msg;
+
+	/* Set expiration time based on client request (in Expires header),
+	 * or package default expiration time.
+	 */
+	event_hdr = pjsip_msg_find_hdr_by_name(msg, &STR_EVENT, NULL);
+	expires = pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+	if (event_hdr && expires) {
+	    struct evpkg *evpkg;
+
+	    evpkg = find_pkg(&event_hdr->event_type);
+	    if (evpkg) {
+		if (expires->ivalue < (pj_int32_t)evpkg->pkg_expires)
+		    sub->expires->ivalue = expires->ivalue;
+		else
+		    sub->expires->ivalue = evpkg->pkg_expires;
+	    }
+	}
+	
+	/* Update time (before calling on_rx_refresh, since application
+	 * will send NOTIFY.
+	 */
+	update_expires(sub, sub->expires->ivalue);
+
+
+	/* Save old state.
+	 * If application respond with non-2xx, revert to old state.
+	 */
+	old_state = sub->state;
+	old_state_str = sub->state_str;
+
+	if (sub->expires->ivalue == 0) {
+	    sub->state = PJSIP_EVSUB_STATE_TERMINATED;
+	    sub->state_str = evsub_state_names[sub->state];
+	} else  if (sub->state == PJSIP_EVSUB_STATE_NULL) {
+	    sub->state = PJSIP_EVSUB_STATE_ACCEPTED;
+	    sub->state_str = evsub_state_names[sub->state];
+	}
+
+	/* Call application's on_rx_refresh, just in case it wants to send
+	 * response other than 200 (OK)
+	 */
+	pj_list_init(&res_hdr);
+
+	(*sub->user.on_rx_refresh)(sub, rdata, &st_code, &st_text, 
+				   &res_hdr, &body);
+
+	/* Application MUST specify final response! */
+	PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; });
+
+	/* Must be a valid status code */
+	PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; });
+
+
+	/* Create and send response */
+	status = create_response(sub, rdata, st_code, st_text, &res_hdr,
+				 body, &tdata);
+	if (status == PJ_SUCCESS) {
+	    /* Add expires header: */
+	    pjsip_msg_add_hdr( tdata->msg,
+			       pjsip_hdr_shallow_clone(tdata->pool, 
+						       sub->expires));
+
+	    /* Send */
+	    status = pjsip_dlg_send_response(sub->dlg, tsx, tdata);
+	}
+
+	/* Update state or revert state */
+	if (st_code/100==2) {
+	    
+	    if (sub->expires->ivalue == 0) {
+		set_state(sub, sub->state, NULL, event);
+	    } else  if (sub->state == PJSIP_EVSUB_STATE_NULL) {
+		set_state(sub, sub->state, NULL, event);
+	    }
+
+	    /* Set UAS timeout timer, when state is not terminated. */
+	    if (sub->state != PJSIP_EVSUB_STATE_TERMINATED) {
+		PJ_LOG(5,(sub->obj_name, "UAS timeout in %d seconds",
+			  sub->expires->ivalue));
+		set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, 
+			  sub->expires->ivalue);
+	    }
+
+	}  else {
+	    sub->state = old_state;
+	    sub->state_str = old_state_str;
+	}
+
+
+    } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0) {
+
+	/* Handle authentication */ 
+	if (tsx->state == PJSIP_TSX_STATE_COMPLETED &&
+	    (tsx->status_code==401 || tsx->status_code==407))
+	{
+	    pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
+	    pjsip_tx_data *tdata;
+	    pj_status_t status;
+
+	    status = pjsip_auth_clt_reinit_req( &sub->dlg->auth_sess, rdata, 
+						tsx->last_tx, &tdata);
+	    if (status == PJ_SUCCESS)
+		status = pjsip_dlg_send_request( sub->dlg, tdata, NULL );
+
+	    if (status != PJ_SUCCESS) {
+		/* Can't authenticate. Terminate session (?) */
+		set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL);
+	    }
+	} 
+
+    } else {
+
+	/*
+	 * Unexpected method!
+	 */
+	PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s",
+		 (int)tsx->method.name.slen, tsx->method.name.ptr));
+    
+    }
+}
+
+
+/*
+ * Notification when transaction state has changed!
+ */
+static void mod_evsub_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
+{
+    pjsip_evsub *sub = pjsip_tsx_get_evsub(tsx);
+
+    if (sub == NULL) {
+	sub = on_new_transaction(tsx, event);
+	if (sub == NULL)
+	    return;
+    }
+
+
+    /* Call on_tsx_state callback, if any. */
+    if (sub->user.on_tsx_state)
+	(*sub->user.on_tsx_state)(sub, tsx, event);
+
+
+    /* Process the event: */
+
+    if (sub->role == PJSIP_ROLE_UAC) {
+	on_tsx_state_uac(sub, tsx, event);
+    } else {
+	on_tsx_state_uas(sub, tsx, event);
+    }
+
+
+    /* Check transaction TERMINATE event */
+    if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+	--sub->pending_tsx;
+
+	if (sub->state == PJSIP_EVSUB_STATE_TERMINATED &&
+	    sub->pending_tsx == 0)
+	{
+	    evsub_destroy(sub);
+	}
+
+    }
+}
+
+
Index: pjproject/trunk/pjsip/src/pjsip-simple/evsub_msg.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-simple/evsub_msg.c	(revision 197)
+++ pjproject/trunk/pjsip/src/pjsip-simple/evsub_msg.c	(revision 197)
@@ -0,0 +1,296 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip/print_util.h>
+#include <pjsip/sip_parser.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/except.h>
+
+/*
+ * Event header.
+ */
+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)
+{
+    pjsip_event_hdr *hdr = pj_pool_zalloc(pool, sizeof(*hdr));
+    hdr->type = PJSIP_H_OTHER;
+    hdr->name.ptr = "Event";
+    hdr->name.slen = 5;
+    hdr->sname.ptr = "o";
+    hdr->sname.slen = 1;
+    hdr->vptr = &event_hdr_vptr;
+    pj_list_init(hdr);
+    pj_list_init(&hdr->other_param);
+    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);
+    
+    printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
+				   &pjsip_PARAM_CHAR_SPEC, 
+				   &pjsip_PARAM_CHAR_SPEC, ';');
+    if (printed < 0)
+	return printed;
+
+    p += printed;
+    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);
+    pjsip_param_clone(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));
+    pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+    return hdr;
+}
+
+
+/*
+ * Allow-Events header.
+ */
+PJ_DEF(pjsip_allow_events_hdr*) pjsip_allow_events_hdr_create(pj_pool_t *pool)
+{
+    const pj_str_t STR_ALLOW_EVENTS = { "Allow-Events", 12};
+    pjsip_allow_events_hdr *hdr;
+
+    hdr = pjsip_generic_array_hdr_create(pool, &STR_ALLOW_EVENTS);
+
+    if (hdr) {
+	hdr->sname.ptr = "u";
+	hdr->sname.slen = 1;
+    }
+
+    return hdr;
+}
+
+
+/*
+ * Subscription-State header.
+ */
+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_zalloc(pool, 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);
+    pj_list_init(&hdr->other_param);
+    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;
+    }
+    
+    printed = pjsip_param_print_on( &hdr->other_param, p, endbuf-p, 
+				    &pjsip_PARAM_CHAR_SPEC,
+				    &pjsip_PARAM_CHAR_SPEC,
+				    ';');
+    if (printed < 0)
+	return printed;
+
+    p += printed;
+
+    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;
+    pjsip_param_clone(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));
+    pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+    return hdr;
+}
+
+
+/*
+ * Parse Event header.
+ */
+static pjsip_hdr *parse_hdr_event(pjsip_parse_ctx *ctx)
+{
+    pjsip_event_hdr *hdr = pjsip_event_hdr_create(ctx->pool);
+    const pj_str_t id_param = { "id", 2 };
+
+    pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->event_type);
+
+    while (*ctx->scanner->curptr == ';') {
+	pj_str_t pname, pvalue;
+
+	pj_scan_get_char(ctx->scanner);
+	pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
+
+	if (pj_stricmp(&pname, &id_param)==0) {
+	    hdr->id_param = pvalue;
+	} else {
+	    pjsip_param *param = pj_pool_alloc(ctx->pool, sizeof(pjsip_param));
+	    param->name = pname;
+	    param->value = pvalue;
+	    pj_list_push_back(&hdr->other_param, param);
+	}
+    }
+    pjsip_parse_end_hdr_imp( ctx->scanner );
+    return (pjsip_hdr*)hdr;
+}
+
+/*
+ * Parse Subscription-State header.
+ */
+static pjsip_hdr* parse_hdr_sub_state( pjsip_parse_ctx *ctx )
+{
+    pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(ctx->pool);
+    const pj_str_t reason = { "reason", 6 },
+		   expires = { "expires", 7 },
+		   retry_after = { "retry-after", 11 };
+    pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->sub_state);
+
+    while (*ctx->scanner->curptr == ';') {
+	pj_str_t pname, pvalue;
+
+	pj_scan_get_char(ctx->scanner);
+	pjsip_parse_param_imp(ctx->scanner, ctx->pool, &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_param *param = pj_pool_alloc(ctx->pool, sizeof(pjsip_param));
+	    param->name = pname;
+	    param->value = pvalue;
+	    pj_list_push_back(&hdr->other_param, param);
+	}
+    }
+
+    pjsip_parse_end_hdr_imp( ctx->scanner );
+    return (pjsip_hdr*)hdr;
+}
+
+/*
+ * Register header parsers.
+ */
+PJ_DEF(void) pjsip_evsub_init_parser(void)
+{
+    pjsip_register_hdr_parser( "Event", NULL, 
+			       &parse_hdr_event);
+
+    pjsip_register_hdr_parser( "Subscription-State", NULL, 
+			       &parse_hdr_sub_state);
+}
+
Index: pjproject/trunk/pjsip/src/pjsip-simple/messaging.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-simple/messaging.c	(revision 196)
+++ 	(revision )
@@ -1,352 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
- */
-#include <pjsip_simple/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_util.h>
-#include <pj/string.h>
-#include <pj/pool.h>
-#include <pj/guid.h>
-#include <pj/string.h>
-#include <pj/log.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#define THIS_FILE   "messaging"
-
-struct messaging_data
-{
-    void		     *token;
-    pjsip_messaging_cb	     cb;
-};
-
-struct pjsip_messaging_session
-{
-    pj_pool_t		*pool;
-    pjsip_endpoint	*endpt;
-    pjsip_from_hdr	*from;
-    pjsip_to_hdr	*to;
-    pjsip_cid_hdr	*call_id;
-    pjsip_cseq_hdr	*cseq;
-};
-
-static int module_id;
-static pjsip_on_new_msg_cb incoming_cb;
-static pjsip_method message_method;
-
-
-/*
- * Set global callback to receive incoming message.
- */
-PJ_DEF(pjsip_on_new_msg_cb) 
-pjsip_messaging_set_incoming_callback(pjsip_on_new_msg_cb cb)
-{
-    pjsip_on_new_msg_cb prev_cb = incoming_cb;
-    incoming_cb = cb;
-    return prev_cb;
-}
-
-
-/*
- * Create an independent message (ie. not associated with a session).
- */
-PJ_DEF(pjsip_tx_data*) 
-pjsip_messaging_create_msg_from_hdr(pjsip_endpoint *endpt, 
-				    const pjsip_uri *target,
-				    const pjsip_from_hdr *param_from,
-				    const pjsip_to_hdr *param_to, 
-				    const pjsip_cid_hdr *param_call_id,
-				    int param_cseq, 
-				    const pj_str_t *param_text)
-{
-    return pjsip_endpt_create_request_from_hdr( endpt, &message_method, 
-						target,
-						param_from, param_to,
-						NULL, param_call_id,
-						param_cseq, param_text );
-}
-
-/*
- * Create independent message from string (instead of from header).
- */
-PJ_DEF(pjsip_tx_data*) 
-pjsip_messaging_create_msg( pjsip_endpoint *endpt, 
-			    const pj_str_t *target,
-			    const pj_str_t *param_from,
-			    const pj_str_t *param_to, 
-			    const pj_str_t *param_call_id,
-			    int param_cseq, 
-			    const pj_str_t *param_text)
-{
-    return pjsip_endpt_create_request( endpt, &message_method, target, 
-				       param_from, param_to, NULL, param_call_id,
-				       param_cseq, param_text);
-}
-
-/*
- * Initiate transaction to send outgoing message.
- */
-PJ_DEF(pj_status_t) 
-pjsip_messaging_send_msg( pjsip_endpoint *endpt, pjsip_tx_data *tdata, 
-			  void *token, pjsip_messaging_cb cb )
-{
-    pjsip_transaction *tsx;
-    struct messaging_data *msg_data;
-
-    /* Create transaction. */
-    tsx = pjsip_endpt_create_tsx(endpt);
-    if (!tsx) {
-	pjsip_tx_data_dec_ref(tdata);
-	return -1;
-    }
-
-    /* Save parameters to messaging data and attach to tsx. */
-    msg_data = pj_pool_calloc(tsx->pool, 1, sizeof(struct messaging_data));
-    msg_data->cb = cb;
-    msg_data->token = token;
-
-    /* Init transaction. */
-    tsx->module_data[module_id] = msg_data;
-    if (pjsip_tsx_init_uac(tsx, tdata) != 0) {
-	pjsip_tx_data_dec_ref(tdata);
-	pjsip_endpt_destroy_tsx(endpt, tsx);
-	return -1;
-    }
-
-    pjsip_endpt_register_tsx(endpt, tsx);
-
-    /* 
-     * Instruct transaction to send message.
-     * Further events will be received via transaction's event.
-     */
-    pjsip_tsx_on_tx_msg(tsx, tdata);
-
-    /* Decrement reference counter. */
-    pjsip_tx_data_dec_ref(tdata);
-    return 0;
-}
-
-
-/*
- * Create 'IM session'.
- */
-PJ_DEF(pjsip_messaging_session*) 
-pjsip_messaging_create_session( pjsip_endpoint *endpt, const pj_str_t *param_from,
-			        const pj_str_t *param_to )
-{
-    pj_pool_t *pool;
-    pjsip_messaging_session *ses;
-    pj_str_t tmp, to;
-
-    pool = pjsip_endpt_create_pool(endpt, "imsess", 1024, 1024);
-    if (!pool)
-	return NULL;
-
-    ses = pj_pool_calloc(pool, 1, sizeof(pjsip_messaging_session));
-    ses->pool = pool;
-    ses->endpt = endpt;
-
-    ses->call_id = pjsip_cid_hdr_create(pool);
-    pj_create_unique_string(pool, &ses->call_id->id);
-
-    ses->cseq = pjsip_cseq_hdr_create(pool);
-    ses->cseq->cseq = pj_rand();
-    ses->cseq->method = message_method;
-
-    ses->from = pjsip_from_hdr_create(pool);
-    pj_strdup_with_null(pool, &tmp, param_from);
-    ses->from->uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR);
-    if (ses->from->uri == NULL) {
-	pjsip_endpt_destroy_pool(endpt, pool);
-	return NULL;
-    }
-    pj_create_unique_string(pool, &ses->from->tag);
-
-    ses->to = pjsip_to_hdr_create(pool);
-    pj_strdup_with_null(pool, &to, param_from);
-    ses->to->uri = pjsip_parse_uri(pool, to.ptr, to.slen, PJSIP_PARSE_URI_AS_NAMEADDR);
-    if (ses->to->uri == NULL) {
-	pjsip_endpt_destroy_pool(endpt, pool);
-	return NULL;
-    }
-
-    PJ_LOG(4,(THIS_FILE, "IM session created: recipient=%s", to.ptr));
-    return ses;
-}
-
-
-/*
- * Send IM message using identification from 'IM session'.
- */
-PJ_DEF(pjsip_tx_data*)
-pjsip_messaging_session_create_msg( pjsip_messaging_session *ses, const pj_str_t *text )
-{
-    return pjsip_endpt_create_request_from_hdr( ses->endpt,
-						&message_method,
-						ses->to->uri,
-						ses->from,
-						ses->to,
-						NULL,
-						ses->call_id,
-						ses->cseq->cseq++,
-						text);
-}
-
-
-/*
- * Destroy 'IM session'.
- */
-PJ_DEF(pj_status_t)
-pjsip_messaging_destroy_session( pjsip_messaging_session *ses )
-{
-    /*
-     * NOTE ABOUT POSSIBLE BUG HERE...
-     *
-     * We don't check number of pending transaction before destroying IM
-     * session. As the result, the headers in the txdata of pending transaction
-     * wil be INVALID once the IM session is deleted (because we only
-     * shallo_clone()-ed them).
-     *
-     * This normally should be okay, because once the message is
-     * submitted to transaction, the transaction (or rather the transport)
-     * will 'print' the message to a buffer, and once it is printed, it
-     * won't try to access the original message again. So even when the 
-     * original message has a dangling pointer, we should be safe.
-     *
-     * However, it will cause a problem if:
-     *	- resolving completes asynchronously and with a substantial delay,
-     *	  and before the resolver/transport finished its job the user
-     *	  destroy the IM session.
-     *	- if the transmit data is invalidated after the IM session is
-     *	  destroyed.
-     */
-
-    pjsip_endpt_destroy_pool(ses->endpt, ses->pool);
-    return 0;
-}
-
-
-static pj_status_t messaging_init( pjsip_endpoint *endpt,
-				   struct pjsip_module *mod, pj_uint32_t id )
-{
-    PJ_UNUSED_ARG(endpt)
-    PJ_UNUSED_ARG(mod)
-
-    module_id = id;
-    return 0;
-}
-
-static pj_status_t messaging_start( struct pjsip_module *mod )
-{
-    PJ_UNUSED_ARG(mod)
-    return 0;
-}
-
-static pj_status_t messaging_deinit( struct pjsip_module *mod )
-{
-    PJ_UNUSED_ARG(mod)
-    return 0;
-}
-
-static void messaging_tsx_handler( struct pjsip_module *mod, pjsip_event *event )
-{
-    pjsip_transaction *tsx = event->obj.tsx;
-    struct messaging_data *mod_data;
-
-    PJ_UNUSED_ARG(mod)
-
-    /* Ignore non transaction event */
-    if (event->type != PJSIP_EVENT_TSX_STATE_CHANGED || tsx == NULL)
-	return;
-
-    /* If this is an incoming message, inform application. */
-    if (tsx->role == PJSIP_ROLE_UAS) {
-	int status = 100;
-	pjsip_tx_data *tdata;
-
-	/* Check if we already answered this request. */
-	if (tsx->status_code >= 200)
-	    return;
-
-	/* Only handle MESSAGE requests!. */
-	if (pjsip_method_cmp(&tsx->method, &message_method) != 0)
-	    return;
-
-	/* Call application callback. */
-	if (incoming_cb)
-	    status = (*incoming_cb)(event->src.rdata);
-
-	if (status < 200 || status >= 700)
-	    status = PJSIP_SC_INTERNAL_SERVER_ERROR;
-
-	/* Respond request. */
-	tdata = pjsip_endpt_create_response(tsx->endpt, event->src.rdata, status );
-	if (tdata)
-	    pjsip_tsx_on_tx_msg(tsx, tdata);
-
-	return;
-    }
-
-    /* Ignore if it's not something that came from messaging module. */
-    mod_data = tsx->module_data[ module_id ];
-    if (mod_data == NULL)
-	return;
-
-    /* Ignore non final response. */
-    if (tsx->status_code < 200)
-	return;
-
-    /* Don't want to call the callback more than once. */
-    tsx->module_data[ module_id ] = NULL;
-
-    /* Now call the callback. */
-    if (mod_data->cb) {
-	(*mod_data->cb)(mod_data->token, tsx->status_code);
-    }
-}
-
-static pjsip_module messaging_module = 
-{
-    { "Messaging", 9},	    /* Name.		*/
-    0,			    /* Flag		*/
-    128,		    /* Priority		*/
-    NULL,		    /* User agent instance, initialized by APP.	*/
-    0,			    /* Number of methods supported (will be initialized later). */
-    { 0 },		    /* Array of methods (will be initialized later) */
-    &messaging_init,	    /* init_module()	*/
-    &messaging_start,	    /* start_module()	*/
-    &messaging_deinit,	    /* deinit_module()	*/
-    &messaging_tsx_handler, /* tsx_handler()	*/
-};
-
-PJ_DEF(pjsip_module*) pjsip_messaging_get_module()
-{
-    static pj_str_t method_str = { "MESSAGE", 7 };
-
-    pjsip_method_init_np( &message_method, &method_str);
-
-    messaging_module.method_cnt = 1;
-    messaging_module.methods[0] = &message_method;
-
-    return &messaging_module;
-}
-
Index: pjproject/trunk/pjsip/src/pjsip-simple/pidf.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-simple/pidf.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsip-simple/pidf.c	(revision 197)
@@ -17,7 +17,9 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
-#include <pjsip_simple/pidf.h>
+#include <pjsip-simple/pidf.h>
 #include <pj/string.h>
 #include <pj/pool.h>
+#include <pj/assert.h>
+
 
 struct pjpidf_op_desc pjpidf_op = 
@@ -133,5 +135,5 @@
 PJ_DEF(void) pjpidf_pres_remove_tuple(pjpidf_pres *pres, pjpidf_tuple *t)
 {
-    PJ_UNUSED_ARG(pres)
+    PJ_UNUSED_ARG(pres);
     pj_list_erase(t);
 }
Index: pjproject/trunk/pjsip/src/pjsip-simple/presence.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-simple/presence.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsip-simple/presence.c	(revision 197)
@@ -17,383 +17,918 @@
  * 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 }
+
+
+#define THIS_FILE		    "presence.c"
+#define PRES_DEFAULT_EXPIRES	    600
+
+/*
+ * Presence module (mod-presence)
+ */
+static struct pjsip_module mod_presence = 
+{
+    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()			*/
 };
-static pjsip_media_type accept_types[] = {
-    {
-	{ "application", 11 },
-	{ "pidf+xml", 8 }
-    },
-    {
-	{ "application", 11 },
-	{ "xpidf+xml", 9 }
-    }
+
+
+/*
+ * 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.		    */
 };
 
-/* 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;
+
+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;
+}
+
+
+/*
+ * 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;
-
-    /* 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;
+    status = pjsip_evsub_create_uac( dlg,  &pres_user, &STR_PRESENCE, &sub);
+    if (status != PJ_SUCCESS)
+	goto on_return;
+
+    /* 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));
+
+    /* 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;
+}
+
+
+/*
+ * Create server subscription.
+ */
+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 )
+{
     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);
+    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;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
+
+    /* Must be request message */
+    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+		     PJSIP_ENOTREQUESTMSG);
+
+    /* 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);
-
+	if (i==accept->count) {
+	    /* Nothing is acceptable */
+	    return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
+	}
+
+    } 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 {
-	    return;
+	    pj_strdup(pres->dlg->pool, 
+		      &pres->status.info[i].id,
+		      &status->info[i].id);
 	}
-
-	(*cb.on_received_update)(pres, is_open);
-    }
-}
-
+	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);
+    }
+}
+
Index: pjproject/trunk/pjsip/src/pjsip-simple/xpidf.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-simple/xpidf.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsip-simple/xpidf.c	(revision 197)
@@ -17,8 +17,9 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
-#include <pjsip_simple/xpidf.h>
+#include <pjsip-simple/xpidf.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
 #include <pj/pool.h>
 #include <pj/string.h>
-#include <pj/guid.h>
 
 static pj_str_t PRESENCE = { "presence", 8 };
Index: pjproject/trunk/pjsip/src/pjsip/sip_endpoint.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip/sip_endpoint.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsip/sip_endpoint.c	(revision 197)
@@ -216,4 +216,7 @@
     /* Done. */
 
+    PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" registered", 
+	      (int)mod->name.slen, mod->name.ptr));
+
 on_return:
     pj_rwmutex_unlock_write(endpt->mod_mutex);
@@ -254,4 +257,7 @@
     }
 
+    /* Module MUST NOT set module ID to -1. */
+    pj_assert(mod->id >= 0);
+
     /* Remove module from array. */
     endpt->modules[mod->id] = NULL;
@@ -265,4 +271,7 @@
     /* Done. */
     status = PJ_SUCCESS;
+
+    PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" unregistered", 
+	      (int)mod->name.slen, mod->name.ptr));
 
 on_return:
Index: pjproject/trunk/pjsip/src/pjsip/sip_errno.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip/sip_errno.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsip/sip_errno.c	(revision 197)
@@ -67,4 +67,5 @@
     /* Transaction errors */
     { PJSIP_ETSXDESTROYED,	"Transaction has been destroyed"},
+    { PJSIP_ENOTSX,		"No transaction (expecting stateful processing)" },
 
     /* Authentication. */
Index: pjproject/trunk/pjsip/src/pjsip/sip_msg.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip/sip_msg.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsip/sip_msg.c	(revision 197)
@@ -146,5 +146,5 @@
     pj_strset2( &status_phrase[487], "Request Terminated");
     pj_strset2( &status_phrase[488], "Not Acceptable Here");
-    pj_strset2( &status_phrase[489], "Unknown Event");
+    pj_strset2( &status_phrase[489], "Bad Event");
     pj_strset2( &status_phrase[490], "Request Updated");
     pj_strset2( &status_phrase[491], "Request Pending");
Index: pjproject/trunk/pjsip/src/pjsip/sip_ua_layer.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip/sip_ua_layer.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsip/sip_ua_layer.c	(revision 197)
@@ -522,18 +522,30 @@
     }
 
-    /* Dialog MUST be found! */
+    /* Dialog may not be found, e.g. in this case:
+     *	- UAC sends SUBSCRIBE, then UAS sends NOTIFY before answering
+     *    SUBSCRIBE request with 2xx.
+     *
+     * In this case, we can accept the request ONLY when the original 
+     * dialog still has empty To tag.
+     */
     if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
 
-	/* Not found. Mulfunction UAC? */
-	pj_mutex_unlock(mod_ua.mutex);
-
-	PJ_LOG(5,(THIS_FILE, 
-	          "Unable to find dialog for %s, answering with 481",
-		  pjsip_rx_data_get_info(rdata)));
-
-	pjsip_endpt_respond_stateless(mod_ua.endpt, rdata,
-				      PJSIP_SC_CALL_TSX_DOES_NOT_EXIST, 
-				      NULL, NULL, NULL);
-	return PJ_TRUE;
+	pjsip_dialog *first_dlg = dlg_set->dlg_list.next;
+
+	if (first_dlg->remote.info->tag.slen != 0) {
+	    /* Not found. Mulfunction UAC? */
+	    pj_mutex_unlock(mod_ua.mutex);
+
+	    PJ_LOG(5,(THIS_FILE, 
+		      "Unable to find dialog for %s, answering with 481",
+		      pjsip_rx_data_get_info(rdata)));
+
+	    pjsip_endpt_respond_stateless(mod_ua.endpt, rdata,
+					  PJSIP_SC_CALL_TSX_DOES_NOT_EXIST, 
+					  NULL, NULL, NULL);
+	    return PJ_TRUE;
+	}
+
+	dlg = first_dlg;
     }
 
Index: pjproject/trunk/pjsip/src/pjsua/main.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua/main.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsua/main.c	(revision 197)
@@ -23,5 +23,6 @@
 #define THIS_FILE	"main.c"
 
-static pjsip_inv_session *inv_session;
+/* Current dialog */
+static struct pjsua_inv_data *inv_session;
 
 /*
@@ -36,10 +37,14 @@
 
     if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
-	if (inv == inv_session)
-	    inv_session = NULL;
+	if (inv == inv_session->inv) {
+	    inv_session = inv_session->next;
+	    if (inv_session == &pjsua.inv_list)
+		inv_session = pjsua.inv_list.next;
+	}
 
     } else {
 
-	inv_session = inv;
+	if (inv_session == &pjsua.inv_list || inv_session == NULL)
+	    inv_session = inv->mod_data[pjsua.mod.id];
 
     }
@@ -57,22 +62,69 @@
 
 
+/*
+ * Print buddy list.
+ */
+static void print_buddy_list(void)
+{
+    unsigned i;
+
+    puts("Buddy list:");
+    //puts("-------------------------------------------------------------------------------");
+    if (pjsua.buddy_cnt == 0)
+	puts(" -none-");
+    else {
+	for (i=0; i<pjsua.buddy_cnt; ++i) {
+	    const char *status;
+
+	    if (pjsua.buddies[i].sub == NULL || 
+		pjsua.buddies[i].status.info_cnt==0)
+	    {
+		status = "   ?   ";
+	    } 
+	    else if (pjsua.buddies[i].status.info[0].basic_open)
+		status = " Online";
+	    else
+		status = "Offline";
+
+	    printf(" [%2d] <%s>  %s\n", 
+		    i+1, status, pjsua.buddies[i].uri.ptr);
+	}
+    }
+    puts("");
+}
 
 /*
  * Show a bit of help.
  */
-static void ui_help(void)
-{
-    puts("");
-    puts("Console keys:");
-    puts("  m    Make a call/another call");
-    puts("  d    Dump application states");
-    puts("  a    Answer incoming call");
-    puts("  h    Hangup current call");
-    puts("  q    Quit");
-    puts("");
+static void keystroke_help(void)
+{
+
+    printf(">>>>\nOnline status: %s\n", 
+	   (pjsua.online_status ? "Online" : "Invisible"));
+    print_buddy_list();
+    
+    //puts("Commands:");
+    puts("+=============================================================================+");
+    puts("|       Call Commands:         |      IM & Presence:      |   Misc:           |");
+    puts("|                              |                          |                   |");
+    puts("|  m  Make new call            |  i  Send IM              |  o  Send OPTIONS  |");
+    puts("|  a  Answer call              |  s  Subscribe presence   |  d  Dump status   |");
+    puts("|  h  Hangup call              |  u  Unsubscribe presence |  d1 Dump detailed |");
+    puts("|  ]  Select next dialog       |  t  Toggle Online status |                   |");
+    puts("|  [  Select previous dialog   |                          |                   |");
+    puts("+-----------------------------------------------------------------------------+");
+    puts("|  q  QUIT                                                                    |");
+    puts("+=============================================================================+");
+    printf(">>> ");
+
+
     fflush(stdout);
 }
 
-static pj_bool_t input(const char *title, char *buf, pj_size_t len)
+
+/*
+ * Input simple string
+ */
+static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
 {
     char *p;
@@ -93,46 +145,129 @@
 }
 
+
+#define NO_NB	-2
+struct input_result
+{
+    int	  nb_result;
+    char *uri_result;
+};
+
+
+/*
+ * Input URL.
+ */
+static void ui_input_url(const char *title, char *buf, int len, 
+			 struct input_result *result)
+{
+    result->nb_result = NO_NB;
+    result->uri_result = NULL;
+
+    print_buddy_list();
+
+    printf("Choices:\n"
+	   "   0         For current dialog.\n"
+	   "  -1         All %d buddies in buddy list\n"
+	   "  [1 -%2d]    Select from buddy list\n"
+	   "  URL        An URL\n"
+	   "  <Enter>    Empty input (or 'q') to cancel\n"
+	   , pjsua.buddy_cnt, pjsua.buddy_cnt);
+    printf("%s: ", title);
+
+    fflush(stdout);
+    fgets(buf, len, stdin);
+    len = strlen(buf);
+
+    /* Left trim */
+    while (isspace(*buf)) {
+	++buf;
+	--len;
+    }
+
+    /* Remove trailing newlines */
+    while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
+	buf[--len] = '\0';
+
+    if (len == 0 || buf[0]=='q')
+	return;
+
+    if (isdigit(*buf) || *buf=='-') {
+	
+	int i;
+	
+	if (*buf=='-')
+	    i = 1;
+	else
+	    i = 0;
+
+	for (; i<len; ++i) {
+	    if (!isdigit(buf[i])) {
+		puts("Invalid input");
+		return;
+	    }
+	}
+
+	result->nb_result = atoi(buf);
+
+	if (result->nb_result > 0 && result->nb_result <= (int)pjsua.buddy_cnt) {
+	    --result->nb_result;
+	    return;
+	}
+	if (result->nb_result == -1)
+	    return;
+
+	puts("Invalid input");
+	result->nb_result = NO_NB;
+	return;
+
+    } else {
+	pj_status_t status;
+
+	if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
+	    pjsua_perror("Invalid URL", status);
+	    return;
+	}
+
+	result->uri_result = buf;
+    }
+}
+
 static void ui_console_main(void)
 {
+    char menuin[10];
     char buf[128];
     pjsip_inv_session *inv;
-
-    //ui_help();
+    struct input_result result;
+
+    //keystroke_help();
 
     for (;;) {
 
-	ui_help();
-	fgets(buf, sizeof(buf), stdin);
-
-	switch (buf[0]) {
+	keystroke_help();
+	fgets(menuin, sizeof(menuin), stdin);
+
+	switch (menuin[0]) {
 
 	case 'm':
-	    if (inv_session != NULL) {
-		puts("Can not make call while another one is in progress");
-		fflush(stdout);
-		continue;
-	    }
-
-#if 1
 	    /* Make call! : */
-	    if (!input("Enter URL to call", buf, sizeof(buf)))
-		continue;
-	    pjsua_invite(buf, &inv);
-
-#else
-
-	    pjsua_invite("sip:localhost:5061", &inv);
-#endif
-	    break;
-
-
-	case 'd':
-	    pjsua_dump();
-	    break;
+	    if (pj_list_size(&pjsua.inv_list))
+		printf("(You have %d calls)\n", pj_list_size(&pjsua.inv_list));
+	    
+	    ui_input_url("Make call", buf, sizeof(buf), &result);
+	    if (result.nb_result != NO_NB) {
+		if (result.nb_result == -1)
+		    puts("You can't do that with make call!");
+		else
+		    pjsua_invite(pjsua.buddies[result.nb_result].uri.ptr, &inv);
+	    } else if (result.uri_result)
+		pjsua_invite(result.uri_result, &inv);
+	    
+	    break;
+
 
 	case 'a':
 
-	    if (inv_session == NULL || inv_session->role != PJSIP_ROLE_UAS ||
-		inv_session->state >= PJSIP_INV_STATE_CONNECTING) 
+	    if (inv_session == &pjsua.inv_list || 
+		inv_session->inv->role != PJSIP_ROLE_UAS ||
+		inv_session->inv->state >= PJSIP_INV_STATE_CONNECTING) 
 	    {
 		puts("No pending incoming call");
@@ -144,5 +279,5 @@
 		pjsip_tx_data *tdata;
 
-		if (!input("Answer with code (100-699)", buf, sizeof(buf)))
+		if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
 		    continue;
 		
@@ -150,8 +285,8 @@
 		    continue;
 
-		status = pjsip_inv_answer(inv_session, atoi(buf), NULL, NULL, 
-					  &tdata);
+		status = pjsip_inv_answer(inv_session->inv, atoi(buf), 
+					  NULL, NULL, &tdata);
 		if (status == PJ_SUCCESS)
-		    status = pjsip_inv_send_msg(inv_session, tdata, NULL);
+		    status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL);
 
 		if (status != PJ_SUCCESS)
@@ -161,7 +296,8 @@
 	    break;
 
+
 	case 'h':
 
-	    if (inv_session == NULL) {
+	    if (inv_session == &pjsua.inv_list) {
 		puts("No current call");
 		fflush(stdout);
@@ -172,6 +308,6 @@
 		pjsip_tx_data *tdata;
 
-		status = pjsip_inv_end_session(inv_session, PJSIP_SC_DECLINE, 
-					       NULL, &tdata);
+		status = pjsip_inv_end_session(inv_session->inv, 
+					       PJSIP_SC_DECLINE, NULL, &tdata);
 		if (status != PJ_SUCCESS) {
 		    pjsua_perror("Failed to create end session message", status);
@@ -179,5 +315,5 @@
 		}
 
-		status = pjsip_inv_send_msg(inv_session, tdata, NULL);
+		status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL);
 		if (status != PJ_SUCCESS) {
 		    pjsua_perror("Failed to send end session message", status);
@@ -185,5 +321,45 @@
 		}
 	    }
-
+	    break;
+
+	case ']':
+	    inv_session = inv_session->next;
+	    if (inv_session == &pjsua.inv_list)
+		inv_session = pjsua.inv_list.next;
+	    break;
+
+	case '[':
+	    inv_session = inv_session->prev;
+	    if (inv_session == &pjsua.inv_list)
+		inv_session = pjsua.inv_list.prev;
+	    break;
+
+	case 's':
+	case 'u':
+	    ui_input_url("Subscribe presence of", buf, sizeof(buf), &result);
+	    if (result.nb_result != NO_NB) {
+		if (result.nb_result == -1) {
+		    unsigned i;
+		    for (i=0; i<pjsua.buddy_cnt; ++i)
+			pjsua.buddies[i].monitor = (menuin[0]=='s');
+		} else {
+		    pjsua.buddies[result.nb_result].monitor = (menuin[0]=='s');
+		}
+
+		pjsua_pres_refresh();
+
+	    } else if (result.uri_result) {
+		puts("Sorry, can only subscribe to buddy's presence, not arbitrary URL (for now)");
+	    }
+
+	    break;
+
+	case 't':
+	    pjsua.online_status = !pjsua.online_status;
+	    pjsua_pres_refresh();
+	    break;
+
+	case 'd':
+	    pjsua_dump();
 	    break;
 
@@ -255,5 +431,5 @@
 {
     NULL, NULL,				/* prev, next.		*/
-    { "mod-console-msg-logger", 22 },	/* Name.		*/
+    { "mod-pjsua-log", 13 },		/* Name.		*/
     -1,					/* Id			*/
     PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority	        */
@@ -386,4 +562,9 @@
 
 
+    /* No current call initially: */
+
+    inv_session = &pjsua.inv_list;
+
+
     /* Start UI console main loop: */
 
Index: pjproject/trunk/pjsip/src/pjsua/pjsua.h
===================================================================
--- pjproject/trunk/pjsip/src/pjsua/pjsua.h	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsua/pjsua.h	(revision 197)
@@ -32,4 +32,7 @@
 #include <pjsip_ua.h>
 
+/* Include all PJSIP-SIMPLE headers */
+#include <pjsip_simple.h>
+
 /* Include all PJLIB-UTIL headers. */
 #include <pjlib-util.h>
@@ -60,4 +63,31 @@
     void		*mod_data[PJSIP_MAX_MODULE];
 };
+
+
+/**
+ * Buddy data.
+ */
+struct pjsua_buddy
+{
+    pj_str_t		 uri;	    /**< Buddy URI		        */
+    pj_bool_t		 monitor;   /**< Should we monitor?		*/
+    pjsip_evsub		*sub;	    /**< Buddy presence subscription	*/
+    pjsip_pres_status	 status;    /**< Buddy presence status.		*/
+};
+
+typedef struct pjsua_buddy pjsua_buddy;
+
+
+/**
+ * Server presence subscription list head.
+ */
+struct pjsua_srv_pres
+{
+    PJ_DECL_LIST_MEMBER(struct pjsua_srv_pres);
+    pjsip_evsub	    *sub;
+    char	    *remote;
+};
+
+typedef struct pjsua_srv_pres pjsua_srv_pres;
 
 
@@ -142,8 +172,11 @@
 
 
-    /* Buddy list: */
+    /* SIMPLE and buddy status: */
+
+    pj_bool_t	    online_status;  /**< Out online status.		*/
+    pjsua_srv_pres  pres_srv_list;  /**< Server subscription list.	*/
 
     unsigned	    buddy_cnt;
-    pj_str_t	    buddies[PJSUA_MAX_BUDDIES];
+    pjsua_buddy	    buddies[PJSUA_MAX_BUDDIES];
 };
 
@@ -236,4 +269,10 @@
 
 
+/**
+ * Terminate all calls.
+ */
+void pjsua_inv_shutdown(void);
+
+
 /*****************************************************************************
  * PJSUA Client Registration API (defined in pjsua_reg.c).
@@ -252,4 +291,31 @@
  */
 void pjsua_regc_update(pj_bool_t renew);
+
+
+
+
+/*****************************************************************************
+ * PJSUA Presence (pjsua_pres.c)
+ */
+
+/**
+ * Init presence.
+ */
+pj_status_t pjsua_pres_init();
+
+/**
+ * Refresh both presence client and server subscriptions.
+ */
+void pjsua_pres_refresh(void);
+
+/**
+ * Terminate all subscriptions
+ */
+void pjsua_pres_shutdown(void);
+
+/**
+ * Dump presence subscriptions.
+ */
+void pjsua_pres_dump(void);
 
 
Index: pjproject/trunk/pjsip/src/pjsua/pjsua_core.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua/pjsua_core.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsua/pjsua_core.c	(revision 197)
@@ -80,4 +80,9 @@
 
     pj_list_init(&pjsua.inv_list);
+
+    /* Init server presence subscription list: */
+    
+    pj_list_init(&pjsua.pres_srv_list);
+
 }
 
@@ -392,12 +397,12 @@
 
 
-static int PJ_THREAD_FUNC pjsua_worker_thread(void *arg)
+static int PJ_THREAD_FUNC pjsua_poll(void *arg)
 {
     PJ_UNUSED_ARG(arg);
 
-    while (!pjsua.quit_flag) {
+    do {
 	pj_time_val timeout = { 0, 10 };
 	pjsip_endpt_handle_events (pjsua.endpt, &timeout);
-    }
+    } while (!pjsua.quit_flag);
 
     return 0;
@@ -436,5 +441,5 @@
 
 
-    /* Init PJSIP and all the modules: */
+    /* Init PJSIP : */
 
     status = init_stack();
@@ -444,4 +449,18 @@
 	return status;
     }
+
+
+    /* Init core SIMPLE module : */
+
+    pjsip_evsub_init_module(pjsua.endpt);
+
+    /* Init presence module: */
+
+    pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance());
+
+
+    /* Init pjsua presence handler: */
+
+    pjsua_pres_init();
 
 
@@ -610,5 +629,5 @@
 
     for (i=0; i<pjsua.thread_cnt; ++i) {
-	status = pj_thread_create( pjsua.pool, "pjsua", &pjsua_worker_thread,
+	status = pj_thread_create( pjsua.pool, "pjsua", &pjsua_poll,
 				   NULL, 0, 0, &pjsua.threads[i]);
 	if (status != PJ_SUCCESS) {
@@ -636,8 +655,23 @@
 
 
-
+    PJ_LOG(3,(THIS_FILE, "PJSUA version %s started", PJ_VERSION));
     return PJ_SUCCESS;
 }
 
+
+/* Sleep with polling */
+static void busy_sleep(unsigned msec)
+{
+    pj_time_val timeout, now;
+
+    pj_gettimeofday(&timeout);
+    timeout.msec += msec;
+    pj_time_val_normalize(&timeout);
+
+    do {
+	pjsua_poll(NULL);
+	pj_gettimeofday(&now);
+    } while (PJ_TIME_VAL_LT(now, timeout));
+}
 
 /*
@@ -648,31 +682,8 @@
     int i;
 
-    /* Unregister, if required: */
-    if (pjsua.regc) {
-
-	pjsua_regc_update(0);
-
-	/* Wait for some time to allow unregistration to complete: */
-
-	pj_thread_sleep(500);
-    }
-
     /* Signal threads to quit: */
-
     pjsua.quit_flag = 1;
 
-
-    /* Shutdown pjmedia-codec: */
-
-    pjmedia_codec_deinit();
-
-
-    /* Destroy sound framework: 
-     * (this should be done in pjmedia_shutdown())
-     */
-    pj_snd_deinit();
-
     /* Wait worker threads to quit: */
-
     for (i=0; i<pjsua.thread_cnt; ++i) {
 	
@@ -684,4 +695,28 @@
     }
 
+
+    /* Terminate all calls. */
+    pjsua_inv_shutdown();
+
+    /* Terminate all presence subscriptions. */
+    pjsua_pres_shutdown();
+
+    /* Unregister, if required: */
+    if (pjsua.regc) {
+	pjsua_regc_update(0);
+    }
+
+    /* Wait for some time to allow unregistration to complete: */
+    PJ_LOG(4,(THIS_FILE, "Shutting down..."));
+    busy_sleep(1000);
+
+    /* Shutdown pjmedia-codec: */
+    pjmedia_codec_deinit();
+
+    /* Destroy sound framework: 
+     * (this should be done in pjmedia_shutdown())
+     */
+    pj_snd_deinit();
+
     /* Destroy endpoint. */
 
Index: pjproject/trunk/pjsip/src/pjsua/pjsua_inv.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua/pjsua_inv.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsua/pjsua_inv.c	(revision 197)
@@ -81,4 +81,5 @@
     inv_data->inv = inv;
     dlg->mod_data[pjsua.mod.id] = inv_data;
+    inv->mod_data[pjsua.mod.id] = inv_data;
 
 
@@ -222,4 +223,5 @@
 	    inv_data->inv = inv;
 	    dlg->mod_data[pjsua.mod.id] = inv_data;
+	    inv->mod_data[pjsua.mod.id] = inv_data;
 
 	    pj_list_push_back(&pjsua.inv_list, inv_data);
@@ -346,2 +348,24 @@
     }
 }
+
+
+/*
+ * Terminate all calls.
+ */
+void pjsua_inv_shutdown()
+{
+    struct pjsua_inv_data *inv_data, *next;
+
+    inv_data = pjsua.inv_list.next;
+    while (inv_data != &pjsua.inv_list) {
+	pjsip_tx_data *tdata;
+
+	next = inv_data->next;
+
+	if (pjsip_inv_end_session(inv_data->inv, 410, NULL, &tdata)==0)
+	    pjsip_inv_send_msg(inv_data->inv, tdata, NULL);
+
+	inv_data = next;
+    }
+}
+
Index: pjproject/trunk/pjsip/src/pjsua/pjsua_opt.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua/pjsua_opt.c	(revision 196)
+++ pjproject/trunk/pjsip/src/pjsua/pjsua_opt.c	(revision 197)
@@ -404,5 +404,5 @@
 		return -1;
 	    }
-	    pjsua.buddies[pjsua.buddy_cnt++] = pj_str(optarg);
+	    pjsua.buddies[pjsua.buddy_cnt++].uri = pj_str(optarg);
 	    break;
 	}
@@ -511,4 +511,5 @@
     pjsip_endpt_dump(pjsua.endpt, 1);
     pjmedia_endpt_dump(pjsua.med_endpt);
+    pjsip_tsx_layer_dump();
     pjsip_ua_dump();
 
@@ -536,4 +537,7 @@
 	}
     }
+
+    /* Dump presence status */
+    pjsua_pres_dump();
 
     pj_log_set_decor(old_decor);
@@ -548,6 +552,7 @@
 {
     int argc = 3;
-    char *argv[] = { "pjsua", "--config-file", (char*)filename, NULL};
-
+    char *argv[4] = { "pjsua", "--config-file", NULL, NULL};
+
+    argv[3] = (char*)filename;
     return pjsua_parse_args(argc, argv);
 }
@@ -655,6 +660,6 @@
     for (i=0; i<pjsua.buddy_cnt; ++i) {
 	pj_ansi_sprintf(line, "--add-buddy %.*s\n",
-			      (int)pjsua.buddies[i].slen,
-			      pjsua.buddies[i].ptr);
+			      (int)pjsua.buddies[i].uri.slen,
+			      pjsua.buddies[i].uri.ptr);
 	pj_strcat2(&cfg, line);
     }
Index: pjproject/trunk/pjsip/src/pjsua/pjsua_pres.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua/pjsua_pres.c	(revision 197)
+++ pjproject/trunk/pjsip/src/pjsua/pjsua_pres.c	(revision 197)
@@ -0,0 +1,471 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include "pjsua.h"
+
+/*
+ * pjsua_pres.c
+ *
+ * Presence related stuffs.
+ */
+
+#define THIS_FILE   "pjsua_pres.c"
+
+
+
+/* **************************************************************************
+ * THE FOLLOWING PART HANDLES SERVER SUBSCRIPTION
+ * **************************************************************************
+ */
+
+/* Proto */
+static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata);
+
+/* The module instance. */
+static pjsip_module mod_pjsua_pres = 
+{
+    NULL, NULL,				/* prev, next.		*/
+    { "mod-pjsua-pres", 14 },		/* Name.		*/
+    -1,					/* Id			*/
+    PJSIP_MOD_PRIORITY_APPLICATION,	/* Priority	        */
+    NULL,				/* User data.		*/
+    NULL,				/* load()		*/
+    NULL,				/* start()		*/
+    NULL,				/* stop()		*/
+    NULL,				/* unload()		*/
+    &pres_on_rx_request,		/* on_rx_request()	*/
+    NULL,				/* on_rx_response()	*/
+    NULL,				/* on_tx_request.	*/
+    NULL,				/* on_tx_response()	*/
+    NULL,				/* on_tsx_state()	*/
+
+};
+
+
+/* Callback called when *server* subscription state has changed. */
+static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event)
+{
+    pjsua_srv_pres *uapres = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+
+    PJ_UNUSED_ARG(event);
+
+    if (uapres) {
+	PJ_LOG(3,(THIS_FILE, "Server subscription to %s is %s",
+		  uapres->remote, pjsip_evsub_get_state_name(sub)));
+
+	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+	    pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
+	    pj_list_erase(uapres);
+	}
+    }
+}
+
+/* This is called when request is received. 
+ * We need to check for incoming SUBSCRIBE request.
+ */
+static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
+{
+    pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
+    pjsua_srv_pres *uapres;
+    pjsip_evsub *sub;
+    pjsip_evsub_user pres_cb;
+    pjsip_tx_data *tdata;
+    pjsip_pres_status pres_status;
+    pjsip_dialog *dlg;
+    pj_status_t status;
+
+    if (pjsip_method_cmp(req_method, &pjsip_subscribe_method) != 0)
+	return PJ_FALSE;
+
+    /* Incoming SUBSCRIBE: */
+
+    /* Create UAS dialog: */
+    status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, 
+				   &pjsua.contact_uri, &dlg);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Unable to create UAS dialog for subscription", status);
+	return PJ_FALSE;
+    }
+
+    /* Init callback: */
+    pj_memset(&pres_cb, 0, sizeof(pres_cb));
+    pres_cb.on_evsub_state = &pres_evsub_on_srv_state;
+
+    /* Create server presence subscription: */
+    status = pjsip_pres_create_uas( dlg, &pres_cb, rdata, &sub);
+    if (status != PJ_SUCCESS) {
+	PJ_TODO(DESTROY_DIALOG);
+	pjsua_perror("Unable to create server subscription", status);
+	return PJ_FALSE;
+    }
+
+    /* Attach our data to the subscription: */
+    uapres = pj_pool_alloc(dlg->pool, sizeof(pjsua_srv_pres));
+    uapres->sub = sub;
+    uapres->remote = pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE);
+    status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri,
+			     uapres->remote, PJSIP_MAX_URL_SIZE);
+    if (status < 1)
+	pj_ansi_strcpy(uapres->remote, "<-- url is too long-->");
+    else
+	uapres->remote[status] = '\0';
+
+    pjsip_evsub_set_mod_data(sub, pjsua.mod.id, uapres);
+
+    /* Add server subscription to the list: */
+    pj_list_push_back(&pjsua.pres_srv_list, uapres);
+
+
+    /* Create and send 200 (OK) to the SUBSCRIBE request: */
+    status = pjsip_pres_accept(sub, rdata, 200, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Unable to accept presence subscription", status);
+	pj_list_erase(uapres);
+	return PJ_FALSE;
+    }
+
+
+    /* Set our online status: */
+    pj_memset(&pres_status, 0, sizeof(pres_status));
+    pres_status.info_cnt = 1;
+    pres_status.info[0].basic_open = pjsua.online_status;
+    //Both pjsua.local_uri and pjsua.contact_uri are enclosed in "<" and ">"
+    //causing XML parsing to fail.
+    //pres_status.info[0].contact = pjsua.local_uri;
+
+    pjsip_pres_set_status(sub, &pres_status);
+
+    /* Create and send the first NOTIFY to active subscription: */
+    status = pjsip_pres_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, NULL,
+			        NULL, &tdata);
+    if (status == PJ_SUCCESS)
+	status = pjsip_pres_send_request( sub, tdata);
+
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Unable to create/send NOTIFY", status);
+	pj_list_erase(uapres);
+	return PJ_FALSE;
+    }
+
+
+    /* Done: */
+
+    return PJ_TRUE;
+}
+
+
+/* Refresh subscription (e.g. when our online status has changed) */
+static void refresh_server_subscription()
+{
+    pjsua_srv_pres *uapres;
+
+    uapres = pjsua.pres_srv_list.next;
+
+    while (uapres != &pjsua.pres_srv_list) {
+	
+	pjsip_pres_status pres_status;
+	pjsip_tx_data *tdata;
+
+	pjsip_pres_get_status(uapres->sub, &pres_status);
+	if (pres_status.info[0].basic_open != pjsua.online_status) {
+	    pres_status.info[0].basic_open = pjsua.online_status;
+	    pjsip_pres_set_status(uapres->sub, &pres_status);
+
+	    if (pjsua.quit_flag) {
+		pj_str_t reason = { "noresource", 10 };
+		if (pjsip_pres_notify(uapres->sub, 
+				      PJSIP_EVSUB_STATE_TERMINATED, NULL,
+				      &reason, &tdata)==PJ_SUCCESS)
+		{
+		    pjsip_pres_send_request(uapres->sub, tdata);
+		}
+	    } else {
+		if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS)
+		    pjsip_pres_send_request(uapres->sub, tdata);
+	    }
+	}
+
+	uapres = uapres->next;
+    }
+}
+
+
+
+/* **************************************************************************
+ * THE FOLLOWING PART HANDLES CLIENT SUBSCRIPTION
+ * **************************************************************************
+ */
+
+/* Callback called when *client* subscription state has changed. */
+static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
+{
+    pjsua_buddy *buddy;
+
+    PJ_UNUSED_ARG(event);
+
+    buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+    if (buddy) {
+	PJ_LOG(3,(THIS_FILE, 
+		  "Presence subscription to %s is %s",
+		  buddy->uri.ptr, 
+		  pjsip_evsub_get_state_name(sub)));
+
+	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+	    buddy->sub = NULL;
+	    buddy->status.info_cnt = 0;
+	    pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
+	}
+    }
+}
+
+/* Callback called when we receive NOTIFY */
+static void pjsua_evsub_on_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)
+{
+    pjsua_buddy *buddy;
+
+    buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+    if (buddy) {
+	/* Update our info. */
+	pjsip_pres_get_status(sub, &buddy->status);
+
+	if (buddy->status.info_cnt) {
+	    PJ_LOG(3,(THIS_FILE, "%s is %s",
+		      buddy->uri.ptr,
+		      (buddy->status.info[0].basic_open?"online":"offline")));
+	} else {
+	    PJ_LOG(3,(THIS_FILE, "No presence info for %s",
+		      buddy->uri.ptr));
+	}
+    }
+
+    /* The default is to send 200 response to NOTIFY.
+     * Just leave it there..
+     */
+    PJ_UNUSED_ARG(rdata);
+    PJ_UNUSED_ARG(p_st_code);
+    PJ_UNUSED_ARG(p_st_text);
+    PJ_UNUSED_ARG(res_hdr);
+    PJ_UNUSED_ARG(p_body);
+}
+
+
+/* Event subscription callback. */
+static pjsip_evsub_user pres_callback = 
+{
+    &pjsua_evsub_on_state,  
+
+    NULL,   /* on_tsx_state: don't care about transaction state. */
+
+    NULL,   /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless 
+	     * we want to authenticate 
+	     */
+
+    &pjsua_evsub_on_rx_notify,
+
+    NULL,   /* on_client_refresh: Use default behaviour, which is to 
+	     * refresh client subscription. */
+
+    NULL,   /* on_server_timeout: Use default behaviour, which is to send 
+	     * NOTIFY to terminate. 
+	     */
+};
+
+
+/* It does what it says.. */
+static void subscribe_buddy_presence(unsigned index)
+{
+    pjsip_dialog *dlg;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    status = pjsip_dlg_create_uac( pjsip_ua_instance(), 
+				   &pjsua.local_uri,
+				   &pjsua.contact_uri,
+				   &pjsua.buddies[index].uri,
+				   NULL, &dlg);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Unable to create dialog", status);
+	return;
+    }
+
+    status = pjsip_pres_create_uac( dlg, &pres_callback, 
+				    &pjsua.buddies[index].sub);
+    if (status != PJ_SUCCESS) {
+	pjsua.buddies[index].sub = NULL;
+	pjsua_perror("Unable to create presence client", status);
+	return;
+    }
+
+    pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id,
+			     &pjsua.buddies[index]);
+
+    status = pjsip_pres_initiate(pjsua.buddies[index].sub, 60, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua.buddies[index].sub = NULL;
+	pjsua_perror("Unable to create initial SUBSCRIBE", status);
+	return;
+    }
+
+    status = pjsip_pres_send_request(pjsua.buddies[index].sub, tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua.buddies[index].sub = NULL;
+	pjsua_perror("Unable to send initial SUBSCRIBE", status);
+	return;
+    }
+
+    PJ_TODO(DESTROY_DIALOG_ON_ERROR);
+}
+
+
+/* It does what it says... */
+static void unsubscribe_buddy_presence(unsigned index)
+{
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    if (pjsua.buddies[index].sub == NULL)
+	return;
+
+    if (pjsip_evsub_get_state(pjsua.buddies[index].sub) == 
+	PJSIP_EVSUB_STATE_TERMINATED)
+    {
+	pjsua.buddies[index].sub = NULL;
+	return;
+    }
+
+    status = pjsip_pres_initiate( pjsua.buddies[index].sub, 0, &tdata);
+    if (status == PJ_SUCCESS)
+	status = pjsip_pres_send_request( pjsua.buddies[index].sub, tdata );
+
+    if (status == PJ_SUCCESS) {
+
+	//pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id,
+	//				 NULL);
+	//pjsua.buddies[index].sub = NULL;
+
+    } else {
+	pjsua_perror("Unable to unsubscribe presence", status);
+    }
+}
+
+
+/* It does what it says.. */
+static void refresh_client_subscription(void)
+{
+    unsigned i;
+
+    for (i=0; i<pjsua.buddy_cnt; ++i) {
+
+	if (pjsua.buddies[i].monitor && !pjsua.buddies[i].sub) {
+	    subscribe_buddy_presence(i);
+
+	} else if (!pjsua.buddies[i].monitor && pjsua.buddies[i].sub) {
+	    unsubscribe_buddy_presence(i);
+
+	}
+    }
+}
+
+
+/*
+ * Init presence
+ */
+pj_status_t pjsua_pres_init()
+{
+    pj_status_t status;
+
+    status = pjsip_endpt_register_module( pjsua.endpt, &mod_pjsua_pres);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Unable to register pjsua presence module", status);
+    }
+
+    return status;
+}
+
+/*
+ * Refresh presence
+ */
+void pjsua_pres_refresh(void)
+{
+    refresh_client_subscription();
+    refresh_server_subscription();
+}
+
+
+/*
+ * Shutdown presence.
+ */
+void pjsua_pres_shutdown(void)
+{
+    unsigned i;
+
+    pjsua.online_status = 0;
+    for (i=0; i<pjsua.buddy_cnt; ++i) {
+	pjsua.buddies[i].monitor = 0;
+    }
+    pjsua_pres_refresh();
+}
+
+/*
+ * Dump presence status.
+ */
+void pjsua_pres_dump(void)
+{
+    unsigned i;
+
+    PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
+    if (pj_list_empty(&pjsua.pres_srv_list)) {
+	PJ_LOG(3,(THIS_FILE, "  - none - "));
+    } else {
+	struct pjsua_srv_pres *uapres;
+
+	uapres = pjsua.pres_srv_list.next;
+	while (uapres != &pjsua.pres_srv_list) {
+	
+	    PJ_LOG(3,(THIS_FILE, "  %10s %s",
+		      pjsip_evsub_get_state_name(uapres->sub),
+		      uapres->remote));
+
+	    uapres = uapres->next;
+	}
+    }
+
+    PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:"));
+    if (pjsua.buddy_cnt == 0) {
+	PJ_LOG(3,(THIS_FILE, "  - no buddy list - "));
+    } else {
+	for (i=0; i<pjsua.buddy_cnt; ++i) {
+
+	    if (pjsua.buddies[i].sub) {
+		PJ_LOG(3,(THIS_FILE, "  %10s %s",
+			  pjsip_evsub_get_state_name(pjsua.buddies[i].sub),
+			  pjsua.buddies[i].uri.ptr));
+	    } else {
+		PJ_LOG(3,(THIS_FILE, "  %10s %s",
+			  "(null)",
+			  pjsua.buddies[i].uri.ptr));
+	    }
+	}
+    }
+}
+
