summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip-simple
diff options
context:
space:
mode:
authorDavid M. Lee <dlee@digium.com>2013-01-07 14:24:28 -0600
committerDavid M. Lee <dlee@digium.com>2013-01-07 14:24:28 -0600
commitf3ab456a17af1c89a6e3be4d20c5944853df1cb0 (patch)
treed00e1a332cd038a6d906a1ea0ac91e1a4458e617 /pjsip/src/pjsip-simple
Import pjproject-2.0.1
Diffstat (limited to 'pjsip/src/pjsip-simple')
-rw-r--r--pjsip/src/pjsip-simple/errno.c116
-rw-r--r--pjsip/src/pjsip-simple/evsub.c2169
-rw-r--r--pjsip/src/pjsip-simple/evsub_msg.c304
-rw-r--r--pjsip/src/pjsip-simple/iscomposing.c218
-rw-r--r--pjsip/src/pjsip-simple/mwi.c598
-rw-r--r--pjsip/src/pjsip-simple/pidf.c365
-rw-r--r--pjsip/src/pjsip-simple/presence.c941
-rw-r--r--pjsip/src/pjsip-simple/presence_body.c288
-rw-r--r--pjsip/src/pjsip-simple/publishc.c790
-rw-r--r--pjsip/src/pjsip-simple/rpid.c279
-rw-r--r--pjsip/src/pjsip-simple/xpidf.c301
11 files changed, 6369 insertions, 0 deletions
diff --git a/pjsip/src/pjsip-simple/errno.c b/pjsip/src/pjsip-simple/errno.c
new file mode 100644
index 0000000..e1bdb95
--- /dev/null
+++ b/pjsip/src/pjsip-simple/errno.c
@@ -0,0 +1,116 @@
+/* $Id: errno.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/errno.h>
+#include <pj/string.h>
+
+/* PJSIP-SIMPLE's own error codes/messages
+ * MUST KEEP THIS ARRAY SORTED!!
+ * Message must be limited to 64 chars!
+ */
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+static const struct
+{
+ int code;
+ const char *msg;
+} err_str[] =
+{
+ /* Event errors */
+ { PJSIP_SIMPLE_ENOPKG, "No SIP event package with the specified name" },
+ { PJSIP_SIMPLE_EPKGEXISTS, "SIP event package already exist" },
+
+ /* Presence errors */
+ { PJSIP_SIMPLE_ENOTSUBSCRIBE, "Expecting SUBSCRIBE request" },
+ { PJSIP_SIMPLE_ENOPRESENCE, "No presence associated with the subscription" },
+ { PJSIP_SIMPLE_ENOPRESENCEINFO, "No presence info in the server subscription" },
+ { PJSIP_SIMPLE_EBADCONTENT, "Bad Content-Type for presence" },
+ { PJSIP_SIMPLE_EBADPIDF, "Bad PIDF content for presence" },
+ { PJSIP_SIMPLE_EBADXPIDF, "Bad XPIDF content for presence" },
+ { PJSIP_SIMPLE_EBADRPID, "Invalid or bad RPID document"},
+
+ /* isComposing errors. */
+ { PJSIP_SIMPLE_EBADISCOMPOSE, "Bad isComposing indication/XML message" },
+};
+
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+/*
+ * pjsipsimple_strerror()
+ */
+PJ_DEF(pj_str_t) pjsipsimple_strerror( pj_status_t statcode,
+ char *buf, pj_size_t bufsize )
+{
+ pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+ if (statcode >= PJSIP_SIMPLE_ERRNO_START &&
+ statcode < PJSIP_SIMPLE_ERRNO_START + PJ_ERRNO_SPACE_SIZE)
+ {
+ /* Find the error in the table.
+ * Use binary search!
+ */
+ int first = 0;
+ int n = PJ_ARRAY_SIZE(err_str);
+
+ while (n > 0) {
+ int half = n/2;
+ int mid = first + half;
+
+ if (err_str[mid].code < statcode) {
+ first = mid+1;
+ n -= (half+1);
+ } else if (err_str[mid].code > statcode) {
+ n = half;
+ } else {
+ first = mid;
+ break;
+ }
+ }
+
+
+ if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+ pj_str_t msg;
+
+ msg.ptr = (char*)err_str[first].msg;
+ msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ }
+ }
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+ /* Error not found. */
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown pjsip-simple error %d",
+ statcode);
+
+ return errstr;
+}
+
diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c
new file mode 100644
index 0000000..b1dfca5
--- /dev/null
+++ b/pjsip/src/pjsip-simple/evsub.c
@@ -0,0 +1,2169 @@
+/* $Id: evsub.c 4082 2012-04-24 13:09:14Z bennylp $ */
+/*
+ * 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/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
+};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_subscribe_method =
+{
+ (pjsip_method_e) PJSIP_SUBSCRIBE_METHOD,
+ { "SUBSCRIBE", 9 }
+};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_notify_method =
+{
+ (pjsip_method_e) PJSIP_NOTIFY_METHOD,
+ { "NOTIFY", 6 }
+};
+
+/**
+ * SUBSCRIBE method constant.
+ */
+PJ_DEF(const pjsip_method*) pjsip_get_subscribe_method()
+{
+ return &pjsip_subscribe_method;
+}
+
+/**
+ * NOTIFY method constant.
+ */
+PJ_DEF(const pjsip_method*) pjsip_get_notify_method()
+{
+ return &pjsip_notify_method;
+}
+
+
+/*
+ * 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 PJSIP_EVSUB_TIME_UAC_REFRESH
+
+/* Time to wait for the final NOTIFY after sending unsubscription */
+#define TIME_UAC_TERMINATE PJSIP_EVSUB_TIME_UAC_TERMINATE
+
+/* 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 PJSIP_EVSUB_TIME_UAC_WAIT_NOTIFY
+
+
+/*
+ * 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,
+
+ /* Max nb of timer types. */
+ TIMER_TYPE_MAX
+};
+
+static const char *timer_names[] =
+{
+ "None",
+ "UAC_REFRESH",
+ "UAS_TIMEOUT"
+ "UAC_TERMINATE",
+ "UAC_WAIT_NOTIFY",
+ "INVALID_TIMER"
+};
+
+/*
+ * 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_DIALOG_USAGE, /* Priority */
+ 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. */
+ unsigned option; /**< Options. */
+ pjsip_evsub_user user; /**< Callback. */
+ pj_bool_t call_cb; /**< Notify 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. */
+ pj_str_t term_reason; /**< Termination reason. */
+ 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. */
+ pjsip_hdr sub_hdr_list; /**< User-defined header. */
+
+ pj_time_val refresh_time; /**< Time to refresh. */
+ pj_timer_entry timer; /**< Internal timer. */
+ int pending_tsx; /**< Number of pending transactions.*/
+ pjsip_transaction *pending_sub; /**< Pending UAC SUBSCRIBE tsx. */
+
+ 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_EVENT_S = { "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;
+}
+
+/* Proto for pjsipsimple_strerror().
+ * Defined in errno.c
+ */
+PJ_DECL(pj_str_t) pjsipsimple_strerror( pj_status_t statcode,
+ char *buf, pj_size_t bufsize );
+
+/*
+ * Init and register module.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt)
+{
+ pj_status_t status;
+ pj_str_t method_tags[] = {
+ { "SUBSCRIBE", 9},
+ { "NOTIFY", 6}
+ };
+
+ status = pj_register_strerror(PJSIP_SIMPLE_ERRNO_START,
+ PJ_ERRNO_SPACE_SIZE,
+ &pjsipsimple_strerror);
+ pj_assert(status == PJ_SUCCESS);
+
+ 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", 512, 512);
+ 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();
+
+ /* Register new methods SUBSCRIBE and NOTIFY in Allow-ed header */
+ pjsip_endpt_add_capability(endpt, &mod_evsub.mod, PJSIP_H_ALLOW, NULL,
+ 2, method_tags);
+
+ /* Done. */
+ 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 (pjsip_evsub*) 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 evsub module has been initialized */
+ PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, PJ_EINVALIDOP);
+
+ /* 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_T(mod_evsub.pool, 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;
+ }
+
+ /* Add to endpoint's Accept header */
+ pjsip_endpt_add_capability(mod_evsub.endpt, &mod_evsub.mod,
+ PJSIP_H_ACCEPT, NULL,
+ pkg->pkg_accept->count,
+ pkg->pkg_accept->values);
+
+
+ /* 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;
+}
+
+
+/*
+ * Retrieve Allow-Events header
+ */
+PJ_DEF(const pjsip_hdr*) pjsip_evsub_get_allow_events_hdr(pjsip_module *m)
+{
+ struct mod_evsub *mod;
+
+ if (m == NULL)
+ m = pjsip_evsub_instance();
+
+ mod = (struct mod_evsub*)m;
+
+ return (pjsip_hdr*) mod->allow_events_hdr;
+}
+
+
+/*
+ * 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);
+ PJ_ASSERT_ON_FAIL(timer_id>TIMER_TYPE_NONE && timer_id<TIMER_TYPE_MAX,
+ 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);
+
+ /* Remove this session from dialog's list of subscription */
+ dlgsub_head = (struct dlgsub *) 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,
+ const pj_str_t *reason)
+{
+ pjsip_evsub_state prev_state = sub->state;
+ pj_str_t old_state_str = sub->state_str;
+ pjsip_event dummy_event;
+
+ 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];
+
+ if (reason && sub->term_reason.slen==0)
+ pj_strdup(sub->pool, &sub->term_reason, reason);
+
+ 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));
+ pj_log_push_indent();
+
+ /* don't call the callback with NULL event, it may crash the app! */
+ if (!event) {
+ PJSIP_EVENT_INIT_USER(dummy_event, 0, 0, 0, 0);
+ event = &dummy_event;
+ }
+
+ if (sub->user.on_evsub_state && sub->call_cb)
+ (*sub->user.on_evsub_state)(sub, event);
+
+ if (state == PJSIP_EVSUB_STATE_TERMINATED &&
+ prev_state != PJSIP_EVSUB_STATE_TERMINATED)
+ {
+ if (sub->pending_tsx == 0) {
+ evsub_destroy(sub);
+ }
+ }
+
+ pj_log_pop_indent();
+}
+
+
+/*
+ * 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 = (pjsip_evsub*) 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->call_cb) {
+ (*sub->user.on_client_refresh)(sub);
+ } else {
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_LOG(5,(sub->obj_name, "Refreshing subscription."));
+ pj_log_push_indent();
+ status = pjsip_evsub_initiate(sub, NULL,
+ sub->expires->ivalue,
+ &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_evsub_send_request(sub, tdata);
+
+ pj_log_pop_indent();
+ }
+ break;
+
+ case TIMER_TYPE_UAS_TIMEOUT:
+ /* Refresh from UAC has not been received */
+ if (sub->user.on_server_timeout && sub->call_cb) {
+ (*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."));
+ pj_log_push_indent();
+ status = pjsip_evsub_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, &STR_TIMEOUT, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_evsub_send_request(sub, tdata);
+
+ pj_log_pop_indent();
+ }
+ break;
+
+ case TIMER_TYPE_UAC_TERMINATE:
+ {
+ pj_str_t timeout = {"timeout", 7};
+
+ PJ_LOG(5,(sub->obj_name, "Timeout waiting for final NOTIFY. "
+ "Terminating.."));
+ pj_log_push_indent();
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL,
+ &timeout);
+ pj_log_pop_indent();
+ }
+ 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.."));
+ pj_log_push_indent();
+ status = pjsip_evsub_initiate( sub, NULL, 0, &tdata);
+ if (status == PJ_SUCCESS)
+ pjsip_evsub_send_request(sub, tdata);
+
+ pj_log_pop_indent();
+ }
+ 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,
+ unsigned option,
+ 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;
+
+
+ /* Must lock dialog before using pool etc. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Init attributes: */
+
+ sub = PJ_POOL_ZALLOC_T(dlg->pool, struct pjsip_evsub);
+ sub->pool = dlg->pool;
+ sub->endpt = dlg->endpt;
+ sub->dlg = dlg;
+ sub->pkg = pkg;
+ sub->role = role;
+ sub->call_cb = PJ_TRUE;
+ sub->option = option;
+ 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_accept_hdr*)
+ pjsip_hdr_clone(sub->pool, pkg->pkg_accept);
+ pj_list_init(&sub->sub_hdr_list);
+
+ sub->timer.user_data = sub;
+ sub->timer.cb = &on_timer;
+
+ /* Set name. */
+ pj_ansi_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);
+
+
+ /* Check if another subscription has been registered to the dialog. In
+ * that case, just add ourselves to the subscription list, otherwise
+ * create and register a new subscription list.
+ */
+ if (pjsip_dlg_has_usage(dlg, &mod_evsub.mod)) {
+ dlgsub_head = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id];
+ dlgsub = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
+ dlgsub->sub = sub;
+ pj_list_push_back(dlgsub_head, dlgsub);
+ } else {
+ dlgsub_head = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub);
+ dlgsub = PJ_POOL_ALLOC_T(sub->pool, 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) {
+ pjsip_dlg_dec_lock(dlg);
+ 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;
+ pjsip_dlg_dec_lock(dlg);
+
+ 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,
+ unsigned option,
+ 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, option, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Add unique Id to Event header, only when PJSIP_EVSUB_NO_EVENT_ID
+ * is not specified.
+ */
+ if ((option & PJSIP_EVSUB_NO_EVENT_ID) == 0) {
+ 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,
+ unsigned option,
+ 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. We need the Event header to get
+ * the package name (don't want to add more arguments in the function).
+ */
+ event_hdr = (pjsip_event_hdr*)
+ pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &STR_EVENT,
+ &STR_EVENT_S, 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, option, &sub);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Just duplicate Event header from the request */
+ sub->event = (pjsip_event_hdr*) 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_accept_hdr*)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;
+}
+
+
+/*
+ * Forcefully destroy subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_terminate( pjsip_evsub *sub,
+ pj_bool_t notify )
+{
+ PJ_ASSERT_RETURN(sub, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(sub->dlg);
+
+ /* I think it's pretty safe to disable this check.
+
+ if (sub->pending_tsx) {
+ pj_assert(!"Unable to terminate when there's pending tsx");
+ pjsip_dlg_dec_lock(sub->dlg);
+ return PJ_EINVALIDOP;
+ }
+ */
+
+ sub->call_cb = notify;
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, NULL);
+
+ pjsip_dlg_dec_lock(sub->dlg);
+ return PJ_SUCCESS;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * Get termination reason.
+ */
+PJ_DEF(const pj_str_t*) pjsip_evsub_get_termination_reason(pjsip_evsub *sub)
+{
+ return &sub->term_reason;
+}
+
+/*
+ * 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*)
+ 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*)
+ pjsip_hdr_shallow_clone(tdata->pool, sub->expires));
+
+ /* Add Supported header (it's optional in RFC 3265, but some event package
+ * RFC may bring this requirement to SHOULD strength - e.g. RFC 5373)
+ */
+ {
+ const pjsip_hdr *hdr = pjsip_endpt_get_capability(sub->endpt,
+ PJSIP_H_SUPPORTED,
+ NULL);
+ if (hdr) {
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr));
+ }
+ }
+
+ /* Add Accept header: */
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, sub->accept));
+
+
+ /* Add Allow-Events header: */
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool,
+ mod_evsub.allow_events_hdr));
+
+
+ /* Add custom headers */
+ {
+ const pjsip_hdr *hdr = sub->sub_hdr_list.next;
+ while (hdr != &sub->sub_hdr_list) {
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+ }
+
+
+ *p_tdata = tdata;
+
+
+on_return:
+
+ pjsip_dlg_dec_lock(sub->dlg);
+ return status;
+}
+
+
+/*
+ * Add custom headers.
+ */
+PJ_DEF(pj_status_t) pjsip_evsub_add_header( pjsip_evsub *sub,
+ const pjsip_hdr *hdr_list )
+{
+ const pjsip_hdr *hdr;
+
+ PJ_ASSERT_RETURN(sub && hdr_list, PJ_EINVAL);
+
+ hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pj_list_push_back(&sub->sub_hdr_list, (pjsip_hdr*)
+ pjsip_hdr_clone(sub->pool, hdr));
+ hdr = hdr->next;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * 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*)
+ pjsip_hdr_shallow_clone(tdata->pool, sub->expires));
+
+ /* Add additional header, if any. */
+ if (hdr_list) {
+ const pjsip_hdr *hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+ }
+
+ /* 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_NULL:
+ case PJSIP_EVSUB_STATE_SENT:
+ pj_assert(!"Invalid state!");
+ /* Treat as pending */
+
+ case PJSIP_EVSUB_STATE_ACCEPTED:
+ 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_get_notify_method(),
+ -1, &tdata);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ /* Add Event header */
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ 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*)
+ pjsip_hdr_shallow_clone(tdata->pool, mod_evsub.allow_events_hdr));
+
+ /* Add Authentication headers. */
+ pjsip_auth_clt_init_req( &sub->dlg->auth_sess, tdata );
+
+ /* Update reason */
+ if (reason)
+ pj_strdup(sub->dlg->pool, &sub->term_reason, reason);
+
+ /* 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, -1, 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, NULL);
+
+ sub->dst_state = PJSIP_EVSUB_STATE_NULL;
+ sub->dst_state_str.slen = 0;
+
+ }
+
+
+on_return:
+ pjsip_dlg_dec_lock(sub->dlg);
+ return status;
+}
+
+
+/* Callback to be called to terminate transaction. */
+static void terminate_timer_cb(pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pj_str_t *key;
+ pjsip_transaction *tsx;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ key = (pj_str_t*)entry->user_data;
+ tsx = pjsip_tsx_layer_find_tsx(key, PJ_FALSE);
+ /* Chance of race condition here */
+ if (tsx) {
+ pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_UPDATED);
+ }
+}
+
+
+/*
+ * 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) {
+ //Note:
+ // this transaction can be other transaction in the dialog.
+ // The assertion below probably only valid for dialog that
+ // only has one event subscription usage.
+ //pj_assert(!"First transaction event is not TX or RX!");
+ return NULL;
+ }
+
+ event_hdr = (pjsip_event_hdr*)
+ pjsip_msg_find_hdr_by_names(msg, &STR_EVENT,
+ &STR_EVENT_S, 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 = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id];
+ if (dlgsub_head == NULL) {
+ dlgsub_head = PJ_POOL_ALLOC_T(dlg->pool, struct dlgsub);
+ pj_list_init(dlgsub_head);
+ dlg->mod_data[mod_evsub.mod.id] = dlgsub_head;
+ }
+ dlgsub = dlgsub_head->next;
+
+ while (dlgsub != dlgsub_head) {
+
+ if (pj_stricmp(&dlgsub->sub->event->event_type,
+ &event_hdr->event_type)==0)
+ {
+ /* Event type matched.
+ * Check if event ID matched too.
+ */
+ if (pj_strcmp(&dlgsub->sub->event->id_param,
+ &event_hdr->id_param)==0)
+ {
+
+ break;
+
+ }
+ /*
+ * Otherwise if it is an UAC subscription, AND
+ * PJSIP_EVSUB_NO_EVENT_ID flag is set, AND
+ * the session's event id is NULL, AND
+ * the incoming request is NOTIFY with event ID, then
+ * we consider it as a match, and update the
+ * session's event id.
+ */
+ else if (dlgsub->sub->role == PJSIP_ROLE_UAC &&
+ (dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID)!=0 &&
+ dlgsub->sub->event->id_param.slen==0 &&
+ !pjsip_method_cmp(&tsx->method, &pjsip_notify_method))
+ {
+ /* Update session's event id. */
+ pj_strdup(dlgsub->sub->pool,
+ &dlgsub->sub->event->id_param,
+ &event_hdr->id_param);
+
+ break;
+ }
+ }
+
+
+
+ dlgsub = dlgsub->next;
+ }
+
+ /* Note:
+ * the second condition is for http://trac.pjsip.org/repos/ticket/911
+ */
+ if (dlgsub == dlgsub_head ||
+ (dlgsub->sub &&
+ pjsip_evsub_get_state(dlgsub->sub)==PJSIP_EVSUB_STATE_TERMINATED))
+ {
+ const char *reason_msg =
+ (dlgsub == dlgsub_head ? "Subscription Does Not Exist" :
+ "Subscription already terminated");
+
+ /* This could be incoming request to create new subscription */
+ PJ_LOG(4,(THIS_FILE,
+ "%s for %.*s, event=%.*s;id=%.*s",
+ reason_msg,
+ (int)tsx->method.name.slen,
+ tsx->method.name.ptr,
+ (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;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ pj_cstr(&reason, reason_msg);
+ 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++;
+
+ /* Special case for outgoing/UAC SUBSCRIBE/REFER transaction.
+ * We can only have one pending UAC SUBSCRIBE/REFER, so if another
+ * transaction is started while previous one still alive, terminate
+ * the older one.
+ *
+ * Sample scenario:
+ * - subscribe sent to destination that doesn't exist, transaction
+ * is still retransmitting request, then unsubscribe is sent.
+ */
+ if (tsx->role == PJSIP_ROLE_UAC &&
+ tsx->state == PJSIP_TSX_STATE_CALLING &&
+ (pjsip_method_cmp(&tsx->method, &sub->method) == 0 ||
+ pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0))
+ {
+
+ if (sub->pending_sub &&
+ sub->pending_sub->state < PJSIP_TSX_STATE_COMPLETED)
+ {
+ pj_timer_entry *timer;
+ pj_str_t *key;
+ pj_time_val timeout = {0, 0};
+
+ PJ_LOG(4,(sub->obj_name,
+ "Cancelling pending subscription request"));
+
+ /* By convention, we use 490 (Request Updated) status code.
+ * When transaction handler (below) see this status code, it
+ * will ignore the transaction.
+ */
+ /* This unfortunately may cause deadlock, because at the moment
+ * we are holding dialog's mutex. If a response to this
+ * transaction is in progress in another thread, that thread
+ * will deadlock when trying to acquire dialog mutex, because
+ * it is holding the transaction mutex.
+ *
+ * So the solution is to register timer to kill this transaction.
+ */
+ //pjsip_tsx_terminate(sub->pending_sub, PJSIP_SC_REQUEST_UPDATED);
+ timer = PJ_POOL_ZALLOC_T(dlg->pool, pj_timer_entry);
+ key = PJ_POOL_ALLOC_T(dlg->pool, pj_str_t);
+ pj_strdup(dlg->pool, key, &sub->pending_sub->transaction_key);
+ timer->cb = &terminate_timer_cb;
+ timer->user_data = key;
+
+ pjsip_endpt_schedule_timer(dlg->endpt, timer, &timeout);
+ }
+
+ sub->pending_sub = 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*)
+ pjsip_hdr_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+
+ /* Add msg body, if any */
+ if (body) {
+ tdata->msg->body = pjsip_msg_body_clone(tdata->pool, body);
+ if (tdata->msg->body == NULL) {
+
+ PJ_LOG(4,(THIS_FILE, "Error: unable to clone msg body"));
+
+ /* 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 ||
+ pjsip_method_cmp(&tsx->method, &pjsip_subscribe_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, NULL);
+ return;
+ }
+
+ /* Only interested in final response */
+ if (tsx->state != PJSIP_TSX_STATE_COMPLETED &&
+ tsx->state != PJSIP_TSX_STATE_TERMINATED)
+ {
+ return;
+ }
+
+ /* Clear pending subscription */
+ if (tsx == sub->pending_sub) {
+ sub->pending_sub = NULL;
+ } else if (sub->pending_sub != NULL) {
+ /* This SUBSCRIBE transaction has been "renewed" with another
+ * SUBSCRIBE, so we can just ignore this. For example, user
+ * sent SUBSCRIBE followed immediately with UN-SUBSCRIBE.
+ */
+ 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, -1, NULL);
+
+ if (status != PJ_SUCCESS) {
+ /* Authentication failed! */
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, event, &tsx->status_text);
+ 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_expires_hdr*)
+ 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, NULL);
+ }
+
+ } 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;
+ }
+
+ /* Ignore 490 (Request Updated) status.
+ * This happens when application sends SUBSCRIBE/REFER while
+ * another one is still in progress.
+ */
+ if (tsx->status_code == PJSIP_SC_REQUEST_UPDATED) {
+ return;
+ }
+
+ /* Kill any timer. */
+ set_timer(sub, TIMER_TYPE_NONE, 0);
+
+ /* Set state to TERMINATED */
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED,
+ NULL, event, &tsx->status_text);
+
+ }
+
+ } 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;
+
+ /* 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_sub_state_hdr*)
+ 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->call_cb) {
+ (*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 {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t reason;
+
+ reason = pj_strerror(status, errmsg, sizeof(errmsg));
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL,
+ &reason);
+ }
+
+ 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))
+ {
+ int next_refresh = sub_state->expires_param;
+ unsigned timeout;
+
+ update_expires(sub, next_refresh);
+
+ /* Start UAC refresh timer, only when we're not unsubscribing */
+ 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,
+ &sub_state->reason_param);
+ } else {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t reason;
+
+ reason = pj_strerror(status, errmsg, sizeof(errmsg));
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event,
+ &reason);
+ }
+
+
+ } 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 ||
+ pjsip_method_cmp(&tsx->method, &pjsip_subscribe_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_str_t reason = { NULL, 0 };
+ 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_event_hdr*)
+ pjsip_msg_find_hdr_by_names(msg, &STR_EVENT,
+ &STR_EVENT, NULL);
+ expires = (pjsip_expires_hdr*)
+ 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);
+
+ if (sub->user.on_rx_refresh && sub->call_cb) {
+ (*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*)
+ 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, &reason);
+ } else if (sub->state == PJSIP_EVSUB_STATE_NULL) {
+ set_state(sub, sub->state, NULL, event, &reason);
+ }
+
+ /* 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, -1, NULL );
+
+ if (status != PJ_SUCCESS) {
+ /* Can't authenticate. Terminate session (?) */
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL,
+ &tsx->status_text);
+ return;
+ }
+
+ }
+ /*
+ * Terminate event usage if we receive 481, 408, and 7 class
+ * responses.
+ */
+ if (sub->state != PJSIP_EVSUB_STATE_TERMINATED &&
+ (tsx->status_code==481 || tsx->status_code==408 ||
+ tsx->status_code/100 == 7))
+ {
+ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event,
+ &tsx->status_text);
+ return;
+ }
+
+ } 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->call_cb)
+ (*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/evsub_msg.c b/pjsip/src/pjsip-simple/evsub_msg.c
new file mode 100644
index 0000000..d91afa9
--- /dev/null
+++ b/pjsip/src/pjsip-simple/evsub_msg.c
@@ -0,0 +1,304 @@
+/* $Id: evsub_msg.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * 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/evsub_msg.h>
+#include <pjsip/print_util.h>
+#include <pjsip/sip_parser.h>
+#include <pjlib-util/string.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/except.h>
+
+/*
+ * Event header.
+ */
+static int pjsip_event_hdr_print( pjsip_event_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool,
+ const pjsip_event_hdr *hdr);
+static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_event_hdr*);
+
+static pjsip_hdr_vptr event_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_event_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_event_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_event_hdr_print,
+};
+
+
+PJ_DEF(pjsip_event_hdr*) pjsip_event_hdr_create(pj_pool_t *pool)
+{
+ pjsip_event_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_event_hdr);
+ hdr->type = PJSIP_H_OTHER;
+ hdr->name.ptr = "Event";
+ hdr->name.slen = 5;
+ hdr->sname.ptr = "o";
+ hdr->sname.slen = 1;
+ hdr->vptr = &event_hdr_vptr;
+ pj_list_init(hdr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+static int pjsip_event_hdr_print( pjsip_event_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf+size;
+ int printed;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ copy_advance(p, hdr->name);
+ *p++ = ':';
+ *p++ = ' ';
+
+ copy_advance(p, hdr->event_type);
+ copy_advance_pair(p, ";id=", 4, hdr->id_param);
+
+ printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return printed;
+
+ p += printed;
+ return p - buf;
+}
+
+static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool,
+ const pjsip_event_hdr *rhs)
+{
+ pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool);
+ pj_strdup(pool, &hdr->event_type, &rhs->event_type);
+ pj_strdup(pool, &hdr->id_param, &rhs->id_param);
+ pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_event_hdr*
+pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_event_hdr *rhs )
+{
+ pjsip_event_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_event_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+
+/*
+ * Allow-Events header.
+ */
+PJ_DEF(pjsip_allow_events_hdr*) pjsip_allow_events_hdr_create(pj_pool_t *pool)
+{
+ const pj_str_t STR_ALLOW_EVENTS = { "Allow-Events", 12};
+ pjsip_allow_events_hdr *hdr;
+
+ hdr = pjsip_generic_array_hdr_create(pool, &STR_ALLOW_EVENTS);
+
+ if (hdr) {
+ hdr->sname.ptr = "u";
+ hdr->sname.slen = 1;
+ }
+
+ return hdr;
+}
+
+
+/*
+ * Subscription-State header.
+ */
+static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr *hdr);
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr*);
+
+static pjsip_hdr_vptr sub_state_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_sub_state_hdr_print,
+};
+
+
+PJ_DEF(pjsip_sub_state_hdr*) pjsip_sub_state_hdr_create(pj_pool_t *pool)
+{
+ pj_str_t sub_state = { "Subscription-State", 18 };
+ pjsip_sub_state_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_sub_state_hdr);
+ hdr->type = PJSIP_H_OTHER;
+ hdr->name = hdr->sname = sub_state;
+ hdr->vptr = &sub_state_hdr_vptr;
+ hdr->expires_param = -1;
+ hdr->retry_after = -1;
+ pj_list_init(hdr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf+size;
+ int printed;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ copy_advance(p, hdr->name);
+ *p++ = ':';
+ *p++ = ' ';
+
+ copy_advance_escape(p, hdr->sub_state, pc->pjsip_TOKEN_SPEC);
+ copy_advance_pair_escape(p, ";reason=", 8, hdr->reason_param,
+ pc->pjsip_TOKEN_SPEC);
+ if (hdr->expires_param >= 0) {
+ pj_memcpy(p, ";expires=", 9);
+ p += 9;
+ printed = pj_utoa(hdr->expires_param, p);
+ p += printed;
+ }
+ if (hdr->retry_after >= 0) {
+ pj_memcpy(p, ";retry-after=", 13);
+ p += 9;
+ printed = pj_utoa(hdr->retry_after, p);
+ p += printed;
+ }
+
+ printed = pjsip_param_print_on( &hdr->other_param, p, endbuf-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC,
+ ';');
+ if (printed < 0)
+ return printed;
+
+ p += printed;
+
+ return p - buf;
+}
+
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr *rhs)
+{
+ pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool);
+ pj_strdup(pool, &hdr->sub_state, &rhs->sub_state);
+ pj_strdup(pool, &hdr->reason_param, &rhs->reason_param);
+ hdr->retry_after = rhs->retry_after;
+ hdr->expires_param = rhs->expires_param;
+ pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr *rhs)
+{
+ pjsip_sub_state_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_sub_state_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+
+/*
+ * Parse Event header.
+ */
+static pjsip_hdr *parse_hdr_event(pjsip_parse_ctx *ctx)
+{
+ pjsip_event_hdr *hdr = pjsip_event_hdr_create(ctx->pool);
+ const pj_str_t id_param = { "id", 2 };
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ pj_scan_get(ctx->scanner, &pc->pjsip_TOKEN_SPEC, &hdr->event_type);
+
+ while (*ctx->scanner->curptr == ';') {
+ pj_str_t pname, pvalue;
+
+ pj_scan_get_char(ctx->scanner);
+ pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
+
+ if (pj_stricmp(&pname, &id_param)==0) {
+ hdr->id_param = pvalue;
+ } else {
+ pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+ param->name = pname;
+ param->value = pvalue;
+ pj_list_push_back(&hdr->other_param, param);
+ }
+ }
+ pjsip_parse_end_hdr_imp( ctx->scanner );
+ return (pjsip_hdr*)hdr;
+}
+
+/*
+ * Parse Subscription-State header.
+ */
+static pjsip_hdr* parse_hdr_sub_state( pjsip_parse_ctx *ctx )
+{
+ pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(ctx->pool);
+ const pj_str_t reason = { "reason", 6 },
+ expires = { "expires", 7 },
+ retry_after = { "retry-after", 11 };
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ pj_scan_get(ctx->scanner, &pc->pjsip_TOKEN_SPEC, &hdr->sub_state);
+
+ while (*ctx->scanner->curptr == ';') {
+ pj_str_t pname, pvalue;
+
+ pj_scan_get_char(ctx->scanner);
+ pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
+
+ if (pj_stricmp(&pname, &reason) == 0) {
+ hdr->reason_param = pvalue;
+
+ } else if (pj_stricmp(&pname, &expires) == 0) {
+ hdr->expires_param = pj_strtoul(&pvalue);
+
+ } else if (pj_stricmp(&pname, &retry_after) == 0) {
+ hdr->retry_after = pj_strtoul(&pvalue);
+
+ } else {
+ pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+ param->name = pname;
+ param->value = pvalue;
+ pj_list_push_back(&hdr->other_param, param);
+ }
+ }
+
+ pjsip_parse_end_hdr_imp( ctx->scanner );
+ return (pjsip_hdr*)hdr;
+}
+
+/*
+ * Register header parsers.
+ */
+PJ_DEF(void) pjsip_evsub_init_parser(void)
+{
+ pjsip_register_hdr_parser( "Event", "o",
+ &parse_hdr_event);
+
+ pjsip_register_hdr_parser( "Subscription-State", NULL,
+ &parse_hdr_sub_state);
+}
+
diff --git a/pjsip/src/pjsip-simple/iscomposing.c b/pjsip/src/pjsip-simple/iscomposing.c
new file mode 100644
index 0000000..7053539
--- /dev/null
+++ b/pjsip/src/pjsip-simple/iscomposing.c
@@ -0,0 +1,218 @@
+/* $Id: iscomposing.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/iscomposing.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip/sip_msg.h>
+#include <pjlib-util/errno.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+/* MIME */
+static const pj_str_t STR_MIME_TYPE = { "application", 11 };
+static const pj_str_t STR_MIME_SUBTYPE = { "im-iscomposing+xml", 18 };
+
+
+/* XML node constants. */
+static const pj_str_t STR_ISCOMPOSING = { "isComposing", 11 };
+static const pj_str_t STR_STATE = { "state", 5 };
+static const pj_str_t STR_ACTIVE = { "active", 6 };
+static const pj_str_t STR_IDLE = { "idle", 4 };
+static const pj_str_t STR_LASTACTIVE = { "lastactive", 10 };
+static const pj_str_t STR_CONTENTTYPE = { "contenttype", 11 };
+static const pj_str_t STR_REFRESH = { "refresh", 7 };
+
+
+/* XML attributes constants */
+static const pj_str_t STR_XMLNS_NAME = { "xmlns", 5 };
+static const pj_str_t STR_XMLNS_VAL = { "urn:ietf:params:xml:ns:im-iscomposing", 37 };
+static const pj_str_t STR_XMLNS_XSI_NAME = { "xmlns:xsi", 9 };
+static const pj_str_t STR_XMLNS_XSI_VAL = { "http://www.w3.org/2001/XMLSchema-instance", 41 };
+static const pj_str_t STR_XSI_SLOC_NAME = { "xsi:schemaLocation", 18 };
+static const pj_str_t STR_XSI_SLOC_VAL = { "urn:ietf:params:xml:ns:im-composing iscomposing.xsd", 51 };
+
+
+PJ_DEF(pj_xml_node*) pjsip_iscomposing_create_xml( pj_pool_t *pool,
+ pj_bool_t is_composing,
+ const pj_time_val *lst_actv,
+ const pj_str_t *content_tp,
+ int refresh)
+{
+ pj_xml_node *doc, *node;
+ pj_xml_attr *attr;
+
+ /* Root document. */
+ doc = pj_xml_node_new(pool, &STR_ISCOMPOSING);
+
+ /* Add attributes */
+ attr = pj_xml_attr_new(pool, &STR_XMLNS_NAME, &STR_XMLNS_VAL);
+ pj_xml_add_attr(doc, attr);
+
+ attr = pj_xml_attr_new(pool, &STR_XMLNS_XSI_NAME, &STR_XMLNS_XSI_VAL);
+ pj_xml_add_attr(doc, attr);
+
+ attr = pj_xml_attr_new(pool, &STR_XSI_SLOC_NAME, &STR_XSI_SLOC_VAL);
+ pj_xml_add_attr(doc, attr);
+
+
+ /* Add state. */
+ node = pj_xml_node_new(pool, &STR_STATE);
+ if (is_composing)
+ node->content = STR_ACTIVE;
+ else
+ node->content = STR_IDLE;
+ pj_xml_add_node(doc, node);
+
+ /* Add lastactive, if any. */
+ PJ_UNUSED_ARG(lst_actv);
+ //if (!is_composing && lst_actv) {
+ // PJ_TODO(IMPLEMENT_LAST_ACTIVE_ATTRIBUTE);
+ //}
+
+ /* Add contenttype, if any. */
+ if (content_tp) {
+ node = pj_xml_node_new(pool, &STR_CONTENTTYPE);
+ pj_strdup(pool, &node->content, content_tp);
+ pj_xml_add_node(doc, node);
+ }
+
+ /* Add refresh, if any. */
+ if (is_composing && refresh > 1 && refresh < 3601) {
+ node = pj_xml_node_new(pool, &STR_REFRESH);
+ node->content.ptr = (char*) pj_pool_alloc(pool, 10);
+ node->content.slen = pj_utoa(refresh, node->content.ptr);
+ pj_xml_add_node(doc, node);
+ }
+
+ /* Done! */
+
+ return doc;
+}
+
+
+
+/*
+ * Function to print XML message body.
+ */
+static int xml_print_body( struct pjsip_msg_body *msg_body,
+ char *buf, pj_size_t size)
+{
+ return pj_xml_print((const pj_xml_node*)msg_body->data, buf, size,
+ PJ_TRUE);
+}
+
+
+/*
+ * Function to clone XML document.
+ */
+static void* xml_clone_data(pj_pool_t *pool, const void *data, unsigned len)
+{
+ PJ_UNUSED_ARG(len);
+ return pj_xml_clone( pool, (const pj_xml_node*)data);
+}
+
+
+
+PJ_DEF(pjsip_msg_body*) pjsip_iscomposing_create_body( pj_pool_t *pool,
+ pj_bool_t is_composing,
+ const pj_time_val *lst_actv,
+ const pj_str_t *content_tp,
+ int refresh)
+{
+ pj_xml_node *doc;
+ pjsip_msg_body *body;
+
+ doc = pjsip_iscomposing_create_xml( pool, is_composing, lst_actv,
+ content_tp, refresh);
+ if (doc == NULL)
+ return NULL;
+
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ body->content_type.type = STR_MIME_TYPE;
+ body->content_type.subtype = STR_MIME_SUBTYPE;
+
+ body->data = doc;
+ body->len = 0;
+
+ body->print_body = &xml_print_body;
+ body->clone_data = &xml_clone_data;
+
+ return body;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_iscomposing_parse( pj_pool_t *pool,
+ char *msg,
+ pj_size_t len,
+ pj_bool_t *p_is_composing,
+ pj_str_t **p_last_active,
+ pj_str_t **p_content_type,
+ int *p_refresh )
+{
+ pj_xml_node *doc, *node;
+
+ /* Set defaults: */
+ if (p_is_composing) *p_is_composing = PJ_FALSE;
+ if (p_last_active) *p_last_active = NULL;
+ if (p_content_type) *p_content_type = NULL;
+
+ /* Parse XML */
+ doc = pj_xml_parse( pool, msg, len);
+ if (!doc)
+ return PJLIB_UTIL_EINXML;
+
+ /* Root document must be "isComposing" */
+ if (pj_stricmp(&doc->name, &STR_ISCOMPOSING) != 0)
+ return PJSIP_SIMPLE_EBADISCOMPOSE;
+
+ /* Get the status. */
+ if (p_is_composing) {
+ node = pj_xml_find_node(doc, &STR_STATE);
+ if (node == NULL)
+ return PJSIP_SIMPLE_EBADISCOMPOSE;
+ *p_is_composing = (pj_stricmp(&node->content, &STR_ACTIVE)==0);
+ }
+
+ /* Get last active. */
+ if (p_last_active) {
+ node = pj_xml_find_node(doc, &STR_LASTACTIVE);
+ if (node)
+ *p_last_active = &node->content;
+ }
+
+ /* Get content type */
+ if (p_content_type) {
+ node = pj_xml_find_node(doc, &STR_CONTENTTYPE);
+ if (node)
+ *p_content_type = &node->content;
+ }
+
+ /* Get refresh */
+ if (p_refresh) {
+ node = pj_xml_find_node(doc, &STR_REFRESH);
+ if (node)
+ *p_refresh = pj_strtoul(&node->content);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjsip/src/pjsip-simple/mwi.c b/pjsip/src/pjsip-simple/mwi.c
new file mode 100644
index 0000000..da86131
--- /dev/null
+++ b/pjsip/src/pjsip-simple/mwi.c
@@ -0,0 +1,598 @@
+/* $Id: mwi.c 4172 2012-06-19 14:35:18Z 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/mwi.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip-simple/evsub_msg.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_dialog.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "mwi.c"
+
+ /*
+ * 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,
+ PJSIP_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; i<accept->count; ++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-simple/pidf.c b/pjsip/src/pjsip-simple/pidf.c
new file mode 100644
index 0000000..0b68d1a
--- /dev/null
+++ b/pjsip/src/pjsip-simple/pidf.c
@@ -0,0 +1,365 @@
+/* $Id: pidf.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * 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/pidf.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+
+
+struct pjpidf_op_desc pjpidf_op =
+{
+ {
+ &pjpidf_pres_construct,
+ &pjpidf_pres_add_tuple,
+ &pjpidf_pres_get_first_tuple,
+ &pjpidf_pres_get_next_tuple,
+ &pjpidf_pres_find_tuple,
+ &pjpidf_pres_remove_tuple,
+ &pjpidf_pres_add_note,
+ &pjpidf_pres_get_first_note,
+ &pjpidf_pres_get_next_note
+ },
+ {
+ &pjpidf_tuple_construct,
+ &pjpidf_tuple_get_id,
+ &pjpidf_tuple_set_id,
+ &pjpidf_tuple_get_status,
+ &pjpidf_tuple_get_contact,
+ &pjpidf_tuple_set_contact,
+ &pjpidf_tuple_set_contact_prio,
+ &pjpidf_tuple_get_contact_prio,
+ &pjpidf_tuple_add_note,
+ &pjpidf_tuple_get_first_note,
+ &pjpidf_tuple_get_next_note,
+ &pjpidf_tuple_get_timestamp,
+ &pjpidf_tuple_set_timestamp,
+ &pjpidf_tuple_set_timestamp_np
+ },
+ {
+ &pjpidf_status_construct,
+ &pjpidf_status_is_basic_open,
+ &pjpidf_status_set_basic_open
+ }
+};
+
+static pj_str_t PRESENCE = { "presence", 8 };
+static pj_str_t ENTITY = { "entity", 6};
+static pj_str_t TUPLE = { "tuple", 5 };
+static pj_str_t ID = { "id", 2 };
+static pj_str_t NOTE = { "note", 4 };
+static pj_str_t STATUS = { "status", 6 };
+static pj_str_t CONTACT = { "contact", 7 };
+static pj_str_t PRIORITY = { "priority", 8 };
+static pj_str_t TIMESTAMP = { "timestamp", 9 };
+static pj_str_t BASIC = { "basic", 5 };
+static pj_str_t OPEN = { "open", 4 };
+static pj_str_t CLOSED = { "closed", 6 };
+static pj_str_t EMPTY_STRING = { NULL, 0 };
+
+static pj_str_t XMLNS = { "xmlns", 5 };
+static pj_str_t PIDF_XMLNS = { "urn:ietf:params:xml:ns:pidf", 27 };
+
+static void xml_init_node(pj_pool_t *pool, pj_xml_node *node,
+ pj_str_t *name, const pj_str_t *value)
+{
+ pj_list_init(&node->attr_head);
+ pj_list_init(&node->node_head);
+ node->name = *name;
+ if (value) pj_strdup(pool, &node->content, value);
+ else node->content.ptr=NULL, node->content.slen=0;
+}
+
+static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name,
+ const pj_str_t *value)
+{
+ pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
+ attr->name = *name;
+ pj_strdup(pool, &attr->value, value);
+ return attr;
+}
+
+/* Presence */
+PJ_DEF(void) pjpidf_pres_construct(pj_pool_t *pool, pjpidf_pres *pres,
+ const pj_str_t *entity)
+{
+ pj_xml_attr *attr;
+
+ xml_init_node(pool, pres, &PRESENCE, NULL);
+ attr = xml_create_attr(pool, &ENTITY, entity);
+ pj_xml_add_attr(pres, attr);
+ attr = xml_create_attr(pool, &XMLNS, &PIDF_XMLNS);
+ pj_xml_add_attr(pres, attr);
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_add_tuple(pj_pool_t *pool, pjpidf_pres *pres,
+ const pj_str_t *id)
+{
+ pjpidf_tuple *t = PJ_POOL_ALLOC_T(pool, pjpidf_tuple);
+ pjpidf_tuple_construct(pool, t, id);
+ pj_xml_add_node(pres, t);
+ return t;
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_get_first_tuple(pjpidf_pres *pres)
+{
+ return pj_xml_find_node(pres, &TUPLE);
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_get_next_tuple(pjpidf_pres *pres,
+ pjpidf_tuple *tuple)
+{
+ return pj_xml_find_next_node(pres, tuple, &TUPLE);
+}
+
+static pj_bool_t find_tuple_by_id(const pj_xml_node *node, const void *id)
+{
+ return pj_xml_find_attr(node, &ID, (const pj_str_t*)id) != NULL;
+}
+
+PJ_DEF(pjpidf_tuple*) pjpidf_pres_find_tuple(pjpidf_pres *pres, const pj_str_t *id)
+{
+ return pj_xml_find(pres, &TUPLE, id, &find_tuple_by_id);
+}
+
+PJ_DEF(void) pjpidf_pres_remove_tuple(pjpidf_pres *pres, pjpidf_tuple *t)
+{
+ PJ_UNUSED_ARG(pres);
+ pj_list_erase(t);
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_pres_add_note(pj_pool_t *pool, pjpidf_pres *pres,
+ const pj_str_t *text)
+{
+ pjpidf_note *note = PJ_POOL_ALLOC_T(pool, pjpidf_note);
+ xml_init_node(pool, note, &NOTE, text);
+ pj_xml_add_node(pres, note);
+ return note;
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_pres_get_first_note(pjpidf_pres *pres)
+{
+ return pj_xml_find_node( pres, &NOTE);
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_pres_get_next_note(pjpidf_pres *t, pjpidf_note *note)
+{
+ return pj_xml_find_next_node(t, note, &NOTE);
+}
+
+
+/* Tuple */
+PJ_DEF(void) pjpidf_tuple_construct(pj_pool_t *pool, pjpidf_tuple *t,
+ const pj_str_t *id)
+{
+ pj_xml_attr *attr;
+ pjpidf_status *st;
+
+ xml_init_node(pool, t, &TUPLE, NULL);
+ attr = xml_create_attr(pool, &ID, id);
+ pj_xml_add_attr(t, attr);
+ st = PJ_POOL_ALLOC_T(pool, pjpidf_status);
+ pjpidf_status_construct(pool, st);
+ pj_xml_add_node(t, st);
+}
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_id(const pjpidf_tuple *t)
+{
+ const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)t, &ID, NULL);
+ pj_assert(attr);
+ return &attr->value;
+}
+
+PJ_DEF(void) pjpidf_tuple_set_id(pj_pool_t *pool, pjpidf_tuple *t, const pj_str_t *id)
+{
+ pj_xml_attr *attr = pj_xml_find_attr(t, &ID, NULL);
+ pj_assert(attr);
+ pj_strdup(pool, &attr->value, id);
+}
+
+
+PJ_DEF(pjpidf_status*) pjpidf_tuple_get_status(pjpidf_tuple *t)
+{
+ pjpidf_status *st = (pjpidf_status*)pj_xml_find_node(t, &STATUS);
+ pj_assert(st);
+ return st;
+}
+
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_contact(const pjpidf_tuple *t)
+{
+ pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &CONTACT);
+ if (!node)
+ return &EMPTY_STRING;
+ return &node->content;
+}
+
+PJ_DEF(void) pjpidf_tuple_set_contact(pj_pool_t *pool, pjpidf_tuple *t,
+ const pj_str_t *contact)
+{
+ pj_xml_node *node = pj_xml_find_node(t, &CONTACT);
+ if (!node) {
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ xml_init_node(pool, node, &CONTACT, contact);
+ pj_xml_add_node(t, node);
+ } else {
+ pj_strdup(pool, &node->content, contact);
+ }
+}
+
+PJ_DEF(void) pjpidf_tuple_set_contact_prio(pj_pool_t *pool, pjpidf_tuple *t,
+ const pj_str_t *prio)
+{
+ pj_xml_node *node = pj_xml_find_node(t, &CONTACT);
+ pj_xml_attr *attr;
+
+ if (!node) {
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ xml_init_node(pool, node, &CONTACT, NULL);
+ pj_xml_add_node(t, node);
+ }
+ attr = pj_xml_find_attr(node, &PRIORITY, NULL);
+ if (!attr) {
+ attr = xml_create_attr(pool, &PRIORITY, prio);
+ pj_xml_add_attr(node, attr);
+ } else {
+ pj_strdup(pool, &attr->value, prio);
+ }
+}
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_contact_prio(const pjpidf_tuple *t)
+{
+ pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &CONTACT);
+ pj_xml_attr *attr;
+
+ if (!node)
+ return &EMPTY_STRING;
+ attr = pj_xml_find_attr(node, &PRIORITY, NULL);
+ if (!attr)
+ return &EMPTY_STRING;
+ return &attr->value;
+}
+
+
+PJ_DEF(pjpidf_note*) pjpidf_tuple_add_note(pj_pool_t *pool, pjpidf_tuple *t,
+ const pj_str_t *text)
+{
+ pjpidf_note *note = PJ_POOL_ALLOC_T(pool, pjpidf_note);
+ xml_init_node(pool, note, &NOTE, text);
+ pj_xml_add_node(t, note);
+ return note;
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_tuple_get_first_note(pjpidf_tuple *t)
+{
+ return pj_xml_find_node(t, &NOTE);
+}
+
+PJ_DEF(pjpidf_note*) pjpidf_tuple_get_next_note(pjpidf_tuple *t, pjpidf_note *n)
+{
+ return pj_xml_find_next_node(t, n, &NOTE);
+}
+
+
+PJ_DEF(const pj_str_t*) pjpidf_tuple_get_timestamp(const pjpidf_tuple *t)
+{
+ pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &TIMESTAMP);
+ return node ? &node->content : &EMPTY_STRING;
+}
+
+PJ_DEF(void) pjpidf_tuple_set_timestamp(pj_pool_t *pool, pjpidf_tuple *t,
+ const pj_str_t *ts)
+{
+ pj_xml_node *node = pj_xml_find_node(t, &TIMESTAMP);
+ if (!node) {
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ xml_init_node(pool, node, &TIMESTAMP, ts);
+ pj_xml_add_node(t, node);
+ } else {
+ pj_strdup(pool, &node->content, ts);
+ }
+}
+
+
+PJ_DEF(void) pjpidf_tuple_set_timestamp_np(pj_pool_t *pool, pjpidf_tuple *t,
+ pj_str_t *ts)
+{
+ pj_xml_node *node = pj_xml_find_node(t, &TIMESTAMP);
+ if (!node) {
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ xml_init_node(pool, node, &TIMESTAMP, ts);
+ } else {
+ node->content = *ts;
+ }
+}
+
+
+/* Status */
+PJ_DEF(void) pjpidf_status_construct(pj_pool_t *pool, pjpidf_status *st)
+{
+ pj_xml_node *node;
+
+ xml_init_node(pool, st, &STATUS, NULL);
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ xml_init_node(pool, node, &BASIC, &CLOSED);
+ pj_xml_add_node(st, node);
+}
+
+PJ_DEF(pj_bool_t) pjpidf_status_is_basic_open(const pjpidf_status *st)
+{
+ pj_xml_node *node = pj_xml_find_node((pj_xml_node*)st, &BASIC);
+ if (!node)
+ return PJ_FALSE;
+ return pj_stricmp(&node->content, &OPEN)==0;
+}
+
+PJ_DEF(void) pjpidf_status_set_basic_open(pjpidf_status *st, pj_bool_t open)
+{
+ pj_xml_node *node = pj_xml_find_node(st, &BASIC);
+ if (node)
+ node->content = open ? OPEN : CLOSED;
+}
+
+PJ_DEF(pjpidf_pres*) pjpidf_create(pj_pool_t *pool, const pj_str_t *entity)
+{
+ pjpidf_pres *pres = PJ_POOL_ALLOC_T(pool, pjpidf_pres);
+ pjpidf_pres_construct(pool, pres, entity);
+ return pres;
+}
+
+PJ_DEF(pjpidf_pres*) pjpidf_parse(pj_pool_t *pool, char *text, int len)
+{
+ pjpidf_pres *pres = pj_xml_parse(pool, text, len);
+ if (pres && pres->name.slen >= 8) {
+ pj_str_t name;
+
+ name.ptr = pres->name.ptr + (pres->name.slen - 8);
+ name.slen = 8;
+
+ if (pj_stricmp(&name, &PRESENCE) == 0)
+ return pres;
+ }
+ return NULL;
+}
+
+PJ_DEF(int) pjpidf_print(const pjpidf_pres* pres, char *buf, int len)
+{
+ return pj_xml_print(pres, buf, len, PJ_TRUE);
+}
+
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);
+ }
+}
+
diff --git a/pjsip/src/pjsip-simple/presence_body.c b/pjsip/src/pjsip-simple/presence_body.c
new file mode 100644
index 0000000..e692f47
--- /dev/null
+++ b/pjsip/src/pjsip-simple/presence_body.c
@@ -0,0 +1,288 @@
+/* $Id: presence_body.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/sip_msg.h>
+#include <pjsip/sip_transport.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_body.c"
+
+
+static const pj_str_t STR_APPLICATION = { "application", 11 };
+static const pj_str_t STR_PIDF_XML = { "pidf+xml", 8 };
+static const pj_str_t STR_XPIDF_XML = { "xpidf+xml", 9 };
+
+
+
+
+/*
+ * Function to print XML message body.
+ */
+static int pres_print_body(struct pjsip_msg_body *msg_body,
+ char *buf, pj_size_t size)
+{
+ return pj_xml_print((const pj_xml_node*)msg_body->data, buf, size,
+ PJ_TRUE);
+}
+
+
+/*
+ * Function to clone XML document.
+ */
+static void* xml_clone_data(pj_pool_t *pool, const void *data, unsigned len)
+{
+ PJ_UNUSED_ARG(len);
+ return pj_xml_clone( pool, (const pj_xml_node*) data);
+}
+
+
+/*
+ * This is a utility function to create PIDF message body from PJSIP
+ * presence status (pjsip_pres_status).
+ */
+PJ_DEF(pj_status_t) pjsip_pres_create_pidf( pj_pool_t *pool,
+ const pjsip_pres_status *status,
+ const pj_str_t *entity,
+ pjsip_msg_body **p_body )
+{
+ pjpidf_pres *pidf;
+ pjsip_msg_body *body;
+ unsigned i;
+
+ /* Create <presence>. */
+ pidf = pjpidf_create(pool, entity);
+
+ /* Create <tuple> */
+ for (i=0; i<status->info_cnt; ++i) {
+
+ pjpidf_tuple *pidf_tuple;
+ pjpidf_status *pidf_status;
+ pj_str_t id;
+
+ /* Add tuple id. */
+ if (status->info[i].id.slen == 0) {
+ /* xs:ID must start with letter */
+ //pj_create_unique_string(pool, &id);
+ id.ptr = (char*)pj_pool_alloc(pool, PJ_GUID_STRING_LENGTH+2);
+ id.ptr += 2;
+ pj_generate_unique_string(&id);
+ id.ptr -= 2;
+ id.ptr[0] = 'p';
+ id.ptr[1] = 'j';
+ id.slen += 2;
+ } else {
+ id = status->info[i].id;
+ }
+
+ pidf_tuple = pjpidf_pres_add_tuple(pool, pidf, &id);
+
+ /* Set <contact> */
+ if (status->info[i].contact.slen)
+ pjpidf_tuple_set_contact(pool, pidf_tuple,
+ &status->info[i].contact);
+
+
+ /* Set basic status */
+ pidf_status = pjpidf_tuple_get_status(pidf_tuple);
+ pjpidf_status_set_basic_open(pidf_status,
+ status->info[i].basic_open);
+
+ /* Add <timestamp> if configured */
+#if defined(PJSIP_PRES_PIDF_ADD_TIMESTAMP) && PJSIP_PRES_PIDF_ADD_TIMESTAMP
+ if (PJSIP_PRES_PIDF_ADD_TIMESTAMP) {
+ char buf[50];
+ int tslen = 0;
+ pj_time_val tv;
+ pj_parsed_time pt;
+
+ pj_gettimeofday(&tv);
+ /* TODO: convert time to GMT! (unsupported by pjlib) */
+ pj_time_decode( &tv, &pt);
+
+ tslen = pj_ansi_snprintf(buf, sizeof(buf),
+ "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
+ pt.year, pt.mon+1, pt.day,
+ pt.hour, pt.min, pt.sec, pt.msec);
+ if (tslen > 0 && tslen < (int)sizeof(buf)) {
+ pj_str_t time = pj_str(buf);
+ pjpidf_tuple_set_timestamp(pool, pidf_tuple, &time);
+ }
+ }
+#endif
+ }
+
+ /* Create <person> (RPID) */
+ if (status->info_cnt) {
+ pjrpid_add_element(pidf, pool, 0, &status->info[0].rpid);
+ }
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ body->data = pidf;
+ body->content_type.type = STR_APPLICATION;
+ body->content_type.subtype = STR_PIDF_XML;
+ body->print_body = &pres_print_body;
+ body->clone_data = &xml_clone_data;
+
+ *p_body = body;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This is a utility function to create X-PIDF message body from PJSIP
+ * presence status (pjsip_pres_status).
+ */
+PJ_DEF(pj_status_t) pjsip_pres_create_xpidf( pj_pool_t *pool,
+ const pjsip_pres_status *status,
+ const pj_str_t *entity,
+ pjsip_msg_body **p_body )
+{
+ /* Note: PJSIP implementation of XPIDF is not complete!
+ */
+ pjxpidf_pres *xpidf;
+ pjsip_msg_body *body;
+
+ PJ_LOG(4,(THIS_FILE, "Warning: XPIDF format is not fully supported "
+ "by PJSIP"));
+
+ /* Create XPIDF document. */
+ xpidf = pjxpidf_create(pool, entity);
+
+ /* Set basic status. */
+ if (status->info_cnt > 0)
+ pjxpidf_set_status( xpidf, status->info[0].basic_open);
+ else
+ pjxpidf_set_status( xpidf, PJ_FALSE);
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ body->data = xpidf;
+ body->content_type.type = STR_APPLICATION;
+ body->content_type.subtype = STR_XPIDF_XML;
+ body->print_body = &pres_print_body;
+ body->clone_data = &xml_clone_data;
+
+ *p_body = body;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * This is a utility function to parse PIDF body into PJSIP presence status.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_parse_pidf( pjsip_rx_data *rdata,
+ pj_pool_t *pool,
+ pjsip_pres_status *pres_status)
+{
+ return pjsip_pres_parse_pidf2((char*)rdata->msg_info.msg->body->data,
+ rdata->msg_info.msg->body->len,
+ pool, pres_status);
+}
+
+PJ_DEF(pj_status_t) pjsip_pres_parse_pidf2(char *body, unsigned body_len,
+ pj_pool_t *pool,
+ pjsip_pres_status *pres_status)
+{
+ pjpidf_pres *pidf;
+ pjpidf_tuple *pidf_tuple;
+
+ pidf = pjpidf_parse(pool, body, body_len);
+ if (pidf == NULL)
+ return PJSIP_SIMPLE_EBADPIDF;
+
+ pres_status->info_cnt = 0;
+
+ pidf_tuple = pjpidf_pres_get_first_tuple(pidf);
+ while (pidf_tuple && pres_status->info_cnt < PJSIP_PRES_STATUS_MAX_INFO) {
+ pjpidf_status *pidf_status;
+
+ pres_status->info[pres_status->info_cnt].tuple_node =
+ pj_xml_clone(pool, pidf_tuple);
+
+ pj_strdup(pool,
+ &pres_status->info[pres_status->info_cnt].id,
+ pjpidf_tuple_get_id(pidf_tuple));
+
+ pj_strdup(pool,
+ &pres_status->info[pres_status->info_cnt].contact,
+ pjpidf_tuple_get_contact(pidf_tuple));
+
+ pidf_status = pjpidf_tuple_get_status(pidf_tuple);
+ if (pidf_status) {
+ pres_status->info[pres_status->info_cnt].basic_open =
+ pjpidf_status_is_basic_open(pidf_status);
+ } else {
+ pres_status->info[pres_status->info_cnt].basic_open = PJ_FALSE;
+ }
+
+ pidf_tuple = pjpidf_pres_get_next_tuple( pidf, pidf_tuple );
+ pres_status->info_cnt++;
+ }
+
+ /* Parse <person> (RPID) */
+ pjrpid_get_element(pidf, pool, &pres_status->info[0].rpid);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This is a utility function to parse X-PIDF body into PJSIP presence status.
+ */
+PJ_DEF(pj_status_t) pjsip_pres_parse_xpidf(pjsip_rx_data *rdata,
+ pj_pool_t *pool,
+ pjsip_pres_status *pres_status)
+{
+ return pjsip_pres_parse_xpidf2((char*)rdata->msg_info.msg->body->data,
+ rdata->msg_info.msg->body->len,
+ pool, pres_status);
+}
+
+PJ_DEF(pj_status_t) pjsip_pres_parse_xpidf2(char *body, unsigned body_len,
+ pj_pool_t *pool,
+ pjsip_pres_status *pres_status)
+{
+ pjxpidf_pres *xpidf;
+
+ xpidf = pjxpidf_parse(pool, body, body_len);
+ if (xpidf == NULL)
+ return PJSIP_SIMPLE_EBADXPIDF;
+
+ pres_status->info_cnt = 1;
+
+ pj_strdup(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;
+ pres_status->info[0].tuple_node = NULL;
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjsip/src/pjsip-simple/publishc.c b/pjsip/src/pjsip-simple/publishc.c
new file mode 100644
index 0000000..efee6fb
--- /dev/null
+++ b/pjsip/src/pjsip-simple/publishc.c
@@ -0,0 +1,790 @@
+/* $Id: publishc.c 4173 2012-06-20 10:39:05Z ming $ */
+/*
+ * 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/publish.h>
+#include <pjsip/sip_auth.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_uri.h>
+#include <pjsip/sip_util.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+#include <pj/timer.h>
+
+
+#define REFRESH_TIMER 1
+#define DELAY_BEFORE_REFRESH PJSIP_PUBLISHC_DELAY_BEFORE_REFRESH
+#define THIS_FILE "publishc.c"
+
+
+/* Let's define this enum, so that it'll trigger compilation error
+ * when somebody define the same enum in sip_msg.h
+ */
+enum
+{
+ PJSIP_PUBLISH_METHOD = PJSIP_OTHER_METHOD,
+};
+
+const pjsip_method pjsip_publish_method =
+{
+ (pjsip_method_e)PJSIP_PUBLISH_METHOD,
+ { "PUBLISH", 7 }
+};
+
+
+/**
+ * Pending request list.
+ */
+typedef struct pending_publish
+{
+ PJ_DECL_LIST_MEMBER(pjsip_tx_data);
+} pending_publish;
+
+
+/**
+ * SIP client publication structure.
+ */
+struct pjsip_publishc
+{
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pj_bool_t _delete_flag;
+ int pending_tsx;
+ pj_bool_t in_callback;
+ pj_mutex_t *mutex;
+
+ pjsip_publishc_opt opt;
+ void *token;
+ pjsip_publishc_cb *cb;
+
+ pj_str_t event;
+ pj_str_t str_target_uri;
+ pjsip_uri *target_uri;
+ pjsip_cid_hdr *cid_hdr;
+ pjsip_cseq_hdr *cseq_hdr;
+ pj_str_t from_uri;
+ pjsip_from_hdr *from_hdr;
+ pjsip_to_hdr *to_hdr;
+ pj_str_t etag;
+ pjsip_expires_hdr *expires_hdr;
+ pj_uint32_t expires;
+ pjsip_route_hdr route_set;
+ pjsip_hdr usr_hdr;
+ pjsip_host_port via_addr;
+ const void *via_tp;
+
+ /* Authorization sessions. */
+ pjsip_auth_clt_sess auth_sess;
+
+ /* Auto refresh publication. */
+ pj_bool_t auto_refresh;
+ pj_time_val last_refresh;
+ pj_time_val next_refresh;
+ pj_timer_entry timer;
+
+ /* Pending PUBLISH request */
+ pending_publish pending_reqs;
+};
+
+
+PJ_DEF(void) pjsip_publishc_opt_default(pjsip_publishc_opt *opt)
+{
+ pj_bzero(opt, sizeof(*opt));
+ opt->queue_request = PJSIP_PUBLISHC_QUEUE_REQUEST;
+}
+
+
+/*
+ * Initialize client publication module.
+ */
+PJ_DEF(pj_status_t) pjsip_publishc_init_module(pjsip_endpoint *endpt)
+{
+ /* Note:
+ Commented out the capability registration below, since it's
+ wrong to include PUBLISH in Allow header of INVITE requests/
+ responses.
+
+ 13.2.1 Creating the Initial INVITE
+ An Allow header field (Section 20.5) SHOULD be present in the
+ INVITE. It indicates what methods can be invoked within a dialog
+
+ 20.5 Allow
+ The Allow header field lists the set of methods supported by the
+ UA generating the message.
+
+ While the semantic of Allow header in non-dialog requests is unclear,
+ it's probably best not to include PUBLISH in Allow header for now
+ until we can find out how to customize the inclusion of methods in
+ Allow header for in-dialog vs out-dialog requests.
+
+ return pjsip_endpt_add_capability( endpt, NULL, PJSIP_H_ALLOW, NULL,
+ 1, &pjsip_publish_method.name);
+ */
+ PJ_UNUSED_ARG(endpt);
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_create( pjsip_endpoint *endpt,
+ const pjsip_publishc_opt *opt,
+ void *token,
+ pjsip_publishc_cb *cb,
+ pjsip_publishc **p_pubc)
+{
+ pj_pool_t *pool;
+ pjsip_publishc *pubc;
+ pjsip_publishc_opt default_opt;
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(endpt && cb && p_pubc, PJ_EINVAL);
+
+ pool = pjsip_endpt_create_pool(endpt, "pubc%p", 1024, 1024);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ pubc = PJ_POOL_ZALLOC_T(pool, pjsip_publishc);
+
+ pubc->pool = pool;
+ pubc->endpt = endpt;
+ pubc->token = token;
+ pubc->cb = cb;
+ pubc->expires = PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED;
+
+ if (!opt) {
+ pjsip_publishc_opt_default(&default_opt);
+ opt = &default_opt;
+ }
+ pj_memcpy(&pubc->opt, opt, sizeof(*opt));
+ pj_list_init(&pubc->pending_reqs);
+
+ status = pj_mutex_create_recursive(pubc->pool, "pubc%p", &pubc->mutex);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ status = pjsip_auth_clt_init(&pubc->auth_sess, endpt, pubc->pool, 0);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_destroy(pubc->mutex);
+ pj_pool_release(pool);
+ return status;
+ }
+
+ pj_list_init(&pubc->route_set);
+ pj_list_init(&pubc->usr_hdr);
+
+ /* Done */
+ *p_pubc = pubc;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_destroy(pjsip_publishc *pubc)
+{
+ PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
+
+ if (pubc->pending_tsx || pubc->in_callback) {
+ pubc->_delete_flag = 1;
+ pubc->cb = NULL;
+ } else {
+ /* Cancel existing timer, if any */
+ if (pubc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
+ pubc->timer.id = 0;
+ }
+
+ if (pubc->mutex)
+ pj_mutex_destroy(pubc->mutex);
+ pjsip_endpt_release_pool(pubc->endpt, pubc->pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_pool_t*) pjsip_publishc_get_pool(pjsip_publishc *pubc)
+{
+ return pubc->pool;
+}
+
+static void set_expires( pjsip_publishc *pubc, pj_uint32_t expires)
+{
+ if (expires != pubc->expires &&
+ expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED)
+ {
+ pubc->expires_hdr = pjsip_expires_hdr_create(pubc->pool, expires);
+ } else {
+ pubc->expires_hdr = NULL;
+ }
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_init(pjsip_publishc *pubc,
+ const pj_str_t *event,
+ const pj_str_t *target_uri,
+ const pj_str_t *from_uri,
+ const pj_str_t *to_uri,
+ pj_uint32_t expires)
+{
+ pj_str_t tmp;
+
+ PJ_ASSERT_RETURN(pubc && event && target_uri && from_uri && to_uri &&
+ expires, PJ_EINVAL);
+
+ /* Copy event type */
+ pj_strdup_with_null(pubc->pool, &pubc->event, event);
+
+ /* Copy server URL. */
+ pj_strdup_with_null(pubc->pool, &pubc->str_target_uri, target_uri);
+
+ /* Set server URL. */
+ tmp = pubc->str_target_uri;
+ pubc->target_uri = pjsip_parse_uri( pubc->pool, tmp.ptr, tmp.slen, 0);
+ if (pubc->target_uri == NULL) {
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Set "From" header. */
+ pj_strdup_with_null(pubc->pool, &pubc->from_uri, from_uri);
+ tmp = pubc->from_uri;
+ pubc->from_hdr = pjsip_from_hdr_create(pubc->pool);
+ pubc->from_hdr->uri = pjsip_parse_uri(pubc->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (!pubc->from_hdr->uri) {
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Set "To" header. */
+ pj_strdup_with_null(pubc->pool, &tmp, to_uri);
+ pubc->to_hdr = pjsip_to_hdr_create(pubc->pool);
+ pubc->to_hdr->uri = pjsip_parse_uri(pubc->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (!pubc->to_hdr->uri) {
+ return PJSIP_EINVALIDURI;
+ }
+
+
+ /* Set "Expires" header, if required. */
+ set_expires( pubc, expires);
+
+ /* Set "Call-ID" header. */
+ pubc->cid_hdr = pjsip_cid_hdr_create(pubc->pool);
+ pj_create_unique_string(pubc->pool, &pubc->cid_hdr->id);
+
+ /* Set "CSeq" header. */
+ pubc->cseq_hdr = pjsip_cseq_hdr_create(pubc->pool);
+ pubc->cseq_hdr->cseq = pj_rand() % 0xFFFF;
+ pjsip_method_set( &pubc->cseq_hdr->method, PJSIP_REGISTER_METHOD);
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_publishc_set_credentials( pjsip_publishc *pubc,
+ int count,
+ const pjsip_cred_info cred[] )
+{
+ PJ_ASSERT_RETURN(pubc && count && cred, PJ_EINVAL);
+ return pjsip_auth_clt_set_credentials(&pubc->auth_sess, count, cred);
+}
+
+PJ_DEF(pj_status_t) pjsip_publishc_set_route_set( pjsip_publishc *pubc,
+ const pjsip_route_hdr *route_set)
+{
+ const pjsip_route_hdr *chdr;
+
+ PJ_ASSERT_RETURN(pubc && route_set, PJ_EINVAL);
+
+ pj_list_init(&pubc->route_set);
+
+ chdr = route_set->next;
+ while (chdr != route_set) {
+ pj_list_push_back(&pubc->route_set, pjsip_hdr_clone(pubc->pool, chdr));
+ chdr = chdr->next;
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_publishc_set_headers( pjsip_publishc *pubc,
+ const pjsip_hdr *hdr_list)
+{
+ const pjsip_hdr *h;
+
+ PJ_ASSERT_RETURN(pubc && hdr_list, PJ_EINVAL);
+
+ pj_list_init(&pubc->usr_hdr);
+ h = hdr_list->next;
+ while (h != hdr_list) {
+ pj_list_push_back(&pubc->usr_hdr, pjsip_hdr_clone(pubc->pool, h));
+ h = h->next;
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_publishc_set_via_sent_by(pjsip_publishc *pubc,
+ pjsip_host_port *via_addr,
+ pjsip_transport *via_tp)
+{
+ PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
+
+ if (!via_addr)
+ pj_bzero(&pubc->via_addr, sizeof(pubc->via_addr));
+ else
+ pubc->via_addr = *via_addr;
+ pubc->via_tp = via_tp;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t create_request(pjsip_publishc *pubc,
+ pjsip_tx_data **p_tdata)
+{
+ const pj_str_t STR_EVENT = { "Event", 5 };
+ pj_status_t status;
+ pjsip_generic_string_hdr *hdr;
+ pjsip_tx_data *tdata;
+
+ PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
+
+ /* Create the request. */
+ status = pjsip_endpt_create_request_from_hdr( pubc->endpt,
+ &pjsip_publish_method,
+ pubc->target_uri,
+ pubc->from_hdr,
+ pubc->to_hdr,
+ NULL,
+ pubc->cid_hdr,
+ pubc->cseq_hdr->cseq,
+ NULL,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add cached authorization headers. */
+ pjsip_auth_clt_init_req( &pubc->auth_sess, tdata );
+
+ /* Add Route headers from route set, ideally after Via header */
+ if (!pj_list_empty(&pubc->route_set)) {
+ pjsip_hdr *route_pos;
+ const pjsip_route_hdr *route;
+
+ route_pos = (pjsip_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ if (!route_pos)
+ route_pos = &tdata->msg->hdr;
+
+ route = pubc->route_set.next;
+ while (route != &pubc->route_set) {
+ pjsip_hdr *new_hdr = (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, route);
+ pj_list_insert_after(route_pos, new_hdr);
+ route_pos = new_hdr;
+ route = route->next;
+ }
+ }
+
+ /* Add Event header */
+ hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_EVENT,
+ &pubc->event);
+ if (hdr)
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+
+
+ /* Add SIP-If-Match if we have etag */
+ if (pubc->etag.slen) {
+ const pj_str_t STR_HNAME = { "SIP-If-Match", 12 };
+
+ hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_HNAME,
+ &pubc->etag);
+ if (hdr)
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+ }
+
+ /* Add user headers */
+ if (!pj_list_empty(&pubc->usr_hdr)) {
+ const pjsip_hdr *hdr;
+
+ hdr = pubc->usr_hdr.next;
+ while (hdr != &pubc->usr_hdr) {
+ pjsip_hdr *new_hdr = (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, hdr);
+ pjsip_msg_add_hdr(tdata->msg, new_hdr);
+ hdr = hdr->next;
+ }
+ }
+
+
+ /* Done. */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_publish(pjsip_publishc *pubc,
+ pj_bool_t auto_refresh,
+ pjsip_tx_data **p_tdata)
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
+
+ status = create_request(pubc, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add Expires header */
+ if (pubc->expires_hdr) {
+ pjsip_hdr *dup;
+
+ dup = (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, pubc->expires_hdr);
+ if (dup)
+ pjsip_msg_add_hdr(tdata->msg, dup);
+ }
+
+ /* Cancel existing timer */
+ if (pubc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
+ pubc->timer.id = 0;
+ }
+
+ pubc->auto_refresh = auto_refresh;
+
+ /* Done */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_unpublish(pjsip_publishc *pubc,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_msg *msg;
+ pjsip_expires_hdr *expires;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
+
+ if (pubc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
+ pubc->timer.id = 0;
+ }
+
+ status = create_request(pubc, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ msg = tdata->msg;
+
+ /* Add Expires:0 header */
+ expires = pjsip_expires_hdr_create(tdata->pool, 0);
+ pjsip_msg_add_hdr( msg, (pjsip_hdr*)expires);
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_update_expires( pjsip_publishc *pubc,
+ pj_uint32_t expires )
+{
+ PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
+ set_expires( pubc, expires );
+ return PJ_SUCCESS;
+}
+
+
+static void call_callback(pjsip_publishc *pubc, pj_status_t status,
+ int st_code, const pj_str_t *reason,
+ pjsip_rx_data *rdata, pj_int32_t expiration)
+{
+ struct pjsip_publishc_cbparam cbparam;
+
+
+ cbparam.pubc = pubc;
+ cbparam.token = pubc->token;
+ cbparam.status = status;
+ cbparam.code = st_code;
+ cbparam.reason = *reason;
+ cbparam.rdata = rdata;
+ cbparam.expiration = expiration;
+
+ (*pubc->cb)(&cbparam);
+}
+
+static void pubc_refresh_timer_cb( pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pjsip_publishc *pubc = (pjsip_publishc*) entry->user_data;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ entry->id = 0;
+ status = pjsip_publishc_publish(pubc, 1, &tdata);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg));
+ call_callback(pubc, status, 400, &reason, NULL, -1);
+ return;
+ }
+
+ status = pjsip_publishc_send(pubc, tdata);
+ /* No need to call callback as it should have been called */
+}
+
+static void tsx_callback(void *token, pjsip_event *event)
+{
+ pj_status_t status;
+ pjsip_publishc *pubc = (pjsip_publishc*) token;
+ pjsip_transaction *tsx = event->body.tsx_state.tsx;
+
+ /* Decrement pending transaction counter. */
+ pj_assert(pubc->pending_tsx > 0);
+ --pubc->pending_tsx;
+
+ /* Mark that we're in callback to prevent deletion (#1164) */
+ ++pubc->in_callback;
+
+ /* If publication data has been deleted by user then remove publication
+ * data from transaction's callback, and don't call callback.
+ */
+ if (pubc->_delete_flag) {
+
+ /* Nothing to do */
+ ;
+
+ } else if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED ||
+ tsx->status_code == PJSIP_SC_UNAUTHORIZED)
+ {
+ pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
+ pjsip_tx_data *tdata;
+
+ status = pjsip_auth_clt_reinit_req( &pubc->auth_sess,
+ rdata,
+ tsx->last_tx,
+ &tdata);
+ if (status != PJ_SUCCESS) {
+ call_callback(pubc, status, tsx->status_code,
+ &rdata->msg_info.msg->line.status.reason,
+ rdata, -1);
+ } else {
+ status = pjsip_publishc_send(pubc, tdata);
+ }
+
+ } else {
+ pjsip_rx_data *rdata;
+ pj_int32_t expiration = 0xFFFF;
+
+ if (tsx->status_code/100 == 2) {
+ pjsip_msg *msg;
+ pjsip_expires_hdr *expires;
+ pjsip_generic_string_hdr *etag_hdr;
+ const pj_str_t STR_ETAG = { "SIP-ETag", 8 };
+
+ rdata = event->body.tsx_state.src.rdata;
+ msg = rdata->msg_info.msg;
+
+ /* Save ETag value */
+ etag_hdr = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(msg, &STR_ETAG, NULL);
+ if (etag_hdr) {
+ pj_strdup(pubc->pool, &pubc->etag, &etag_hdr->hvalue);
+ } else {
+ pubc->etag.slen = 0;
+ }
+
+ /* Update expires value */
+ expires = (pjsip_expires_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
+
+ if (pubc->auto_refresh && expires)
+ expiration = expires->ivalue;
+
+ if (pubc->auto_refresh && expiration!=0 && expiration!=0xFFFF) {
+ pj_time_val delay = { 0, 0};
+
+ /* Cancel existing timer, if any */
+ if (pubc->timer.id != 0) {
+ pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
+ pubc->timer.id = 0;
+ }
+
+ delay.sec = expiration - DELAY_BEFORE_REFRESH;
+ if (pubc->expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED &&
+ delay.sec > (pj_int32_t)pubc->expires)
+ {
+ delay.sec = pubc->expires;
+ }
+ if (delay.sec < DELAY_BEFORE_REFRESH)
+ delay.sec = DELAY_BEFORE_REFRESH;
+ pubc->timer.cb = &pubc_refresh_timer_cb;
+ pubc->timer.id = REFRESH_TIMER;
+ pubc->timer.user_data = pubc;
+ pjsip_endpt_schedule_timer( pubc->endpt, &pubc->timer, &delay);
+ pj_gettimeofday(&pubc->last_refresh);
+ pubc->next_refresh = pubc->last_refresh;
+ pubc->next_refresh.sec += delay.sec;
+ }
+
+ } else {
+ rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ?
+ event->body.tsx_state.src.rdata : NULL;
+ }
+
+
+ /* Call callback. */
+ if (expiration == 0xFFFF) expiration = -1;
+
+ /* Temporarily increment pending_tsx to prevent callback from
+ * destroying pubc.
+ */
+ ++pubc->pending_tsx;
+
+ call_callback(pubc, PJ_SUCCESS, tsx->status_code,
+ (rdata ? &rdata->msg_info.msg->line.status.reason
+ : pjsip_get_status_text(tsx->status_code)),
+ rdata, expiration);
+
+ --pubc->pending_tsx;
+
+ /* If we have pending request(s), send them now */
+ pj_mutex_lock(pubc->mutex);
+ while (!pj_list_empty(&pubc->pending_reqs)) {
+ pjsip_tx_data *tdata = pubc->pending_reqs.next;
+ pj_list_erase(tdata);
+
+ /* Add SIP-If-Match if we have etag and the request doesn't have
+ * one (http://trac.pjsip.org/repos/ticket/996)
+ */
+ if (pubc->etag.slen) {
+ const pj_str_t STR_HNAME = { "SIP-If-Match", 12 };
+ pjsip_generic_string_hdr *sim_hdr;
+
+ sim_hdr = (pjsip_generic_string_hdr*)
+ pjsip_msg_find_hdr_by_name(tdata->msg, &STR_HNAME, NULL);
+ if (!sim_hdr) {
+ /* Create the header */
+ sim_hdr = pjsip_generic_string_hdr_create(tdata->pool,
+ &STR_HNAME,
+ &pubc->etag);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sim_hdr);
+
+ } else {
+ /* Update */
+ if (pj_strcmp(&pubc->etag, &sim_hdr->hvalue))
+ pj_strdup(tdata->pool, &sim_hdr->hvalue, &pubc->etag);
+ }
+ }
+
+ status = pjsip_publishc_send(pubc, tdata);
+ if (status == PJ_EPENDING) {
+ pj_assert(!"Not expected");
+ pj_list_erase(tdata);
+ pjsip_tx_data_dec_ref(tdata);
+ } else if (status == PJ_SUCCESS) {
+ break;
+ }
+ }
+ pj_mutex_unlock(pubc->mutex);
+ }
+
+ /* No longer in callback. */
+ --pubc->in_callback;
+
+ /* Delete the record if user destroy pubc during the callback. */
+ if (pubc->_delete_flag && pubc->pending_tsx==0) {
+ pjsip_publishc_destroy(pubc);
+ }
+}
+
+
+PJ_DEF(pj_status_t) pjsip_publishc_send(pjsip_publishc *pubc,
+ pjsip_tx_data *tdata)
+{
+ pj_status_t status;
+ pjsip_cseq_hdr *cseq_hdr;
+ pj_uint32_t cseq;
+
+ PJ_ASSERT_RETURN(pubc && tdata, PJ_EINVAL);
+
+ /* Make sure we don't have pending transaction. */
+ pj_mutex_lock(pubc->mutex);
+ if (pubc->pending_tsx) {
+ if (pubc->opt.queue_request) {
+ pj_list_push_back(&pubc->pending_reqs, tdata);
+ pj_mutex_unlock(pubc->mutex);
+ PJ_LOG(4,(THIS_FILE, "Request is queued, pubc has another "
+ "transaction pending"));
+ return PJ_EPENDING;
+ } else {
+ pjsip_tx_data_dec_ref(tdata);
+ pj_mutex_unlock(pubc->mutex);
+ PJ_LOG(4,(THIS_FILE, "Unable to send request, pubc has another "
+ "transaction pending"));
+ return PJ_EBUSY;
+ }
+ }
+ pj_mutex_unlock(pubc->mutex);
+
+ /* If via_addr is set, use this address for the Via header. */
+ if (pubc->via_addr.host.slen > 0) {
+ tdata->via_addr = pubc->via_addr;
+ tdata->via_tp = pubc->via_tp;
+ }
+
+ /* Invalidate message buffer. */
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Increment CSeq */
+ cseq = ++pubc->cseq_hdr->cseq;
+ cseq_hdr = (pjsip_cseq_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
+ cseq_hdr->cseq = cseq;
+
+ /* Increment pending transaction first, since transaction callback
+ * may be called even before send_request() returns!
+ */
+ ++pubc->pending_tsx;
+ status = pjsip_endpt_send_request(pubc->endpt, tdata, -1, pubc,
+ &tsx_callback);
+ if (status!=PJ_SUCCESS) {
+ // no need to decrement, callback has been called and it should
+ // already decremented pending_tsx. Decrementing this here may
+ // cause accessing freed memory location.
+ //--pubc->pending_tsx;
+ PJ_LOG(4,(THIS_FILE, "Error sending request, status=%d", status));
+ }
+
+ return status;
+}
+
diff --git a/pjsip/src/pjsip-simple/rpid.c b/pjsip/src/pjsip-simple/rpid.c
new file mode 100644
index 0000000..9ec3bb0
--- /dev/null
+++ b/pjsip/src/pjsip-simple/rpid.c
@@ -0,0 +1,279 @@
+/* $Id: rpid.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/rpid.h>
+#include <pjsip-simple/errno.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+static const pj_str_t DM_NAME = {"xmlns:dm", 8};
+static const pj_str_t DM_VAL = {"urn:ietf:params:xml:ns:pidf:data-model", 38};
+static const pj_str_t RPID_NAME = {"xmlns:rpid", 10};
+static const pj_str_t RPID_VAL = {"urn:ietf:params:xml:ns:pidf:rpid", 32};
+
+static const pj_str_t DM_NOTE = {"dm:note", 7};
+static const pj_str_t DM_PERSON = {"dm:person", 9};
+static const pj_str_t ID = {"id", 2};
+static const pj_str_t NOTE = {"note", 4};
+static const pj_str_t RPID_ACTIVITIES = {"rpid:activities", 15};
+static const pj_str_t RPID_AWAY = {"rpid:away", 9};
+static const pj_str_t RPID_BUSY = {"rpid:busy", 9};
+static const pj_str_t RPID_UNKNOWN = {"rpid:unknown", 12};
+
+
+/* Duplicate RPID element */
+PJ_DEF(void) pjrpid_element_dup(pj_pool_t *pool, pjrpid_element *dst,
+ const pjrpid_element *src)
+{
+ pj_memcpy(dst, src, sizeof(pjrpid_element));
+ pj_strdup(pool, &dst->id, &src->id);
+ pj_strdup(pool, &dst->note, &src->note);
+}
+
+
+/* Update RPID namespaces. */
+static void update_namespaces(pjpidf_pres *pres,
+ pj_pool_t *pool)
+{
+ /* Check if namespace is already present. */
+ if (pj_xml_find_attr(pres, &DM_NAME, NULL) != NULL)
+ return;
+
+ pj_xml_add_attr(pres, pj_xml_attr_new(pool, &DM_NAME, &DM_VAL));
+ pj_xml_add_attr(pres, pj_xml_attr_new(pool, &RPID_NAME, &RPID_VAL));
+}
+
+
+/* Comparison function to find node name substring */
+static pj_bool_t substring_match(const pj_xml_node *node,
+ const char *part_name,
+ int part_len)
+{
+ pj_str_t end_name;
+
+ if (part_len < 1)
+ part_len = pj_ansi_strlen(part_name);
+
+ if (node->name.slen < part_len)
+ return PJ_FALSE;
+
+ end_name.ptr = node->name.ptr + (node->name.slen - part_len);
+ end_name.slen = part_len;
+
+ return pj_strnicmp2(&end_name, part_name, part_len)==0;
+}
+
+/* Util to find child node with the specified substring */
+static pj_xml_node *find_node(const pj_xml_node *parent,
+ const char *part_name)
+{
+ const pj_xml_node *node = parent->node_head.next,
+ *head = (pj_xml_node*) &parent->node_head;
+ int part_len = pj_ansi_strlen(part_name);
+
+ while (node != head) {
+ if (substring_match(node, part_name, part_len))
+ return (pj_xml_node*) node;
+
+ node = node->next;
+ }
+
+ return NULL;
+}
+
+/*
+ * Add RPID element into existing PIDF document.
+ */
+PJ_DEF(pj_status_t) pjrpid_add_element(pjpidf_pres *pres,
+ pj_pool_t *pool,
+ unsigned options,
+ const pjrpid_element *elem)
+{
+ pj_xml_node *nd_person, *nd_activities, *nd_activity, *nd_note;
+ pj_xml_attr *attr;
+
+ PJ_ASSERT_RETURN(pres && pool && options==0 && elem, PJ_EINVAL);
+
+ PJ_UNUSED_ARG(options);
+
+ /* Check if we need to add RPID information into the PIDF document. */
+ if (elem->id.slen==0 &&
+ elem->activity==PJRPID_ACTIVITY_UNKNOWN &&
+ elem->note.slen==0)
+ {
+ /* No RPID information to be added. */
+ return PJ_SUCCESS;
+ }
+
+ /* Add <note> to <tuple> */
+ if (elem->note.slen != 0) {
+ pj_xml_node *nd_tuple;
+
+ nd_tuple = find_node(pres, "tuple");
+
+ if (nd_tuple) {
+ nd_note = pj_xml_node_new(pool, &NOTE);
+ pj_strdup(pool, &nd_note->content, &elem->note);
+ pj_xml_add_node(nd_tuple, nd_note);
+ nd_note = NULL;
+ }
+ }
+
+ /* Update namespace */
+ update_namespaces(pres, pool);
+
+ /* Add <person> */
+ nd_person = pj_xml_node_new(pool, &DM_PERSON);
+ if (elem->id.slen != 0) {
+ attr = pj_xml_attr_new(pool, &ID, &elem->id);
+ } else {
+ pj_str_t person_id;
+ /* xs:ID must start with letter */
+ //pj_create_unique_string(pool, &person_id);
+ person_id.ptr = (char*)pj_pool_alloc(pool, PJ_GUID_STRING_LENGTH+2);
+ person_id.ptr += 2;
+ pj_generate_unique_string(&person_id);
+ person_id.ptr -= 2;
+ person_id.ptr[0] = 'p';
+ person_id.ptr[1] = 'j';
+ person_id.slen += 2;
+
+ attr = pj_xml_attr_new(pool, &ID, &person_id);
+ }
+ pj_xml_add_attr(nd_person, attr);
+ pj_xml_add_node(pres, nd_person);
+
+ /* Add <activities> */
+ nd_activities = pj_xml_node_new(pool, &RPID_ACTIVITIES);
+ pj_xml_add_node(nd_person, nd_activities);
+
+ /* Add the activity */
+ switch (elem->activity) {
+ case PJRPID_ACTIVITY_AWAY:
+ nd_activity = pj_xml_node_new(pool, &RPID_AWAY);
+ break;
+ case PJRPID_ACTIVITY_BUSY:
+ nd_activity = pj_xml_node_new(pool, &RPID_BUSY);
+ break;
+ case PJRPID_ACTIVITY_UNKNOWN:
+ default:
+ nd_activity = pj_xml_node_new(pool, &RPID_UNKNOWN);
+ break;
+ }
+ pj_xml_add_node(nd_activities, nd_activity);
+
+ /* Add custom text if required. */
+ if (elem->note.slen != 0) {
+ nd_note = pj_xml_node_new(pool, &DM_NOTE);
+ pj_strdup(pool, &nd_note->content, &elem->note);
+ pj_xml_add_node(nd_person, nd_note);
+ }
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+
+/* Get <note> element from PIDF <tuple> element */
+static pj_status_t get_tuple_note(const pjpidf_pres *pres,
+ pj_pool_t *pool,
+ pjrpid_element *elem)
+{
+ const pj_xml_node *nd_tuple, *nd_note;
+
+ nd_tuple = find_node(pres, "tuple");
+ if (!nd_tuple)
+ return PJSIP_SIMPLE_EBADRPID;
+
+ nd_note = find_node(pres, "note");
+ if (nd_note) {
+ pj_strdup(pool, &elem->note, &nd_note->content);
+ return PJ_SUCCESS;
+ }
+
+ return PJSIP_SIMPLE_EBADRPID;
+}
+
+/*
+ * Get RPID element from PIDF document, if any.
+ */
+PJ_DEF(pj_status_t) pjrpid_get_element(const pjpidf_pres *pres,
+ pj_pool_t *pool,
+ pjrpid_element *elem)
+{
+ const pj_xml_node *nd_person, *nd_activities, *nd_note = NULL;
+ const pj_xml_attr *attr;
+
+ /* Reset */
+ pj_bzero(elem, sizeof(*elem));
+ elem->activity = PJRPID_ACTIVITY_UNKNOWN;
+
+ /* Find <person> */
+ nd_person = find_node(pres, "person");
+ if (!nd_person) {
+ /* <person> not found, try to get <note> from <tuple> */
+ return get_tuple_note(pres, pool, elem);
+ }
+
+ /* Get element id attribute */
+ attr = pj_xml_find_attr((pj_xml_node*)nd_person, &ID, NULL);
+ if (attr)
+ pj_strdup(pool, &elem->id, &attr->value);
+
+ /* Get <activities> */
+ nd_activities = find_node(nd_person, "activities");
+ if (nd_activities) {
+ const pj_xml_node *nd_activity;
+
+ /* Try to get <note> from <activities> */
+ nd_note = find_node(nd_activities, "note");
+
+ /* Get the activity */
+ nd_activity = nd_activities->node_head.next;
+ if (nd_activity == nd_note)
+ nd_activity = nd_activity->next;
+
+ if (nd_activity != (pj_xml_node*) &nd_activities->node_head) {
+ if (substring_match(nd_activity, "busy", -1))
+ elem->activity = PJRPID_ACTIVITY_BUSY;
+ else if (substring_match(nd_activity, "away", -1))
+ elem->activity = PJRPID_ACTIVITY_AWAY;
+ else
+ elem->activity = PJRPID_ACTIVITY_UNKNOWN;
+
+ }
+ }
+
+ /* If <note> is not found, get <note> from <person> */
+ if (nd_note == NULL)
+ nd_note = find_node(nd_person, "note");
+
+ if (nd_note) {
+ pj_strdup(pool, &elem->note, &nd_note->content);
+ } else {
+ get_tuple_note(pres, pool, elem);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjsip/src/pjsip-simple/xpidf.c b/pjsip/src/pjsip-simple/xpidf.c
new file mode 100644
index 0000000..22801d7
--- /dev/null
+++ b/pjsip/src/pjsip-simple/xpidf.c
@@ -0,0 +1,301 @@
+/* $Id: xpidf.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/xpidf.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+static pj_str_t STR_PRESENCE = { "presence", 8 };
+static pj_str_t STR_STATUS = { "status", 6 };
+static pj_str_t STR_OPEN = { "open", 4 };
+static pj_str_t STR_CLOSED = { "closed", 6 };
+static pj_str_t STR_URI = { "uri", 3 };
+static pj_str_t STR_ATOM = { "atom", 4 };
+static pj_str_t STR_ATOMID = { "atomid", 6 };
+static pj_str_t STR_ID = { "id", 2 };
+static pj_str_t STR_ADDRESS = { "address", 7 };
+static pj_str_t STR_SUBSCRIBE_PARAM = { ";method=SUBSCRIBE", 17 };
+static pj_str_t STR_PRESENTITY = { "presentity", 10 };
+static pj_str_t STR_EMPTY_STRING = { NULL, 0 };
+
+static pj_xml_node* xml_create_node(pj_pool_t *pool,
+ pj_str_t *name, const pj_str_t *value)
+{
+ pj_xml_node *node;
+
+ node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+ pj_list_init(&node->attr_head);
+ pj_list_init(&node->node_head);
+ node->name = *name;
+ if (value) pj_strdup(pool, &node->content, value);
+ else node->content.ptr=NULL, node->content.slen=0;
+
+ return node;
+}
+
+static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name,
+ const pj_str_t *value)
+{
+ pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
+ attr->name = *name;
+ pj_strdup(pool, &attr->value, value);
+ return attr;
+}
+
+
+PJ_DEF(pjxpidf_pres*) pjxpidf_create(pj_pool_t *pool, const pj_str_t *uri_cstr)
+{
+ pjxpidf_pres *pres;
+ pj_xml_node *presentity;
+ pj_xml_node *atom;
+ pj_xml_node *addr;
+ pj_xml_node *status;
+ pj_xml_attr *attr;
+ pj_str_t uri;
+ pj_str_t tmp;
+
+ /* <presence> */
+ pres = xml_create_node(pool, &STR_PRESENCE, NULL);
+
+ /* <presentity> */
+ presentity = xml_create_node(pool, &STR_PRESENTITY, NULL);
+ pj_xml_add_node(pres, presentity);
+
+ /* uri attribute */
+ uri.ptr = (char*) pj_pool_alloc(pool, uri_cstr->slen +
+ STR_SUBSCRIBE_PARAM.slen);
+ pj_strcpy( &uri, uri_cstr);
+ pj_strcat( &uri, &STR_SUBSCRIBE_PARAM);
+ attr = xml_create_attr(pool, &STR_URI, &uri);
+ pj_xml_add_attr(presentity, attr);
+
+ /* <atom> */
+ atom = xml_create_node(pool, &STR_ATOM, NULL);
+ pj_xml_add_node(pres, atom);
+
+ /* atom id */
+ pj_create_unique_string(pool, &tmp);
+ attr = xml_create_attr(pool, &STR_ATOMID, &tmp);
+ pj_xml_add_attr(atom, attr);
+
+ /* address */
+ addr = xml_create_node(pool, &STR_ADDRESS, NULL);
+ pj_xml_add_node(atom, addr);
+
+ /* address'es uri */
+ attr = xml_create_attr(pool, &STR_URI, uri_cstr);
+ pj_xml_add_attr(addr, attr);
+
+ /* status */
+ status = xml_create_node(pool, &STR_STATUS, NULL);
+ pj_xml_add_node(addr, status);
+
+ /* status attr */
+ attr = xml_create_attr(pool, &STR_STATUS, &STR_OPEN);
+ pj_xml_add_attr(status, attr);
+
+ return pres;
+}
+
+
+
+PJ_DEF(pjxpidf_pres*) pjxpidf_parse(pj_pool_t *pool, char *text, pj_size_t len)
+{
+ pjxpidf_pres *pres;
+ pj_xml_node *node;
+
+ pres = pj_xml_parse(pool, text, len);
+ if (!pres)
+ return NULL;
+
+ /* Validate <presence> */
+ if (pj_stricmp(&pres->name, &STR_PRESENCE) != 0)
+ return NULL;
+
+ /* Validate <presentity> */
+ node = pj_xml_find_node(pres, &STR_PRESENTITY);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &STR_URI, NULL) == NULL)
+ return NULL;
+
+ /* Validate <atom> */
+ node = pj_xml_find_node(pres, &STR_ATOM);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &STR_ATOMID, NULL) == NULL &&
+ pj_xml_find_attr(node, &STR_ID, NULL) == NULL)
+ {
+ return NULL;
+ }
+
+ /* Address */
+ node = pj_xml_find_node(node, &STR_ADDRESS);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &STR_URI, NULL) == NULL)
+ return NULL;
+
+
+ /* Status */
+ node = pj_xml_find_node(node, &STR_STATUS);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &STR_STATUS, NULL) == NULL)
+ return NULL;
+
+ return pres;
+}
+
+
+PJ_DEF(int) pjxpidf_print( pjxpidf_pres *pres, char *text, pj_size_t len)
+{
+ return pj_xml_print(pres, text, len, PJ_TRUE);
+}
+
+
+PJ_DEF(pj_str_t*) pjxpidf_get_uri(pjxpidf_pres *pres)
+{
+ pj_xml_node *presentity;
+ pj_xml_attr *attr;
+
+ presentity = pj_xml_find_node(pres, &STR_PRESENTITY);
+ if (!presentity)
+ return &STR_EMPTY_STRING;
+
+ attr = pj_xml_find_attr(presentity, &STR_URI, NULL);
+ if (!attr)
+ return &STR_EMPTY_STRING;
+
+ return &attr->value;
+}
+
+
+PJ_DEF(pj_status_t) pjxpidf_set_uri(pj_pool_t *pool, pjxpidf_pres *pres,
+ const pj_str_t *uri)
+{
+ pj_xml_node *presentity;
+ pj_xml_node *atom;
+ pj_xml_node *addr;
+ pj_xml_attr *attr;
+ pj_str_t dup_uri;
+
+ presentity = pj_xml_find_node(pres, &STR_PRESENTITY);
+ if (!presentity) {
+ pj_assert(0);
+ return -1;
+ }
+ atom = pj_xml_find_node(pres, &STR_ATOM);
+ if (!atom) {
+ pj_assert(0);
+ return -1;
+ }
+ addr = pj_xml_find_node(atom, &STR_ADDRESS);
+ if (!addr) {
+ pj_assert(0);
+ return -1;
+ }
+
+ /* Set uri in presentity */
+ attr = pj_xml_find_attr(presentity, &STR_URI, NULL);
+ if (!attr) {
+ pj_assert(0);
+ return -1;
+ }
+ pj_strdup(pool, &dup_uri, uri);
+ attr->value = dup_uri;
+
+ /* Set uri in address. */
+ attr = pj_xml_find_attr(addr, &STR_URI, NULL);
+ if (!attr) {
+ pj_assert(0);
+ return -1;
+ }
+ attr->value = dup_uri;
+
+ return 0;
+}
+
+
+PJ_DEF(pj_bool_t) pjxpidf_get_status(pjxpidf_pres *pres)
+{
+ pj_xml_node *atom;
+ pj_xml_node *addr;
+ pj_xml_node *status;
+ pj_xml_attr *attr;
+
+ atom = pj_xml_find_node(pres, &STR_ATOM);
+ if (!atom) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+ addr = pj_xml_find_node(atom, &STR_ADDRESS);
+ if (!addr) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+ status = pj_xml_find_node(addr, &STR_STATUS);
+ if (!status) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+ attr = pj_xml_find_attr(status, &STR_STATUS, NULL);
+ if (!attr) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+
+ return pj_stricmp(&attr->value, &STR_OPEN)==0 ? PJ_TRUE : PJ_FALSE;
+}
+
+
+PJ_DEF(pj_status_t) pjxpidf_set_status(pjxpidf_pres *pres, pj_bool_t online_status)
+{
+ pj_xml_node *atom;
+ pj_xml_node *addr;
+ pj_xml_node *status;
+ pj_xml_attr *attr;
+
+ atom = pj_xml_find_node(pres, &STR_ATOM);
+ if (!atom) {
+ pj_assert(0);
+ return -1;
+ }
+ addr = pj_xml_find_node(atom, &STR_ADDRESS);
+ if (!addr) {
+ pj_assert(0);
+ return -1;
+ }
+ status = pj_xml_find_node(addr, &STR_STATUS);
+ if (!status) {
+ pj_assert(0);
+ return -1;
+ }
+ attr = pj_xml_find_attr(status, &STR_STATUS, NULL);
+ if (!attr) {
+ pj_assert(0);
+ return -1;
+ }
+
+ attr->value = ( online_status ? STR_OPEN : STR_CLOSED );
+ return 0;
+}
+