summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip-simple/event_notify.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip/src/pjsip-simple/event_notify.c')
-rw-r--r--pjsip/src/pjsip-simple/event_notify.c3288
1 files changed, 1644 insertions, 1644 deletions
diff --git a/pjsip/src/pjsip-simple/event_notify.c b/pjsip/src/pjsip-simple/event_notify.c
index 4879f884..25869c40 100644
--- a/pjsip/src/pjsip-simple/event_notify.c
+++ b/pjsip/src/pjsip-simple/event_notify.c
@@ -1,1644 +1,1644 @@
-/* $Id$ */
-/*
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-#include <pjsip_simple/event_notify.h>
-#include <pjsip/sip_msg.h>
-#include <pjsip/sip_util.h>
-#include <pjsip/sip_endpoint.h>
-#include <pjsip/sip_module.h>
-#include <pjsip/sip_transaction.h>
-#include <pjsip/sip_event.h>
-#include <pj/pool.h>
-#include <pj/timer.h>
-#include <pj/string.h>
-#include <pj/hash.h>
-#include <pj/os.h>
-#include <pj/except.h>
-#include <pj/log.h>
-#include <pj/guid.h>
-
-#define THIS_FILE "event_sub"
-
-/* String names for state.
- * The names here should be compliant with sub_state names in RFC3265.
- */
-static const pj_str_t state[] = {
- { "null", 4 },
- { "active", 6 },
- { "pending", 7 },
- { "terminated", 10 },
- { "unknown", 7 }
-};
-
-/* Timer IDs */
-#define TIMER_ID_REFRESH 1
-#define TIMER_ID_UAS_EXPIRY 2
-
-/* Static configuration. */
-#define SECONDS_BEFORE_EXPIRY 10
-#define MGR_POOL_SIZE 512
-#define MGR_POOL_INC 0
-#define SUB_POOL_SIZE 2048
-#define SUB_POOL_INC 0
-#define HASH_TABLE_SIZE 32
-
-/* Static vars. */
-static int mod_id;
-static const pjsip_method SUBSCRIBE = { PJSIP_OTHER_METHOD, {"SUBSCRIBE", 9}};
-static const pjsip_method NOTIFY = { PJSIP_OTHER_METHOD, { "NOTIFY", 6}};
-
-typedef struct package
-{
- PJ_DECL_LIST_MEMBER(struct package)
- pj_str_t event;
- int accept_cnt;
- pj_str_t *accept;
- pjsip_event_sub_pkg_cb cb;
-} package;
-
-/* Event subscription manager singleton instance. */
-static struct pjsip_event_sub_mgr
-{
- pj_pool_t *pool;
- pj_hash_table_t *ht;
- pjsip_endpoint *endpt;
- pj_mutex_t *mutex;
- pjsip_allow_events_hdr *allow_events;
- package pkg_list;
-} mgr;
-
-/* Fordward declarations for static functions. */
-static pj_status_t mod_init(pjsip_endpoint *, pjsip_module *, pj_uint32_t);
-static pj_status_t mod_deinit(pjsip_module*);
-static void tsx_handler(pjsip_module*, pjsip_event*);
-static pjsip_event_sub *find_sub(pjsip_rx_data *);
-static void on_subscribe_request(pjsip_transaction*, pjsip_rx_data*);
-static void on_subscribe_response(void *, pjsip_event*);
-static void on_notify_request(pjsip_transaction *, pjsip_rx_data*);
-static void on_notify_response(void *, pjsip_event *);
-static void refresh_timer_cb(pj_timer_heap_t*, pj_timer_entry*);
-static void uas_expire_timer_cb(pj_timer_heap_t*, pj_timer_entry*);
-static pj_status_t send_sub_refresh( pjsip_event_sub *sub );
-
-/* Module descriptor. */
-static pjsip_module event_sub_module =
-{
- {"EventSub", 8}, /* Name. */
- 0, /* Flag */
- 128, /* Priority */
- &mgr, /* User data. */
- 2, /* Number of methods supported . */
- { &SUBSCRIBE, &NOTIFY }, /* Array of methods */
- &mod_init, /* init_module() */
- NULL, /* start_module() */
- &mod_deinit, /* deinit_module() */
- &tsx_handler, /* tsx_handler() */
-};
-
-/*
- * Module initialization.
- * This will be called by endpoint when it initializes all modules.
- */
-static pj_status_t mod_init( pjsip_endpoint *endpt,
- struct pjsip_module *mod, pj_uint32_t id )
-{
- pj_pool_t *pool;
-
- pool = pjsip_endpt_create_pool(endpt, "esubmgr", MGR_POOL_SIZE, MGR_POOL_INC);
- if (!pool)
- return -1;
-
- /* Manager initialization: create hash table and mutex. */
- mgr.pool = pool;
- mgr.endpt = endpt;
- mgr.ht = pj_hash_create(pool, HASH_TABLE_SIZE);
- if (!mgr.ht)
- return -1;
-
- mgr.mutex = pj_mutex_create(pool, "esubmgr", PJ_MUTEX_SIMPLE);
- if (!mgr.mutex)
- return -1;
-
- /* Attach manager to module. */
- mod->mod_data = &mgr;
-
- /* Init package list. */
- pj_list_init(&mgr.pkg_list);
-
- /* Init Allow-Events header. */
- mgr.allow_events = pjsip_allow_events_hdr_create(mgr.pool);
-
- /* Save the module ID. */
- mod_id = id;
-
- pjsip_event_notify_init_parser();
- return 0;
-}
-
-/*
- * Module deinitialization.
- * Called by endpoint.
- */
-static pj_status_t mod_deinit( struct pjsip_module *mod )
-{
- pj_mutex_lock(mgr.mutex);
- pj_mutex_destroy(mgr.mutex);
- pjsip_endpt_destroy_pool(mgr.endpt, mgr.pool);
- return 0;
-}
-
-/*
- * This public function is called by application to register callback.
- * In exchange, the instance of the module is returned.
- */
-PJ_DEF(pjsip_module*) pjsip_event_sub_get_module(void)
-{
- return &event_sub_module;
-}
-
-/*
- * Register event package.
- */
-PJ_DEF(pj_status_t) pjsip_event_sub_register_pkg( const pj_str_t *event,
- int accept_cnt,
- const pj_str_t accept[],
- const pjsip_event_sub_pkg_cb *cb )
-{
- package *pkg;
- int i;
-
- pj_mutex_lock(mgr.mutex);
-
- /* Create and register new package. */
- pkg = pj_pool_alloc(mgr.pool, sizeof(*pkg));
- pj_strdup(mgr.pool, &pkg->event, event);
- pj_list_insert_before(&mgr.pkg_list, pkg);
-
- /* Save Accept specification. */
- pkg->accept_cnt = accept_cnt;
- pkg->accept = pj_pool_alloc(mgr.pool, accept_cnt*sizeof(pj_str_t));
- for (i=0; i<accept_cnt; ++i) {
- pj_strdup(mgr.pool, &pkg->accept[i], &accept[i]);
- }
-
- /* Copy callback. */
- pj_memcpy(&pkg->cb, cb, sizeof(*cb));
-
- /* Update Allow-Events header. */
- pj_assert(mgr.allow_events->event_cnt < PJSIP_MAX_ALLOW_EVENTS);
- mgr.allow_events->events[mgr.allow_events->event_cnt++] = pkg->event;
-
- pj_mutex_unlock(mgr.mutex);
- return 0;
-}
-
-/*
- * Create subscription key (for hash table).
- */
-static void create_subscriber_key( pj_str_t *key, pj_pool_t *pool,
- pjsip_role_e role,
- const pj_str_t *call_id, const pj_str_t *from_tag)
-{
- char *p;
-
- p = key->ptr = pj_pool_alloc(pool, call_id->slen + from_tag->slen + 3);
- *p++ = (role == PJSIP_ROLE_UAS ? 'S' : 'C');
- *p++ = '$';
- pj_memcpy(p, call_id->ptr, call_id->slen);
- p += call_id->slen;
- *p++ = '$';
- pj_memcpy(p, from_tag->ptr, from_tag->slen);
- p += from_tag->slen;
-
- key->slen = p - key->ptr;
-}
-
-
-/*
- * Create UAC subscription.
- */
-PJ_DEF(pjsip_event_sub*) pjsip_event_sub_create( pjsip_endpoint *endpt,
- const pj_str_t *from,
- const pj_str_t *to,
- const pj_str_t *event,
- int expires,
- int accept_cnt,
- const pj_str_t accept[],
- void *user_data,
- const pjsip_event_sub_cb *cb)
-{
- pjsip_tx_data *tdata;
- pj_pool_t *pool;
- const pjsip_hdr *hdr;
- pjsip_event_sub *sub;
- PJ_USE_EXCEPTION;
-
- PJ_LOG(5,(THIS_FILE, "Creating event subscription %.*s to %.*s",
- event->slen, event->ptr, to->slen, to->ptr));
-
- /* Create pool for the event subscription. */
- pool = pjsip_endpt_create_pool(endpt, "esub", SUB_POOL_SIZE, SUB_POOL_INC);
- if (!pool) {
- return NULL;
- }
-
- /* Init subscription. */
- sub = pj_pool_calloc(pool, 1, sizeof(*sub));
- sub->pool = pool;
- sub->endpt = endpt;
- sub->role = PJSIP_ROLE_UAC;
- sub->state = PJSIP_EVENT_SUB_STATE_PENDING;
- sub->state_str = state[sub->state];
- sub->user_data = user_data;
- sub->timer.id = 0;
- sub->default_interval = expires;
- pj_memcpy(&sub->cb, cb, sizeof(*cb));
- pj_list_init(&sub->auth_sess);
- pj_list_init(&sub->route_set);
- sub->mutex = pj_mutex_create(pool, "esub", PJ_MUTEX_RECURSE);
- if (!sub->mutex) {
- pjsip_endpt_destroy_pool(endpt, pool);
- return NULL;
- }
-
- /* The easiest way to parse the parameters is to create a dummy request! */
- tdata = pjsip_endpt_create_request( endpt, &SUBSCRIBE, to, from, to, from,
- NULL, -1, NULL);
- if (!tdata) {
- pj_mutex_destroy(sub->mutex);
- pjsip_endpt_destroy_pool(endpt, pool);
- return NULL;
- }
-
- /*
- * Duplicate headers in the request to our structure.
- */
- PJ_TRY {
- int i;
-
- /* From */
- hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_FROM, NULL);
- pj_assert(hdr != NULL);
- sub->from = pjsip_hdr_clone(pool, hdr);
-
- /* To */
- hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_TO, NULL);
- pj_assert(hdr != NULL);
- sub->to = pjsip_hdr_clone(pool, hdr);
-
- /* Contact. */
- sub->contact = pjsip_contact_hdr_create(pool);
- sub->contact->uri = sub->from->uri;
-
- /* Call-ID */
- hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CALL_ID, NULL);
- pj_assert(hdr != NULL);
- sub->call_id = pjsip_hdr_clone(pool, hdr);
-
- /* CSeq */
- sub->cseq = pj_rand() % 0xFFFF;
-
- /* Event. */
- sub->event = pjsip_event_hdr_create(sub->pool);
- pj_strdup(pool, &sub->event->event_type, event);
-
- /* Expires. */
- sub->uac_expires = pjsip_expires_hdr_create(pool);
- sub->uac_expires->ivalue = expires;
-
- /* Accept. */
- sub->local_accept = pjsip_accept_hdr_create(pool);
- for (i=0; i<accept_cnt && i < PJSIP_MAX_ACCEPT_COUNT; ++i) {
- sub->local_accept->count++;
- pj_strdup(sub->pool, &sub->local_accept->values[i], &accept[i]);
- }
-
- /* Register to hash table. */
- create_subscriber_key( &sub->key, pool, PJSIP_ROLE_UAC,
- &sub->call_id->id, &sub->from->tag);
- pj_mutex_lock( mgr.mutex );
- pj_hash_set( pool, mgr.ht, sub->key.ptr, sub->key.slen, sub);
- pj_mutex_unlock( mgr.mutex );
-
- }
- PJ_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 );
- }
-}
-
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip_simple/event_notify.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_event.h>
+#include <pj/pool.h>
+#include <pj/timer.h>
+#include <pj/string.h>
+#include <pj/hash.h>
+#include <pj/os.h>
+#include <pj/except.h>
+#include <pj/log.h>
+#include <pj/guid.h>
+
+#define THIS_FILE "event_sub"
+
+/* String names for state.
+ * The names here should be compliant with sub_state names in RFC3265.
+ */
+static const pj_str_t state[] = {
+ { "null", 4 },
+ { "active", 6 },
+ { "pending", 7 },
+ { "terminated", 10 },
+ { "unknown", 7 }
+};
+
+/* Timer IDs */
+#define TIMER_ID_REFRESH 1
+#define TIMER_ID_UAS_EXPIRY 2
+
+/* Static configuration. */
+#define SECONDS_BEFORE_EXPIRY 10
+#define MGR_POOL_SIZE 512
+#define MGR_POOL_INC 0
+#define SUB_POOL_SIZE 2048
+#define SUB_POOL_INC 0
+#define HASH_TABLE_SIZE 32
+
+/* Static vars. */
+static int mod_id;
+static const pjsip_method SUBSCRIBE = { PJSIP_OTHER_METHOD, {"SUBSCRIBE", 9}};
+static const pjsip_method NOTIFY = { PJSIP_OTHER_METHOD, { "NOTIFY", 6}};
+
+typedef struct package
+{
+ PJ_DECL_LIST_MEMBER(struct package)
+ pj_str_t event;
+ int accept_cnt;
+ pj_str_t *accept;
+ pjsip_event_sub_pkg_cb cb;
+} package;
+
+/* Event subscription manager singleton instance. */
+static struct pjsip_event_sub_mgr
+{
+ pj_pool_t *pool;
+ pj_hash_table_t *ht;
+ pjsip_endpoint *endpt;
+ pj_mutex_t *mutex;
+ pjsip_allow_events_hdr *allow_events;
+ package pkg_list;
+} mgr;
+
+/* Fordward declarations for static functions. */
+static pj_status_t mod_init(pjsip_endpoint *, pjsip_module *, pj_uint32_t);
+static pj_status_t mod_deinit(pjsip_module*);
+static void tsx_handler(pjsip_module*, pjsip_event*);
+static pjsip_event_sub *find_sub(pjsip_rx_data *);
+static void on_subscribe_request(pjsip_transaction*, pjsip_rx_data*);
+static void on_subscribe_response(void *, pjsip_event*);
+static void on_notify_request(pjsip_transaction *, pjsip_rx_data*);
+static void on_notify_response(void *, pjsip_event *);
+static void refresh_timer_cb(pj_timer_heap_t*, pj_timer_entry*);
+static void uas_expire_timer_cb(pj_timer_heap_t*, pj_timer_entry*);
+static pj_status_t send_sub_refresh( pjsip_event_sub *sub );
+
+/* Module descriptor. */
+static pjsip_module event_sub_module =
+{
+ {"EventSub", 8}, /* Name. */
+ 0, /* Flag */
+ 128, /* Priority */
+ &mgr, /* User data. */
+ 2, /* Number of methods supported . */
+ { &SUBSCRIBE, &NOTIFY }, /* Array of methods */
+ &mod_init, /* init_module() */
+ NULL, /* start_module() */
+ &mod_deinit, /* deinit_module() */
+ &tsx_handler, /* tsx_handler() */
+};
+
+/*
+ * Module initialization.
+ * This will be called by endpoint when it initializes all modules.
+ */
+static pj_status_t mod_init( pjsip_endpoint *endpt,
+ struct pjsip_module *mod, pj_uint32_t id )
+{
+ pj_pool_t *pool;
+
+ pool = pjsip_endpt_create_pool(endpt, "esubmgr", MGR_POOL_SIZE, MGR_POOL_INC);
+ if (!pool)
+ return -1;
+
+ /* Manager initialization: create hash table and mutex. */
+ mgr.pool = pool;
+ mgr.endpt = endpt;
+ mgr.ht = pj_hash_create(pool, HASH_TABLE_SIZE);
+ if (!mgr.ht)
+ return -1;
+
+ mgr.mutex = pj_mutex_create(pool, "esubmgr", PJ_MUTEX_SIMPLE);
+ if (!mgr.mutex)
+ return -1;
+
+ /* Attach manager to module. */
+ mod->mod_data = &mgr;
+
+ /* Init package list. */
+ pj_list_init(&mgr.pkg_list);
+
+ /* Init Allow-Events header. */
+ mgr.allow_events = pjsip_allow_events_hdr_create(mgr.pool);
+
+ /* Save the module ID. */
+ mod_id = id;
+
+ pjsip_event_notify_init_parser();
+ return 0;
+}
+
+/*
+ * Module deinitialization.
+ * Called by endpoint.
+ */
+static pj_status_t mod_deinit( struct pjsip_module *mod )
+{
+ pj_mutex_lock(mgr.mutex);
+ pj_mutex_destroy(mgr.mutex);
+ pjsip_endpt_destroy_pool(mgr.endpt, mgr.pool);
+ return 0;
+}
+
+/*
+ * This public function is called by application to register callback.
+ * In exchange, the instance of the module is returned.
+ */
+PJ_DEF(pjsip_module*) pjsip_event_sub_get_module(void)
+{
+ return &event_sub_module;
+}
+
+/*
+ * Register event package.
+ */
+PJ_DEF(pj_status_t) pjsip_event_sub_register_pkg( const pj_str_t *event,
+ int accept_cnt,
+ const pj_str_t accept[],
+ const pjsip_event_sub_pkg_cb *cb )
+{
+ package *pkg;
+ int i;
+
+ pj_mutex_lock(mgr.mutex);
+
+ /* Create and register new package. */
+ pkg = pj_pool_alloc(mgr.pool, sizeof(*pkg));
+ pj_strdup(mgr.pool, &pkg->event, event);
+ pj_list_insert_before(&mgr.pkg_list, pkg);
+
+ /* Save Accept specification. */
+ pkg->accept_cnt = accept_cnt;
+ pkg->accept = pj_pool_alloc(mgr.pool, accept_cnt*sizeof(pj_str_t));
+ for (i=0; i<accept_cnt; ++i) {
+ pj_strdup(mgr.pool, &pkg->accept[i], &accept[i]);
+ }
+
+ /* Copy callback. */
+ pj_memcpy(&pkg->cb, cb, sizeof(*cb));
+
+ /* Update Allow-Events header. */
+ pj_assert(mgr.allow_events->event_cnt < PJSIP_MAX_ALLOW_EVENTS);
+ mgr.allow_events->events[mgr.allow_events->event_cnt++] = pkg->event;
+
+ pj_mutex_unlock(mgr.mutex);
+ return 0;
+}
+
+/*
+ * Create subscription key (for hash table).
+ */
+static void create_subscriber_key( pj_str_t *key, pj_pool_t *pool,
+ pjsip_role_e role,
+ const pj_str_t *call_id, const pj_str_t *from_tag)
+{
+ char *p;
+
+ p = key->ptr = pj_pool_alloc(pool, call_id->slen + from_tag->slen + 3);
+ *p++ = (role == PJSIP_ROLE_UAS ? 'S' : 'C');
+ *p++ = '$';
+ pj_memcpy(p, call_id->ptr, call_id->slen);
+ p += call_id->slen;
+ *p++ = '$';
+ pj_memcpy(p, from_tag->ptr, from_tag->slen);
+ p += from_tag->slen;
+
+ key->slen = p - key->ptr;
+}
+
+
+/*
+ * Create UAC subscription.
+ */
+PJ_DEF(pjsip_event_sub*) pjsip_event_sub_create( pjsip_endpoint *endpt,
+ const pj_str_t *from,
+ const pj_str_t *to,
+ const pj_str_t *event,
+ int expires,
+ int accept_cnt,
+ const pj_str_t accept[],
+ void *user_data,
+ const pjsip_event_sub_cb *cb)
+{
+ pjsip_tx_data *tdata;
+ pj_pool_t *pool;
+ const pjsip_hdr *hdr;
+ pjsip_event_sub *sub;
+ PJ_USE_EXCEPTION;
+
+ PJ_LOG(5,(THIS_FILE, "Creating event subscription %.*s to %.*s",
+ event->slen, event->ptr, to->slen, to->ptr));
+
+ /* Create pool for the event subscription. */
+ pool = pjsip_endpt_create_pool(endpt, "esub", SUB_POOL_SIZE, SUB_POOL_INC);
+ if (!pool) {
+ return NULL;
+ }
+
+ /* Init subscription. */
+ sub = pj_pool_calloc(pool, 1, sizeof(*sub));
+ sub->pool = pool;
+ sub->endpt = endpt;
+ sub->role = PJSIP_ROLE_UAC;
+ sub->state = PJSIP_EVENT_SUB_STATE_PENDING;
+ sub->state_str = state[sub->state];
+ sub->user_data = user_data;
+ sub->timer.id = 0;
+ sub->default_interval = expires;
+ pj_memcpy(&sub->cb, cb, sizeof(*cb));
+ pj_list_init(&sub->auth_sess);
+ pj_list_init(&sub->route_set);
+ sub->mutex = pj_mutex_create(pool, "esub", PJ_MUTEX_RECURSE);
+ if (!sub->mutex) {
+ pjsip_endpt_destroy_pool(endpt, pool);
+ return NULL;
+ }
+
+ /* The easiest way to parse the parameters is to create a dummy request! */
+ tdata = pjsip_endpt_create_request( endpt, &SUBSCRIBE, to, from, to, from,
+ NULL, -1, NULL);
+ if (!tdata) {
+ pj_mutex_destroy(sub->mutex);
+ pjsip_endpt_destroy_pool(endpt, pool);
+ return NULL;
+ }
+
+ /*
+ * Duplicate headers in the request to our structure.
+ */
+ PJ_TRY {
+ int i;
+
+ /* From */
+ hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_FROM, NULL);
+ pj_assert(hdr != NULL);
+ sub->from = pjsip_hdr_clone(pool, hdr);
+
+ /* To */
+ hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_TO, NULL);
+ pj_assert(hdr != NULL);
+ sub->to = pjsip_hdr_clone(pool, hdr);
+
+ /* Contact. */
+ sub->contact = pjsip_contact_hdr_create(pool);
+ sub->contact->uri = sub->from->uri;
+
+ /* Call-ID */
+ hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CALL_ID, NULL);
+ pj_assert(hdr != NULL);
+ sub->call_id = pjsip_hdr_clone(pool, hdr);
+
+ /* CSeq */
+ sub->cseq = pj_rand() % 0xFFFF;
+
+ /* Event. */
+ sub->event = pjsip_event_hdr_create(sub->pool);
+ pj_strdup(pool, &sub->event->event_type, event);
+
+ /* Expires. */
+ sub->uac_expires = pjsip_expires_hdr_create(pool);
+ sub->uac_expires->ivalue = expires;
+
+ /* Accept. */
+ sub->local_accept = pjsip_accept_hdr_create(pool);
+ for (i=0; i<accept_cnt && i < PJSIP_MAX_ACCEPT_COUNT; ++i) {
+ sub->local_accept->count++;
+ pj_strdup(sub->pool, &sub->local_accept->values[i], &accept[i]);
+ }
+
+ /* Register to hash table. */
+ create_subscriber_key( &sub->key, pool, PJSIP_ROLE_UAC,
+ &sub->call_id->id, &sub->from->tag);
+ pj_mutex_lock( mgr.mutex );
+ pj_hash_set( pool, mgr.ht, sub->key.ptr, sub->key.slen, sub);
+ pj_mutex_unlock( mgr.mutex );
+
+ }
+ PJ_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 );
+ }
+}
+