summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip-simple/presence.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip/src/pjsip-simple/presence.c')
-rw-r--r--pjsip/src/pjsip-simple/presence.c941
1 files changed, 941 insertions, 0 deletions
diff --git a/pjsip/src/pjsip-simple/presence.c b/pjsip/src/pjsip-simple/presence.c
new file mode 100644
index 0000000..5b96ec7
--- /dev/null
+++ b/pjsip/src/pjsip-simple/presence.c
@@ -0,0 +1,941 @@
+/* $Id: presence.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 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/presence.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_multipart.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_dialog.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "presence.c"
+#define PRES_DEFAULT_EXPIRES PJSIP_PRES_DEFAULT_EXPIRES
+
+#if PJSIP_PRES_BAD_CONTENT_RESPONSE < 200 || \
+ PJSIP_PRES_BAD_CONTENT_RESPONSE > 699 || \
+ PJSIP_PRES_BAD_CONTENT_RESPONSE/100 == 3
+# error Invalid PJSIP_PRES_BAD_CONTENT_RESPONSE value
+#endif
+
+/*
+ * Presence module (mod-presence)
+ */
+static struct pjsip_module mod_presence =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-presence", 12 }, /* 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() */
+};
+
+
+/*
+ * Presence message body type.
+ */
+typedef enum content_type_e
+{
+ CONTENT_TYPE_NONE,
+ CONTENT_TYPE_PIDF,
+ CONTENT_TYPE_XPIDF,
+} content_type_e;
+
+/*
+ * 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_e content_type; /**< Content-Type. */
+ pj_pool_t *status_pool; /**< Pool for pres_status */
+ pjsip_pres_status status; /**< Presence status. */
+ pj_pool_t *tmp_pool; /**< Pool for tmp_status */
+ pjsip_pres_status tmp_status; /**< Temp, before NOTIFY is answred.*/
+ pjsip_evsub_user user_cb; /**< The user callback. */
+};
+
+
+typedef struct pjsip_pres pjsip_pres;
+
+
+/*
+ * Forward decl for evsub callback.
+ */
+static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
+static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
+ pjsip_event *event);
+static void pres_on_evsub_rx_refresh( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body);
+static void pres_on_evsub_rx_notify( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body);
+static void pres_on_evsub_client_refresh(pjsip_evsub *sub);
+static void pres_on_evsub_server_timeout(pjsip_evsub *sub);
+
+
+/*
+ * Event subscription callback for presence.
+ */
+static pjsip_evsub_user pres_user =
+{
+ &pres_on_evsub_state,
+ &pres_on_evsub_tsx_state,
+ &pres_on_evsub_rx_refresh,
+ &pres_on_evsub_rx_notify,
+ &pres_on_evsub_client_refresh,
+ &pres_on_evsub_server_timeout,
+};
+
+
+/*
+ * Some static constants.
+ */
+const pj_str_t STR_EVENT = { "Event", 5 };
+const pj_str_t STR_PRESENCE = { "presence", 8 };
+const pj_str_t STR_APPLICATION = { "application", 11 };
+const pj_str_t STR_PIDF_XML = { "pidf+xml", 8};
+const pj_str_t STR_XPIDF_XML = { "xpidf+xml", 9};
+const pj_str_t STR_APP_PIDF_XML = { "application/pidf+xml", 20 };
+const pj_str_t STR_APP_XPIDF_XML = { "application/xpidf+xml", 21 };
+
+
+/*
+ * Init presence module.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_init_module( pjsip_endpoint *endpt,
+ pjsip_module *mod_evsub)
+{
+ pj_status_t status;
+ pj_str_t accept[2];
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL);
+
+ /* Must have not been registered */
+ PJ_ASSERT_RETURN(mod_presence.id == -1, PJ_EINVALIDOP);
+
+ /* Register to endpoint */
+ status = pjsip_endpt_register_module(endpt, &mod_presence);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ accept[0] = STR_APP_PIDF_XML;
+ accept[1] = STR_APP_XPIDF_XML;
+
+ /* Register event package to event module. */
+ status = pjsip_evsub_register_pkg( &mod_presence, &STR_PRESENCE,
+ PRES_DEFAULT_EXPIRES,
+ PJ_ARRAY_SIZE(accept), accept);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_unregister_module(endpt, &mod_presence);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get presence module instance.
+ */
+PJ_DEF(pjsip_module*) pjsip_pres_instance(void)
+{
+ return &mod_presence;
+}
+
+
+/*
+ * Create client subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg,
+ const pjsip_evsub_user *user_cb,
+ unsigned options,
+ pjsip_evsub **p_evsub )
+{
+ pj_status_t status;
+ pjsip_pres *pres;
+ char obj_name[PJ_MAX_OBJ_NAME];
+ pjsip_evsub *sub;
+
+ PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Create event subscription */
+ status = pjsip_evsub_create_uac( dlg, &pres_user, &STR_PRESENCE,
+ options, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create presence */
+ pres = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_pres);
+ pres->dlg = dlg;
+ pres->sub = sub;
+ if (user_cb)
+ pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user));
+
+ pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "pres%p", dlg->pool);
+ pres->status_pool = pj_pool_create(dlg->pool->factory, obj_name,
+ 512, 512, NULL);
+ pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "tmpres%p", dlg->pool);
+ pres->tmp_pool = pj_pool_create(dlg->pool->factory, obj_name,
+ 512, 512, NULL);
+
+ /* Attach to evsub */
+ pjsip_evsub_set_mod_data(sub, mod_presence.id, pres);
+
+ *p_evsub = sub;
+
+on_return:
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+}
+
+
+/*
+ * Create server subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_create_uas( pjsip_dialog *dlg,
+ const pjsip_evsub_user *user_cb,
+ pjsip_rx_data *rdata,
+ pjsip_evsub **p_evsub )
+{
+ pjsip_accept_hdr *accept;
+ pjsip_event_hdr *event;
+ content_type_e content_type = CONTENT_TYPE_NONE;
+ pjsip_evsub *sub;
+ pjsip_pres *pres;
+ 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 "presence" */
+ 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_PRESENCE) != 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; 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;
+ }
+
+ /* Lock dialog */
+ pjsip_dlg_inc_lock(dlg);
+
+
+ /* Create server subscription */
+ status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, 0, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Create server presence subscription */
+ pres = PJ_POOL_ZALLOC_T(dlg->pool, 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));
+
+ pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "pres%p", dlg->pool);
+ pres->status_pool = pj_pool_create(dlg->pool->factory, obj_name,
+ 512, 512, NULL);
+ pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "tmpres%p", dlg->pool);
+ pres->tmp_pool = pj_pool_create(dlg->pool->factory, obj_name,
+ 512, 512, NULL);
+
+ /* 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;
+}
+
+
+/*
+ * Forcefully terminate presence.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_terminate( pjsip_evsub *sub,
+ pj_bool_t notify )
+{
+ return pjsip_evsub_terminate(sub, notify);
+}
+
+/*
+ * Create SUBSCRIBE
+ */
+PJ_DEF(pj_status_t) pjsip_pres_initiate( pjsip_evsub *sub,
+ pj_int32_t expires,
+ pjsip_tx_data **p_tdata)
+{
+ return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires,
+ p_tdata);
+}
+
+
+/*
+ * Add custom headers.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_add_header( pjsip_evsub *sub,
+ const pjsip_hdr *hdr_list )
+{
+ return pjsip_evsub_add_header( sub, hdr_list );
+}
+
+
+/*
+ * Accept incoming subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_accept( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pjsip_hdr *hdr_list )
+{
+ return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
+}
+
+
+/*
+ * Get presence status.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_get_status( pjsip_evsub *sub,
+ pjsip_pres_status *status )
+{
+ pjsip_pres *pres;
+
+ PJ_ASSERT_RETURN(sub && status, PJ_EINVAL);
+
+ pres = (pjsip_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_ASSERT_RETURN(pres->tmp_pool!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
+ pj_memcpy(status, &pres->tmp_status, sizeof(pjsip_pres_status));
+ } else {
+ PJ_ASSERT_RETURN(pres->status_pool!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
+ pj_memcpy(status, &pres->status, sizeof(pjsip_pres_status));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set presence status.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_set_status( pjsip_evsub *sub,
+ const pjsip_pres_status *status )
+{
+ unsigned i;
+ pj_pool_t *tmp;
+ pjsip_pres *pres;
+
+ PJ_ASSERT_RETURN(sub && status, PJ_EINVAL);
+
+ pres = (pjsip_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 (pres->status.info[i].id.slen) {
+ /* Id already set */
+ } else 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->tmp_pool,
+ &pres->status.info[i].contact,
+ &status->info[i].contact);
+
+ /* Duplicate <person> */
+ pres->status.info[i].rpid.activity =
+ status->info[i].rpid.activity;
+ pj_strdup(pres->tmp_pool,
+ &pres->status.info[i].rpid.id,
+ &status->info[i].rpid.id);
+ pj_strdup(pres->tmp_pool,
+ &pres->status.info[i].rpid.note,
+ &status->info[i].rpid.note);
+
+ }
+
+ pres->status.info_cnt = status->info_cnt;
+
+ /* Swap pools */
+ tmp = pres->tmp_pool;
+ pres->tmp_pool = pres->status_pool;
+ pres->status_pool = tmp;
+ pj_pool_reset(pres->tmp_pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create message body.
+ */
+static pj_status_t pres_create_msg_body( pjsip_pres *pres,
+ pjsip_tx_data *tdata)
+{
+ pj_str_t entity;
+
+ /* Get publisher URI */
+ entity.ptr = (char*) pj_pool_alloc(tdata->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 PJ_ENOMEM;
+
+ if (pres->content_type == CONTENT_TYPE_PIDF) {
+
+ return pjsip_pres_create_pidf(tdata->pool, &pres->status,
+ &entity, &tdata->msg->body);
+
+ } else if (pres->content_type == CONTENT_TYPE_XPIDF) {
+
+ return pjsip_pres_create_xpidf(tdata->pool, &pres->status,
+ &entity, &tdata->msg->body);
+
+ } else {
+ return PJSIP_SIMPLE_EBADCONTENT;
+ }
+}
+
+
+/*
+ * Create NOTIFY
+ */
+PJ_DEF(pj_status_t) pjsip_pres_notify( pjsip_evsub *sub,
+ pjsip_evsub_state state,
+ const pj_str_t *state_str,
+ const pj_str_t *reason,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_pres *pres;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+ /* Get the presence object. */
+ pres = (pjsip_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, unless state is
+ * PJSIP_EVSUB_STATE_TERMINATED. This could happen if subscription
+ * has not been active (e.g. we're waiting for user authorization)
+ * and remote cancels the subscription.
+ */
+ PJ_ASSERT_RETURN(state==PJSIP_EVSUB_STATE_TERMINATED ||
+ 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.
+ * Only do this if we have presence status info to send (see above).
+ */
+ if (pres->status.info_cnt > 0) {
+ 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_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE);
+
+ /* We may not have a presence info yet, e.g. when we receive SUBSCRIBE
+ * to refresh subscription while we're waiting for user authorization.
+ */
+ //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. */
+ if (pres->status.info_cnt > 0) {
+ 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_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);
+
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ if (pres->status_pool) {
+ pj_pool_release(pres->status_pool);
+ pres->status_pool = NULL;
+ }
+ if (pres->tmp_pool) {
+ pj_pool_release(pres->tmp_pool);
+ pres->tmp_pool = NULL;
+ }
+ }
+}
+
+/*
+ * 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_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ if (pres->user_cb.on_tsx_state)
+ (*pres->user_cb.on_tsx_state)(sub, tsx, event);
+}
+
+
+/*
+ * Called when SUBSCRIBE is received.
+ */
+static void pres_on_evsub_rx_refresh( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsip_pres *pres;
+
+ pres = (pjsip_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);
+ }
+}
+
+
+/*
+ * Process the content of incoming NOTIFY request and update temporary
+ * status.
+ *
+ * return PJ_SUCCESS if incoming request is acceptable. If return value
+ * is not PJ_SUCCESS, res_hdr may be added with Warning header.
+ */
+static pj_status_t pres_process_rx_notify( pjsip_pres *pres,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr)
+{
+ const pj_str_t STR_MULTIPART = { "multipart", 9 };
+ pjsip_ctype_hdr *ctype_hdr;
+ pj_status_t status = PJ_SUCCESS;
+
+ *p_st_text = NULL;
+
+ /* 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 PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
+ }
+
+ /* Parse content. */
+ if (pj_stricmp(&ctype_hdr->media.type, &STR_MULTIPART)==0) {
+ pjsip_multipart_part *mpart;
+ pjsip_media_type ctype;
+
+ pjsip_media_type_init(&ctype, (pj_str_t*)&STR_APPLICATION,
+ (pj_str_t*)&STR_PIDF_XML);
+ mpart = pjsip_multipart_find_part(rdata->msg_info.msg->body,
+ &ctype, NULL);
+ if (mpart) {
+ status = pjsip_pres_parse_pidf2((char*)mpart->body->data,
+ mpart->body->len, pres->tmp_pool,
+ &pres->tmp_status);
+ }
+
+ if (mpart==NULL) {
+ pjsip_media_type_init(&ctype, (pj_str_t*)&STR_APPLICATION,
+ (pj_str_t*)&STR_XPIDF_XML);
+ mpart = pjsip_multipart_find_part(rdata->msg_info.msg->body,
+ &ctype, NULL);
+ if (mpart) {
+ status = pjsip_pres_parse_xpidf2((char*)mpart->body->data,
+ mpart->body->len,
+ pres->tmp_pool,
+ &pres->tmp_status);
+ } else {
+ status = PJSIP_SIMPLE_EBADCONTENT;
+ }
+ }
+ }
+ else
+ if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
+ pj_stricmp(&ctype_hdr->media.subtype, &STR_PIDF_XML)==0)
+ {
+ status = pjsip_pres_parse_pidf( rdata, pres->tmp_pool,
+ &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 = pjsip_pres_parse_xpidf( rdata, pres->tmp_pool,
+ &pres->tmp_status);
+ }
+ else
+ {
+ status = PJSIP_SIMPLE_EBADCONTENT;
+ }
+
+ if (status != PJ_SUCCESS) {
+ /* Unsupported or bad Content-Type */
+ if (PJSIP_PRES_BAD_CONTENT_RESPONSE >= 300) {
+ pjsip_accept_hdr *accept_hdr;
+ pjsip_warning_hdr *warn_hdr;
+
+ *p_st_code = PJSIP_PRES_BAD_CONTENT_RESPONSE;
+
+ /* 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 status;
+ } else {
+ pj_assert(PJSIP_PRES_BAD_CONTENT_RESPONSE/100 == 2);
+ PJ_PERROR(4,(THIS_FILE, status,
+ "Ignoring presence error due to "
+ "PJSIP_PRES_BAD_CONTENT_RESPONSE setting [%d]",
+ PJSIP_PRES_BAD_CONTENT_RESPONSE));
+ *p_st_code = PJSIP_PRES_BAD_CONTENT_RESPONSE;
+ status = PJ_SUCCESS;
+ }
+ }
+
+ /* If application calls pres_get_status(), redirect the call to
+ * retrieve the temporary status.
+ */
+ pres->tmp_status._is_valid = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Called when NOTIFY is received.
+ */
+static void pres_on_evsub_rx_notify( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsip_pres *pres;
+ pj_status_t status;
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ if (rdata->msg_info.msg->body) {
+ status = pres_process_rx_notify( pres, rdata, p_st_code, p_st_text,
+ res_hdr );
+ if (status != PJ_SUCCESS)
+ return;
+
+ } else {
+#if 1
+ /* This is the newest change, http://trac.pjsip.org/repos/ticket/873
+ * Some app want to be notified about the empty NOTIFY, e.g. to
+ * decide whether it should consider the buddy as offline.
+ * In this case, leave the buddy state unchanged, but set the
+ * "tuple_node" in pjsip_pres_status to NULL.
+ */
+ unsigned i;
+ for (i=0; i<pres->status.info_cnt; ++i) {
+ pres->status.info[i].tuple_node = NULL;
+ }
+
+#elif 0
+ /* This has just been changed. Previously, we treat incoming NOTIFY
+ * with no message body as having the presence subscription closed.
+ * Now we treat it as no change in presence status (ref: EyeBeam).
+ */
+ *p_st_code = 200;
+ return;
+#else
+ unsigned i;
+ /* Subscription is terminated. Consider contact is offline */
+ pres->tmp_status._is_valid = PJ_TRUE;
+ for (i=0; i<pres->tmp_status.info_cnt; ++i)
+ pres->tmp_status.info[i].basic_open = PJ_FALSE;
+#endif
+ }
+
+ /* 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_pool_t *tmp;
+
+ pj_memcpy(&pres->status, &pres->tmp_status, sizeof(pjsip_pres_status));
+
+ /* Swap the pool */
+ tmp = pres->tmp_pool;
+ pres->tmp_pool = pres->status_pool;
+ pres->status_pool = tmp;
+ }
+
+ pres->tmp_status._is_valid = PJ_FALSE;
+ pj_pool_reset(pres->tmp_pool);
+
+ /* Done */
+}
+
+/*
+ * Called when it's time to send SUBSCRIBE.
+ */
+static void pres_on_evsub_client_refresh(pjsip_evsub *sub)
+{
+ pjsip_pres *pres;
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ if (pres->user_cb.on_client_refresh) {
+ (*pres->user_cb.on_client_refresh)(sub);
+ } else {
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ status = pjsip_pres_initiate(sub, -1, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_pres_send_request(sub, tdata);
+ }
+}
+
+/*
+ * Called when no refresh is received after the interval.
+ */
+static void pres_on_evsub_server_timeout(pjsip_evsub *sub)
+{
+ pjsip_pres *pres;
+
+ pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ if (pres->user_cb.on_server_timeout) {
+ (*pres->user_cb.on_server_timeout)(sub);
+ } else {
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+ pj_str_t reason = { "timeout", 7 };
+
+ status = pjsip_pres_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, &reason, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_pres_send_request(sub, tdata);
+ }
+}
+