diff options
Diffstat (limited to 'pjsip')
35 files changed, 4350 insertions, 3277 deletions
diff --git a/pjsip/build/pjsip.dsw b/pjsip/build/pjsip.dsw index 48914e80..d9fc2ed5 100644 --- a/pjsip/build/pjsip.dsw +++ b/pjsip/build/pjsip.dsw @@ -93,6 +93,18 @@ Package=<4> ###############################################################################
+Project: "pjsip_simple"=.\pjsip_simple.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
Project: "pjsip_ua"=.\pjsip_ua.dsp - Package Owner=<4>
Package=<5>
@@ -134,6 +146,9 @@ Package=<4> Begin Project Dependency
Project_Dep_Name pjlib_util
End Project Dependency
+ Begin Project Dependency
+ Project_Dep_Name pjsip_simple
+ End Project Dependency
}}}
###############################################################################
diff --git a/pjsip/build/pjsip_simple.dsp b/pjsip/build/pjsip_simple.dsp index 5990238b..0739df59 100644 --- a/pjsip/build/pjsip_simple.dsp +++ b/pjsip/build/pjsip_simple.dsp @@ -32,16 +32,16 @@ RSC=rc.exe # 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"
BSC32=bscmake.exe
@@ -49,22 +49,22 @@ BSC32=bscmake.exe # ADD BSC32 /nologo
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"
# 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"
BSC32=bscmake.exe
@@ -72,7 +72,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo
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
@@ -85,27 +85,23 @@ LIB32=link.exe -lib # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# 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
# Begin Group "Header Files"
@@ -113,31 +109,35 @@ SOURCE=..\src\pjsip_simple\xpidf.c # PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
-SOURCE=..\src\pjsip_simple\event_notify.h
+SOURCE="..\include\pjsip-simple\errno.h"
+# End Source File
+# Begin Source File
+
+SOURCE="..\include\pjsip-simple\evsub.h"
# End Source File
# Begin Source File
-SOURCE=..\src\pjsip_simple\event_notify_msg.h
+SOURCE="..\include\pjsip-simple\evsub_msg.h"
# End Source File
# Begin Source File
-SOURCE=..\src\pjsip_simple\messaging.h
+SOURCE="..\include\pjsip-simple\pidf.h"
# End Source File
# Begin Source File
-SOURCE=..\src\pjsip_simple\pidf.h
+SOURCE=..\include\pjsip_simple.h
# End Source File
# Begin Source File
-SOURCE=..\src\pjsip_simple.h
+SOURCE="..\include\pjsip-simple\presence.h"
# End Source File
# Begin Source File
-SOURCE=..\src\pjsip_simple\presence.h
+SOURCE="..\include\pjsip-simple\types.h"
# End Source File
# Begin Source File
-SOURCE=..\src\pjsip_simple\xpidf.h
+SOURCE="..\include\pjsip-simple\xpidf.h"
# End Source File
# End Group
# End Target
diff --git a/pjsip/build/pjsip_ua.dsp b/pjsip/build/pjsip_ua.dsp index d2edb14c..b1269b89 100644 --- a/pjsip/build/pjsip_ua.dsp +++ b/pjsip/build/pjsip_ua.dsp @@ -32,13 +32,13 @@ RSC=rc.exe # 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
# ADD CPP /nologo /MD /W4 /Zi /O2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /c
@@ -50,19 +50,19 @@ BSC32=bscmake.exe # ADD BSC32 /nologo
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"
# 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
# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c
@@ -74,7 +74,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo
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
@@ -92,13 +92,6 @@ SOURCE="..\src\pjsip-ua\sip_inv.c" # Begin Source File
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
# Begin Group "Header Files"
diff --git a/pjsip/build/pjsua.dsp b/pjsip/build/pjsua.dsp index 73fc7a57..604c9939 100644 --- a/pjsip/build/pjsua.dsp +++ b/pjsip/build/pjsua.dsp @@ -32,13 +32,13 @@ RSC=rc.exe # 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 ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
@@ -58,13 +58,13 @@ LINK32=link.exe # 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 ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
@@ -110,6 +110,10 @@ SOURCE=..\src\pjsua\pjsua_opt.c # End Source File
# Begin Source File
+SOURCE=..\src\pjsua\pjsua_pres.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjsua\pjsua_reg.c
# End Source File
# End Group
diff --git a/pjsip/include/pjsip-simple/errno.h b/pjsip/include/pjsip-simple/errno.h new file mode 100644 index 00000000..a09def7b --- /dev/null +++ b/pjsip/include/pjsip-simple/errno.h @@ -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__ */ + diff --git a/pjsip/include/pjsip-simple/event_notify.h b/pjsip/include/pjsip-simple/event_notify.h deleted file mode 100644 index 5f002582..00000000 --- a/pjsip/include/pjsip-simple/event_notify.h +++ /dev/null @@ -1,330 +0,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__ */ diff --git a/pjsip/include/pjsip-simple/evsub.h b/pjsip/include/pjsip-simple/evsub.h new file mode 100644 index 00000000..ee6315ee --- /dev/null +++ b/pjsip/include/pjsip-simple/evsub.h @@ -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__ */ diff --git a/pjsip/include/pjsip-simple/event_notify_msg.h b/pjsip/include/pjsip-simple/evsub_msg.h index 76b7d35c..4f4a9b92 100644 --- a/pjsip/include/pjsip-simple/event_notify_msg.h +++ b/pjsip/include/pjsip-simple/evsub_msg.h @@ -41,10 +41,10 @@ PJ_BEGIN_DECL */ typedef struct pjsip_event_hdr { - PJSIP_DECL_HDR_MEMBER(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_param other_param; /**< Other parameter. */ } pjsip_event_hdr; /** @@ -60,12 +60,7 @@ 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; +typedef pjsip_generic_array_hdr pjsip_allow_events_hdr; /** @@ -83,12 +78,12 @@ PJ_DECL(pjsip_allow_events_hdr*) pjsip_allow_events_hdr_create(pj_pool_t *pool); */ typedef struct pjsip_sub_state_hdr { - PJSIP_DECL_HDR_MEMBER(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_param other_param; /**< Other parameters. */ } pjsip_sub_state_hdr; /** @@ -103,7 +98,7 @@ 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_DEF(void) pjsip_evsub_init_parser(void); PJ_END_DECL diff --git a/pjsip/include/pjsip-simple/messaging.h b/pjsip/include/pjsip-simple/messaging.h deleted file mode 100644 index 742d739c..00000000 --- a/pjsip/include/pjsip-simple/messaging.h +++ /dev/null @@ -1,268 +0,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 diff --git a/pjsip/include/pjsip-simple/pidf.h b/pjsip/include/pjsip-simple/pidf.h index b9dd4509..1b57c5d2 100644 --- a/pjsip/include/pjsip-simple/pidf.h +++ b/pjsip/include/pjsip-simple/pidf.h @@ -23,8 +23,8 @@ * @file pidf.h * @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 diff --git a/pjsip/include/pjsip-simple/presence.h b/pjsip/include/pjsip-simple/presence.h index 180ac4d1..e02498a7 100644 --- a/pjsip/include/pjsip-simple/presence.h +++ b/pjsip/include/pjsip-simple/presence.h @@ -23,9 +23,9 @@ * @file presence.h * @brief SIP Extension for Presence (RFC 3856) */ -#include <pjsip_simple/event_notify.h> -#include <pjsip_simple/pidf.h> -#include <pjsip_simple/xpidf.h> +#include <pjsip-simple/evsub.h> +#include <pjsip-simple/pidf.h> +#include <pjsip-simple/xpidf.h> PJ_BEGIN_DECL @@ -38,185 +38,206 @@ PJ_BEGIN_DECL * * 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. + * 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. */ -typedef enum pjsip_pres_type -{ - PJSIP_PRES_TYPE_PIDF, - PJSIP_PRES_TYPE_XPIDF, -} pjsip_pres_type; +PJ_DECL(pj_status_t) pjsip_pres_init_module(pjsip_endpoint *endpt, + pjsip_module *mod_evsub); + /** - * This structure describe a presentity, for both subscriber and notifier. + * Get the presence module instance. + * + * @return The presence module instance. */ -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; +PJ_DECL(pjsip_module*) pjsip_pres_instance(void); + +#define PJSIP_PRES_STATUS_MAX_INFO 8 /** - * This structure describe callback that is registered to receive notification - * from the presence module. + * This structure describes presence status of a presentity. */ -typedef struct pjsip_presence_cb +struct pjsip_pres_status { - /** - * 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); + unsigned info_cnt; /**< Number of info in the status. */ + struct { - /** - * 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); + pj_bool_t basic_open; /**< Basic status/availability. */ + pj_str_t id; /**< Tuple id. */ + pj_str_t contact; /**< Optional contact address. */ - /** - * 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); + } info[PJSIP_PRES_STATUS_MAX_INFO]; /**< Array of info. */ - /** - * 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); + pj_bool_t _is_valid; /**< Internal flag. */ +}; - /** - * 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; +/** + * @see pjsip_pres_status + */ +typedef struct pjsip_pres_status pjsip_pres_status; /** - * Initialize the presence module and register callback. + * Create presence client subscription session. * - * @param cb Callback structure. + * @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(void) pjsip_presence_init(const pjsip_presence_cb *cb); +PJ_DECL(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_evsub **p_evsub ); /** - * Create to presence subscription of a presentity URL. + * Create presence server subscription session. * - * @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. + * @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 The presence structure if successfull, or NULL if - * failed. + * @return PJ_SUCCESS on success. */ -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 ); +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 ); + /** - * Set credentials to be used by this presentity for outgoing requests. + * Call this function to create request to initiate presence subscription, to + * refresh subcription, or to request subscription termination. * - * @param pres Presentity instance. - * @param count Number of credentials in the array. - * @param cred Array of credentials. + * @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 Zero on success. + * @return PJ_SUCCESS on success. */ -PJ_DECL(pj_status_t) pjsip_presence_set_credentials( pjsip_presentity *pres, - int count, - const pjsip_cred_info cred[]); +PJ_DECL(pj_status_t) pjsip_pres_initiate( pjsip_evsub *sub, + pj_int32_t expires, + pjsip_tx_data **p_tdata); + + /** - * Set route set for outgoing requests. + * Accept the incoming subscription request by sending 2xx response to + * incoming SUBSCRIBE request. * - * @param pres Presentity instance. - * @param route_set List of route headers. + * @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 Zero on success. + * @return PJ_SUCCESS on success. */ -PJ_DECL(pj_status_t) pjsip_presence_set_route_set( pjsip_presentity *pres, - const pjsip_route_hdr *hdr ); +PJ_DECL(pj_status_t) pjsip_pres_accept( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list ); + + + /** - * Send SUBSCRIBE request for the specified presentity. + * 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 pres The presentity instance. + * @param sub Server subscription object. + * @param p_tdata Pointer to receive request. * - * @return Zero on success. + * @return PJ_SUCCESS on success. */ -PJ_DECL(pj_status_t) pjsip_presence_subscribe( pjsip_presentity *pres ); +PJ_DECL(pj_status_t) pjsip_pres_current_notify( pjsip_evsub *sub, + pjsip_tx_data **p_tdata ); + + /** - * Ceased the presence subscription. + * Send request. * - * @param pres The presence structure. - * - * @return Zero on success. + * @param sub The subscription object. + * @param tdata Request message to be sent. + * + * @return PJ_SUCCESS on success. */ -PJ_DECL(pj_status_t) pjsip_presence_unsubscribe( pjsip_presentity *pres ); +PJ_DECL(pj_status_t) pjsip_pres_send_request( pjsip_evsub *sub, + pjsip_tx_data *tdata ); + /** - * Notify subscriber about change in local status. + * Get the presence status. Client normally would call this function + * after receiving NOTIFY request from server. * - * @param pres The presence structure. - * @param state Set the state of the subscription. - * @param open Set the presence status (open or closed). + * @param sub The client or server subscription. + * @param status The structure to receive presence status. * - * @return Zero if a NOTIFY request can be sent. + * @return PJ_SUCCESS on success. */ -PJ_DECL(pj_status_t) pjsip_presence_notify( pjsip_presentity *pres, - pjsip_event_sub_state state, - pj_bool_t open ); +PJ_DECL(pj_status_t) pjsip_pres_get_status( pjsip_evsub *sub, + pjsip_pres_status *status ); + /** - * Destroy presence structure and the underlying subscription. + * 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 pres The presence structure. + * @param sub The server subscription. + * @param status Status to be set. * - * @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. + * @return PJ_SUCCESS on success. */ -PJ_DECL(pj_status_t) pjsip_presence_destroy( pjsip_presentity *pres ); +PJ_DECL(pj_status_t) pjsip_pres_set_status( pjsip_evsub *sub, + const pjsip_pres_status *status ); /** diff --git a/pjsip/include/pjsip-simple/types.h b/pjsip/include/pjsip-simple/types.h new file mode 100644 index 00000000..e202a2b9 --- /dev/null +++ b/pjsip/include/pjsip-simple/types.h @@ -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__ */ + diff --git a/pjsip/include/pjsip-simple/xpidf.h b/pjsip/include/pjsip-simple/xpidf.h index 59d3c398..c5736c54 100644 --- a/pjsip/include/pjsip-simple/xpidf.h +++ b/pjsip/include/pjsip-simple/xpidf.h @@ -23,8 +23,8 @@ * @file xpidf.h * @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 diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h index ec4a4aad..48d86c63 100644 --- a/pjsip/include/pjsip/sip_config.h +++ b/pjsip/include/pjsip/sip_config.h @@ -33,8 +33,8 @@ #define PJSIP_POOL_RDATA_INC 4000 #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 #define PJSIP_TRANSPORT_CLOSE_TIMEOUT 30 @@ -79,7 +79,7 @@ #define PJSIP_MAX_TIMED_OUT_ENTRIES 10 /* Module related constants. */ -#define PJSIP_MAX_MODULE 8 +#define PJSIP_MAX_MODULE 16 /* Maximum header types. */ #define PJSIP_MAX_HEADER_TYPES 64 diff --git a/pjsip/include/pjsip/sip_errno.h b/pjsip/include/pjsip/sip_errno.h index d5000955..70cd884e 100644 --- a/pjsip/include/pjsip/sip_errno.h +++ b/pjsip/include/pjsip/sip_errno.h @@ -207,6 +207,11 @@ PJ_BEGIN_DECL * Transaction has just been destroyed. */ #define PJSIP_ETSXDESTROYED (PJSIP_ERRNO_START_PJSIP + 70) /* 171070 */ +/** + * @hideinitializer + * No transaction. + */ +#define PJSIP_ENOTSX (PJSIP_ERRNO_START_PJSIP + 71) /* 171071 */ /************************************************************ diff --git a/pjsip/include/pjsip/sip_msg.h b/pjsip/include/pjsip/sip_msg.h index a6a1d656..0cef6a36 100644 --- a/pjsip/include/pjsip/sip_msg.h +++ b/pjsip/include/pjsip/sip_msg.h @@ -411,7 +411,7 @@ typedef enum pjsip_status_code PJSIP_SC_BUSY_HERE = 486, 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, PJSIP_SC_UNDECIPHERABLE = 493, @@ -498,7 +498,7 @@ typedef struct pjsip_media_type * When application needs to attach message body to outgoing SIP message, it * must fill in all members of this structure. */ -typedef struct pjsip_msg_body +struct pjsip_msg_body { /** MIME content type. * For incoming messages, the parser will fill in this member with the @@ -565,7 +565,7 @@ typedef struct pjsip_msg_body */ void* (*clone_data)(pj_pool_t *pool, const void *data, unsigned len); -} pjsip_msg_body; +}; /** * General purpose function to textual data in a SIP body. Attach this function diff --git a/pjsip/include/pjsip/sip_types.h b/pjsip/include/pjsip/sip_types.h index 8ea6c414..53035529 100644 --- a/pjsip/include/pjsip/sip_types.h +++ b/pjsip/include/pjsip/sip_types.h @@ -92,6 +92,11 @@ typedef struct pjsip_rx_data pjsip_rx_data; typedef struct pjsip_msg pjsip_msg; /** + * Forward declaration for message body (sip_msg.h). + */ +typedef struct pjsip_msg_body pjsip_msg_body; + +/** * Forward declaration for header field (sip_msg.h). */ typedef struct pjsip_hdr pjsip_hdr; @@ -143,8 +148,14 @@ typedef enum pjsip_dialog_state pjsip_dialog_state; */ 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; diff --git a/pjsip/include/pjsip_simple.h b/pjsip/include/pjsip_simple.h index 29034e63..a8c955ca 100644 --- a/pjsip/include/pjsip_simple.h +++ b/pjsip/include/pjsip_simple.h @@ -33,9 +33,9 @@ #ifndef __PJSIP_SIMPLE_H__ #define __PJSIP_SIMPLE_H__ -#include <pjsip_simple/messaging.h> -#include <pjsip_simple/event_notify.h> -#include <pjsip_simple/pidf.h> -#include <pjsip_simple/presence.h> +#include <pjsip-simple/evsub.h> +#include <pjsip-simple/presence.h> +#include <pjsip-simple/pidf.h> +#include <pjsip-simple/xpidf.h> #endif /* __PJSIP_SIMPLE_H__ */ diff --git a/pjsip/src/pjsip-simple/event_notify.c b/pjsip/src/pjsip-simple/event_notify.c deleted file mode 100644 index 51fe7694..00000000 --- a/pjsip/src/pjsip-simple/event_notify.c +++ /dev/null @@ -1,1644 +0,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 ); - } -} - diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c new file mode 100644 index 00000000..30bd8525 --- /dev/null +++ b/pjsip/src/pjsip-simple/evsub.c @@ -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); + } + + } +} + + diff --git a/pjsip/src/pjsip-simple/event_notify_msg.c b/pjsip/src/pjsip-simple/evsub_msg.c index a5f946ef..7fd44d32 100644 --- a/pjsip/src/pjsip-simple/event_notify_msg.c +++ b/pjsip/src/pjsip-simple/evsub_msg.c @@ -16,13 +16,16 @@ * 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-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, @@ -40,12 +43,15 @@ static pjsip_hdr_vptr event_hdr_vptr = 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)); + pjsip_event_hdr *hdr = pj_pool_zalloc(pool, sizeof(*hdr)); hdr->type = PJSIP_H_OTHER; - hdr->name = hdr->sname = event; + 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; } @@ -62,8 +68,14 @@ static int pjsip_event_hdr_print( pjsip_event_hdr *hdr, 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); + + 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; } @@ -73,93 +85,43 @@ static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool, 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); + 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 ) +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; } -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, -}; - - +/* + * Allow-Events header. + */ 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; + const pj_str_t STR_ALLOW_EVENTS = { "Allow-Events", 12}; + pjsip_allow_events_hdr *hdr; - copy_advance(p, hdr->name); - *p++ = ':'; - *p++ = ' '; + hdr = pjsip_generic_array_hdr_create(pool, &STR_ALLOW_EVENTS); - 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]); - } + if (hdr) { + hdr->sname.ptr = "u"; + hdr->sname.slen = 1; } - 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; } +/* + * 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* @@ -180,13 +142,14 @@ static pjsip_hdr_vptr sub_state_hdr_vptr = 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)); + 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; } @@ -215,8 +178,15 @@ static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr, printed = pj_utoa(hdr->retry_after, p); p += printed; } - if (hdr->other_param.slen) - copy_advance(p, hdr->other_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; } @@ -230,7 +200,7 @@ pjsip_sub_state_hdr_clone(pj_pool_t *pool, 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); + pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param); return hdr; } @@ -240,83 +210,87 @@ pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool, { 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; } -static pjsip_event_hdr *parse_hdr_event(pj_scanner *scanner, - pj_pool_t *pool) + +/* + * Parse Event header. + */ +static pjsip_hdr *parse_hdr_event(pjsip_parse_ctx *ctx) { - pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool); + pjsip_event_hdr *hdr = pjsip_event_hdr_create(ctx->pool); const pj_str_t id_param = { "id", 2 }; - pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->event_type); + pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->event_type); - while (*scanner->current == ';') { + while (*ctx->scanner->curptr == ';') { pj_str_t pname, pvalue; - pj_scan_get_char(scanner); - pjsip_parse_param_imp(scanner, &pname, &pvalue, 0); + + 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_concat_param_imp(&hdr->other_param, pool, &pname, &pvalue, ';'); + 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( scanner ); - return hdr; + pjsip_parse_end_hdr_imp( ctx->scanner ); + return (pjsip_hdr*)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) +/* + * 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(pool); + 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(scanner, pjsip_TOKEN_SPEC, &hdr->sub_state); + pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->sub_state); - while (*scanner->current == ';') { + while (*ctx->scanner->curptr == ';') { pj_str_t pname, pvalue; - pj_scan_get_char(scanner); - pjsip_parse_param_imp(scanner, &pname, &pvalue, 0); + 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_concat_param_imp(&hdr->other_param, pool, &pname, &pvalue, ';'); + 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( scanner ); - return hdr; + pjsip_parse_end_hdr_imp( ctx->scanner ); + return (pjsip_hdr*)hdr; } -PJ_DEF(void) pjsip_event_notify_init_parser(void) +/* + * Register header parsers. + */ +PJ_DEF(void) pjsip_evsub_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); + pjsip_register_hdr_parser( "Event", NULL, + &parse_hdr_event); + + pjsip_register_hdr_parser( "Subscription-State", NULL, + &parse_hdr_sub_state); } + diff --git a/pjsip/src/pjsip-simple/messaging.c b/pjsip/src/pjsip-simple/messaging.c deleted file mode 100644 index 6e08af68..00000000 --- a/pjsip/src/pjsip-simple/messaging.c +++ /dev/null @@ -1,352 +0,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; -} - diff --git a/pjsip/src/pjsip-simple/pidf.c b/pjsip/src/pjsip-simple/pidf.c index 7ababcad..acd20092 100644 --- a/pjsip/src/pjsip-simple/pidf.c +++ b/pjsip/src/pjsip-simple/pidf.c @@ -16,9 +16,11 @@ * 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/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 = { @@ -132,7 +134,7 @@ PJ_DEF(pjpidf_tuple*) pjpidf_pres_find_tuple(pjpidf_pres *pres, const pj_str_t * 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); } diff --git a/pjsip/src/pjsip-simple/presence.c b/pjsip/src/pjsip-simple/presence.c index a9cc6108..ca033f5b 100644 --- a/pjsip/src/pjsip-simple/presence.c +++ b/pjsip/src/pjsip-simple/presence.c @@ -16,384 +16,919 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <pjsip_simple/presence.h> -#include <pjsip/sip_transport.h> -#include <pj/pool.h> -#include <pj/string.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 <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 } +#include <pj/pool.h> +#include <pj/string.h> + + +#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 }; +typedef struct pjsip_pres pjsip_pres; -/* 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. + * Forward decl for evsub callback. */ -PJ_DEF(void) pjsip_presence_init(const pjsip_presence_cb *pcb) +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 = { - pj_memcpy(&cb, pcb, sizeof(*pcb)); - pjsip_event_sub_register_pkg( &PRESENCE_EVENT, - sizeof(accept_names)/sizeof(accept_names[0]), - accept_names, - &pkg_cb); + &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 presence subscription. + * Create client 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 ) +PJ_DEF(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_evsub **p_evsub ) { - pjsip_event_sub *sub; - pjsip_presentity *pres; + pj_status_t status; + pjsip_pres *pres; + pjsip_evsub *sub; - if (expires < 0) - expires = 300; + PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); /* Create event subscription */ - sub = pjsip_event_sub_create(endpt, local_url, remote_url, &PRESENCE_EVENT, - expires, - sizeof(accept_names)/sizeof(accept_names[0]), - accept_names, - NULL, &sub_cb); - if (!sub) - return NULL; + status = pjsip_evsub_create_uac( dlg, &pres_user, &STR_PRESENCE, &sub); + if (status != PJ_SUCCESS) + goto on_return; - /* Allocate presence descriptor. */ - pres = pj_pool_calloc(sub->pool, 1, sizeof(*pres)); - pres->sub = sub; - pres->user_data = user_data; - sub->user_data = pres; + /* Create presence */ + pres = pj_pool_zalloc(dlg->pool, sizeof(pjsip_pres)); + pres->dlg = dlg; + if (user_cb) + pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_presence.id, pres); - return pres; + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; } + /* - * Send SUBSCRIBE. + * Create server subscription. */ -PJ_DEF(pj_status_t) pjsip_presence_subscribe( pjsip_presentity *pres ) +PJ_DEF(pj_status_t) pjsip_pres_create_uas( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_rx_data *rdata, + pjsip_evsub **p_evsub ) { - return pjsip_event_sub_subscribe( pres->sub ); + pjsip_accept_hdr *accept; + pjsip_event_hdr *event; + pjsip_expires_hdr *expires_hdr; + unsigned expires; + content_type content_type = CONTENT_TYPE_NONE; + pjsip_evsub *sub; + pjsip_pres *pres; + pj_status_t status; + + /* 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; + 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==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; } + /* - * Set credentials to be used for outgoing requests. + * Create SUBSCRIBE */ -PJ_DEF(pj_status_t) pjsip_presence_set_credentials( pjsip_presentity *pres, - int count, - const pjsip_cred_info cred[]) +PJ_DEF(pj_status_t) pjsip_pres_initiate( pjsip_evsub *sub, + pj_int32_t expires, + pjsip_tx_data **p_tdata) { - return pjsip_event_sub_set_credentials(pres->sub, count, cred); + return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires, + p_tdata); } + /* - * Set route-set. + * Accept incoming subscription. */ -PJ_DEF(pj_status_t) pjsip_presence_set_route_set( pjsip_presentity *pres, - const pjsip_route_hdr *hdr ) +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_event_sub_set_route_set( pres->sub, hdr ); + return pjsip_evsub_accept( sub, rdata, st_code, hdr_list ); } + /* - * Unsubscribe. + * Get presence status. */ -PJ_DEF(pj_status_t) pjsip_presence_unsubscribe( pjsip_presentity *pres ) +PJ_DEF(pj_status_t) pjsip_pres_get_status( pjsip_evsub *sub, + pjsip_pres_status *status ) { - return pjsip_event_sub_unsubscribe(pres->sub); + 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; } + /* - * This is the pjsip_msg_body callback to print XML body. + * Set presence status. */ -static int print_xml(pjsip_msg_body *body, char *buf, pj_size_t size) +PJ_DEF(pj_status_t) pjsip_pres_set_status( pjsip_evsub *sub, + const pjsip_pres_status *status ) { - return pj_xml_print( body->data, buf, size, PJ_TRUE ); + unsigned i; + pjsip_pres *pres; + + PJ_ASSERT_RETURN(sub && status, PJ_EINVAL); + + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE); + + for (i=0; i<status->info_cnt; ++i) { + pres->status.info[i].basic_open = status->info[i].basic_open; + if (status->info[i].id.slen == 0) { + pj_create_unique_string(pres->dlg->pool, + &pres->status.info[i].id); + } else { + pj_strdup(pres->dlg->pool, + &pres->status.info[i].id, + &status->info[i].id); + } + pj_strdup(pres->dlg->pool, + &pres->status.info[i].contact, + &status->info[i].contact); + } + + pres->status.info_cnt = status->info_cnt; + + return PJ_SUCCESS; } + /* - * Create and initialize PIDF document and msg body (notifier only). + * Create PIDF document based on the presence info. */ -static pj_status_t init_presence_info( pjsip_presentity *pres ) +static pjpidf_pres* pres_create_pidf( pj_pool_t *pool, + pjsip_pres *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); + 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; - /* 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; + /* Create <presence>. */ + pidf = pjpidf_create(pool, &entity); - if (pres->pres_type == PJSIP_PRES_TYPE_PIDF) { - pj_str_t s; + /* Create <tuple> */ + for (i=0; i<pres->status.info_cnt; ++i) { - /* Create <presence>. */ - pres->uas_data.pidf = pjpidf_create(pool, &s); + pjpidf_tuple *pidf_tuple; + pjpidf_status *pidf_status; - /* Create <tuple> */ - pj_create_unique_string(pool, &s); - tuple = pjpidf_pres_add_tuple(pool, pres->uas_data.pidf, &s); + /* Add tuple id. */ + pidf_tuple = pjpidf_pres_add_tuple(pool, pidf, + &pres->status.info[i].id); /* 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); + if (pres->status.info[i].contact.slen) + pjpidf_tuple_set_contact(pool, pidf_tuple, + &pres->status.info[i].contact); - /* Content-Type */ - content_type = &accept_types[PJSIP_PRES_TYPE_PIDF]; - } else if (pres->pres_type == PJSIP_PRES_TYPE_XPIDF) { + /* Set basic status */ + pidf_status = pjpidf_tuple_get_status(pidf_tuple); + pjpidf_status_set_basic_open(pidf_status, + pres->status.info[i].basic_open); + } - /* Create XPIDF */ - pres->uas_data.xpidf = pjxpidf_create(pool, &uri); + return pidf; +} - /* 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; +/* + * 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); - return 0; + /* 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; } + /* - * Send NOTIFY and set subscription state. + * Function to print XML message body. */ -PJ_DEF(pj_status_t) pjsip_presence_notify( pjsip_presentity *pres, - pjsip_event_sub_state state, - pj_bool_t is_online ) +static int pres_print_body(struct pjsip_msg_body *msg_body, + char *buf, pj_size_t size) { - pj_str_t reason = { "", 0 }; + return pj_xml_print(msg_body->data, buf, size, PJ_TRUE); +} - 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); +/* + * 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->pres_type == PJSIP_PRES_TYPE_XPIDF) { - pjxpidf_set_status( pres->uas_data.xpidf, is_online ); + } 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 { - pj_assert(0); + return PJSIP_SIMPLE_EBADCONTENT; } - /* Send notify. */ - return pjsip_event_sub_notify( pres->sub, state, &reason, pres->uas_body); + + body->print_body = &pres_print_body; + body->clone_data = &xml_clone_data; + + tdata->msg->body = body; + + return PJ_SUCCESS; } + /* - * Destroy subscription (can be called for both subscriber and notifier). + * Create NOTIFY */ -PJ_DEF(pj_status_t) pjsip_presence_destroy( pjsip_presentity *pres ) +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) { - return pjsip_event_sub_destroy(pres->sub); + 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); +} + + /* - * This callback is called by event framework to query whether we want to - * accept an incoming subscription. + * Called when SUBSCRIBE is received. */ -static void on_query_subscribe(pjsip_rx_data *rdata, int *status) +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) { - if (cb.accept_presence) { - (*cb.accept_presence)(rdata, status); + 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); } } /* - * This callback is called by event framework after we accept the incoming - * subscription, to notify about the new subscription instance. + * Parse PIDF to info. */ -static void on_subscribe(pjsip_event_sub *sub, pjsip_rx_data *rdata, - pjsip_event_sub_cb **set_sub_cb, int *expires) +static pj_status_t pres_parse_pidf( pjsip_pres *pres, + pjsip_rx_data *rdata, + pjsip_pres_status *pres_status) { - pjsip_presentity *pres; - pjsip_accept_hdr *accept; + pjpidf_pres *pidf; + pjpidf_tuple *pidf_tuple; - 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; + 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; - accept = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_ACCEPT, NULL); - if (accept) { - unsigned i; - int found = 0; - for (i=0; i<accept->count && !found; ++i) { - int j; - for (j=0; j<sizeof(accept_names)/sizeof(accept_names[0]); ++j) { - if (!pj_stricmp(&accept->values[i], &accept_names[j])) { - pres->pres_type = j; - found = 1; - break; - } - } + 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; } - pj_assert(found ); + + pidf_tuple = pjpidf_pres_get_next_tuple( pidf, pidf_tuple ); + pres_status->info_cnt++; } - (*cb.on_received_request)(pres, rdata, expires); + return PJ_SUCCESS; } /* - * This callback is called by event framework when the subscription is - * terminated. + * Parse XPIDF info. */ -static void on_sub_terminated(pjsip_event_sub *sub, const pj_str_t *reason) +static pj_status_t pres_parse_xpidf( pjsip_pres *pres, + pjsip_rx_data *rdata, + pjsip_pres_status *pres_status) { - pjsip_presentity *pres = sub->user_data; - if (cb.on_terminated) - (*cb.on_terminated)(pres, reason); + 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; } + /* - * This callback is called by event framework when it receives incoming - * SUBSCRIBE request to refresh the subscription. + * Called when NOTIFY is received. */ -static void on_sub_received_refresh(pjsip_event_sub *sub, pjsip_rx_data *rdata) +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_presentity *pres = sub->user_data; - if (cb.on_received_refresh) - (*cb.on_received_refresh)(pres, rdata); + 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 */ } /* - * This callback is called by event framework when it receives incoming - * NOTIFY request. + * Called when it's time to send SUBSCRIBE. */ -static void on_received_notify(pjsip_event_sub *sub, pjsip_rx_data *rdata) +static void pres_on_evsub_client_refresh(pjsip_evsub *sub) { - pjsip_presentity *pres = sub->user_data; + pjsip_pres *pres; - if (cb.on_received_update) { - pj_status_t is_open; - pjsip_msg_body *body; - int i; + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); - body = rdata->msg->body; - if (!body) - return; + if (pres->user_cb.on_client_refresh) { + (*pres->user_cb.on_client_refresh)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; - for (i=0; i<sizeof(accept_types)/sizeof(accept_types[0]); ++i) { - if (!pj_stricmp(&body->content_type.type, &accept_types[i].type) && - !pj_stricmp(&body->content_type.subtype, &accept_types[i].subtype)) - { - break; - } - } + status = pjsip_pres_initiate(sub, -1, &tdata); + if (status == PJ_SUCCESS) + pjsip_pres_send_request(sub, tdata); + } +} - 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); +/* + * Called when no refresh is received after the interval. + */ +static void pres_on_evsub_server_timeout(pjsip_evsub *sub) +{ + pjsip_pres *pres; - } else { - return; - } + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); - (*cb.on_received_update)(pres, is_open); + 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); } } diff --git a/pjsip/src/pjsip-simple/xpidf.c b/pjsip/src/pjsip-simple/xpidf.c index 7cc377ba..12024827 100644 --- a/pjsip/src/pjsip-simple/xpidf.c +++ b/pjsip/src/pjsip-simple/xpidf.c @@ -16,10 +16,11 @@ * 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/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 }; static pj_str_t STATUS = { "status", 6 }; diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c index e4db5dbb..30d6febe 100644 --- a/pjsip/src/pjsip/sip_endpoint.c +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -215,6 +215,9 @@ PJ_DEF(pj_status_t) pjsip_endpt_register_module( pjsip_endpoint *endpt, /* 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); return status; @@ -253,6 +256,9 @@ PJ_DEF(pj_status_t) pjsip_endpt_unregister_module( pjsip_endpoint *endpt, if (status != PJ_SUCCESS) goto on_return; } + /* Module MUST NOT set module ID to -1. */ + pj_assert(mod->id >= 0); + /* Remove module from array. */ endpt->modules[mod->id] = NULL; @@ -265,6 +271,9 @@ PJ_DEF(pj_status_t) pjsip_endpt_unregister_module( pjsip_endpoint *endpt, /* Done. */ status = PJ_SUCCESS; + PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" unregistered", + (int)mod->name.slen, mod->name.ptr)); + on_return: pj_rwmutex_unlock_write(endpt->mod_mutex); return status; diff --git a/pjsip/src/pjsip/sip_errno.c b/pjsip/src/pjsip/sip_errno.c index a54bf630..d62d9188 100644 --- a/pjsip/src/pjsip/sip_errno.c +++ b/pjsip/src/pjsip/sip_errno.c @@ -66,6 +66,7 @@ static const struct /* Transaction errors */ { PJSIP_ETSXDESTROYED, "Transaction has been destroyed"}, + { PJSIP_ENOTSX, "No transaction (expecting stateful processing)" }, /* Authentication. */ { PJSIP_EFAILEDCREDENTIAL, "Credential failed to authenticate"}, diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c index 585c6b42..b3a3438b 100644 --- a/pjsip/src/pjsip/sip_msg.c +++ b/pjsip/src/pjsip/sip_msg.c @@ -145,7 +145,7 @@ static int init_status_phrase() pj_strset2( &status_phrase[486], "Busy Here"); 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"); pj_strset2( &status_phrase[493], "Undecipherable"); diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c index 71368a1b..6dbc9600 100644 --- a/pjsip/src/pjsip/sip_ua_layer.c +++ b/pjsip/src/pjsip/sip_ua_layer.c @@ -521,20 +521,32 @@ static pj_bool_t mod_ua_on_rx_request(pjsip_rx_data *rdata) dlg = dlg->next; } - /* 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); + pjsip_dialog *first_dlg = dlg_set->dlg_list.next; - PJ_LOG(5,(THIS_FILE, - "Unable to find dialog for %s, answering with 481", - pjsip_rx_data_get_info(rdata))); + if (first_dlg->remote.info->tag.slen != 0) { + /* Not found. Mulfunction UAC? */ + pj_mutex_unlock(mod_ua.mutex); - pjsip_endpt_respond_stateless(mod_ua.endpt, rdata, - PJSIP_SC_CALL_TSX_DOES_NOT_EXIST, - NULL, NULL, NULL); - return PJ_TRUE; + 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; } /* Mark the dialog id of the request. */ diff --git a/pjsip/src/pjsua/main.c b/pjsip/src/pjsua/main.c index 37fde773..4a40d324 100644 --- a/pjsip/src/pjsua/main.c +++ b/pjsip/src/pjsua/main.c @@ -22,7 +22,8 @@ #define THIS_FILE "main.c" -static pjsip_inv_session *inv_session; +/* Current dialog */ +static struct pjsua_inv_data *inv_session; /* * Notify UI when invite state has changed. @@ -35,12 +36,16 @@ void pjsua_ui_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e) pjsua_inv_state_names[inv->state])); 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]; } } @@ -56,24 +61,71 @@ void pjsua_ui_regc_on_state_changed(int code) } +/* + * 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) +static void keystroke_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(""); + + 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; @@ -92,48 +144,131 @@ static pj_bool_t input(const char *title, char *buf, pj_size_t len) return PJ_TRUE; } + +#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; + struct input_result result; - //ui_help(); + //keystroke_help(); for (;;) { - ui_help(); - fgets(buf, sizeof(buf), stdin); + keystroke_help(); + fgets(menuin, sizeof(menuin), stdin); - switch (buf[0]) { + 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 + 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 'd': - pjsua_dump(); - 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"); fflush(stdout); @@ -143,16 +278,16 @@ static void ui_console_main(void) pj_status_t status; 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; if (atoi(buf) < 100) 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) pjsua_perror("Unable to create/send response", status); @@ -160,9 +295,10 @@ static void ui_console_main(void) break; + case 'h': - if (inv_session == NULL) { + if (inv_session == &pjsua.inv_list) { puts("No current call"); fflush(stdout); continue; @@ -171,22 +307,62 @@ static void ui_console_main(void) pj_status_t status; 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); continue; } - 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); continue; } } + 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; + case 'q': goto on_exit; } @@ -254,7 +430,7 @@ static pj_status_t console_on_tx_msg(pjsip_tx_data *tdata) static pjsip_module console_msg_logger = { NULL, NULL, /* prev, next. */ - { "mod-console-msg-logger", 22 }, /* Name. */ + { "mod-pjsua-log", 13 }, /* Name. */ -1, /* Id */ PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ NULL, /* User data. */ @@ -385,6 +561,11 @@ int main(int argc, char *argv[]) pj_thread_sleep(500); + /* No current call initially: */ + + inv_session = &pjsua.inv_list; + + /* Start UI console main loop: */ ui_console_main(); diff --git a/pjsip/src/pjsua/pjsua.h b/pjsip/src/pjsua/pjsua.h index ebb97a0d..202c2840 100644 --- a/pjsip/src/pjsua/pjsua.h +++ b/pjsip/src/pjsua/pjsua.h @@ -31,6 +31,9 @@ /* Include all PJSIP-UA headers */ #include <pjsip_ua.h> +/* Include all PJSIP-SIMPLE headers */ +#include <pjsip_simple.h> + /* Include all PJLIB-UTIL headers. */ #include <pjlib-util.h> @@ -61,6 +64,33 @@ struct pjsua_inv_data }; +/** + * 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; + + /* PJSUA application variables. */ struct pjsua @@ -141,10 +171,13 @@ struct pjsua struct pjsua_inv_data inv_list; - /* 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]; }; @@ -235,6 +268,12 @@ void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e); void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status); +/** + * Terminate all calls. + */ +void pjsua_inv_shutdown(void); + + /***************************************************************************** * PJSUA Client Registration API (defined in pjsua_reg.c). */ @@ -253,6 +292,33 @@ pj_status_t pjsua_regc_init(void); 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); + + /***************************************************************************** * User Interface API. * diff --git a/pjsip/src/pjsua/pjsua_core.c b/pjsip/src/pjsua/pjsua_core.c index f97c8f43..2d059d92 100644 --- a/pjsip/src/pjsua/pjsua_core.c +++ b/pjsip/src/pjsua/pjsua_core.c @@ -79,6 +79,11 @@ void pjsua_default(void) /* Init invite session list: */ pj_list_init(&pjsua.inv_list); + + /* Init server presence subscription list: */ + + pj_list_init(&pjsua.pres_srv_list); + } @@ -391,14 +396,14 @@ on_error: } -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; } @@ -435,7 +440,7 @@ pj_status_t pjsua_init(void) pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL); - /* Init PJSIP and all the modules: */ + /* Init PJSIP : */ status = init_stack(); if (status != PJ_SUCCESS) { @@ -445,6 +450,20 @@ pj_status_t pjsua_init(void) } + /* 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(); + + /* Init media endpoint: */ status = pjmedia_endpt_create(&pjsua.cp.factory, &pjsua.med_endpt); @@ -609,7 +628,7 @@ pj_status_t pjsua_start(void) /* Create worker thread(s), if required: */ 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) { pjsua.quit_flag = 1; @@ -635,11 +654,26 @@ pj_status_t pjsua_start(void) } - + 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)); +} + /* * Destroy pjsua. */ @@ -647,42 +681,43 @@ pj_status_t pjsua_destroy(void) { int i; - /* Unregister, if required: */ - if (pjsua.regc) { + /* Signal threads to quit: */ + pjsua.quit_flag = 1; - pjsua_regc_update(0); + /* Wait worker threads to quit: */ + for (i=0; i<pjsua.thread_cnt; ++i) { + + if (pjsua.threads[i]) { + pj_thread_join(pjsua.threads[i]); + pj_thread_destroy(pjsua.threads[i]); + pjsua.threads[i] = NULL; + } + } - /* Wait for some time to allow unregistration to complete: */ - pj_thread_sleep(500); - } + /* Terminate all calls. */ + pjsua_inv_shutdown(); - /* Signal threads to quit: */ + /* Terminate all presence subscriptions. */ + pjsua_pres_shutdown(); - pjsua.quit_flag = 1; + /* 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(); - /* Wait worker threads to quit: */ - - for (i=0; i<pjsua.thread_cnt; ++i) { - - if (pjsua.threads[i]) { - pj_thread_join(pjsua.threads[i]); - pj_thread_destroy(pjsua.threads[i]); - pjsua.threads[i] = NULL; - } - } - /* Destroy endpoint. */ pjsip_endpt_destroy(pjsua.endpt); diff --git a/pjsip/src/pjsua/pjsua_inv.c b/pjsip/src/pjsua/pjsua_inv.c index 7c6dbf26..6f9607b5 100644 --- a/pjsip/src/pjsua/pjsua_inv.c +++ b/pjsip/src/pjsua/pjsua_inv.c @@ -80,6 +80,7 @@ pj_status_t pjsua_invite(const char *cstr_dest_uri, inv_data = pj_pool_zalloc( dlg->pool, sizeof(struct pjsua_inv_data)); inv_data->inv = inv; dlg->mod_data[pjsua.mod.id] = inv_data; + inv->mod_data[pjsua.mod.id] = inv_data; /* Set dialog Route-Set: */ @@ -221,6 +222,7 @@ pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata) inv_data = pj_pool_zalloc(dlg->pool, sizeof(struct pjsua_inv_data)); 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); @@ -345,3 +347,25 @@ void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status) PJ_LOG(3,(THIS_FILE,"Media has been started successfully")); } } + + +/* + * 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; + } +} + diff --git a/pjsip/src/pjsua/pjsua_opt.c b/pjsip/src/pjsua/pjsua_opt.c index 61e5adba..5ec1a56a 100644 --- a/pjsip/src/pjsua/pjsua_opt.c +++ b/pjsip/src/pjsua/pjsua_opt.c @@ -403,7 +403,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) printf("Error: too many buddies in buddy list.\n"); return -1; } - pjsua.buddies[pjsua.buddy_cnt++] = pj_str(optarg); + pjsua.buddies[pjsua.buddy_cnt++].uri = pj_str(optarg); break; } } @@ -510,6 +510,7 @@ void pjsua_dump(void) pjsip_endpt_dump(pjsua.endpt, 1); pjmedia_endpt_dump(pjsua.med_endpt); + pjsip_tsx_layer_dump(); pjsip_ua_dump(); @@ -536,6 +537,9 @@ void pjsua_dump(void) } } + /* Dump presence status */ + pjsua_pres_dump(); + pj_log_set_decor(old_decor); PJ_LOG(3,(THIS_FILE, "Dump complete")); } @@ -547,8 +551,9 @@ void pjsua_dump(void) pj_status_t pjsua_load_settings(const char *filename) { 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); } @@ -654,8 +659,8 @@ pj_status_t pjsua_save_settings(const char *filename) /* Add buddies. */ 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); } diff --git a/pjsip/src/pjsua/pjsua_pres.c b/pjsip/src/pjsua/pjsua_pres.c new file mode 100644 index 00000000..db009eed --- /dev/null +++ b/pjsip/src/pjsua/pjsua_pres.c @@ -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)); + } + } + } +} + |