summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip-simple
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2005-11-09 16:36:21 +0000
committerBenny Prijono <bennylp@teluu.com>2005-11-09 16:36:21 +0000
commitfebc030a59404ecbb3bfde1ceff6531588149ca9 (patch)
treeaa5d796b4e81fd7ea3402c64f447546808bd6700 /pjsip/src/pjsip-simple
parent9f379fcbe89024353a25b3737d79ec7a82b6408f (diff)
Organizing pjsip directory structure
git-svn-id: http://svn.pjsip.org/repos/pjproject/main@39 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src/pjsip-simple')
-rw-r--r--pjsip/src/pjsip-simple/event_notify.c1629
-rw-r--r--pjsip/src/pjsip-simple/event_notify_msg.c307
-rw-r--r--pjsip/src/pjsip-simple/messaging.c337
-rw-r--r--pjsip/src/pjsip-simple/pidf.c335
-rw-r--r--pjsip/src/pjsip-simple/presence.c384
-rw-r--r--pjsip/src/pjsip-simple/xpidf.c279
6 files changed, 3271 insertions, 0 deletions
diff --git a/pjsip/src/pjsip-simple/event_notify.c b/pjsip/src/pjsip-simple/event_notify.c
new file mode 100644
index 00000000..24aa68a0
--- /dev/null
+++ b/pjsip/src/pjsip-simple/event_notify.c
@@ -0,0 +1,1629 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/event_notify.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_misc.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_event.h>
+#include <pj/pool.h>
+#include <pj/timer.h>
+#include <pj/string.h>
+#include <pj/hash.h>
+#include <pj/os.h>
+#include <pj/except.h>
+#include <pj/log.h>
+#include <pj/guid.h>
+
+#define THIS_FILE "event_sub"
+
+/* String names for state.
+ * The names here should be compliant with sub_state names in RFC3265.
+ */
+static const pj_str_t state[] = {
+ { "null", 4 },
+ { "active", 6 },
+ { "pending", 7 },
+ { "terminated", 10 },
+ { "unknown", 7 }
+};
+
+/* Timer IDs */
+#define TIMER_ID_REFRESH 1
+#define TIMER_ID_UAS_EXPIRY 2
+
+/* Static configuration. */
+#define SECONDS_BEFORE_EXPIRY 10
+#define MGR_POOL_SIZE 512
+#define MGR_POOL_INC 0
+#define SUB_POOL_SIZE 2048
+#define SUB_POOL_INC 0
+#define HASH_TABLE_SIZE 32
+
+/* Static vars. */
+static int mod_id;
+static const pjsip_method SUBSCRIBE = { PJSIP_OTHER_METHOD, {"SUBSCRIBE", 9}};
+static const pjsip_method NOTIFY = { PJSIP_OTHER_METHOD, { "NOTIFY", 6}};
+
+typedef struct package
+{
+ PJ_DECL_LIST_MEMBER(struct package)
+ pj_str_t event;
+ int accept_cnt;
+ pj_str_t *accept;
+ pjsip_event_sub_pkg_cb cb;
+} package;
+
+/* Event subscription manager singleton instance. */
+static struct pjsip_event_sub_mgr
+{
+ pj_pool_t *pool;
+ pj_hash_table_t *ht;
+ pjsip_endpoint *endpt;
+ pj_mutex_t *mutex;
+ pjsip_allow_events_hdr *allow_events;
+ package pkg_list;
+} mgr;
+
+/* Fordward declarations for static functions. */
+static pj_status_t mod_init(pjsip_endpoint *, pjsip_module *, pj_uint32_t);
+static pj_status_t mod_deinit(pjsip_module*);
+static void tsx_handler(pjsip_module*, pjsip_event*);
+static pjsip_event_sub *find_sub(pjsip_rx_data *);
+static void on_subscribe_request(pjsip_transaction*, pjsip_rx_data*);
+static void on_subscribe_response(void *, pjsip_event*);
+static void on_notify_request(pjsip_transaction *, pjsip_rx_data*);
+static void on_notify_response(void *, pjsip_event *);
+static void refresh_timer_cb(pj_timer_heap_t*, pj_timer_entry*);
+static void uas_expire_timer_cb(pj_timer_heap_t*, pj_timer_entry*);
+static pj_status_t send_sub_refresh( pjsip_event_sub *sub );
+
+/* Module descriptor. */
+static pjsip_module event_sub_module =
+{
+ {"EventSub", 8}, /* Name. */
+ 0, /* Flag */
+ 128, /* Priority */
+ &mgr, /* User data. */
+ 2, /* Number of methods supported . */
+ { &SUBSCRIBE, &NOTIFY }, /* Array of methods */
+ &mod_init, /* init_module() */
+ NULL, /* start_module() */
+ &mod_deinit, /* deinit_module() */
+ &tsx_handler, /* tsx_handler() */
+};
+
+/*
+ * Module initialization.
+ * This will be called by endpoint when it initializes all modules.
+ */
+static pj_status_t mod_init( pjsip_endpoint *endpt,
+ struct pjsip_module *mod, pj_uint32_t id )
+{
+ pj_pool_t *pool;
+
+ pool = pjsip_endpt_create_pool(endpt, "esubmgr", MGR_POOL_SIZE, MGR_POOL_INC);
+ if (!pool)
+ return -1;
+
+ /* Manager initialization: create hash table and mutex. */
+ mgr.pool = pool;
+ mgr.endpt = endpt;
+ mgr.ht = pj_hash_create(pool, HASH_TABLE_SIZE);
+ if (!mgr.ht)
+ return -1;
+
+ mgr.mutex = pj_mutex_create(pool, "esubmgr", PJ_MUTEX_SIMPLE);
+ if (!mgr.mutex)
+ return -1;
+
+ /* Attach manager to module. */
+ mod->mod_data = &mgr;
+
+ /* Init package list. */
+ pj_list_init(&mgr.pkg_list);
+
+ /* Init Allow-Events header. */
+ mgr.allow_events = pjsip_allow_events_hdr_create(mgr.pool);
+
+ /* Save the module ID. */
+ mod_id = id;
+
+ pjsip_event_notify_init_parser();
+ return 0;
+}
+
+/*
+ * Module deinitialization.
+ * Called by endpoint.
+ */
+static pj_status_t mod_deinit( struct pjsip_module *mod )
+{
+ pj_mutex_lock(mgr.mutex);
+ pj_mutex_destroy(mgr.mutex);
+ pjsip_endpt_destroy_pool(mgr.endpt, mgr.pool);
+ return 0;
+}
+
+/*
+ * This public function is called by application to register callback.
+ * In exchange, the instance of the module is returned.
+ */
+PJ_DEF(pjsip_module*) pjsip_event_sub_get_module(void)
+{
+ return &event_sub_module;
+}
+
+/*
+ * Register event package.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_register_pkg( const pj_str_t *event,
+ int accept_cnt,
+ const pj_str_t accept[],
+ const pjsip_event_sub_pkg_cb *cb )
+{
+ package *pkg;
+ int i;
+
+ pj_mutex_lock(mgr.mutex);
+
+ /* Create and register new package. */
+ pkg = pj_pool_alloc(mgr.pool, sizeof(*pkg));
+ pj_strdup(mgr.pool, &pkg->event, event);
+ pj_list_insert_before(&mgr.pkg_list, pkg);
+
+ /* Save Accept specification. */
+ pkg->accept_cnt = accept_cnt;
+ pkg->accept = pj_pool_alloc(mgr.pool, accept_cnt*sizeof(pj_str_t));
+ for (i=0; i<accept_cnt; ++i) {
+ pj_strdup(mgr.pool, &pkg->accept[i], &accept[i]);
+ }
+
+ /* Copy callback. */
+ pj_memcpy(&pkg->cb, cb, sizeof(*cb));
+
+ /* Update Allow-Events header. */
+ pj_assert(mgr.allow_events->event_cnt < PJSIP_MAX_ALLOW_EVENTS);
+ mgr.allow_events->events[mgr.allow_events->event_cnt++] = pkg->event;
+
+ pj_mutex_unlock(mgr.mutex);
+ return 0;
+}
+
+/*
+ * Create subscription key (for hash table).
+ */
+static void create_subscriber_key( pj_str_t *key, pj_pool_t *pool,
+ pjsip_role_e role,
+ const pj_str_t *call_id, const pj_str_t *from_tag)
+{
+ char *p;
+
+ p = key->ptr = pj_pool_alloc(pool, call_id->slen + from_tag->slen + 3);
+ *p++ = (role == PJSIP_ROLE_UAS ? 'S' : 'C');
+ *p++ = '$';
+ pj_memcpy(p, call_id->ptr, call_id->slen);
+ p += call_id->slen;
+ *p++ = '$';
+ pj_memcpy(p, from_tag->ptr, from_tag->slen);
+ p += from_tag->slen;
+
+ key->slen = p - key->ptr;
+}
+
+
+/*
+ * Create UAC subscription.
+ */
+PJ_DEF(pjsip_event_sub*) pjsip_event_sub_create( pjsip_endpoint *endpt,
+ const pj_str_t *from,
+ const pj_str_t *to,
+ const pj_str_t *event,
+ int expires,
+ int accept_cnt,
+ const pj_str_t accept[],
+ void *user_data,
+ const pjsip_event_sub_cb *cb)
+{
+ pjsip_tx_data *tdata;
+ pj_pool_t *pool;
+ const pjsip_hdr *hdr;
+ pjsip_event_sub *sub;
+ PJ_USE_EXCEPTION;
+
+ PJ_LOG(5,(THIS_FILE, "Creating event subscription %.*s to %.*s",
+ event->slen, event->ptr, to->slen, to->ptr));
+
+ /* Create pool for the event subscription. */
+ pool = pjsip_endpt_create_pool(endpt, "esub", SUB_POOL_SIZE, SUB_POOL_INC);
+ if (!pool) {
+ return NULL;
+ }
+
+ /* Init subscription. */
+ sub = pj_pool_calloc(pool, 1, sizeof(*sub));
+ sub->pool = pool;
+ sub->endpt = endpt;
+ sub->role = PJSIP_ROLE_UAC;
+ sub->state = PJSIP_EVENT_SUB_STATE_PENDING;
+ sub->state_str = state[sub->state];
+ sub->user_data = user_data;
+ sub->timer.id = 0;
+ sub->default_interval = expires;
+ pj_memcpy(&sub->cb, cb, sizeof(*cb));
+ pj_list_init(&sub->auth_sess);
+ pj_list_init(&sub->route_set);
+ sub->mutex = pj_mutex_create(pool, "esub", PJ_MUTEX_RECURSE);
+ if (!sub->mutex) {
+ pjsip_endpt_destroy_pool(endpt, pool);
+ return NULL;
+ }
+
+ /* The easiest way to parse the parameters is to create a dummy request! */
+ tdata = pjsip_endpt_create_request( endpt, &SUBSCRIBE, to, from, to, from,
+ NULL, -1, NULL);
+ if (!tdata) {
+ pj_mutex_destroy(sub->mutex);
+ pjsip_endpt_destroy_pool(endpt, pool);
+ return NULL;
+ }
+
+ /*
+ * Duplicate headers in the request to our structure.
+ */
+ PJ_TRY {
+ int i;
+
+ /* From */
+ hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_FROM, NULL);
+ pj_assert(hdr != NULL);
+ sub->from = pjsip_hdr_clone(pool, hdr);
+
+ /* To */
+ hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_TO, NULL);
+ pj_assert(hdr != NULL);
+ sub->to = pjsip_hdr_clone(pool, hdr);
+
+ /* Contact. */
+ sub->contact = pjsip_contact_hdr_create(pool);
+ sub->contact->uri = sub->from->uri;
+
+ /* Call-ID */
+ hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CALL_ID, NULL);
+ pj_assert(hdr != NULL);
+ sub->call_id = pjsip_hdr_clone(pool, hdr);
+
+ /* CSeq */
+ sub->cseq = pj_rand() % 0xFFFF;
+
+ /* Event. */
+ sub->event = pjsip_event_hdr_create(sub->pool);
+ pj_strdup(pool, &sub->event->event_type, event);
+
+ /* Expires. */
+ sub->uac_expires = pjsip_expires_hdr_create(pool);
+ sub->uac_expires->ivalue = expires;
+
+ /* Accept. */
+ sub->local_accept = pjsip_accept_hdr_create(pool);
+ for (i=0; i<accept_cnt && i < PJSIP_MAX_ACCEPT_COUNT; ++i) {
+ sub->local_accept->count++;
+ pj_strdup(sub->pool, &sub->local_accept->values[i], &accept[i]);
+ }
+
+ /* Register to hash table. */
+ create_subscriber_key( &sub->key, pool, PJSIP_ROLE_UAC,
+ &sub->call_id->id, &sub->from->tag);
+ pj_mutex_lock( mgr.mutex );
+ pj_hash_set( pool, mgr.ht, sub->key.ptr, sub->key.slen, sub);
+ pj_mutex_unlock( mgr.mutex );
+
+ }
+ PJ_DEFAULT {
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): caught exception %d during init",
+ sub, state[sub->state].ptr, PJ_GET_EXCEPTION()));
+
+ pjsip_tx_data_dec_ref(tdata);
+ pj_mutex_destroy(sub->mutex);
+ pjsip_endpt_destroy_pool(endpt, sub->pool);
+ return NULL;
+ }
+ PJ_END;
+
+ /* All set, delete temporary transmit data as we don't need it. */
+ pjsip_tx_data_dec_ref(tdata);
+
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): client created, target=%.*s, event=%.*s",
+ sub, state[sub->state].ptr,
+ to->slen, to->ptr, event->slen, event->ptr));
+
+ return sub;
+}
+
+/*
+ * Set credentials.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_set_credentials( pjsip_event_sub *sub,
+ int count,
+ const pjsip_cred_info cred[])
+{
+ pj_mutex_lock(sub->mutex);
+ if (count > 0) {
+ sub->cred_info = pj_pool_alloc(sub->pool, count*sizeof(pjsip_cred_info));
+ pj_memcpy( sub->cred_info, cred, count*sizeof(pjsip_cred_info));
+ }
+ sub->cred_cnt = count;
+ pj_mutex_unlock(sub->mutex);
+ return 0;
+}
+
+/*
+ * Set route-set.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_set_route_set( pjsip_event_sub *sub,
+ const pjsip_route_hdr *route_set )
+{
+ const pjsip_route_hdr *hdr;
+
+ pj_mutex_lock(sub->mutex);
+
+ /* Clear existing route set. */
+ pj_list_init(&sub->route_set);
+
+ /* Duplicate route headers. */
+ hdr = route_set->next;
+ while (hdr != route_set) {
+ pjsip_route_hdr *new_hdr = pjsip_hdr_clone(sub->pool, hdr);
+ pj_list_insert_before(&sub->route_set, new_hdr);
+ hdr = hdr->next;
+ }
+
+ pj_mutex_unlock(sub->mutex);
+
+ return 0;
+}
+
+/*
+ * Send subscribe request.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_subscribe( pjsip_event_sub *sub )
+{
+ pj_status_t status;
+
+ pj_mutex_lock(sub->mutex);
+ status = send_sub_refresh(sub);
+ pj_mutex_unlock(sub->mutex);
+
+ return status;
+}
+
+/*
+ * Destroy subscription.
+ * If there are pending transactions, then this will just set the flag.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_destroy(pjsip_event_sub *sub)
+{
+ pj_assert(sub != NULL);
+ if (sub == NULL)
+ return -1;
+
+ /* Application must terminate the subscription first. */
+ pj_assert(sub->state == PJSIP_EVENT_SUB_STATE_NULL ||
+ sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED);
+
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): about to be destroyed",
+ sub, state[sub->state].ptr));
+
+ pj_mutex_lock(mgr.mutex);
+ pj_mutex_lock(sub->mutex);
+
+ /* Set delete flag. */
+ sub->delete_flag = 1;
+
+ /* Unregister timer, if any. */
+ if (sub->timer.id != 0) {
+ pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+ sub->timer.id = 0;
+ }
+
+ if (sub->pending_tsx > 0) {
+ pj_mutex_unlock(sub->mutex);
+ pj_mutex_unlock(mgr.mutex);
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): has %d pending, will destroy later",
+ sub, state[sub->state].ptr,
+ sub->pending_tsx));
+ return 1;
+ }
+
+ /* Unregister from hash table. */
+ pj_hash_set(sub->pool, mgr.ht, sub->key.ptr, sub->key.slen, NULL);
+
+ /* Destroy. */
+ pj_mutex_destroy(sub->mutex);
+ pjsip_endpt_destroy_pool(sub->endpt, sub->pool);
+
+ pj_mutex_unlock(mgr.mutex);
+
+ PJ_LOG(4,(THIS_FILE, "event_sub%p: destroyed", sub));
+ return 0;
+}
+
+/* Change state. */
+static void sub_set_state( pjsip_event_sub *sub, int new_state)
+{
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): changed state to %s",
+ sub, state[sub->state].ptr, state[new_state].ptr));
+ sub->state = new_state;
+ sub->state_str = state[new_state];
+}
+
+/*
+ * Refresh subscription.
+ */
+static pj_status_t send_sub_refresh( pjsip_event_sub *sub )
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+ const pjsip_route_hdr *route;
+
+ pj_assert(sub->role == PJSIP_ROLE_UAC);
+ pj_assert(sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED);
+ if (sub->role != PJSIP_ROLE_UAC ||
+ sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED)
+ {
+ return -1;
+ }
+
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): refreshing subscription",
+ sub, state[sub->state].ptr));
+
+ /* Create request. */
+ tdata = pjsip_endpt_create_request_from_hdr( sub->endpt,
+ &SUBSCRIBE,
+ sub->to->uri,
+ sub->from, sub->to,
+ sub->contact, sub->call_id,
+ sub->cseq++,
+ NULL);
+
+ if (!tdata) {
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): refresh: unable to create tx data!",
+ sub, state[sub->state].ptr));
+ return -1;
+ }
+
+ pjsip_msg_add_hdr( tdata->msg,
+ pjsip_hdr_shallow_clone(tdata->pool, sub->event));
+ pjsip_msg_add_hdr( tdata->msg,
+ pjsip_hdr_shallow_clone(tdata->pool, sub->uac_expires));
+ pjsip_msg_add_hdr( tdata->msg,
+ pjsip_hdr_shallow_clone(tdata->pool, sub->local_accept));
+ pjsip_msg_add_hdr( tdata->msg,
+ pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events));
+
+ /* Authentication */
+ pjsip_auth_init_req( sub->pool, tdata, &sub->auth_sess,
+ sub->cred_cnt, sub->cred_info);
+
+ /* Route set. */
+ route = sub->route_set.next;
+ while (route != &sub->route_set) {
+ pj_list_insert_before( &tdata->msg->hdr,
+ pjsip_hdr_shallow_clone(tdata->pool, route));
+ route = route->next;
+ }
+
+ /* Send */
+ status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub,
+ &on_subscribe_response);
+ if (status == 0) {
+ sub->pending_tsx++;
+ } else {
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): FAILED to refresh subscription!",
+ sub, state[sub->state].ptr));
+ }
+
+ return status;
+}
+
+/*
+ * Stop subscription.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_unsubscribe( pjsip_event_sub *sub )
+{
+ pjsip_tx_data *tdata;
+ const pjsip_route_hdr *route;
+ pj_status_t status;
+
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): unsubscribing...",
+ sub, state[sub->state].ptr));
+
+ /* Lock subscription. */
+ pj_mutex_lock(sub->mutex);
+
+ pj_assert(sub->role == PJSIP_ROLE_UAC);
+
+ /* Kill refresh timer, if any. */
+ if (sub->timer.id != 0) {
+ sub->timer.id = 0;
+ pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+ }
+
+ /* Create request. */
+ tdata = pjsip_endpt_create_request_from_hdr( sub->endpt,
+ &SUBSCRIBE,
+ sub->to->uri,
+ sub->from, sub->to,
+ sub->contact, sub->call_id,
+ sub->cseq++,
+ NULL);
+
+ if (!tdata) {
+ pj_mutex_unlock(sub->mutex);
+ return -1;
+ }
+
+ /* Add headers to request. */
+ pjsip_msg_add_hdr( tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, sub->event));
+ sub->uac_expires->ivalue = 0;
+ pjsip_msg_add_hdr( tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, sub->uac_expires));
+
+ /* Add authentication. */
+ pjsip_auth_init_req( sub->pool, tdata, &sub->auth_sess,
+ sub->cred_cnt, sub->cred_info);
+
+
+ /* Route set. */
+ route = sub->route_set.next;
+ while (route != &sub->route_set) {
+ pj_list_insert_before( &tdata->msg->hdr,
+ pjsip_hdr_shallow_clone(tdata->pool, route));
+ route = route->next;
+ }
+
+ /* Prevent timer from refreshing itself. */
+ sub->default_interval = 0;
+
+ /* Set state. */
+ sub_set_state( sub, PJSIP_EVENT_SUB_STATE_TERMINATED );
+
+ /* Send the request. */
+ status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub,
+ &on_subscribe_response);
+ if (status == 0) {
+ sub->pending_tsx++;
+ }
+
+ pj_mutex_unlock(sub->mutex);
+
+ if (status != 0) {
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): FAILED to unsubscribe!",
+ sub, state[sub->state].ptr));
+ }
+
+ return status;
+}
+
+/*
+ * Send notify.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_notify(pjsip_event_sub *sub,
+ pjsip_event_sub_state new_state,
+ const pj_str_t *reason,
+ pjsip_msg_body *body)
+{
+ pjsip_tx_data *tdata;
+ pjsip_sub_state_hdr *ss_hdr;
+ const pjsip_route_hdr *route;
+ pj_time_val now;
+ pj_status_t status;
+ pjsip_event_sub_state old_state = sub->state;
+
+ pj_gettimeofday(&now);
+
+ pj_assert(sub->role == PJSIP_ROLE_UAS);
+ if (sub->role != PJSIP_ROLE_UAS)
+ return -1;
+
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): sending NOTIFY",
+ sub, state[new_state].ptr));
+
+ /* Lock subscription. */
+ pj_mutex_lock(sub->mutex);
+
+ /* Can not send NOTIFY if current state is NULL. We can accept TERMINATED. */
+ if (sub->state==PJSIP_EVENT_SUB_STATE_NULL) {
+ pj_assert(0);
+ pj_mutex_unlock(sub->mutex);
+ return -1;
+ }
+
+ /* Update state no matter what. */
+ sub_set_state(sub, new_state);
+
+ /* Create transmit data. */
+ tdata = pjsip_endpt_create_request_from_hdr( sub->endpt,
+ &NOTIFY,
+ sub->to->uri,
+ sub->from, sub->to,
+ sub->contact, sub->call_id,
+ sub->cseq++,
+ NULL);
+ if (!tdata) {
+ pj_mutex_unlock(sub->mutex);
+ return -1;
+ }
+
+ /* Add Event header. */
+ pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, sub->event));
+
+ /* Add Subscription-State header. */
+ ss_hdr = pjsip_sub_state_hdr_create(tdata->pool);
+ ss_hdr->sub_state = state[new_state];
+ ss_hdr->expires_param = sub->expiry_time.sec - now.sec;
+ if (ss_hdr->expires_param < 0)
+ ss_hdr->expires_param = 0;
+ if (reason)
+ pj_strdup(tdata->pool, &ss_hdr->reason_param, reason);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)ss_hdr);
+
+ /* Add Allow-Events header. */
+ pjsip_msg_add_hdr( tdata->msg,
+ pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events));
+
+ /* Add authentication */
+ pjsip_auth_init_req( sub->pool, tdata, &sub->auth_sess,
+ sub->cred_cnt, sub->cred_info);
+
+ /* Route set. */
+ route = sub->route_set.next;
+ while (route != &sub->route_set) {
+ pj_list_insert_before( &tdata->msg->hdr,
+ pjsip_hdr_shallow_clone(tdata->pool, route));
+ route = route->next;
+ }
+
+ /* Attach body. */
+ tdata->msg->body = body;
+
+ /* That's it, send! */
+ status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub, &on_notify_response);
+ if (status == 0)
+ sub->pending_tsx++;
+
+ /* If terminated notify application. */
+ if (new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) {
+ if (sub->cb.on_sub_terminated) {
+ sub->pending_tsx++;
+ (*sub->cb.on_sub_terminated)(sub, reason);
+ sub->pending_tsx--;
+ }
+ }
+
+ /* Unlock subscription. */
+ pj_mutex_unlock(sub->mutex);
+
+ if (status != 0) {
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): failed to send NOTIFY",
+ sub, state[sub->state].ptr));
+ }
+
+ if (sub->delete_flag && sub->pending_tsx <= 0) {
+ pjsip_event_sub_destroy(sub);
+ }
+ return status;
+}
+
+
+/* If this timer callback is called, it means subscriber hasn't refreshed its
+ * subscription on-time. Set the state to terminated. This will also send
+ * NOTIFY with Subscription-State set to terminated.
+ */
+static void uas_expire_timer_cb( pj_timer_heap_t *timer_heap, pj_timer_entry *entry)
+{
+ pjsip_event_sub *sub = entry->user_data;
+ pj_str_t reason = { "timeout", 7 };
+
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): UAS subscription expired!",
+ sub, state[sub->state].ptr));
+
+ pj_mutex_lock(sub->mutex);
+ sub->timer.id = 0;
+
+ if (sub->cb.on_sub_terminated && sub->state!=PJSIP_EVENT_SUB_STATE_TERMINATED) {
+ /* Notify application, but prevent app from destroying the sub. */
+ ++sub->pending_tsx;
+ (*sub->cb.on_sub_terminated)(sub, &reason);
+ --sub->pending_tsx;
+ }
+ //pjsip_event_sub_notify( sub, PJSIP_EVENT_SUB_STATE_TERMINATED,
+ // &reason, NULL);
+ pj_mutex_unlock(sub->mutex);
+
+}
+
+/* Schedule notifier expiration. */
+static void sub_schedule_uas_expire( pjsip_event_sub *sub, int sec_delay)
+{
+ pj_time_val delay = { 0, 0 };
+ pj_parsed_time pt;
+
+ if (sub->timer.id != 0)
+ pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+
+ pj_gettimeofday(&sub->expiry_time);
+ sub->expiry_time.sec += sec_delay;
+
+ sub->timer.id = TIMER_ID_UAS_EXPIRY;
+ sub->timer.user_data = sub;
+ sub->timer.cb = &uas_expire_timer_cb;
+ delay.sec = sec_delay;
+ pjsip_endpt_schedule_timer( sub->endpt, &sub->timer, &delay);
+
+ pj_time_decode(&sub->expiry_time, &pt);
+ PJ_LOG(4,(THIS_FILE,
+ "event_sub%p (%s)(UAS): will expire at %02d:%02d:%02d (in %d secs)",
+ sub, state[sub->state].ptr, pt.hour, pt.min, pt.sec, sec_delay));
+}
+
+/* This timer is called for UAC to refresh the subscription. */
+static void refresh_timer_cb( pj_timer_heap_t *timer_heap, pj_timer_entry *entry)
+{
+ pjsip_event_sub *sub = entry->user_data;
+
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): refresh subscription timer",
+ sub, state[sub->state].ptr));
+
+ pj_mutex_lock(sub->mutex);
+ sub->timer.id = 0;
+ send_sub_refresh(sub);
+ pj_mutex_unlock(sub->mutex);
+}
+
+
+/* This will update the UAC's refresh schedule. */
+static void update_next_refresh(pjsip_event_sub *sub, int interval)
+{
+ pj_time_val delay = {0, 0};
+ pj_parsed_time pt;
+
+ if (interval < SECONDS_BEFORE_EXPIRY) {
+ PJ_LOG(4,(THIS_FILE,
+ "event_sub%p (%s): expiration delay too short (%d sec)! updated.",
+ sub, state[sub->state].ptr, interval));
+ interval = SECONDS_BEFORE_EXPIRY;
+ }
+
+ if (sub->timer.id != 0)
+ pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+
+ sub->timer.id = TIMER_ID_REFRESH;
+ sub->timer.user_data = sub;
+ sub->timer.cb = &refresh_timer_cb;
+ pj_gettimeofday(&sub->expiry_time);
+ delay.sec = interval - SECONDS_BEFORE_EXPIRY;
+ sub->expiry_time.sec += delay.sec;
+
+ pj_time_decode(&sub->expiry_time, &pt);
+ PJ_LOG(4,(THIS_FILE,
+ "event_sub%p (%s): will send SUBSCRIBE at %02d:%02d:%02d (in %d secs)",
+ sub, state[sub->state].ptr,
+ pt.hour, pt.min, pt.sec,
+ delay.sec));
+
+ pjsip_endpt_schedule_timer( sub->endpt, &sub->timer, &delay );
+}
+
+
+/* Find subscription in the hash table.
+ * If found, lock the subscription before returning to caller.
+ */
+static pjsip_event_sub *find_sub(pjsip_rx_data *rdata)
+{
+ pj_str_t key;
+ pjsip_role_e role;
+ pjsip_event_sub *sub;
+ pjsip_method *method = &rdata->msg->line.req.method;
+ pj_str_t *tag;
+
+ if (rdata->msg->type == PJSIP_REQUEST_MSG) {
+ if (pjsip_method_cmp(method, &SUBSCRIBE)==0) {
+ role = PJSIP_ROLE_UAS;
+ tag = &rdata->to_tag;
+ } else {
+ pj_assert(pjsip_method_cmp(method, &NOTIFY) == 0);
+ role = PJSIP_ROLE_UAC;
+ tag = &rdata->to_tag;
+ }
+ } else {
+ if (pjsip_method_cmp(&rdata->cseq->method, &SUBSCRIBE)==0) {
+ role = PJSIP_ROLE_UAC;
+ tag = &rdata->from_tag;
+ } else {
+ pj_assert(pjsip_method_cmp(method, &NOTIFY) == 0);
+ role = PJSIP_ROLE_UAS;
+ tag = &rdata->from_tag;
+ }
+ }
+ create_subscriber_key( &key, rdata->pool, role, &rdata->call_id, tag);
+
+ pj_mutex_lock(mgr.mutex);
+ sub = pj_hash_get(mgr.ht, key.ptr, key.slen);
+ if (sub)
+ pj_mutex_lock(sub->mutex);
+ pj_mutex_unlock(mgr.mutex);
+
+ return sub;
+}
+
+
+/* This function is called when we receive SUBSCRIBE request message
+ * to refresh existing subscription.
+ */
+static void on_received_sub_refresh( pjsip_event_sub *sub,
+ pjsip_transaction *tsx, pjsip_rx_data *rdata)
+{
+ pjsip_event_hdr *e;
+ pjsip_expires_hdr *expires;
+ pj_str_t hname;
+ int status = 200;
+ pj_str_t reason_phrase = { NULL, 0 };
+ int new_state = sub->state;
+ int old_state = sub->state;
+ int new_interval = 0;
+ pjsip_tx_data *tdata;
+
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): received target refresh",
+ sub, state[sub->state].ptr));
+
+ /* Check that the event matches. */
+ hname = pj_str("Event");
+ e = pjsip_msg_find_hdr_by_name( rdata->msg, &hname, NULL);
+ if (!e) {
+ status = 400;
+ reason_phrase = pj_str("Missing Event header");
+ goto send_response;
+ }
+ if (pj_stricmp(&e->event_type, &sub->event->event_type) != 0 ||
+ pj_stricmp(&e->id_param, &sub->event->id_param) != 0)
+ {
+ status = 481;
+ reason_phrase = pj_str("Subscription does not exist");
+ goto send_response;
+ }
+
+ /* Check server state. */
+ if (sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED) {
+ status = 481;
+ reason_phrase = pj_str("Subscription does not exist");
+ goto send_response;
+ }
+
+ /* Check expires header. */
+ expires = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_EXPIRES, NULL);
+ if (!expires) {
+ /*
+ status = 400;
+ reason_phrase = pj_str("Missing Expires header");
+ goto send_response;
+ */
+ new_interval = sub->default_interval;
+ } else {
+ /* Check that interval is not too short.
+ * Note that expires time may be zero (for unsubscription).
+ */
+ new_interval = expires->ivalue;
+ if (new_interval != 0 && new_interval < SECONDS_BEFORE_EXPIRY) {
+ status = PJSIP_SC_INTERVAL_TOO_BRIEF;
+ goto send_response;
+ }
+ }
+
+ /* Update interval. */
+ sub->default_interval = new_interval;
+ pj_gettimeofday(&sub->expiry_time);
+ sub->expiry_time.sec += new_interval;
+
+ /* Update timer only if this is not unsubscription. */
+ if (new_interval > 0) {
+ sub->default_interval = new_interval;
+ sub_schedule_uas_expire( sub, new_interval );
+
+ /* Call callback. */
+ if (sub->cb.on_received_refresh) {
+ sub->pending_tsx++;
+ (*sub->cb.on_received_refresh)(sub, rdata);
+ sub->pending_tsx--;
+ }
+ }
+
+send_response:
+ tdata = pjsip_endpt_create_response( sub->endpt, rdata, status);
+ if (tdata) {
+ if (reason_phrase.slen)
+ tdata->msg->line.status.reason = reason_phrase;
+
+ /* Add Expires header. */
+ expires = pjsip_expires_hdr_create(tdata->pool);
+ expires->ivalue = sub->default_interval;
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)expires);
+
+ if (PJSIP_IS_STATUS_IN_CLASS(status,200)) {
+ pjsip_msg_add_hdr(tdata->msg,
+ pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events));
+ }
+ /* Send down to transaction. */
+ pjsip_tsx_on_tx_msg(tsx, tdata);
+ }
+
+ if (sub->default_interval==0 || !PJSIP_IS_STATUS_IN_CLASS(status,200)) {
+ /* Notify application if sub is terminated. */
+ new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
+ sub_set_state(sub, new_state);
+ if (new_state!=old_state && sub->cb.on_sub_terminated) {
+ pj_str_t reason = {"", 0};
+ if (reason_phrase.slen) reason = reason_phrase;
+ else reason = *pjsip_get_status_text(status);
+
+ sub->pending_tsx++;
+ (*sub->cb.on_sub_terminated)(sub, &reason);
+ sub->pending_tsx--;
+ }
+ }
+
+ pj_mutex_unlock(sub->mutex);
+
+ /* Prefer to call log when we're not holding the mutex. */
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): sent refresh response %s, status=%d",
+ sub, state[sub->state].ptr,
+ (tdata ? tdata->obj_name : "null"), status));
+
+ /* Check if application has requested deletion. */
+ if (sub->delete_flag && sub->pending_tsx <= 0) {
+ pjsip_event_sub_destroy(sub);
+ }
+
+}
+
+
+/* This function is called when we receive SUBSCRIBE request message for
+ * a new subscription.
+ */
+static void on_new_subscription( pjsip_transaction *tsx, pjsip_rx_data *rdata )
+{
+ package *pkg;
+ pj_pool_t *pool;
+ pjsip_event_sub *sub = NULL;
+ pj_str_t hname;
+ int status = 200;
+ pj_str_t reason = { NULL, 0 };
+ pjsip_tx_data *tdata;
+ pjsip_expires_hdr *expires;
+ pjsip_accept_hdr *accept;
+ pjsip_event_hdr *evhdr;
+
+ /* Get the Event header. */
+ hname = pj_str("Event");
+ evhdr = pjsip_msg_find_hdr_by_name(rdata->msg, &hname, NULL);
+ if (!evhdr) {
+ status = 400;
+ reason = pj_str("No Event header in request");
+ goto send_response;
+ }
+
+ /* Find corresponding package.
+ * We don't lock the manager's mutex since we assume the package list
+ * won't change once the application is running!
+ */
+ pkg = mgr.pkg_list.next;
+ while (pkg != &mgr.pkg_list) {
+ if (pj_stricmp(&pkg->event, &evhdr->event_type) == 0)
+ break;
+ pkg = pkg->next;
+ }
+
+ if (pkg == &mgr.pkg_list) {
+ /* Event type is not supported by any packages! */
+ status = 489;
+ reason = pj_str("Bad Event");
+ goto send_response;
+ }
+
+ /* First check that the Accept specification matches the
+ * package's Accept types.
+ */
+ accept = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_ACCEPT, NULL);
+ if (accept) {
+ unsigned i;
+ pj_str_t *content_type = NULL;
+
+ for (i=0; i<accept->count && !content_type; ++i) {
+ int j;
+ for (j=0; j<pkg->accept_cnt; ++j) {
+ if (pj_stricmp(&accept->values[i], &pkg->accept[j])==0) {
+ content_type = &pkg->accept[j];
+ break;
+ }
+ }
+ }
+
+ if (!content_type) {
+ status = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+ goto send_response;
+ }
+ }
+
+ /* Check whether the package wants to accept the subscription. */
+ pj_assert(pkg->cb.on_query_subscribe != NULL);
+ (*pkg->cb.on_query_subscribe)(rdata, &status);
+ if (!PJSIP_IS_STATUS_IN_CLASS(status,200))
+ goto send_response;
+
+ /* Create new subscription record. */
+ pool = pjsip_endpt_create_pool(tsx->endpt, "esub",
+ SUB_POOL_SIZE, SUB_POOL_INC);
+ if (!pool) {
+ status = 500;
+ goto send_response;
+ }
+ sub = pj_pool_calloc(pool, 1, sizeof(*sub));
+ sub->pool = pool;
+ sub->mutex = pj_mutex_create(pool, "esub", PJ_MUTEX_RECURSE);
+ if (!sub->mutex) {
+ status = 500;
+ goto send_response;
+ }
+
+ PJ_LOG(4,(THIS_FILE, "event_sub%p: notifier is created.", sub));
+
+ /* Start locking mutex. */
+ pj_mutex_lock(sub->mutex);
+
+ /* Init UAS subscription */
+ sub->endpt = tsx->endpt;
+ sub->role = PJSIP_ROLE_UAS;
+ sub->state = PJSIP_EVENT_SUB_STATE_PENDING;
+ sub->state_str = state[sub->state];
+ pj_list_init(&sub->auth_sess);
+ pj_list_init(&sub->route_set);
+ sub->from = pjsip_hdr_clone(pool, rdata->to);
+ pjsip_fromto_set_from(sub->from);
+ if (sub->from->tag.slen == 0) {
+ pj_create_unique_string(pool, &sub->from->tag);
+ rdata->to->tag = sub->from->tag;
+ }
+ sub->to = pjsip_hdr_clone(pool, rdata->from);
+ pjsip_fromto_set_to(sub->to);
+ sub->contact = pjsip_contact_hdr_create(pool);
+ sub->contact->uri = sub->from->uri;
+ sub->call_id = pjsip_cid_hdr_create(pool);
+ pj_strdup(pool, &sub->call_id->id, &rdata->call_id);
+ sub->cseq = pj_rand() % 0xFFFF;
+
+ expires = pjsip_msg_find_hdr( rdata->msg, PJSIP_H_EXPIRES, NULL);
+ if (expires) {
+ sub->default_interval = expires->ivalue;
+ if (sub->default_interval > 0 &&
+ sub->default_interval < SECONDS_BEFORE_EXPIRY)
+ {
+ status = 423; /* Interval too short. */
+ goto send_response;
+ }
+ } else {
+ sub->default_interval = 600;
+ }
+
+ /* Clone Event header. */
+ sub->event = pjsip_hdr_clone(pool, evhdr);
+
+ /* Register to hash table. */
+ create_subscriber_key(&sub->key, pool, PJSIP_ROLE_UAS, &sub->call_id->id,
+ &sub->from->tag);
+ pj_mutex_lock(mgr.mutex);
+ pj_hash_set(pool, mgr.ht, sub->key.ptr, sub->key.slen, sub);
+ pj_mutex_unlock(mgr.mutex);
+
+ /* Set timer where subscription will expire only when expires<>0.
+ * Subscriber may send new subscription with expires==0.
+ */
+ if (sub->default_interval != 0) {
+ sub_schedule_uas_expire( sub, sub->default_interval-SECONDS_BEFORE_EXPIRY);
+ }
+
+ /* Notify application. */
+ if (pkg->cb.on_subscribe) {
+ pjsip_event_sub_cb *cb = NULL;
+ sub->pending_tsx++;
+ (*pkg->cb.on_subscribe)(sub, rdata, &cb, &sub->default_interval);
+ sub->pending_tsx--;
+ if (cb == NULL)
+ pj_memset(&sub->cb, 0, sizeof(*cb));
+ else
+ pj_memcpy(&sub->cb, cb, sizeof(*cb));
+ }
+
+
+send_response:
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s)(UAS): status=%d",
+ sub, state[sub->state].ptr, status));
+
+ tdata = pjsip_endpt_create_response( tsx->endpt, rdata, status);
+ if (tdata) {
+ if (reason.slen) {
+ /* Customize reason text. */
+ tdata->msg->line.status.reason = reason;
+ }
+ if (PJSIP_IS_STATUS_IN_CLASS(status,200)) {
+ /* Add Expires header. */
+ pjsip_expires_hdr *hdr;
+
+ hdr = pjsip_expires_hdr_create(tdata->pool);
+ hdr->ivalue = sub->default_interval;
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hdr );
+ }
+ if (status == 423) {
+ /* Add Min-Expires header. */
+ pjsip_min_expires_hdr *hdr;
+
+ hdr = pjsip_min_expires_hdr_create(tdata->pool);
+ hdr->ivalue = SECONDS_BEFORE_EXPIRY;
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hdr);
+ }
+ if (status == 489 ||
+ status==PJSIP_SC_NOT_ACCEPTABLE_HERE ||
+ PJSIP_IS_STATUS_IN_CLASS(status,200))
+ {
+ /* Add Allow-Events header. */
+ pjsip_hdr *hdr;
+ hdr = pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events);
+ pjsip_msg_add_hdr(tdata->msg, hdr);
+
+ /* Should add Accept header?. */
+ }
+
+ pjsip_tsx_on_tx_msg(tsx, tdata);
+ }
+
+ /* If received new subscription with expires=0, terminate. */
+ if (sub && sub->default_interval == 0) {
+ pj_assert(sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED);
+ if (sub->cb.on_sub_terminated) {
+ pj_str_t reason = { "timeout", 7 };
+ (*sub->cb.on_sub_terminated)(sub, &reason);
+ }
+ }
+
+ if (!PJSIP_IS_STATUS_IN_CLASS(status,200) || (sub && sub->delete_flag)) {
+ if (sub && sub->mutex) {
+ pjsip_event_sub_destroy(sub);
+ } else if (sub) {
+ pjsip_endpt_destroy_pool(tsx->endpt, sub->pool);
+ }
+ } else {
+ pj_assert(status >= 200);
+ pj_mutex_unlock(sub->mutex);
+ }
+}
+
+/* This is the main callback when SUBSCRIBE request is received. */
+static void on_subscribe_request(pjsip_transaction *tsx, pjsip_rx_data *rdata)
+{
+ pjsip_event_sub *sub = find_sub(rdata);
+
+ if (sub)
+ on_received_sub_refresh(sub, tsx, rdata);
+ else
+ on_new_subscription(tsx, rdata);
+}
+
+
+/* This callback is called when response to SUBSCRIBE is received. */
+static void on_subscribe_response(void *token, pjsip_event *event)
+{
+ pjsip_event_sub *sub = token;
+ pjsip_transaction *tsx = event->obj.tsx;
+ int new_state, old_state = sub->state;
+
+ pj_assert(tsx->status_code >= 200);
+ if (tsx->status_code < 200)
+ return;
+
+ pj_assert(sub->role == PJSIP_ROLE_UAC);
+
+ /* Lock mutex. */
+ pj_mutex_lock(sub->mutex);
+
+ /* If request failed with 401/407 error, silently retry the request. */
+ if (tsx->status_code==401 || tsx->status_code==407) {
+ pjsip_tx_data *tdata;
+ tdata = pjsip_auth_reinit_req(sub->endpt,
+ sub->pool, &sub->auth_sess,
+ sub->cred_cnt, sub->cred_info,
+ tsx->last_tx, event->src.rdata );
+ if (tdata) {
+ int status;
+ pjsip_cseq_hdr *cseq;
+ cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
+ cseq->cseq = sub->cseq++;
+ status = pjsip_endpt_send_request( sub->endpt, tdata,
+ -1, sub,
+ &on_subscribe_response);
+ if (status == 0) {
+ pj_mutex_unlock(sub->mutex);
+ return;
+ }
+ }
+ }
+
+ if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code,200)) {
+ /* Update To tag. */
+ if (sub->to->tag.slen == 0)
+ pj_strdup(sub->pool, &sub->to->tag, &event->src.rdata->to_tag);
+
+ new_state = sub->state;
+
+ } else if (tsx->status_code == 481) {
+ new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
+
+ } else if (tsx->status_code >= 300) {
+ /* RFC 3265 Section 3.1.4.2:
+ * If a SUBSCRIBE request to refresh a subscription fails
+ * with a non-481 response, the original subscription is still
+ * considered valid for the duration of original exires.
+ *
+ * Note:
+ * Since we normally send SUBSCRIBE for refreshing the subscription,
+ * it means the subscription already expired anyway. So we terminate
+ * the subscription now.
+ */
+ if (sub->state != PJSIP_EVENT_SUB_STATE_ACTIVE) {
+ new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
+ } else {
+ /* Use this to be compliant with Section 3.1.4.2
+ new_state = sub->state;
+ */
+ new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
+ }
+ } else {
+ pj_assert(0);
+ new_state = sub->state;
+ }
+
+ if (new_state != sub->state && sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED) {
+ sub_set_state(sub, new_state);
+ }
+
+ if (sub->state == PJSIP_EVENT_SUB_STATE_ACTIVE ||
+ sub->state == PJSIP_EVENT_SUB_STATE_PENDING)
+ {
+ /*
+ * Register timer for next subscription refresh, but only when
+ * we're not unsubscribing. Also update default_interval and Expires
+ * header.
+ */
+ if (sub->default_interval > 0 && !sub->delete_flag) {
+ pjsip_expires_hdr *exp = NULL;
+
+ /* Could be transaction timeout. */
+ if (event->src_type == PJSIP_EVENT_RX_MSG) {
+ exp = pjsip_msg_find_hdr(event->src.rdata->msg,
+ PJSIP_H_EXPIRES, NULL);
+ }
+
+ if (exp) {
+ int delay = exp->ivalue;
+ if (delay > 0) {
+ pj_time_val new_expiry;
+ pj_gettimeofday(&new_expiry);
+ new_expiry.sec += delay;
+ if (sub->timer.id==0 ||
+ new_expiry.sec < sub->expiry_time.sec-SECONDS_BEFORE_EXPIRY/2)
+ {
+ //if (delay > 0 && delay < sub->default_interval) {
+ sub->default_interval = delay;
+ sub->uac_expires->ivalue = delay;
+ update_next_refresh(sub, delay);
+ }
+ }
+ }
+ }
+ }
+
+ /* Call callback. */
+ if (!sub->delete_flag) {
+ if (sub->cb.on_received_sub_response) {
+ (*sub->cb.on_received_sub_response)(sub, event);
+ }
+ }
+
+ /* Notify application if we're terminated. */
+ if (new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) {
+ if (sub->cb.on_sub_terminated) {
+ pj_str_t reason;
+ if (event->src_type == PJSIP_EVENT_RX_MSG)
+ reason = event->src.rdata->msg->line.status.reason;
+ else
+ reason = *pjsip_get_status_text(tsx->status_code);
+
+ (*sub->cb.on_sub_terminated)(sub, &reason);
+ }
+ }
+
+ /* Decrement pending tsx count. */
+ --sub->pending_tsx;
+ pj_assert(sub->pending_tsx >= 0);
+
+ if (sub->delete_flag && sub->pending_tsx <= 0) {
+ pjsip_event_sub_destroy(sub);
+ } else {
+ pj_mutex_unlock(sub->mutex);
+ }
+
+ /* DO NOT ACCESS sub FROM NOW ON! IT MIGHT HAVE BEEN DELETED */
+}
+
+/*
+ * This callback called when we receive incoming NOTIFY request.
+ */
+static void on_notify_request(pjsip_transaction *tsx, pjsip_rx_data *rdata)
+{
+ pjsip_event_sub *sub;
+ pjsip_tx_data *tdata;
+ int status = 200;
+ int old_state;
+ pj_str_t reason = { NULL, 0 };
+ pj_str_t reason_phrase = { NULL, 0 };
+ int new_state = PJSIP_EVENT_SUB_STATE_NULL;
+
+ /* Find subscription based on Call-ID and From tag.
+ * This will also automatically lock the subscription, if it's found.
+ */
+ sub = find_sub(rdata);
+ if (!sub) {
+ /* RFC 3265: Section 3.2 Description of NOTIFY Behavior:
+ * Answer with 481 Subscription does not exist.
+ */
+ PJ_LOG(4,(THIS_FILE, "Unable to find subscription for incoming NOTIFY!"));
+ status = 481;
+ reason_phrase = pj_str("Subscription does not exist");
+
+ } else {
+ pj_assert(sub->role == PJSIP_ROLE_UAC);
+ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): received NOTIFY",
+ sub, state[sub->state].ptr));
+
+ }
+
+ new_state = old_state = sub->state;
+
+ /* RFC 3265: Section 3.2.1
+ * Check that the Event header match the subscription.
+ */
+ if (status == 200) {
+ pjsip_event_hdr *hdr;
+ pj_str_t hname = { "Event", 5 };
+
+ hdr = pjsip_msg_find_hdr_by_name(rdata->msg, &hname, NULL);
+ if (!hdr) {
+ status = PJSIP_SC_BAD_REQUEST;
+ reason_phrase = pj_str("No Event header found");
+ } else if (pj_stricmp(&hdr->event_type, &sub->event->event_type) != 0 ||
+ pj_stricmp(&hdr->id_param, &sub->event->id_param) != 0)
+ {
+ status = 481;
+ reason_phrase = pj_str("Subscription does not exist");
+ }
+ }
+
+ /* Update subscription state and timer. */
+ if (status == 200) {
+ pjsip_sub_state_hdr *hdr;
+ const pj_str_t hname = { "Subscription-State", 18 };
+ const pj_str_t state_active = { "active", 6 },
+ state_pending = { "pending", 7},
+ state_terminated = { "terminated", 10 };
+
+ hdr = pjsip_msg_find_hdr_by_name( rdata->msg, &hname, NULL);
+ if (!hdr) {
+ status = PJSIP_SC_BAD_REQUEST;
+ reason_phrase = pj_str("No Subscription-State header found");
+ goto process;
+ }
+
+ /*
+ * Update subscription state.
+ */
+ if (pj_stricmp(&hdr->sub_state, &state_active) == 0) {
+ if (sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED)
+ new_state = PJSIP_EVENT_SUB_STATE_ACTIVE;
+ } else if (pj_stricmp(&hdr->sub_state, &state_pending) == 0) {
+ if (sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED)
+ new_state = PJSIP_EVENT_SUB_STATE_PENDING;
+ } else if (pj_stricmp(&hdr->sub_state, &state_terminated) == 0) {
+ new_state = PJSIP_EVENT_SUB_STATE_TERMINATED;
+ } else {
+ new_state = PJSIP_EVENT_SUB_STATE_UNKNOWN;
+ }
+
+ reason = hdr->reason_param;
+
+ if (new_state != sub->state && new_state != PJSIP_EVENT_SUB_STATE_NULL &&
+ sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED)
+ {
+ sub_set_state(sub, new_state);
+ if (new_state == PJSIP_EVENT_SUB_STATE_UNKNOWN) {
+ pj_strdup_with_null(sub->pool, &sub->state_str, &hdr->sub_state);
+ } else {
+ sub->state_str = state[new_state];
+ }
+ }
+
+ /*
+ * Update timeout timer in required, just in case notifier changed the
+ * expiration to shorter time.
+ * Section 3.2.2: the expires param can only shorten the interval.
+ */
+ if ((sub->state==PJSIP_EVENT_SUB_STATE_ACTIVE ||
+ sub->state==PJSIP_EVENT_SUB_STATE_PENDING) && hdr->expires_param > 0)
+ {
+ pj_time_val now, new_expiry;
+
+ pj_gettimeofday(&now);
+ new_expiry.sec = now.sec + hdr->expires_param;
+ if (sub->timer.id==0 ||
+ new_expiry.sec < sub->expiry_time.sec-SECONDS_BEFORE_EXPIRY/2)
+ {
+ update_next_refresh(sub, hdr->expires_param);
+ }
+ }
+ }
+
+process:
+ /* Note: here we sub MAY BE NULL! */
+
+ /* Send response to NOTIFY */
+ tdata = pjsip_endpt_create_response( tsx->endpt, rdata, status );
+ if (tdata) {
+ if (reason_phrase.slen)
+ tdata->msg->line.status.reason = reason_phrase;
+
+ if (PJSIP_IS_STATUS_IN_CLASS(status,200)) {
+ pjsip_hdr *hdr;
+ hdr = pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events);
+ pjsip_msg_add_hdr( tdata->msg, hdr);
+ }
+
+ pjsip_tsx_on_tx_msg(tsx, tdata);
+ }
+
+ /* Call NOTIFY callback, if any. */
+ if (sub && PJSIP_IS_STATUS_IN_CLASS(status,200) && sub->cb.on_received_notify) {
+ sub->pending_tsx++;
+ (*sub->cb.on_received_notify)(sub, rdata);
+ sub->pending_tsx--;
+ }
+
+ /* Check if subscription is terminated and call callback. */
+ if (sub && new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) {
+ if (sub->cb.on_sub_terminated) {
+ sub->pending_tsx++;
+ (*sub->cb.on_sub_terminated)(sub, &reason);
+ sub->pending_tsx--;
+ }
+ }
+
+ /* Check if application has requested deletion. */
+ if (sub && sub->delete_flag && sub->pending_tsx <= 0) {
+ pjsip_event_sub_destroy(sub);
+ } else if (sub) {
+ pj_mutex_unlock(sub->mutex);
+ }
+}
+
+/* This callback is called when we received NOTIFY response. */
+static void on_notify_response(void *token, pjsip_event *event)
+{
+ pjsip_event_sub *sub = token;
+ pjsip_event_sub_state old_state = sub->state;
+ pjsip_transaction *tsx = event->obj.tsx;
+
+ /* Lock the subscription. */
+ pj_mutex_lock(sub->mutex);
+
+ pj_assert(sub->role == PJSIP_ROLE_UAS);
+
+ /* If request failed with authorization failure, silently retry. */
+ if (tsx->status_code==401 || tsx->status_code==407) {
+ pjsip_tx_data *tdata;
+ tdata = pjsip_auth_reinit_req(sub->endpt,
+ sub->pool, &sub->auth_sess,
+ sub->cred_cnt, sub->cred_info,
+ tsx->last_tx, event->src.rdata );
+ if (tdata) {
+ int status;
+ pjsip_cseq_hdr *cseq;
+ cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
+ cseq->cseq = sub->cseq++;
+ status = pjsip_endpt_send_request( sub->endpt, tdata,
+ -1, sub,
+ &on_notify_response);
+ if (status == 0) {
+ pj_mutex_unlock(sub->mutex);
+ return;
+ }
+ }
+ }
+
+ /* Notify application. */
+ if (sub->cb.on_received_notify_response)
+ (*sub->cb.on_received_notify_response)(sub, event);
+
+ /* Check for response 481. */
+ if (event->obj.tsx->status_code == 481) {
+ /* Remote says that the subscription does not exist!
+ * Terminate subscription!
+ */
+ sub_set_state(sub, PJSIP_EVENT_SUB_STATE_TERMINATED);
+ if (sub->timer.id) {
+ pjsip_endpt_cancel_timer(sub->endpt, &sub->timer);
+ sub->timer.id = 0;
+ }
+
+ PJ_LOG(4, (THIS_FILE,
+ "event_sub%p (%s): got 481 response to NOTIFY. Terminating...",
+ sub, state[sub->state].ptr));
+
+ /* Notify app. */
+ if (sub->state!=old_state && sub->cb.on_sub_terminated)
+ (*sub->cb.on_sub_terminated)(sub, &event->src.rdata->msg->line.status.reason);
+ }
+
+ /* Decrement pending transaction count. */
+ --sub->pending_tsx;
+ pj_assert(sub->pending_tsx >= 0);
+
+ /* Check that the subscription is marked for deletion. */
+ if (sub->delete_flag && sub->pending_tsx <= 0) {
+ pjsip_event_sub_destroy(sub);
+ } else {
+ pj_mutex_unlock(sub->mutex);
+ }
+
+ /* DO NOT ACCESS sub, IT MIGHT HAVE BEEN DESTROYED! */
+}
+
+
+/* This is the transaction handler for incoming SUBSCRIBE and NOTIFY
+ * requests.
+ */
+static void tsx_handler( struct pjsip_module *mod, pjsip_event *event )
+{
+ pjsip_msg *msg;
+ pjsip_rx_data *rdata;
+
+ /* Only want incoming message events. */
+ if (event->src_type != PJSIP_EVENT_RX_MSG)
+ return;
+
+ rdata = event->src.rdata;
+ msg = rdata->msg;
+
+ /* Only want to process request messages. */
+ if (msg->type != PJSIP_REQUEST_MSG)
+ return;
+
+ /* Only want the first notification. */
+ if (event->obj.tsx && event->obj.tsx->status_code >= 100)
+ return;
+
+ if (pjsip_method_cmp(&msg->line.req.method, &SUBSCRIBE)==0) {
+ /* Process incoming SUBSCRIBE request. */
+ on_subscribe_request( event->obj.tsx, rdata );
+ } else if (pjsip_method_cmp(&msg->line.req.method, &NOTIFY)==0) {
+ /* Process incoming NOTIFY request. */
+ on_notify_request( event->obj.tsx, rdata );
+ }
+}
+
diff --git a/pjsip/src/pjsip-simple/event_notify_msg.c b/pjsip/src/pjsip-simple/event_notify_msg.c
new file mode 100644
index 00000000..9d0f0cd7
--- /dev/null
+++ b/pjsip/src/pjsip-simple/event_notify_msg.c
@@ -0,0 +1,307 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/event_notify_msg.h>
+#include <pjsip/print.h>
+#include <pjsip/sip_parser.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/except.h>
+
+static int pjsip_event_hdr_print( pjsip_event_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool,
+ const pjsip_event_hdr *hdr);
+static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_event_hdr*);
+
+static pjsip_hdr_vptr event_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_event_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_event_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_event_hdr_print,
+};
+
+
+PJ_DEF(pjsip_event_hdr*) pjsip_event_hdr_create(pj_pool_t *pool)
+{
+ pj_str_t event = { "Event", 5 };
+ pjsip_event_hdr *hdr = pj_pool_calloc(pool, 1, sizeof(*hdr));
+ hdr->type = PJSIP_H_OTHER;
+ hdr->name = hdr->sname = event;
+ hdr->vptr = &event_hdr_vptr;
+ pj_list_init(hdr);
+ return hdr;
+}
+
+static int pjsip_event_hdr_print( pjsip_event_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf+size;
+ int printed;
+
+ copy_advance(p, hdr->name);
+ *p++ = ':';
+ *p++ = ' ';
+
+ copy_advance(p, hdr->event_type);
+ copy_advance_pair(p, ";id=", 4, hdr->id_param);
+ if (hdr->other_param.slen)
+ copy_advance(p, hdr->other_param);
+ return p - buf;
+}
+
+static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool,
+ const pjsip_event_hdr *rhs)
+{
+ pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool);
+ pj_strdup(pool, &hdr->event_type, &rhs->event_type);
+ pj_strdup(pool, &hdr->id_param, &rhs->id_param);
+ pj_strdup(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_event_hdr *rhs )
+{
+ pjsip_event_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr));
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+
+static int pjsip_allow_events_hdr_print(pjsip_allow_events_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_allow_events_hdr*
+pjsip_allow_events_hdr_clone(pj_pool_t *pool,
+ const pjsip_allow_events_hdr *hdr);
+static pjsip_allow_events_hdr*
+pjsip_allow_events_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_allow_events_hdr*);
+
+static pjsip_hdr_vptr allow_event_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_allow_events_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_allow_events_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_allow_events_hdr_print,
+};
+
+
+PJ_DEF(pjsip_allow_events_hdr*) pjsip_allow_events_hdr_create(pj_pool_t *pool)
+{
+ pj_str_t allow_events = { "Allow-Events", 12 };
+ pjsip_allow_events_hdr *hdr = pj_pool_calloc(pool, 1, sizeof(*hdr));
+ hdr->type = PJSIP_H_OTHER;
+ hdr->name = hdr->sname = allow_events;
+ hdr->vptr = &allow_event_hdr_vptr;
+ pj_list_init(hdr);
+ return hdr;
+}
+
+static int pjsip_allow_events_hdr_print(pjsip_allow_events_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf+size;
+ int printed;
+
+ copy_advance(p, hdr->name);
+ *p++ = ':';
+ *p++ = ' ';
+
+ if (hdr->event_cnt > 0) {
+ int i;
+ copy_advance(p, hdr->events[0]);
+ for (i=1; i<hdr->event_cnt; ++i) {
+ copy_advance_pair(p, ",", 1, hdr->events[i]);
+ }
+ }
+
+ return p - buf;
+}
+
+static pjsip_allow_events_hdr*
+pjsip_allow_events_hdr_clone(pj_pool_t *pool,
+ const pjsip_allow_events_hdr *rhs)
+{
+ int i;
+
+ pjsip_allow_events_hdr *hdr = pjsip_allow_events_hdr_create(pool);
+ hdr->event_cnt = rhs->event_cnt;
+ for (i=0; i<rhs->event_cnt; ++i) {
+ pj_strdup(pool, &hdr->events[i], &rhs->events[i]);
+ }
+ return hdr;
+}
+
+static pjsip_allow_events_hdr*
+pjsip_allow_events_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_allow_events_hdr *rhs)
+{
+ pjsip_allow_events_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr));
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+
+static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr *hdr);
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr*);
+
+static pjsip_hdr_vptr sub_state_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_sub_state_hdr_print,
+};
+
+
+PJ_DEF(pjsip_sub_state_hdr*) pjsip_sub_state_hdr_create(pj_pool_t *pool)
+{
+ pj_str_t sub_state = { "Subscription-State", 18 };
+ pjsip_sub_state_hdr *hdr = pj_pool_calloc(pool, 1, sizeof(*hdr));
+ hdr->type = PJSIP_H_OTHER;
+ hdr->name = hdr->sname = sub_state;
+ hdr->vptr = &sub_state_hdr_vptr;
+ hdr->expires_param = -1;
+ hdr->retry_after = -1;
+ pj_list_init(hdr);
+ return hdr;
+}
+
+static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf+size;
+ int printed;
+
+ copy_advance(p, hdr->name);
+ *p++ = ':';
+ *p++ = ' ';
+
+ copy_advance(p, hdr->sub_state);
+ copy_advance_pair(p, ";reason=", 8, hdr->reason_param);
+ if (hdr->expires_param >= 0) {
+ pj_memcpy(p, ";expires=", 9);
+ p += 9;
+ printed = pj_utoa(hdr->expires_param, p);
+ p += printed;
+ }
+ if (hdr->retry_after >= 0) {
+ pj_memcpy(p, ";retry-after=", 13);
+ p += 9;
+ printed = pj_utoa(hdr->retry_after, p);
+ p += printed;
+ }
+ if (hdr->other_param.slen)
+ copy_advance(p, hdr->other_param);
+
+ return p - buf;
+}
+
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr *rhs)
+{
+ pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool);
+ pj_strdup(pool, &hdr->sub_state, &rhs->sub_state);
+ pj_strdup(pool, &hdr->reason_param, &rhs->reason_param);
+ hdr->retry_after = rhs->retry_after;
+ hdr->expires_param = rhs->expires_param;
+ pj_strdup(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_sub_state_hdr*
+pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_sub_state_hdr *rhs)
+{
+ pjsip_sub_state_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr));
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+static pjsip_event_hdr *parse_hdr_event(pj_scanner *scanner,
+ pj_pool_t *pool)
+{
+ pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool);
+ const pj_str_t id_param = { "id", 2 };
+
+ pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->event_type);
+
+ while (*scanner->current == ';') {
+ pj_str_t pname, pvalue;
+ pj_scan_get_char(scanner);
+ pjsip_parse_param_imp(scanner, &pname, &pvalue, 0);
+ if (pj_stricmp(&pname, &id_param)==0) {
+ hdr->id_param = pvalue;
+ } else {
+ pjsip_concat_param_imp(&hdr->other_param, pool, &pname, &pvalue, ';');
+ }
+ }
+ pjsip_parse_end_hdr_imp( scanner );
+ return hdr;
+}
+
+static pjsip_allow_events_hdr *parse_hdr_allow_events(pj_scanner *scanner,
+ pj_pool_t *pool)
+{
+ pjsip_allow_events_hdr *hdr = pjsip_allow_events_hdr_create(pool);
+
+ pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->events[0]);
+ hdr->event_cnt = 1;
+
+ while (*scanner->current == ',') {
+ pj_scan_get_char(scanner);
+ pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->events[hdr->event_cnt++]);
+ if (hdr->event_cnt == PJSIP_MAX_ALLOW_EVENTS) {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+ }
+
+ pjsip_parse_end_hdr_imp( scanner );
+ return hdr;
+}
+
+static pjsip_sub_state_hdr *parse_hdr_sub_state(pj_scanner *scanner,
+ pj_pool_t *pool)
+{
+ pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool);
+ const pj_str_t reason = { "reason", 6 },
+ expires = { "expires", 7 },
+ retry_after = { "retry-after", 11 };
+ pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->sub_state);
+
+ while (*scanner->current == ';') {
+ pj_str_t pname, pvalue;
+
+ pj_scan_get_char(scanner);
+ pjsip_parse_param_imp(scanner, &pname, &pvalue, 0);
+ if (pj_stricmp(&pname, &reason) == 0) {
+ hdr->reason_param = pvalue;
+ } else if (pj_stricmp(&pname, &expires) == 0) {
+ hdr->expires_param = pj_strtoul(&pvalue);
+ } else if (pj_stricmp(&pname, &retry_after) == 0) {
+ hdr->retry_after = pj_strtoul(&pvalue);
+ } else {
+ pjsip_concat_param_imp(&hdr->other_param, pool, &pname, &pvalue, ';');
+ }
+ }
+
+ pjsip_parse_end_hdr_imp( scanner );
+ return hdr;
+}
+
+PJ_DEF(void) pjsip_event_notify_init_parser(void)
+{
+ pjsip_register_hdr_parser( "Event", NULL, (pjsip_parse_hdr_func*) &parse_hdr_event);
+ pjsip_register_hdr_parser( "Allow-Events", NULL, (pjsip_parse_hdr_func*) &parse_hdr_allow_events);
+ pjsip_register_hdr_parser( "Subscription-State", NULL, (pjsip_parse_hdr_func*) &parse_hdr_sub_state);
+}
diff --git a/pjsip/src/pjsip-simple/messaging.c b/pjsip/src/pjsip-simple/messaging.c
new file mode 100644
index 00000000..c3992b4c
--- /dev/null
+++ b/pjsip/src/pjsip-simple/messaging.c
@@ -0,0 +1,337 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/messaging.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_misc.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/guid.h>
+#include <pj/string.h>
+#include <pj/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define THIS_FILE "messaging"
+
+struct messaging_data
+{
+ void *token;
+ pjsip_messaging_cb cb;
+};
+
+struct pjsip_messaging_session
+{
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pjsip_from_hdr *from;
+ pjsip_to_hdr *to;
+ pjsip_cid_hdr *call_id;
+ pjsip_cseq_hdr *cseq;
+};
+
+static int module_id;
+static pjsip_on_new_msg_cb incoming_cb;
+static pjsip_method message_method;
+
+
+/*
+ * Set global callback to receive incoming message.
+ */
+PJ_DEF(pjsip_on_new_msg_cb)
+pjsip_messaging_set_incoming_callback(pjsip_on_new_msg_cb cb)
+{
+ pjsip_on_new_msg_cb prev_cb = incoming_cb;
+ incoming_cb = cb;
+ return prev_cb;
+}
+
+
+/*
+ * Create an independent message (ie. not associated with a session).
+ */
+PJ_DEF(pjsip_tx_data*)
+pjsip_messaging_create_msg_from_hdr(pjsip_endpoint *endpt,
+ const pjsip_uri *target,
+ const pjsip_from_hdr *param_from,
+ const pjsip_to_hdr *param_to,
+ const pjsip_cid_hdr *param_call_id,
+ int param_cseq,
+ const pj_str_t *param_text)
+{
+ return pjsip_endpt_create_request_from_hdr( endpt, &message_method,
+ target,
+ param_from, param_to,
+ NULL, param_call_id,
+ param_cseq, param_text );
+}
+
+/*
+ * Create independent message from string (instead of from header).
+ */
+PJ_DEF(pjsip_tx_data*)
+pjsip_messaging_create_msg( pjsip_endpoint *endpt,
+ const pj_str_t *target,
+ const pj_str_t *param_from,
+ const pj_str_t *param_to,
+ const pj_str_t *param_call_id,
+ int param_cseq,
+ const pj_str_t *param_text)
+{
+ return pjsip_endpt_create_request( endpt, &message_method, target,
+ param_from, param_to, NULL, param_call_id,
+ param_cseq, param_text);
+}
+
+/*
+ * Initiate transaction to send outgoing message.
+ */
+PJ_DEF(pj_status_t)
+pjsip_messaging_send_msg( pjsip_endpoint *endpt, pjsip_tx_data *tdata,
+ void *token, pjsip_messaging_cb cb )
+{
+ pjsip_transaction *tsx;
+ struct messaging_data *msg_data;
+
+ /* Create transaction. */
+ tsx = pjsip_endpt_create_tsx(endpt);
+ if (!tsx) {
+ pjsip_tx_data_dec_ref(tdata);
+ return -1;
+ }
+
+ /* Save parameters to messaging data and attach to tsx. */
+ msg_data = pj_pool_calloc(tsx->pool, 1, sizeof(struct messaging_data));
+ msg_data->cb = cb;
+ msg_data->token = token;
+
+ /* Init transaction. */
+ tsx->module_data[module_id] = msg_data;
+ if (pjsip_tsx_init_uac(tsx, tdata) != 0) {
+ pjsip_tx_data_dec_ref(tdata);
+ pjsip_endpt_destroy_tsx(endpt, tsx);
+ return -1;
+ }
+
+ pjsip_endpt_register_tsx(endpt, tsx);
+
+ /*
+ * Instruct transaction to send message.
+ * Further events will be received via transaction's event.
+ */
+ pjsip_tsx_on_tx_msg(tsx, tdata);
+
+ /* Decrement reference counter. */
+ pjsip_tx_data_dec_ref(tdata);
+ return 0;
+}
+
+
+/*
+ * Create 'IM session'.
+ */
+PJ_DEF(pjsip_messaging_session*)
+pjsip_messaging_create_session( pjsip_endpoint *endpt, const pj_str_t *param_from,
+ const pj_str_t *param_to )
+{
+ pj_pool_t *pool;
+ pjsip_messaging_session *ses;
+ pj_str_t tmp, to;
+
+ pool = pjsip_endpt_create_pool(endpt, "imsess", 1024, 1024);
+ if (!pool)
+ return NULL;
+
+ ses = pj_pool_calloc(pool, 1, sizeof(pjsip_messaging_session));
+ ses->pool = pool;
+ ses->endpt = endpt;
+
+ ses->call_id = pjsip_cid_hdr_create(pool);
+ pj_create_unique_string(pool, &ses->call_id->id);
+
+ ses->cseq = pjsip_cseq_hdr_create(pool);
+ ses->cseq->cseq = pj_rand();
+ ses->cseq->method = message_method;
+
+ ses->from = pjsip_from_hdr_create(pool);
+ pj_strdup_with_null(pool, &tmp, param_from);
+ ses->from->uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (ses->from->uri == NULL) {
+ pjsip_endpt_destroy_pool(endpt, pool);
+ return NULL;
+ }
+ pj_create_unique_string(pool, &ses->from->tag);
+
+ ses->to = pjsip_to_hdr_create(pool);
+ pj_strdup_with_null(pool, &to, param_from);
+ ses->to->uri = pjsip_parse_uri(pool, to.ptr, to.slen, PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (ses->to->uri == NULL) {
+ pjsip_endpt_destroy_pool(endpt, pool);
+ return NULL;
+ }
+
+ PJ_LOG(4,(THIS_FILE, "IM session created: recipient=%s", to.ptr));
+ return ses;
+}
+
+
+/*
+ * Send IM message using identification from 'IM session'.
+ */
+PJ_DEF(pjsip_tx_data*)
+pjsip_messaging_session_create_msg( pjsip_messaging_session *ses, const pj_str_t *text )
+{
+ return pjsip_endpt_create_request_from_hdr( ses->endpt,
+ &message_method,
+ ses->to->uri,
+ ses->from,
+ ses->to,
+ NULL,
+ ses->call_id,
+ ses->cseq->cseq++,
+ text);
+}
+
+
+/*
+ * Destroy 'IM session'.
+ */
+PJ_DEF(pj_status_t)
+pjsip_messaging_destroy_session( pjsip_messaging_session *ses )
+{
+ /*
+ * NOTE ABOUT POSSIBLE BUG HERE...
+ *
+ * We don't check number of pending transaction before destroying IM
+ * session. As the result, the headers in the txdata of pending transaction
+ * wil be INVALID once the IM session is deleted (because we only
+ * shallo_clone()-ed them).
+ *
+ * This normally should be okay, because once the message is
+ * submitted to transaction, the transaction (or rather the transport)
+ * will 'print' the message to a buffer, and once it is printed, it
+ * won't try to access the original message again. So even when the
+ * original message has a dangling pointer, we should be safe.
+ *
+ * However, it will cause a problem if:
+ * - resolving completes asynchronously and with a substantial delay,
+ * and before the resolver/transport finished its job the user
+ * destroy the IM session.
+ * - if the transmit data is invalidated after the IM session is
+ * destroyed.
+ */
+
+ pjsip_endpt_destroy_pool(ses->endpt, ses->pool);
+ return 0;
+}
+
+
+static pj_status_t messaging_init( pjsip_endpoint *endpt,
+ struct pjsip_module *mod, pj_uint32_t id )
+{
+ PJ_UNUSED_ARG(endpt)
+ PJ_UNUSED_ARG(mod)
+
+ module_id = id;
+ return 0;
+}
+
+static pj_status_t messaging_start( struct pjsip_module *mod )
+{
+ PJ_UNUSED_ARG(mod)
+ return 0;
+}
+
+static pj_status_t messaging_deinit( struct pjsip_module *mod )
+{
+ PJ_UNUSED_ARG(mod)
+ return 0;
+}
+
+static void messaging_tsx_handler( struct pjsip_module *mod, pjsip_event *event )
+{
+ pjsip_transaction *tsx = event->obj.tsx;
+ struct messaging_data *mod_data;
+
+ PJ_UNUSED_ARG(mod)
+
+ /* Ignore non transaction event */
+ if (event->type != PJSIP_EVENT_TSX_STATE_CHANGED || tsx == NULL)
+ return;
+
+ /* If this is an incoming message, inform application. */
+ if (tsx->role == PJSIP_ROLE_UAS) {
+ int status = 100;
+ pjsip_tx_data *tdata;
+
+ /* Check if we already answered this request. */
+ if (tsx->status_code >= 200)
+ return;
+
+ /* Only handle MESSAGE requests!. */
+ if (pjsip_method_cmp(&tsx->method, &message_method) != 0)
+ return;
+
+ /* Call application callback. */
+ if (incoming_cb)
+ status = (*incoming_cb)(event->src.rdata);
+
+ if (status < 200 || status >= 700)
+ status = PJSIP_SC_INTERNAL_SERVER_ERROR;
+
+ /* Respond request. */
+ tdata = pjsip_endpt_create_response(tsx->endpt, event->src.rdata, status );
+ if (tdata)
+ pjsip_tsx_on_tx_msg(tsx, tdata);
+
+ return;
+ }
+
+ /* Ignore if it's not something that came from messaging module. */
+ mod_data = tsx->module_data[ module_id ];
+ if (mod_data == NULL)
+ return;
+
+ /* Ignore non final response. */
+ if (tsx->status_code < 200)
+ return;
+
+ /* Don't want to call the callback more than once. */
+ tsx->module_data[ module_id ] = NULL;
+
+ /* Now call the callback. */
+ if (mod_data->cb) {
+ (*mod_data->cb)(mod_data->token, tsx->status_code);
+ }
+}
+
+static pjsip_module messaging_module =
+{
+ { "Messaging", 9}, /* Name. */
+ 0, /* Flag */
+ 128, /* Priority */
+ NULL, /* User agent instance, initialized by APP. */
+ 0, /* Number of methods supported (will be initialized later). */
+ { 0 }, /* Array of methods (will be initialized later) */
+ &messaging_init, /* init_module() */
+ &messaging_start, /* start_module() */
+ &messaging_deinit, /* deinit_module() */
+ &messaging_tsx_handler, /* tsx_handler() */
+};
+
+PJ_DEF(pjsip_module*) pjsip_messaging_get_module()
+{
+ static pj_str_t method_str = { "MESSAGE", 7 };
+
+ pjsip_method_init_np( &message_method, &method_str);
+
+ messaging_module.method_cnt = 1;
+ messaging_module.methods[0] = &message_method;
+
+ return &messaging_module;
+}
+
diff --git a/pjsip/src/pjsip-simple/pidf.c b/pjsip/src/pjsip-simple/pidf.c
new file mode 100644
index 00000000..f1de9c9d
--- /dev/null
+++ b/pjsip/src/pjsip-simple/pidf.c
@@ -0,0 +1,335 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/pidf.h>
+#include <pj/string.h>
+#include <pj/pool.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 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(pool, sizeof(*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);
+}
+
+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(pool, sizeof(*t));
+ 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(pj_xml_node *node, const void *id)
+{
+ return pj_xml_find_attr(node, &ID, 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(pool, sizeof(*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(pool, sizeof(*st));
+ 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(pool, sizeof(*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(pool, sizeof(*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(pool, sizeof(*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(pool, sizeof(*node));
+ xml_init_node(pool, node, &TIMESTAMP, ts);
+ } 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(pool, sizeof(*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(pool, sizeof(*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);
+ pj_assert(node != NULL);
+ 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);
+ pj_assert(node != NULL);
+ 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(pool, sizeof(*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) {
+ if (pj_stricmp(&pres->name, &PRESENCE) != 0)
+ return NULL;
+ }
+ return pres;
+}
+
+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 00000000..32ed8e3c
--- /dev/null
+++ b/pjsip/src/pjsip-simple/presence.c
@@ -0,0 +1,384 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/presence.h>
+#include <pjsip/sip_transport.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/guid.h>
+#include <pj/os.h>
+#include <stdio.h>
+
+/* Forward declarations. */
+static void on_query_subscribe(pjsip_rx_data *rdata, int *status);
+static void on_subscribe(pjsip_event_sub *sub, pjsip_rx_data *rdata,
+ pjsip_event_sub_cb **cb, int *expires);
+static void on_sub_terminated(pjsip_event_sub *sub, const pj_str_t *reason);
+static void on_sub_received_refresh(pjsip_event_sub *sub, pjsip_rx_data *rdata);
+static void on_received_notify(pjsip_event_sub *sub, pjsip_rx_data *rdata);
+
+/* Some string constants. */
+static pj_str_t PRESENCE_EVENT = { "presence", 8 };
+
+/* Accept types. */
+static pj_str_t accept_names[] = {
+ { "application/pidf+xml", 20 },
+ { "application/xpidf+xml", 21 }
+};
+static pjsip_media_type accept_types[] = {
+ {
+ { "application", 11 },
+ { "pidf+xml", 8 }
+ },
+ {
+ { "application", 11 },
+ { "xpidf+xml", 9 }
+ }
+};
+
+/* Callback that is registered by application. */
+static pjsip_presence_cb cb;
+
+/* Package callback to be register to event_notify */
+static pjsip_event_sub_pkg_cb pkg_cb = { &on_query_subscribe,
+ &on_subscribe };
+
+/* Global/static callback to be registered to event_notify */
+static pjsip_event_sub_cb sub_cb = { &on_sub_terminated,
+ &on_sub_received_refresh,
+ NULL,
+ &on_received_notify,
+ NULL };
+
+/*
+ * Initialize presence module.
+ * This will register event package "presence" to event framework.
+ */
+PJ_DEF(void) pjsip_presence_init(const pjsip_presence_cb *pcb)
+{
+ pj_memcpy(&cb, pcb, sizeof(*pcb));
+ pjsip_event_sub_register_pkg( &PRESENCE_EVENT,
+ sizeof(accept_names)/sizeof(accept_names[0]),
+ accept_names,
+ &pkg_cb);
+}
+
+/*
+ * Create presence subscription.
+ */
+PJ_DEF(pjsip_presentity*) pjsip_presence_create( pjsip_endpoint *endpt,
+ const pj_str_t *local_url,
+ const pj_str_t *remote_url,
+ int expires,
+ void *user_data )
+{
+ pjsip_event_sub *sub;
+ pjsip_presentity *pres;
+
+ if (expires < 0)
+ expires = 300;
+
+ /* Create event subscription */
+ sub = pjsip_event_sub_create(endpt, local_url, remote_url, &PRESENCE_EVENT,
+ expires,
+ sizeof(accept_names)/sizeof(accept_names[0]),
+ accept_names,
+ NULL, &sub_cb);
+ if (!sub)
+ return NULL;
+
+ /* Allocate presence descriptor. */
+ pres = pj_pool_calloc(sub->pool, 1, sizeof(*pres));
+ pres->sub = sub;
+ pres->user_data = user_data;
+ sub->user_data = pres;
+
+ return pres;
+}
+
+/*
+ * Send SUBSCRIBE.
+ */
+PJ_DEF(pj_status_t) pjsip_presence_subscribe( pjsip_presentity *pres )
+{
+ return pjsip_event_sub_subscribe( pres->sub );
+}
+
+/*
+ * Set credentials to be used for outgoing requests.
+ */
+PJ_DEF(pj_status_t) pjsip_presence_set_credentials( pjsip_presentity *pres,
+ int count,
+ const pjsip_cred_info cred[])
+{
+ return pjsip_event_sub_set_credentials(pres->sub, count, cred);
+}
+
+/*
+ * Set route-set.
+ */
+PJ_DEF(pj_status_t) pjsip_presence_set_route_set( pjsip_presentity *pres,
+ const pjsip_route_hdr *hdr )
+{
+ return pjsip_event_sub_set_route_set( pres->sub, hdr );
+}
+
+/*
+ * Unsubscribe.
+ */
+PJ_DEF(pj_status_t) pjsip_presence_unsubscribe( pjsip_presentity *pres )
+{
+ return pjsip_event_sub_unsubscribe(pres->sub);
+}
+
+/*
+ * This is the pjsip_msg_body callback to print XML body.
+ */
+static int print_xml(pjsip_msg_body *body, char *buf, pj_size_t size)
+{
+ return pj_xml_print( body->data, buf, size, PJ_TRUE );
+}
+
+/*
+ * Create and initialize PIDF document and msg body (notifier only).
+ */
+static pj_status_t init_presence_info( pjsip_presentity *pres )
+{
+ pj_str_t uri;
+ pj_pool_t *pool = pres->sub->pool;
+ char tmp[PJSIP_MAX_URL_SIZE];
+ pjpidf_tuple *tuple;
+ const pjsip_media_type *content_type = NULL;
+
+ pj_assert(pres->uas_body == NULL);
+
+ /* Make entity_id */
+ uri.ptr = tmp;
+ uri.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, pres->sub->from->uri,
+ tmp, sizeof(tmp));
+ if (uri.slen < 0)
+ return -1;
+
+ if (pres->pres_type == PJSIP_PRES_TYPE_PIDF) {
+ pj_str_t s;
+
+ /* Create <presence>. */
+ pres->uas_data.pidf = pjpidf_create(pool, &s);
+
+ /* Create <tuple> */
+ pj_create_unique_string(pool, &s);
+ tuple = pjpidf_pres_add_tuple(pool, pres->uas_data.pidf, &s);
+
+ /* Set <contact> */
+ s.ptr = tmp;
+ s.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, pres->sub->contact->uri, tmp, sizeof(tmp));
+ if (s.slen < 0)
+ return -1;
+ pjpidf_tuple_set_contact(pool, tuple, &s);
+
+ /* Content-Type */
+ content_type = &accept_types[PJSIP_PRES_TYPE_PIDF];
+
+ } else if (pres->pres_type == PJSIP_PRES_TYPE_XPIDF) {
+
+ /* Create XPIDF */
+ pres->uas_data.xpidf = pjxpidf_create(pool, &uri);
+
+ /* Content-Type. */
+ content_type = &accept_types[PJSIP_PRES_TYPE_XPIDF];
+ }
+
+ /* Create message body */
+ pres->uas_body = pj_pool_alloc(pool, sizeof(pjsip_msg_body));
+ pres->uas_body->content_type = *content_type;
+ pres->uas_body->data = pres->uas_data.pidf;
+ pres->uas_body->len = 0;
+ pres->uas_body->print_body = &print_xml;
+
+ return 0;
+}
+
+/*
+ * Send NOTIFY and set subscription state.
+ */
+PJ_DEF(pj_status_t) pjsip_presence_notify( pjsip_presentity *pres,
+ pjsip_event_sub_state state,
+ pj_bool_t is_online )
+{
+ pj_str_t reason = { "", 0 };
+
+ if (pres->uas_data.pidf == NULL) {
+ if (init_presence_info(pres) != 0)
+ return -1;
+ }
+
+ /* Update basic status in PIDF/XPIDF document. */
+ if (pres->pres_type == PJSIP_PRES_TYPE_PIDF) {
+ pjpidf_tuple *first;
+ pjpidf_status *status;
+ pj_time_val now;
+ pj_parsed_time pnow;
+
+ first = pjpidf_op.pres.get_first_tuple(pres->uas_data.pidf);
+ pj_assert(first);
+ status = pjpidf_op.tuple.get_status(first);
+ pj_assert(status);
+ pjpidf_op.status.set_basic_open(status, is_online);
+
+ /* Update timestamp. */
+ if (pres->timestamp.ptr == 0) {
+ pres->timestamp.ptr = pj_pool_alloc(pres->sub->pool, 24);
+ }
+ pj_gettimeofday(&now);
+ pj_time_decode(&now, &pnow);
+ pres->timestamp.slen = sprintf(pres->timestamp.ptr,
+ "%04d-%02d-%02dT%02d:%02d:%02dZ",
+ pnow.year, pnow.mon, pnow.day,
+ pnow.hour, pnow.min, pnow.sec);
+ pjpidf_op.tuple.set_timestamp_np(pres->sub->pool, first, &pres->timestamp);
+
+ } else if (pres->pres_type == PJSIP_PRES_TYPE_XPIDF) {
+ pjxpidf_set_status( pres->uas_data.xpidf, is_online );
+
+ } else {
+ pj_assert(0);
+ }
+
+ /* Send notify. */
+ return pjsip_event_sub_notify( pres->sub, state, &reason, pres->uas_body);
+}
+
+/*
+ * Destroy subscription (can be called for both subscriber and notifier).
+ */
+PJ_DEF(pj_status_t) pjsip_presence_destroy( pjsip_presentity *pres )
+{
+ return pjsip_event_sub_destroy(pres->sub);
+}
+
+/*
+ * This callback is called by event framework to query whether we want to
+ * accept an incoming subscription.
+ */
+static void on_query_subscribe(pjsip_rx_data *rdata, int *status)
+{
+ if (cb.accept_presence) {
+ (*cb.accept_presence)(rdata, status);
+ }
+}
+
+/*
+ * This callback is called by event framework after we accept the incoming
+ * subscription, to notify about the new subscription instance.
+ */
+static void on_subscribe(pjsip_event_sub *sub, pjsip_rx_data *rdata,
+ pjsip_event_sub_cb **set_sub_cb, int *expires)
+{
+ pjsip_presentity *pres;
+ pjsip_accept_hdr *accept;
+
+ pres = pj_pool_calloc(sub->pool, 1, sizeof(*pres));
+ pres->sub = sub;
+ pres->pres_type = PJSIP_PRES_TYPE_PIDF;
+ sub->user_data = pres;
+ *set_sub_cb = &sub_cb;
+
+ accept = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_ACCEPT, NULL);
+ if (accept) {
+ unsigned i;
+ int found = 0;
+ for (i=0; i<accept->count && !found; ++i) {
+ int j;
+ for (j=0; j<sizeof(accept_names)/sizeof(accept_names[0]); ++j) {
+ if (!pj_stricmp(&accept->values[i], &accept_names[j])) {
+ pres->pres_type = j;
+ found = 1;
+ break;
+ }
+ }
+ }
+ pj_assert(found );
+ }
+
+ (*cb.on_received_request)(pres, rdata, expires);
+}
+
+/*
+ * This callback is called by event framework when the subscription is
+ * terminated.
+ */
+static void on_sub_terminated(pjsip_event_sub *sub, const pj_str_t *reason)
+{
+ pjsip_presentity *pres = sub->user_data;
+ if (cb.on_terminated)
+ (*cb.on_terminated)(pres, reason);
+}
+
+/*
+ * This callback is called by event framework when it receives incoming
+ * SUBSCRIBE request to refresh the subscription.
+ */
+static void on_sub_received_refresh(pjsip_event_sub *sub, pjsip_rx_data *rdata)
+{
+ pjsip_presentity *pres = sub->user_data;
+ if (cb.on_received_refresh)
+ (*cb.on_received_refresh)(pres, rdata);
+}
+
+/*
+ * This callback is called by event framework when it receives incoming
+ * NOTIFY request.
+ */
+static void on_received_notify(pjsip_event_sub *sub, pjsip_rx_data *rdata)
+{
+ pjsip_presentity *pres = sub->user_data;
+
+ if (cb.on_received_update) {
+ pj_status_t is_open;
+ pjsip_msg_body *body;
+ int i;
+
+ body = rdata->msg->body;
+ if (!body)
+ return;
+
+ for (i=0; i<sizeof(accept_types)/sizeof(accept_types[0]); ++i) {
+ if (!pj_stricmp(&body->content_type.type, &accept_types[i].type) &&
+ !pj_stricmp(&body->content_type.subtype, &accept_types[i].subtype))
+ {
+ break;
+ }
+ }
+
+ if (i==PJSIP_PRES_TYPE_PIDF) {
+ pjpidf_pres *pres;
+ pjpidf_tuple *tuple;
+ pjpidf_status *status;
+
+ pres = pjpidf_parse(rdata->pool, body->data, body->len);
+ if (!pres)
+ return;
+ tuple = pjpidf_pres_get_first_tuple(pres);
+ if (!tuple)
+ return;
+ status = pjpidf_tuple_get_status(tuple);
+ if (!status)
+ return;
+ is_open = pjpidf_status_is_basic_open(status);
+
+ } else if (i==PJSIP_PRES_TYPE_XPIDF) {
+ pjxpidf_pres *pres;
+
+ pres = pjxpidf_parse(rdata->pool, body->data, body->len);
+ if (!pres)
+ return;
+ is_open = pjxpidf_get_status(pres);
+
+ } else {
+ return;
+ }
+
+ (*cb.on_received_update)(pres, is_open);
+ }
+}
+
diff --git a/pjsip/src/pjsip-simple/xpidf.c b/pjsip/src/pjsip-simple/xpidf.c
new file mode 100644
index 00000000..5787f674
--- /dev/null
+++ b/pjsip/src/pjsip-simple/xpidf.c
@@ -0,0 +1,279 @@
+/* $Id$
+ *
+ */
+#include <pjsip_simple/xpidf.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/guid.h>
+
+static pj_str_t PRESENCE = { "presence", 8 };
+static pj_str_t STATUS = { "status", 6 };
+static pj_str_t OPEN = { "open", 4 };
+static pj_str_t CLOSED = { "closed", 6 };
+static pj_str_t URI = { "uri", 3 };
+static pj_str_t ATOM = { "atom", 4 };
+static pj_str_t ATOMID = { "atomid", 6 };
+static pj_str_t ADDRESS = { "address", 7 };
+static pj_str_t SUBSCRIBE_PARAM = { ";method=SUBSCRIBE", 17 };
+static pj_str_t PRESENTITY = { "presentity", 10 };
+static pj_str_t 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(pool, sizeof(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(pool, sizeof(*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, &PRESENCE, NULL);
+
+ /* <presentity> */
+ presentity = xml_create_node(pool, &PRESENTITY, NULL);
+ pj_xml_add_node(pres, presentity);
+
+ /* uri attribute */
+ uri.ptr = pj_pool_alloc(pool, uri_cstr->slen + SUBSCRIBE_PARAM.slen);
+ pj_strcpy( &uri, uri_cstr);
+ pj_strcat( &uri, &SUBSCRIBE_PARAM);
+ attr = xml_create_attr(pool, &URI, &uri);
+ pj_xml_add_attr(presentity, attr);
+
+ /* <atom> */
+ atom = xml_create_node(pool, &ATOM, NULL);
+ pj_xml_add_node(pres, atom);
+
+ /* atom id */
+ pj_create_unique_string(pool, &tmp);
+ attr = xml_create_attr(pool, &ATOMID, &tmp);
+ pj_xml_add_attr(atom, attr);
+
+ /* address */
+ addr = xml_create_node(pool, &ADDRESS, NULL);
+ pj_xml_add_node(atom, addr);
+
+ /* address'es uri */
+ attr = xml_create_attr(pool, &URI, uri_cstr);
+ pj_xml_add_attr(addr, attr);
+
+ /* status */
+ status = xml_create_node(pool, &STATUS, NULL);
+ pj_xml_add_node(addr, status);
+
+ /* status attr */
+ attr = xml_create_attr(pool, &STATUS, &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, &PRESENCE) != 0)
+ return NULL;
+ if (pj_xml_find_attr(pres, &URI, NULL) == NULL)
+ return NULL;
+
+ /* Validate <presentity> */
+ node = pj_xml_find_node(pres, &PRESENTITY);
+ if (node == NULL)
+ return NULL;
+
+ /* Validate <atom> */
+ node = pj_xml_find_node(pres, &ATOM);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &ATOMID, NULL) == NULL)
+ return NULL;
+
+ /* Address */
+ node = pj_xml_find_node(node, &ADDRESS);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &URI, NULL) == NULL)
+ return NULL;
+
+
+ /* Status */
+ node = pj_xml_find_node(node, &STATUS);
+ if (node == NULL)
+ return NULL;
+ if (pj_xml_find_attr(node, &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, &PRESENTITY);
+ if (!presentity)
+ return &EMPTY_STRING;
+
+ attr = pj_xml_find_attr(presentity, &URI, NULL);
+ if (!attr)
+ return &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, &PRESENTITY);
+ if (!presentity) {
+ pj_assert(0);
+ return -1;
+ }
+ atom = pj_xml_find_node(pres, &ATOM);
+ if (!atom) {
+ pj_assert(0);
+ return -1;
+ }
+ addr = pj_xml_find_node(atom, &ADDRESS);
+ if (!addr) {
+ pj_assert(0);
+ return -1;
+ }
+
+ /* Set uri in presentity */
+ attr = pj_xml_find_attr(presentity, &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, &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, &ATOM);
+ if (!atom) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+ addr = pj_xml_find_node(atom, &ADDRESS);
+ if (!addr) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+ status = pj_xml_find_node(atom, &STATUS);
+ if (!status) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+ attr = pj_xml_find_attr(status, &STATUS, NULL);
+ if (!attr) {
+ pj_assert(0);
+ return PJ_FALSE;
+ }
+
+ return pj_stricmp(&attr->value, &OPEN) ? 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, &ATOM);
+ if (!atom) {
+ pj_assert(0);
+ return -1;
+ }
+ addr = pj_xml_find_node(atom, &ADDRESS);
+ if (!addr) {
+ pj_assert(0);
+ return -1;
+ }
+ status = pj_xml_find_node(addr, &STATUS);
+ if (!status) {
+ pj_assert(0);
+ return -1;
+ }
+ attr = pj_xml_find_attr(status, &STATUS, NULL);
+ if (!attr) {
+ pj_assert(0);
+ return -1;
+ }
+
+ attr->value = ( online_status ? OPEN : CLOSED );
+ return 0;
+}
+