From 058d9c4c6d15338e1d3b2e05ffea340eec565d9a Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Mon, 26 Oct 2009 11:21:37 +0000 Subject: Implement ticket #982: Support for SIP Message Summary/Message Waiting Indication (MWI, RFC 3842) - PJSIP-SIMPLE: - implement MWI - PJSUA-LIB: - added "mwi_enabled" flag in account config - added "on_mwi_info" callback - pjsua app: - added "--mwi" option to enable MWI on account - added simple callback to log the NOTIFY message - other: - added SIPp scenario files to simulate UAS side - build: - added MWI support on VS6, VS2005, MMP, and Makefile git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2968 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/build/Makefile | 2 +- pjsip/build/pjsip_simple.vcproj | 624 ++++++++++++++++--------------- pjsip/include/pjsip-simple/mwi.h | 208 +++++++++++ pjsip/include/pjsip/sip_msg.h | 12 + pjsip/include/pjsip_simple.h | 1 + pjsip/include/pjsua-lib/pjsua.h | 30 ++ pjsip/include/pjsua-lib/pjsua_internal.h | 7 + pjsip/src/pjsip-simple/mwi.c | 599 +++++++++++++++++++++++++++++ pjsip/src/pjsip/sip_msg.c | 14 + pjsip/src/pjsua-lib/pjsua_acc.c | 10 +- pjsip/src/pjsua-lib/pjsua_core.c | 3 + pjsip/src/pjsua-lib/pjsua_pres.c | 236 +++++++++++- 12 files changed, 1433 insertions(+), 313 deletions(-) create mode 100644 pjsip/include/pjsip-simple/mwi.h create mode 100644 pjsip/src/pjsip-simple/mwi.c (limited to 'pjsip') diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile index 9dc959f3..c0ecf992 100644 --- a/pjsip/build/Makefile +++ b/pjsip/build/Makefile @@ -64,7 +64,7 @@ export PJSIP_UA_CFLAGS += $(_CFLAGS) export PJSIP_SIMPLE_SRCDIR = ../src/pjsip-simple export PJSIP_SIMPLE_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ errno.o evsub.o evsub_msg.o iscomposing.o \ - pidf.o presence.o presence_body.o publishc.o \ + mwi.o pidf.o presence.o presence_body.o publishc.o \ rpid.o xpidf.o export PJSIP_SIMPLE_CFLAGS += $(_CFLAGS) diff --git a/pjsip/build/pjsip_simple.vcproj b/pjsip/build/pjsip_simple.vcproj index 76d1d07b..f2504c82 100644 --- a/pjsip/build/pjsip_simple.vcproj +++ b/pjsip/build/pjsip_simple.vcproj @@ -34,11 +34,11 @@ + + + + - - + + + + - - + + - - - - - - + + @@ -3311,6 +3315,10 @@ RelativePath="..\include\pjsip-simple\iscomposing.h" > + + diff --git a/pjsip/include/pjsip-simple/mwi.h b/pjsip/include/pjsip-simple/mwi.h new file mode 100644 index 00000000..c1128372 --- /dev/null +++ b/pjsip/include/pjsip-simple/mwi.h @@ -0,0 +1,208 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * + * 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_MWI_H__ +#define __PJSIP_SIMPLE_MWI_H__ + +/** + * @file mwi.h + * @brief SIP Extension for MWI (RFC 3842) + */ +#include +#include + + +PJ_BEGIN_DECL + + +/** + * @defgroup mwi SIP Message Summary and Message Waiting Indication (RFC 3842) + * @ingroup PJSIP_SIMPLE + * @brief Support for SIP MWI Extension (RFC 3842) + * @{ + * + * This module implements RFC 3842: A Message Summary and Message Waiting + * Indication Event Package for the Session Initiation Protocol (SIP). + * It uses the SIP Event Notification framework (evsub.h) and extends the + * framework by implementing "message-summary" event package. + */ + + +/** + * Initialize the MWI module and register it as endpoint module and + * package to the event subscription module. + * + * @param endpt The endpoint instance. + * @param mod_evsub The event subscription module instance. + * + * @return PJ_SUCCESS if the module is successfully + * initialized and registered to both endpoint + * and the event subscription module. + */ +PJ_DECL(pj_status_t) pjsip_mwi_init_module(pjsip_endpoint *endpt, + pjsip_module *mod_evsub); + +/** + * Get the MWI module instance. + * + * @return The MWI module instance. + */ +PJ_DECL(pjsip_module*) pjsip_mwi_instance(void); + +/** + * Create MWI client subscription session. + * + * @param dlg The underlying dialog to use. + * @param user_cb Pointer to callbacks to receive MWI subscription + * events. + * @param options Option flags. Currently only PJSIP_EVSUB_NO_EVENT_ID + * is recognized. + * @param p_evsub Pointer to receive the MWI subscription + * session. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_mwi_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + unsigned options, + pjsip_evsub **p_evsub ); + +/** + * Create MWI server subscription session. + * + * @param dlg The underlying dialog to use. + * @param user_cb Pointer to callbacks to receive MWI subscription + * events. + * @param rdata The incoming SUBSCRIBE request that creates the event + * subscription. + * @param p_evsub Pointer to receive the MWI subscription + * session. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_mwi_create_uas( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_rx_data *rdata, + pjsip_evsub **p_evsub ); + +/** + * Forcefully destroy the MWI subscription. This function should only + * be called on special condition, such as when the subscription + * initialization has failed. For other conditions, application MUST terminate + * the subscription by sending the appropriate un(SUBSCRIBE) or NOTIFY. + * + * @param sub The MWI subscription. + * @param notify Specify whether the state notification callback + * should be called. + * + * @return PJ_SUCCESS if subscription session has been destroyed. + */ +PJ_DECL(pj_status_t) pjsip_mwi_terminate( pjsip_evsub *sub, + pj_bool_t notify ); + +/** + * Call this function to create request to initiate MWI subscription, to + * refresh subcription, or to request subscription termination. + * + * @param sub Client subscription instance. + * @param expires Subscription expiration. If the value is set to zero, + * this will request unsubscription. + * @param p_tdata Pointer to receive the request. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_mwi_initiate( pjsip_evsub *sub, + pj_int32_t expires, + pjsip_tx_data **p_tdata); + +/** + * Accept the incoming subscription request by sending 2xx response to + * incoming SUBSCRIBE request. + * + * @param sub Server subscription instance. + * @param rdata The incoming subscription request message. + * @param st_code Status code, which MUST be final response. + * @param hdr_list Optional list of headers to be added in the response. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_mwi_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 mime_type MIME type/content type of the message body. + * @param body Message body to be included in the NOTIFY request. + * @param p_tdata Pointer to receive the request. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_mwi_notify( pjsip_evsub *sub, + pjsip_evsub_state state, + const pj_str_t *state_str, + const pj_str_t *reason, + const pjsip_media_type *mime_type, + const pj_str_t *body, + pjsip_tx_data **p_tdata); + +/** + * Create NOTIFY request containing message body from the last NOITFY + * message created. + * + * @param sub Server subscription object. + * @param p_tdata Pointer to receive request. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_mwi_current_notify( pjsip_evsub *sub, + pjsip_tx_data **p_tdata ); + + +/** + * Send request message that was previously created with initiate(), notify(), + * or current_notify(). Application may also send request created with other + * functions, e.g. authentication. But the request MUST be either request + * that creates/refresh subscription or NOTIFY request. + * + * @param sub The subscription object. + * @param tdata Request message to be sent. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_mwi_send_request( pjsip_evsub *sub, + pjsip_tx_data *tdata ); + +/** + * @} + */ + +PJ_END_DECL + + +#endif /* __PJSIP_SIMPLE_MWI_H__ */ diff --git a/pjsip/include/pjsip/sip_msg.h b/pjsip/include/pjsip/sip_msg.h index 437f9b2a..1ca065fe 100644 --- a/pjsip/include/pjsip/sip_msg.h +++ b/pjsip/include/pjsip/sip_msg.h @@ -511,6 +511,18 @@ typedef struct pjsip_media_type pj_str_t param; /**< Media type parameters (concatenated). */ } pjsip_media_type; + +/** + * Copy SIP media type to another. + * + * @param pool Pool to duplicate strings. + * @param dst Destination structure. + * @param src Source structure. + */ +PJ_DECL(void) pjsip_media_type_cp(pj_pool_t *pool, + pjsip_media_type *dst, + const pjsip_media_type *src); + /** * @} */ diff --git a/pjsip/include/pjsip_simple.h b/pjsip/include/pjsip_simple.h index 8fda8152..87ad120e 100644 --- a/pjsip/include/pjsip_simple.h +++ b/pjsip/include/pjsip_simple.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 356cab76..a8bb2f39 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -378,6 +378,17 @@ PJ_DECL(void) pjsua_logging_config_dup(pj_pool_t *pool, const pjsua_logging_config *src); +/** + * Structure to be passed on MWI callback. + */ +typedef struct pjsua_mwi_info +{ + pjsip_evsub *evsub; /**< Event subscription session, for + reference. */ + pjsip_rx_data *rdata; /**< The received NOTIFY request. */ +} pjsua_mwi_info; + + /** * This structure describes application callback to receive various event * notification from PJSUA-API. All of these callbacks are OPTIONAL, @@ -821,6 +832,17 @@ typedef struct pjsua_callback const pjsip_uri *target, const pjsip_event *e); + /** + * This callback is called when a NOTIFY request for message summary / + * message waiting indication is received. + * + * @param acc_id The account ID. + * @param mwi_info Structure containing details of the event, + * including the received NOTIFY request in the + * \a rdata field. + */ + void (*on_mwi_info)(pjsua_acc_id acc_id, pjsua_mwi_info *mwi_info); + } pjsua_callback; @@ -1859,6 +1881,14 @@ typedef struct pjsua_acc_config */ pj_str_t reg_uri; + /** + * Enable message summary and message waiting indication subscription + * (RFC 3842) for this account. + * + * Default: no + */ + pj_bool_t mwi_enabled; + /** * If this flag is set, the presence information of this account will * be PUBLISH-ed to the server where the account belongs. diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h index 0f777967..c90da124 100644 --- a/pjsip/include/pjsua-lib/pjsua_internal.h +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -144,6 +144,8 @@ typedef struct pjsua_acc pjsip_publishc *publish_sess; /**< Client publication session. */ pj_bool_t publish_state; /**< Last published online status */ + pjsip_evsub *mwi_sub; /**< MWI client subscription */ + pjsip_dialog *mwi_dlg; /**< Dialog for MWI sub. */ } pjsua_acc; @@ -451,6 +453,11 @@ void pjsua_pres_delete_acc(int acc_id); */ pj_status_t pjsua_im_init(void); +/** + * Start MWI subscription + */ +void pjsua_start_mwi(pjsua_acc *acc); + /** * Init call subsystem. */ diff --git a/pjsip/src/pjsip-simple/mwi.c b/pjsip/src/pjsip-simple/mwi.c new file mode 100644 index 00000000..9be0a6dc --- /dev/null +++ b/pjsip/src/pjsip-simple/mwi.c @@ -0,0 +1,599 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define THIS_FILE "mwi.c" +#define MWI_DEFAULT_EXPIRES 3600 + + /* + * MWI module (mod-mdi) + */ +static struct pjsip_module mod_mwi = +{ + NULL, NULL, /* prev, next. */ + { "mod-mwi", 7 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_DIALOG_USAGE,/* Priority */ + 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() */ +}; + + +/* + * This structure describe an mwi agent (both client and server) + */ +typedef struct pjsip_mwi +{ + pjsip_evsub *sub; /**< Event subscribtion record. */ + pjsip_dialog *dlg; /**< The dialog. */ + pjsip_evsub_user user_cb; /**< The user callback. */ + + /* These are for server subscriptions */ + pj_pool_t *body_pool; /**< Pool to save message body */ + pjsip_media_type mime_type; /**< MIME type of last msg body */ + pj_str_t body; /**< Last sent message body */ +} pjsip_mwi; + + +/* + * Forward decl for evsub callbacks. + */ +static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); +static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event); +static void mwi_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 mwi_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 mwi_on_evsub_client_refresh(pjsip_evsub *sub); +static void mwi_on_evsub_server_timeout(pjsip_evsub *sub); + + +/* + * Event subscription callback for mwi. + */ +static pjsip_evsub_user mwi_user = +{ + &mwi_on_evsub_state, + &mwi_on_evsub_tsx_state, + &mwi_on_evsub_rx_refresh, + &mwi_on_evsub_rx_notify, + &mwi_on_evsub_client_refresh, + &mwi_on_evsub_server_timeout, +}; + + +/* + * Some static constants. + */ +static const pj_str_t STR_EVENT = { "Event", 5 }; +static const pj_str_t STR_MWI = { "message-summary", 15 }; +static const pj_str_t STR_APP_SIMPLE_SMS = { "application/simple-message-summary", 34}; + +/* + * Init mwi module. + */ +PJ_DEF(pj_status_t) pjsip_mwi_init_module( pjsip_endpoint *endpt, + pjsip_module *mod_evsub) +{ + pj_status_t status; + pj_str_t accept[1]; + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL); + + /* Must have not been registered */ + PJ_ASSERT_RETURN(mod_mwi.id == -1, PJ_EINVALIDOP); + + /* Register to endpoint */ + status = pjsip_endpt_register_module(endpt, &mod_mwi); + if (status != PJ_SUCCESS) + return status; + + accept[0] = STR_APP_SIMPLE_SMS; + + /* Register event package to event module. */ + status = pjsip_evsub_register_pkg( &mod_mwi, &STR_MWI, + MWI_DEFAULT_EXPIRES, + PJ_ARRAY_SIZE(accept), accept); + if (status != PJ_SUCCESS) { + pjsip_endpt_unregister_module(endpt, &mod_mwi); + return status; + } + + return PJ_SUCCESS; +} + + +/* + * Get mwi module instance. + */ +PJ_DEF(pjsip_module*) pjsip_mwi_instance(void) +{ + return &mod_mwi; +} + + +/* + * Create client subscription. + */ +PJ_DEF(pj_status_t) pjsip_mwi_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + unsigned options, + pjsip_evsub **p_evsub ) +{ + pj_status_t status; + pjsip_mwi *mwi; + pjsip_evsub *sub; + + PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL); + + PJ_UNUSED_ARG(options); + + pjsip_dlg_inc_lock(dlg); + + /* Create event subscription */ + status = pjsip_evsub_create_uac( dlg, &mwi_user, &STR_MWI, + options, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create mwi */ + mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi); + mwi->dlg = dlg; + mwi->sub = sub; + if (user_cb) + pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi); + + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Create server subscription. + */ +PJ_DEF(pj_status_t) pjsip_mwi_create_uas( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_rx_data *rdata, + pjsip_evsub **p_evsub ) +{ + pjsip_accept_hdr *accept; + pjsip_event_hdr *event; + pjsip_evsub *sub; + pjsip_mwi *mwi; + char obj_name[PJ_MAX_OBJ_NAME]; + 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 "mwi" */ + event = (pjsip_event_hdr*) + 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_MWI) != 0) { + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT); + } + + /* Check that request contains compatible Accept header. */ + accept = (pjsip_accept_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL); + if (accept) { + unsigned i; + for (i=0; icount; ++i) { + if (pj_stricmp(&accept->values[i], &STR_APP_SIMPLE_SMS)==0) { + break; + } + } + + if (i==accept->count) { + /* Nothing is acceptable */ + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE); + } + + } else { + /* No Accept header. + * Assume client supports "application/simple-message-summary" + */ + } + + /* Lock dialog */ + pjsip_dlg_inc_lock(dlg); + + + /* Create server subscription */ + status = pjsip_evsub_create_uas( dlg, &mwi_user, rdata, 0, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create server mwi subscription */ + mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi); + mwi->dlg = dlg; + mwi->sub = sub; + if (user_cb) + pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "mwibd%p", dlg->pool); + mwi->body_pool = pj_pool_create(dlg->pool->factory, obj_name, + 512, 512, NULL); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi); + + /* Done: */ + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Forcefully terminate mwi. + */ +PJ_DEF(pj_status_t) pjsip_mwi_terminate( pjsip_evsub *sub, + pj_bool_t notify ) +{ + return pjsip_evsub_terminate(sub, notify); +} + +/* + * Create SUBSCRIBE + */ +PJ_DEF(pj_status_t) pjsip_mwi_initiate( pjsip_evsub *sub, + pj_int32_t expires, + pjsip_tx_data **p_tdata) +{ + return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires, + p_tdata); +} + + +/* + * Accept incoming subscription. + */ +PJ_DEF(pj_status_t) pjsip_mwi_accept( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list ) +{ + return pjsip_evsub_accept( sub, rdata, st_code, hdr_list ); +} + +/* + * Create message body and attach it to the (NOTIFY) request. + */ +static pj_status_t mwi_create_msg_body( pjsip_mwi *mwi, + pjsip_tx_data *tdata) +{ + pjsip_msg_body *body; + pj_str_t dup_text; + + PJ_ASSERT_RETURN(mwi->mime_type.type.slen && mwi->body.slen, PJ_EINVALIDOP); + + /* Clone the message body and mime type */ + pj_strdup(tdata->pool, &dup_text, &mwi->body); + + /* Create the message body */ + body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body); + pjsip_media_type_cp(tdata->pool, &body->content_type, &mwi->mime_type); + body->data = dup_text.ptr; + body->len = (unsigned)dup_text.slen; + body->print_body = &pjsip_print_text_body; + body->clone_data = &pjsip_clone_text_data; + + /* Attach to tdata */ + tdata->msg->body = body; + + return PJ_SUCCESS; +} + + +/* + * Create NOTIFY + */ +PJ_DEF(pj_status_t) pjsip_mwi_notify( pjsip_evsub *sub, + pjsip_evsub_state state, + const pj_str_t *state_str, + const pj_str_t *reason, + const pjsip_media_type *mime_type, + const pj_str_t *body, + pjsip_tx_data **p_tdata) +{ + pjsip_mwi *mwi; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub && mime_type && body && p_tdata, PJ_EINVAL); + + /* Get the mwi object. */ + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP); + + /* Lock object. */ + pjsip_dlg_inc_lock(mwi->dlg); + + /* Create the NOTIFY request. */ + status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Update the cached message body */ + if (mime_type || body) + pj_pool_reset(mwi->body_pool); + if (mime_type) + pjsip_media_type_cp(mwi->body_pool, &mwi->mime_type, mime_type); + if (body) + pj_strdup(mwi->body_pool, &mwi->body, body); + + /* Create message body */ + status = mwi_create_msg_body( mwi, tdata ); + if (status != PJ_SUCCESS) + goto on_return; + + /* Done. */ + *p_tdata = tdata; + +on_return: + pjsip_dlg_dec_lock(mwi->dlg); + return status; +} + + +/* + * Create NOTIFY that reflect current state. + */ +PJ_DEF(pj_status_t) pjsip_mwi_current_notify( pjsip_evsub *sub, + pjsip_tx_data **p_tdata ) +{ + pjsip_mwi *mwi; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL); + + /* Get the mwi object. */ + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP); + + /* Lock object. */ + pjsip_dlg_inc_lock(mwi->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 mwi status. */ + status = mwi_create_msg_body( mwi, tdata ); + if (status != PJ_SUCCESS) + goto on_return; + + /* Done. */ + *p_tdata = tdata; + +on_return: + pjsip_dlg_dec_lock(mwi->dlg); + return status; +} + + +/* + * Send request. + */ +PJ_DEF(pj_status_t) pjsip_mwi_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 mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + if (mwi->user_cb.on_evsub_state) + (*mwi->user_cb.on_evsub_state)(sub, event); + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + if (mwi->body_pool) { + pj_pool_release(mwi->body_pool); + mwi->body_pool = NULL; + } + } +} + +/* + * Called when transaction state has changed. + */ +static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + if (mwi->user_cb.on_tsx_state) + (*mwi->user_cb.on_tsx_state)(sub, tsx, event); +} + + +/* + * Called when SUBSCRIBE is received. + */ +static void mwi_on_evsub_rx_refresh( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + if (mwi->user_cb.on_rx_refresh) { + (*mwi->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_mwi_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, &timeout, NULL, NULL, &tdata); + } else { + status = pjsip_mwi_current_notify(sub, &tdata); + } + + if (status == PJ_SUCCESS) + pjsip_mwi_send_request(sub, tdata); + } +} + + +/* + * Called when NOTIFY is received. + */ +static void mwi_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_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + /* Just notify application. */ + if (mwi->user_cb.on_rx_notify) { + (*mwi->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text, + res_hdr, p_body); + } +} + +/* + * Called when it's time to send SUBSCRIBE. + */ +static void mwi_on_evsub_client_refresh(pjsip_evsub *sub) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + if (mwi->user_cb.on_client_refresh) { + (*mwi->user_cb.on_client_refresh)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_mwi_initiate(sub, -1, &tdata); + if (status == PJ_SUCCESS) + pjsip_mwi_send_request(sub, tdata); + } +} + +/* + * Called when no refresh is received after the interval. + */ +static void mwi_on_evsub_server_timeout(pjsip_evsub *sub) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + if (mwi->user_cb.on_server_timeout) { + (*mwi->user_cb.on_server_timeout)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + pj_str_t reason = { "timeout", 7 }; + + status = pjsip_mwi_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, &reason, NULL, NULL, &tdata); + if (status == PJ_SUCCESS) + pjsip_mwi_send_request(sub, tdata); + } +} + diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c index 6e37a277..fe86362e 100644 --- a/pjsip/src/pjsip/sip_msg.c +++ b/pjsip/src/pjsip/sip_msg.c @@ -598,6 +598,20 @@ PJ_DEF(const pj_str_t*) pjsip_get_status_text(int code) &status_phrase[code] : &status_phrase[0]; } +/////////////////////////////////////////////////////////////////////////////// +/* + * Media type + */ +PJ_DEF(void) pjsip_media_type_cp( pj_pool_t *pool, + pjsip_media_type *dst, + const pjsip_media_type *src) +{ + PJ_ASSERT_ON_FAIL(pool && dst && src, return); + pj_strdup(pool, &dst->type, &src->type); + pj_strdup(pool, &dst->subtype, &src->subtype); + pj_strdup(pool, &dst->param, &src->param); +} + /////////////////////////////////////////////////////////////////////////////// /* * Generic pjsip_hdr_names/hvalue header. diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c index 4bfe98c1..26ff39a7 100644 --- a/pjsip/src/pjsua-lib/pjsua_acc.c +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -328,7 +328,11 @@ PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg, /* If accounts has registration enabled, start registration */ if (pjsua_var.acc[id].cfg.reg_uri.slen) pjsua_acc_set_registration(id, PJ_TRUE); - + else { + /* Otherwise subscribe to MWI, if it's enabled */ + if (pjsua_var.acc[id].cfg.mwi_enabled) + pjsua_start_mwi(&pjsua_var.acc[id]); + } return PJ_SUCCESS; } @@ -1056,6 +1060,10 @@ static void regc_cb(struct pjsip_regc_cbparam *param) /* Send initial PUBLISH if it is enabled */ if (acc->cfg.publish_enabled && acc->publish_sess==NULL) pjsua_pres_init_publish_acc(acc->index); + + /* Subscribe to MWI, if it's enabled */ + if (acc->cfg.mwi_enabled) + pjsua_start_mwi(acc); } } else { diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 0e390763..6e41cbe0 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -820,6 +820,9 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, status = pjsip_pres_init_module( pjsua_var.endpt, pjsip_evsub_instance()); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + /* Initialize MWI support */ + status = pjsip_mwi_init_module(pjsua_var.endpt, pjsip_evsub_instance()); + /* Init PUBLISH module */ pjsip_publishc_init_module(pjsua_var.endpt); diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c index e7866377..80337299 100644 --- a/pjsip/src/pjsua-lib/pjsua_pres.c +++ b/pjsip/src/pjsua-lib/pjsua_pres.c @@ -1853,6 +1853,230 @@ static pj_status_t refresh_client_subscriptions(void) return PJ_SUCCESS; } +/*************************************************************************** + * MWI + */ +/* Callback called when *client* subscription state has changed. */ +static void mwi_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsua_acc *acc; + + PJ_UNUSED_ARG(event); + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!acc) + return; + + PJ_LOG(4,(THIS_FILE, + "MWI subscription for %.*s is %s", + (int)acc->cfg.id.slen, acc->cfg.id.ptr, + pjsip_evsub_get_state_name(sub))); + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + /* Clear subscription */ + acc->mwi_dlg = NULL; + acc->mwi_sub = NULL; + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + + } +} + +/* Callback called when we receive NOTIFY */ +static void mwi_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_mwi_info mwi_info; + pjsua_acc *acc; + + PJ_UNUSED_ARG(p_st_code); + PJ_UNUSED_ARG(p_st_text); + PJ_UNUSED_ARG(res_hdr); + PJ_UNUSED_ARG(p_body); + + acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!acc) + return; + + /* Construct mwi_info */ + pj_bzero(&mwi_info, sizeof(mwi_info)); + mwi_info.evsub = sub; + mwi_info.rdata = rdata; + + /* Call callback */ + if (pjsua_var.ua_cfg.cb.on_mwi_info) { + (*pjsua_var.ua_cfg.cb.on_mwi_info)(acc->index, &mwi_info); + } +} + + +/* Event subscription callback. */ +static pjsip_evsub_user mwi_cb = +{ + &mwi_evsub_on_state, + NULL, /* on_tsx_state: not interested */ + NULL, /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless + * we want to authenticate + */ + + &mwi_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. + */ +}; + +void pjsua_start_mwi(pjsua_acc *acc) +{ + pj_pool_t *tmp_pool = NULL; + pj_str_t contact; + pjsip_tx_data *tdata; + pj_status_t status; + + if (!acc->cfg.mwi_enabled) { + if (acc->mwi_sub) { + /* Terminate MWI subscription */ + pjsip_tx_data *tdata; + pjsip_evsub *sub = acc->mwi_sub; + + /* Detach sub from this account */ + acc->mwi_sub = NULL; + acc->mwi_dlg = NULL; + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + + /* Unsubscribe */ + status = pjsip_mwi_initiate(acc->mwi_sub, 0, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_mwi_send_request(acc->mwi_sub, tdata); + } + } + return; + } + + if (acc->mwi_sub) { + /* Subscription is already active */ + return; + + } + + /* Generate suitable Contact header unless one is already set in + * the account + */ + if (acc->contact.slen) { + contact = acc->contact; + } else { + tmp_pool = pjsua_pool_create("tmpmwi", 512, 256); + status = pjsua_acc_create_uac_contact(tmp_pool, &contact, + acc->index, &acc->cfg.id); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + pj_pool_release(tmp_pool); + return; + } + } + + /* Create UAC dialog */ + status = pjsip_dlg_create_uac( pjsip_ua_instance(), + &acc->cfg.id, + &contact, + &acc->cfg.id, + NULL, &acc->mwi_dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create dialog", status); + if (tmp_pool) pj_pool_release(tmp_pool); + return; + } + + /* Increment the dialog's lock otherwise when presence session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(acc->mwi_dlg); + + /* Create UAC subscription */ + status = pjsip_mwi_create_uac(acc->mwi_dlg, &mwi_cb, + PJSIP_EVSUB_NO_EVENT_ID, &acc->mwi_sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating MWI subscription", status); + if (tmp_pool) pj_pool_release(tmp_pool); + pjsip_dlg_dec_lock(acc->mwi_dlg); + return; + } + + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(acc->mwi_dlg, &tp_sel); + } + + /* Set route-set */ + if (!pj_list_empty(&acc->route_set)) { + pjsip_dlg_set_route_set(acc->mwi_dlg, &acc->route_set); + } + + /* Set credentials */ + if (acc->cred_cnt) { + pjsip_auth_clt_set_credentials( &acc->mwi_dlg->auth_sess, + acc->cred_cnt, acc->cred); + } + + /* Set authentication preference */ + pjsip_auth_clt_set_prefs(&acc->mwi_dlg->auth_sess, &acc->cfg.auth_pref); + + pjsip_evsub_set_mod_data(acc->mwi_sub, pjsua_var.mod.id, acc); + + status = pjsip_mwi_initiate(acc->mwi_sub, -1, &tdata); + if (status != PJ_SUCCESS) { + pjsip_dlg_dec_lock(acc->mwi_dlg); + if (acc->mwi_sub) { + pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE); + } + acc->mwi_sub = NULL; + acc->mwi_dlg = NULL; + pjsua_perror(THIS_FILE, "Unable to create initial MWI SUBSCRIBE", + status); + if (tmp_pool) pj_pool_release(tmp_pool); + return; + } + + pjsua_process_msg_data(tdata, NULL); + + status = pjsip_pres_send_request(acc->mwi_sub, tdata); + if (status != PJ_SUCCESS) { + pjsip_dlg_dec_lock(acc->mwi_dlg); + if (acc->mwi_sub) { + pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE); + } + acc->mwi_sub = NULL; + acc->mwi_dlg = NULL; + pjsua_perror(THIS_FILE, "Unable to send initial MWI SUBSCRIBE", + status); + if (tmp_pool) pj_pool_release(tmp_pool); + return; + } + + pjsip_dlg_dec_lock(acc->mwi_dlg); + if (tmp_pool) pj_pool_release(tmp_pool); + +} + + +/***************************************************************************/ + /* Timer callback to re-create client subscription */ static void pres_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry) @@ -1860,14 +2084,20 @@ static void pres_timer_cb(pj_timer_heap_t *th, unsigned i; pj_time_val delay = { PJSUA_PRES_TIMER, 0 }; - /* Retry failed PUBLISH requests */ + entry->id = PJ_FALSE; + + /* Retry failed PUBLISH and MWI SUBSCRIBE requests */ for (i=0; icfg.publish_enabled && acc->publish_sess==NULL) pjsua_pres_init_publish_acc(acc->index); - } - entry->id = PJ_FALSE; + /* Re-subscribe MWI subscription if it's terminated prematurely */ + if (acc->cfg.mwi_enabled && !acc->mwi_sub) + pjsua_start_mwi(acc); + } /* #937: No need to do bulk client refresh, as buddies have their * own individual timer now. -- cgit v1.2.3