diff options
Diffstat (limited to 'pjsip/src/pjsip-simple')
-rw-r--r-- | pjsip/src/pjsip-simple/event_notify.c | 1644 | ||||
-rw-r--r-- | pjsip/src/pjsip-simple/evsub.c | 1785 | ||||
-rw-r--r-- | pjsip/src/pjsip-simple/evsub_msg.c (renamed from pjsip/src/pjsip-simple/event_notify_msg.c) | 218 | ||||
-rw-r--r-- | pjsip/src/pjsip-simple/messaging.c | 352 | ||||
-rw-r--r-- | pjsip/src/pjsip-simple/pidf.c | 6 | ||||
-rw-r--r-- | pjsip/src/pjsip-simple/presence.c | 1057 | ||||
-rw-r--r-- | pjsip/src/pjsip-simple/xpidf.c | 5 |
7 files changed, 2684 insertions, 2383 deletions
diff --git a/pjsip/src/pjsip-simple/event_notify.c b/pjsip/src/pjsip-simple/event_notify.c deleted file mode 100644 index 51fe7694..00000000 --- a/pjsip/src/pjsip-simple/event_notify.c +++ /dev/null @@ -1,1644 +0,0 @@ -/* $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_CATCH_ANY { - 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/evsub.c b/pjsip/src/pjsip-simple/evsub.c new file mode 100644 index 00000000..30bd8525 --- /dev/null +++ b/pjsip/src/pjsip-simple/evsub.c @@ -0,0 +1,1785 @@ +/* $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/evsub.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip-simple/errno.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_dialog.h> +#include <pjsip/sip_auth.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_event.h> +#include <pj/assert.h> +#include <pj/guid.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/string.h> + + +#define THIS_FILE "evsub.c" + +/* + * Global constant + */ + +/* Let's define this enum, so that it'll trigger compilation error + * when somebody define the same enum in sip_msg.h + */ +enum +{ + PJSIP_SUBSCRIBE_METHOD = PJSIP_OTHER_METHOD, + PJSIP_NOTIFY_METHOD = PJSIP_OTHER_METHOD +}; + +const pjsip_method pjsip_subscribe_method = +{ + PJSIP_SUBSCRIBE_METHOD, + { "SUBSCRIBE", 9 } +}; + +const pjsip_method pjsip_notify_method = +{ + PJSIP_NOTIFY_METHOD, + { "NOTIFY", 6 } +}; + +/* + * Static prototypes. + */ +static void mod_evsub_on_tsx_state(pjsip_transaction*, pjsip_event*); +static pj_status_t mod_evsub_unload(void); + + +/* + * State names. + */ +static pj_str_t evsub_state_names[] = +{ + { "NULL", 4}, + { "SENT", 4}, + { "ACCEPTED", 8}, + { "PENDING", 7}, + { "ACTIVE", 6}, + { "TERMINATED", 10}, + { "UNKNOWN", 7} +}; + +/* + * Timer constants. + */ + +/* Number of seconds to send SUBSCRIBE before the actual expiration */ +#define TIME_UAC_REFRESH 5 + +/* Time to wait for the final NOTIFY after sending unsubscription */ +#define TIME_UAC_TERMINATE 5 + +/* If client responds NOTIFY with non-2xx final response (such as 401), + * wait for this seconds for further NOTIFY, otherwise client will + * unsubscribe + */ +#define TIME_UAC_WAIT_NOTIFY 5 + + +/* + * Timer id + */ +enum timer_id +{ + /* No timer. */ + TIMER_TYPE_NONE, + + /* Time to refresh client subscription. + * The action is to call on_client_refresh() callback. + */ + TIMER_TYPE_UAC_REFRESH, + + /* UAS timeout after to subscription refresh. + * The action is to call on_server_timeout() callback. + */ + TIMER_TYPE_UAS_TIMEOUT, + + /* UAC waiting for final NOTIFY after unsubscribing + * The action is to terminate. + */ + TIMER_TYPE_UAC_TERMINATE, + + /* UAC waiting for further NOTIFY after sending non-2xx response to + * NOTIFY. The action is to unsubscribe. + */ + TIMER_TYPE_UAC_WAIT_NOTIFY, + +}; + +static const char *timer_names[] = +{ + "None", + "UAC_REFRESH", + "UAS_TIMEOUT" + "UAC_TERMINATE", + "UAC_WAIT_NOTIFY", +}; + +/* + * Definition of event package. + */ +struct evpkg +{ + PJ_DECL_LIST_MEMBER(struct evpkg); + + pj_str_t pkg_name; + pjsip_module *pkg_mod; + unsigned pkg_expires; + pjsip_accept_hdr *pkg_accept; +}; + + +/* + * Event subscription module (mod-evsub). + */ +static struct mod_evsub +{ + pjsip_module mod; + pj_pool_t *pool; + pjsip_endpoint *endpt; + struct evpkg pkg_list; + pjsip_allow_events_hdr *allow_events_hdr; + +} mod_evsub = +{ + { + NULL, NULL, /* prev, next. */ + { "mod-evsub", 9 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* User data. */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + &mod_evsub_unload, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + &mod_evsub_on_tsx_state, /* on_tsx_state() */ + } +}; + + +/* + * Event subscription session. + */ +struct pjsip_evsub +{ + char obj_name[PJ_MAX_OBJ_NAME]; /**< Name. */ + pj_pool_t *pool; /**< Pool. */ + pjsip_endpoint *endpt; /**< Endpoint instance. */ + pjsip_dialog *dlg; /**< Underlying dialog. */ + struct evpkg *pkg; /**< The event package. */ + pjsip_evsub_user user; /**< Callback. */ + pjsip_role_e role; /**< UAC=subscriber, UAS=notifier */ + pjsip_evsub_state state; /**< Subscription state. */ + pj_str_t state_str; /**< String describing the state. */ + pjsip_evsub_state dst_state; /**< Pending state to be set. */ + pj_str_t dst_state_str;/**< Pending state to be set. */ + pjsip_method method; /**< Method that established subscr.*/ + pjsip_event_hdr *event; /**< Event description. */ + pjsip_expires_hdr *expires; /**< Expires header */ + pjsip_accept_hdr *accept; /**< Local Accept header. */ + + pj_time_val refresh_time; /**< Time to refresh. */ + pj_timer_entry timer; /**< Internal timer. */ + int pending_tsx; /**< Number of pending transactions.*/ + + void *mod_data[PJSIP_MAX_MODULE]; /**< Module data. */ +}; + + +/* + * This is the structure that will be "attached" to dialog. + * The purpose is to allow multiple subscriptions inside a dialog. + */ +struct dlgsub +{ + PJ_DECL_LIST_MEMBER(struct dlgsub); + pjsip_evsub *sub; +}; + + +/* Static vars. */ +static const pj_str_t STR_EVENT = { "Event", 5 }; +static const pj_str_t STR_SUB_STATE = { "Subscription-State", 18 }; +static const pj_str_t STR_TERMINATED = { "terminated", 10 }; +static const pj_str_t STR_ACTIVE = { "active", 6 }; +static const pj_str_t STR_PENDING = { "pending", 7 }; +static const pj_str_t STR_TIMEOUT = { "timeout", 7}; + +/* + * On unload module. + */ +static pj_status_t mod_evsub_unload(void) +{ + pjsip_endpt_release_pool(mod_evsub.endpt, mod_evsub.pool); + mod_evsub.pool = NULL; + + return PJ_SUCCESS; +} + +/* + * Init and register module. + */ +PJ_DEF(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(mod_evsub.mod.id == -1, PJ_EINVALIDOP); + + /* Keep endpoint for future reference: */ + mod_evsub.endpt = endpt; + + /* Init event package list: */ + pj_list_init(&mod_evsub.pkg_list); + + /* Create pool: */ + mod_evsub.pool = pjsip_endpt_create_pool(endpt, "evsub", 4000, 4000); + if (!mod_evsub.pool) + return PJ_ENOMEM; + + /* Register module: */ + status = pjsip_endpt_register_module(endpt, &mod_evsub.mod); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create Allow-Events header: */ + mod_evsub.allow_events_hdr = pjsip_allow_events_hdr_create(mod_evsub.pool); + + /* Register SIP-event specific headers parser: */ + pjsip_evsub_init_parser(); + + return PJ_SUCCESS; + +on_error: + if (mod_evsub.pool) { + pjsip_endpt_release_pool(endpt, mod_evsub.pool); + mod_evsub.pool = NULL; + } + mod_evsub.endpt = NULL; + return status; +} + + +/* + * Get the instance of the module. + */ +PJ_DEF(pjsip_module*) pjsip_evsub_instance(void) +{ + PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, NULL); + + return &mod_evsub.mod; +} + + +/* + * Get the event subscription instance in the transaction. + */ +PJ_DEF(pjsip_evsub*) pjsip_tsx_get_evsub(pjsip_transaction *tsx) +{ + return tsx->mod_data[mod_evsub.mod.id]; +} + + +/* + * Set event subscription's module data. + */ +PJ_DEF(void) pjsip_evsub_set_mod_data( pjsip_evsub *sub, unsigned mod_id, + void *data ) +{ + PJ_ASSERT_ON_FAIL(mod_id < PJSIP_MAX_MODULE, return); + sub->mod_data[mod_id] = data; +} + + +/* + * Get event subscription's module data. + */ +PJ_DEF(void*) pjsip_evsub_get_mod_data( pjsip_evsub *sub, unsigned mod_id ) +{ + PJ_ASSERT_RETURN(mod_id < PJSIP_MAX_MODULE, NULL); + return sub->mod_data[mod_id]; +} + + +/* + * Find registered event package with matching name. + */ +static struct evpkg* find_pkg(const pj_str_t *event_name) +{ + struct evpkg *pkg; + + pkg = mod_evsub.pkg_list.next; + while (pkg != &mod_evsub.pkg_list) { + + if (pj_stricmp(&pkg->pkg_name, event_name) == 0) { + return pkg; + } + + pkg = pkg->next; + } + + return NULL; +} + +/* + * Register an event package + */ +PJ_DEF(pj_status_t) pjsip_evsub_register_pkg( pjsip_module *pkg_mod, + const pj_str_t *event_name, + unsigned expires, + unsigned accept_cnt, + const pj_str_t accept[]) +{ + struct evpkg *pkg; + unsigned i; + + PJ_ASSERT_RETURN(pkg_mod && event_name, PJ_EINVAL); + PJ_ASSERT_RETURN(accept_cnt < PJ_ARRAY_SIZE(pkg->pkg_accept->values), + PJ_ETOOMANY); + + /* Make sure no module with the specified name already registered: */ + + PJ_ASSERT_RETURN(find_pkg(event_name) == NULL, PJSIP_SIMPLE_EPKGEXISTS); + + + /* Create new event package: */ + + pkg = pj_pool_alloc(mod_evsub.pool, sizeof(struct evpkg)); + pkg->pkg_mod = pkg_mod; + pkg->pkg_expires = expires; + pj_strdup(mod_evsub.pool, &pkg->pkg_name, event_name); + + pkg->pkg_accept = pjsip_accept_hdr_create(mod_evsub.pool); + pkg->pkg_accept->count = accept_cnt; + for (i=0; i<accept_cnt; ++i) { + pj_strdup(mod_evsub.pool, &pkg->pkg_accept->values[i], &accept[i]); + } + + /* Add to package list: */ + + pj_list_push_back(&mod_evsub.pkg_list, pkg); + + /* Add to Allow-Events header: */ + + if (mod_evsub.allow_events_hdr->count != + PJ_ARRAY_SIZE(mod_evsub.allow_events_hdr->values)) + { + mod_evsub.allow_events_hdr->values[mod_evsub.allow_events_hdr->count] = + pkg->pkg_name; + ++mod_evsub.allow_events_hdr->count; + } + + + /* Done */ + + PJ_LOG(5,(THIS_FILE, "Event pkg \"%.*s\" registered by %.*s", + (int)event_name->slen, event_name->ptr, + (int)pkg_mod->name.slen, pkg_mod->name.ptr)); + + return PJ_SUCCESS; +} + + + +/* + * Update expiration time. + */ +static void update_expires( pjsip_evsub *sub, pj_uint32_t interval ) +{ + pj_gettimeofday(&sub->refresh_time); + sub->refresh_time.sec += interval; +} + + +/* + * Schedule timer. + */ +static void set_timer( pjsip_evsub *sub, int timer_id, + pj_int32_t seconds) +{ + if (sub->timer.id != TIMER_TYPE_NONE) { + PJ_LOG(5,(sub->obj_name, "%s %s timer", + (timer_id==sub->timer.id ? "Updating" : "Cancelling"), + timer_names[sub->timer.id])); + pjsip_endpt_cancel_timer(sub->endpt, &sub->timer); + sub->timer.id = TIMER_TYPE_NONE; + } + + if (timer_id != TIMER_TYPE_NONE) { + pj_time_val timeout; + + PJ_ASSERT_ON_FAIL(seconds > 0, return); + + timeout.sec = seconds; + timeout.msec = 0; + sub->timer.id = timer_id; + + pjsip_endpt_schedule_timer(sub->endpt, &sub->timer, &timeout); + + PJ_LOG(5,(sub->obj_name, "Timer %s scheduled in %d seconds", + timer_names[sub->timer.id], timeout.sec)); + } +} + + +/* + * Destroy session. + */ +static void evsub_destroy( pjsip_evsub *sub ) +{ + struct dlgsub *dlgsub_head, *dlgsub; + + PJ_LOG(4,(sub->obj_name, "Subscription destroyed")); + + /* Kill timer */ + set_timer(sub, TIMER_TYPE_NONE, 0); + + /* Remote this session from dialog's list of subscription */ + dlgsub_head = sub->dlg->mod_data[mod_evsub.mod.id]; + dlgsub = dlgsub_head->next; + while (dlgsub != dlgsub_head) { + + if (dlgsub->sub == sub) { + pj_list_erase(dlgsub); + break; + } + + dlgsub = dlgsub->next; + } + + /* Decrement dialog's session */ + pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod); +} + +/* + * Set subscription session state. + */ +static void set_state( pjsip_evsub *sub, pjsip_evsub_state state, + const pj_str_t *state_str, pjsip_event *event) +{ + pj_str_t old_state_str = sub->state_str; + + sub->state = state; + + if (state_str && state_str->slen) + pj_strdup_with_null(sub->pool, &sub->state_str, state_str); + else + sub->state_str = evsub_state_names[state]; + + PJ_LOG(4,(sub->obj_name, + "Subscription state changed %.*s --> %.*s", + (int)old_state_str.slen, + old_state_str.ptr, + (int)sub->state_str.slen, + sub->state_str.ptr)); + + if (sub->user.on_evsub_state) + (*sub->user.on_evsub_state)(sub, event); + + if (state == PJSIP_EVSUB_STATE_TERMINATED) { + + if (sub->pending_tsx == 0) { + evsub_destroy(sub); + } + } +} + + +/* + * Timer callback. + */ +static void on_timer( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pjsip_evsub *sub; + int timer_id; + + PJ_UNUSED_ARG(timer_heap); + + sub = entry->user_data; + + pjsip_dlg_inc_lock(sub->dlg); + + timer_id = entry->id; + entry->id = TIMER_TYPE_NONE; + + switch (timer_id) { + + case TIMER_TYPE_UAC_REFRESH: + /* Time for UAC to refresh subscription */ + if (sub->user.on_client_refresh) { + (*sub->user.on_client_refresh)(sub); + } else { + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(5,(sub->obj_name, "Refreshing subscription.")); + status = pjsip_evsub_initiate(sub, &sub->method, + sub->expires->ivalue, + &tdata); + if (status == PJ_SUCCESS) + pjsip_evsub_send_request(sub, tdata); + } + break; + + case TIMER_TYPE_UAS_TIMEOUT: + /* Refresh from UAC has not been received */ + if (sub->user.on_server_timeout) { + (*sub->user.on_server_timeout)(sub); + } else { + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(5,(sub->obj_name, "Timeout waiting for refresh. " + "Sending NOTIFY to terminate.")); + status = pjsip_evsub_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, &STR_TIMEOUT, &tdata); + if (status == PJ_SUCCESS) + pjsip_evsub_send_request(sub, tdata); + } + break; + + case TIMER_TYPE_UAC_TERMINATE: + { + pjsip_event event; + pj_str_t reason = { "unsubscribing", 13}; + + PJSIP_EVENT_INIT_TIMER(event, entry); + PJ_LOG(5,(sub->obj_name, "Timeout waiting for final NOTIFY. " + "Terminating..")); + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, &event); + } + break; + + case TIMER_TYPE_UAC_WAIT_NOTIFY: + { + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(5,(sub->obj_name, + "Timeout waiting for subsequent NOTIFY (we did " + "send non-2xx response for previous NOTIFY). " + "Unsubscribing..")); + status = pjsip_evsub_initiate( sub, &sub->method, 0, &tdata); + if (status == PJ_SUCCESS) + pjsip_evsub_send_request(sub, tdata); + } + break; + + default: + pj_assert(!"Invalid timer id"); + } + + pjsip_dlg_dec_lock(sub->dlg); +} + + +/* + * Create subscription session, used for both client and notifier. + */ +static pj_status_t evsub_create( pjsip_dialog *dlg, + pjsip_role_e role, + const pjsip_evsub_user *user_cb, + const pj_str_t *event, + pjsip_evsub **p_evsub ) +{ + pjsip_evsub *sub; + struct evpkg *pkg; + struct dlgsub *dlgsub_head, *dlgsub; + pj_status_t status; + + /* Make sure there's package register for the event name: */ + + pkg = find_pkg(event); + if (pkg == NULL) + return PJSIP_SIMPLE_ENOPKG; + + + /* Init attributes: */ + + sub = pj_pool_zalloc(dlg->pool, sizeof(struct pjsip_evsub)); + sub->pool = dlg->pool; + sub->endpt = dlg->endpt; + sub->dlg = dlg; + sub->pkg = pkg; + sub->role = role; + sub->state = PJSIP_EVSUB_STATE_NULL; + sub->state_str = evsub_state_names[sub->state]; + sub->expires = pjsip_expires_hdr_create(sub->pool, pkg->pkg_expires); + sub->accept = pjsip_hdr_clone(sub->pool, pkg->pkg_accept); + + sub->timer.user_data = sub; + sub->timer.cb = &on_timer; + + /* Set name. */ + pj_snprintf(sub->obj_name, PJ_ARRAY_SIZE(sub->obj_name), + "evsub%p", sub); + + + /* Copy callback, if any: */ + if (user_cb) + pj_memcpy(&sub->user, user_cb, sizeof(pjsip_evsub_user)); + + + /* Create Event header: */ + sub->event = pjsip_event_hdr_create(sub->pool); + pj_strdup(sub->pool, &sub->event->event_type, event); + + + /* Create subcription list: */ + + dlgsub_head = pj_pool_alloc(sub->pool, sizeof(struct dlgsub)); + dlgsub = pj_pool_alloc(sub->pool, sizeof(struct dlgsub)); + dlgsub->sub = sub; + + pj_list_init(dlgsub_head); + pj_list_push_back(dlgsub_head, dlgsub); + + + /* Register as dialog usage: */ + + status = pjsip_dlg_add_usage(dlg, &mod_evsub.mod, dlgsub_head); + if (status != PJ_SUCCESS) + return status; + + + PJ_LOG(5,(sub->obj_name, "%s subscription created, using dialog %s", + (role==PJSIP_ROLE_UAC ? "UAC" : "UAS"), + dlg->obj_name)); + + *p_evsub = sub; + + return PJ_SUCCESS; +} + + + +/* + * Create client subscription session. + */ +PJ_DEF(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + const pj_str_t *event, + pjsip_evsub **p_evsub) +{ + pjsip_evsub *sub; + pj_status_t status; + + PJ_ASSERT_RETURN(dlg && event && p_evsub, PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); + status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Add unique Id to Event header */ + pj_create_unique_string(sub->pool, &sub->event->id_param); + + /* Increment dlg session. */ + pjsip_dlg_inc_session(sub->dlg, &mod_evsub.mod); + + /* Done */ + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Create server subscription session from incoming request. + */ +PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_rx_data *rdata, + pjsip_evsub **p_evsub) +{ + pjsip_evsub *sub; + pjsip_transaction *tsx; + pjsip_accept_hdr *accept_hdr; + pjsip_event_hdr *event_hdr; + pjsip_expires_hdr *expires_hdr; + pj_status_t status; + + /* Check arguments: */ + PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL); + + /* MUST be request message: */ + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Transaction MUST have been created (in the dialog) */ + tsx = pjsip_rdata_get_tsx(rdata); + PJ_ASSERT_RETURN(tsx != NULL, PJSIP_ENOTSX); + + /* No subscription must have been attached to transaction */ + PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] == NULL, + PJSIP_ETYPEEXISTS); + + /* Package MUST implement on_rx_refresh */ + PJ_ASSERT_RETURN(user_cb->on_rx_refresh, PJ_EINVALIDOP); + + /* Request MUST have "Event" header: */ + + event_hdr = (pjsip_event_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL); + if (event_hdr == NULL) { + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST); + } + + /* Start locking the mutex: */ + + pjsip_dlg_inc_lock(dlg); + + /* Create the session: */ + + status = evsub_create(dlg, PJSIP_UAS_ROLE, user_cb, + &event_hdr->event_type, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Just duplicate Event header from the request */ + sub->event = pjsip_hdr_clone(sub->pool, event_hdr); + + /* Set the method: */ + pjsip_method_copy(sub->pool, &sub->method, + &rdata->msg_info.msg->line.req.method); + + /* Update expiration time according to client request: */ + + expires_hdr = (pjsip_expires_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL); + if (expires_hdr) { + sub->expires->ivalue = expires_hdr->ivalue; + } + + /* Update time. */ + update_expires(sub, sub->expires->ivalue); + + /* Update Accept header: */ + + accept_hdr = (pjsip_accept_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL); + if (accept_hdr) + sub->accept = pjsip_hdr_clone(sub->pool, accept_hdr); + + /* We can start the session: */ + + pjsip_dlg_inc_session(dlg, &mod_evsub.mod); + sub->pending_tsx++; + tsx->mod_data[mod_evsub.mod.id] = sub; + + + /* Done. */ + *p_evsub = sub; + + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Get subscription state. + */ +PJ_DEF(pjsip_evsub_state) pjsip_evsub_get_state(pjsip_evsub *sub) +{ + return sub->state; +} + +/* + * Get state name. + */ +PJ_DEF(const char*) pjsip_evsub_get_state_name(pjsip_evsub *sub) +{ + return sub->state_str.ptr; +} + + +/* + * Initiate client subscription + */ +PJ_DEF(pj_status_t) pjsip_evsub_initiate( pjsip_evsub *sub, + const pjsip_method *method, + pj_int32_t expires, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL); + + /* Use SUBSCRIBE if method is not specified */ + if (method == NULL) + method = &pjsip_subscribe_method; + + pjsip_dlg_inc_lock(sub->dlg); + + /* Update method: */ + if (sub->state == PJSIP_EVSUB_STATE_NULL) + pjsip_method_copy(sub->pool, &sub->method, method); + + status = pjsip_dlg_create_request( sub->dlg, method, -1, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Add Event header: */ + pjsip_msg_add_hdr( tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, sub->event)); + + /* Update and add expires header: */ + if (expires >= 0) + sub->expires->ivalue = expires; + pjsip_msg_add_hdr( tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, sub->expires)); + + /* Add Accept header: */ + pjsip_msg_add_hdr( tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, sub->accept)); + + + /* Add Allow-Events header: */ + pjsip_msg_add_hdr( tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, + mod_evsub.allow_events_hdr)); + + + *p_tdata = tdata; + + +on_return: + + pjsip_dlg_dec_lock(sub->dlg); + return status; +} + + +/* + * Accept incoming subscription request. + */ +PJ_DEF(pj_status_t) pjsip_evsub_accept( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list ) +{ + pjsip_tx_data *tdata; + pjsip_transaction *tsx; + pj_status_t status; + + /* Check arguments */ + PJ_ASSERT_RETURN(sub && rdata, PJ_EINVAL); + + /* Can only be for server subscription: */ + PJ_ASSERT_RETURN(sub->role == PJSIP_ROLE_UAS, PJ_EINVALIDOP); + + /* Only expect 2xx status code (for now) */ + PJ_ASSERT_RETURN(st_code/100 == 2, PJ_EINVALIDOP); + + /* Subscription MUST have been attached to the transaction. + * Initial subscription request will be attached on evsub_create_uas(), + * while subsequent requests will be attached in tsx_state() + */ + tsx = pjsip_rdata_get_tsx(rdata); + PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] != NULL, + PJ_EINVALIDOP); + + /* Lock dialog */ + pjsip_dlg_inc_lock(sub->dlg); + + /* Create response: */ + status = pjsip_dlg_create_response( sub->dlg, rdata, st_code, NULL, + &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Add expires header: */ + pjsip_msg_add_hdr( tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, sub->expires)); + + + /* Send the response: */ + status = pjsip_dlg_send_response( sub->dlg, tsx, tdata ); + if (status != PJ_SUCCESS) + goto on_return; + + +on_return: + + pjsip_dlg_dec_lock(sub->dlg); + return status; +} + + +/* + * Create Subscription-State header based on current server subscription + * state. + */ +static pjsip_sub_state_hdr* sub_state_create( pj_pool_t *pool, + pjsip_evsub *sub, + pjsip_evsub_state state, + const pj_str_t *state_str, + const pj_str_t *reason ) +{ + pjsip_sub_state_hdr *sub_state; + pj_time_val now, delay; + + /* Get the remaining time before refresh is required */ + pj_gettimeofday(&now); + delay = sub->refresh_time; + PJ_TIME_VAL_SUB(delay, now); + + /* Create the Subscription-State header */ + sub_state = pjsip_sub_state_hdr_create(pool); + + /* Fill up the header */ + switch (state) { + case PJSIP_EVSUB_STATE_SENT: + case PJSIP_EVSUB_STATE_ACCEPTED: + pj_assert(!"Invalid state!"); + /* Treat as pending */ + + case PJSIP_EVSUB_STATE_PENDING: + sub_state->sub_state = STR_PENDING; + sub_state->expires_param = delay.sec; + break; + + case PJSIP_EVSUB_STATE_ACTIVE: + sub_state->sub_state = STR_ACTIVE; + sub_state->expires_param = delay.sec; + break; + + case PJSIP_EVSUB_STATE_TERMINATED: + sub_state->sub_state = STR_TERMINATED; + if (reason != NULL) + pj_strdup(pool, &sub_state->reason_param, reason); + break; + + case PJSIP_EVSUB_STATE_UNKNOWN: + pj_assert(state_str != NULL); + pj_strdup(pool, &sub_state->sub_state, state_str); + break; + } + + return sub_state; +} + +/* + * Create and send NOTIFY request. + */ +PJ_DEF(pj_status_t) pjsip_evsub_notify( pjsip_evsub *sub, + pjsip_evsub_state state, + const pj_str_t *state_str, + const pj_str_t *reason, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsip_sub_state_hdr *sub_state; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(sub->dlg); + + /* Create NOTIFY request */ + status = pjsip_dlg_create_request( sub->dlg, &pjsip_notify_method, -1, + &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Add Event header */ + pjsip_msg_add_hdr(tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, sub->event)); + + /* Add Subscription-State header */ + sub_state = sub_state_create(tdata->pool, sub, state, state_str, + reason); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sub_state); + + /* Add Allow-Events header */ + pjsip_msg_add_hdr(tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, mod_evsub.allow_events_hdr)); + + /* Add Authentication headers. */ + pjsip_auth_clt_init_req( &sub->dlg->auth_sess, tdata ); + + + + /* Save destination state. */ + sub->dst_state = state; + if (state_str) + pj_strdup(sub->pool, &sub->dst_state_str, state_str); + else + sub->dst_state_str.slen = 0; + + + *p_tdata = tdata; + +on_return: + /* Unlock dialog */ + pjsip_dlg_dec_lock(sub->dlg); + return status; +} + + +/* + * Create NOTIFY to reflect current status. + */ +PJ_DEF(pj_status_t) pjsip_evsub_current_notify( pjsip_evsub *sub, + pjsip_tx_data **p_tdata ) +{ + return pjsip_evsub_notify( sub, sub->state, &sub->state_str, + NULL, p_tdata ); +} + + +/* + * Send request. + */ +PJ_DEF(pj_status_t) pjsip_evsub_send_request( pjsip_evsub *sub, + pjsip_tx_data *tdata) +{ + pj_status_t status; + + /* Must be request message. */ + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Lock */ + pjsip_dlg_inc_lock(sub->dlg); + + /* Send the request. */ + status = pjsip_dlg_send_request(sub->dlg, tdata, NULL); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Special case for NOTIFY: + * The new state was set in pjsip_evsub_notify(), but we apply the + * new state now, when the request was actually sent. + */ + if (pjsip_method_cmp(&tdata->msg->line.req.method, + &pjsip_notify_method)==0) + { + PJ_ASSERT_ON_FAIL( sub->dst_state!=PJSIP_EVSUB_STATE_NULL, + {goto on_return;}); + + set_state(sub, sub->dst_state, + (sub->dst_state_str.slen ? &sub->dst_state_str : NULL), + NULL); + + sub->dst_state = PJSIP_EVSUB_STATE_NULL; + sub->dst_state_str.slen = 0; + + } + + +on_return: + pjsip_dlg_dec_lock(sub->dlg); + return status; +} + + + +/* + * Attach subscription session to newly created transaction, if appropriate. + */ +static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx, + pjsip_event *event) +{ + /* + * Newly created transaction will not have subscription session + * attached to it. Find the subscription session from the dialog, + * by matching the Event header. + */ + pjsip_dialog *dlg; + pjsip_event_hdr *event_hdr; + pjsip_msg *msg; + struct dlgsub *dlgsub_head, *dlgsub; + pjsip_evsub *sub; + + dlg = pjsip_tsx_get_dlg(tsx); + if (!dlg) { + pj_assert(!"Transaction should have a dialog instance!"); + return NULL; + } + + switch (event->body.tsx_state.type) { + case PJSIP_EVENT_RX_MSG: + msg = event->body.tsx_state.src.rdata->msg_info.msg; + break; + case PJSIP_EVENT_TX_MSG: + msg = event->body.tsx_state.src.tdata->msg; + break; + default: + if (tsx->role == PJSIP_ROLE_UAC) + msg = tsx->last_tx->msg; + else + msg = NULL; + break; + } + + if (!msg) { + pj_assert(!"First transaction event is not TX or RX!"); + return NULL; + } + + event_hdr = pjsip_msg_find_hdr_by_name(msg, &STR_EVENT, NULL); + if (!event_hdr) { + /* Not subscription related message */ + return NULL; + } + + /* Find the subscription in the dialog, based on the content + * of Event header: + */ + + dlgsub_head = dlg->mod_data[mod_evsub.mod.id]; + if (dlgsub_head == NULL) { + dlgsub_head = pj_pool_alloc(dlg->pool, sizeof(struct dlgsub)); + pj_list_init(dlgsub_head); + dlg->mod_data[mod_evsub.mod.id] = dlgsub_head; + } + dlgsub = dlgsub_head->next; + + while (dlgsub != dlgsub_head) { + + /* Match event type and Id */ + if (pj_strcmp(&dlgsub->sub->event->id_param, &event_hdr->id_param)==0 && + pj_stricmp(&dlgsub->sub->event->event_type, &event_hdr->event_type)==0) + { + break; + } + dlgsub = dlgsub->next; + } + + if (dlgsub == dlgsub_head) { + /* This could be incoming request to create new subscription */ + PJ_LOG(4,(THIS_FILE, + "Subscription not found for %.*s, event=%.*s;id=%.*s", + (int)event_hdr->event_type.slen, + event_hdr->event_type.ptr, + (int)event_hdr->id_param.slen, + event_hdr->id_param.ptr)); + + /* If this is an incoming NOTIFY, reject with 481 */ + if (tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0) + { + pj_str_t reason = pj_str("Subscription Does Not Exist"); + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_dlg_create_response(dlg, + event->body.tsx_state.src.rdata, + 481, &reason, + &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(dlg, tsx, tdata); + } + } + return NULL; + } + + /* Found! */ + sub = dlgsub->sub; + + /* Attach session to the transaction */ + tsx->mod_data[mod_evsub.mod.id] = sub; + sub->pending_tsx++; + + return sub; +} + + +/* + * Create response, adding custome headers and msg body. + */ +static pj_status_t create_response( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pj_str_t *st_text, + const pjsip_hdr *res_hdr, + const pjsip_msg_body *body, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsip_hdr *hdr; + pj_status_t status; + + status = pjsip_dlg_create_response(sub->dlg, rdata, + st_code, st_text, &tdata); + if (status != PJ_SUCCESS) + return status; + + *p_tdata = tdata; + + /* Add response headers. */ + hdr = res_hdr->next; + while (hdr != res_hdr) { + pjsip_msg_add_hdr( tdata->msg, + pjsip_hdr_clone(tdata->pool, hdr)); + hdr = hdr->next; + } + + /* Add msg body, if any */ + if (body) { + tdata->msg->body = pj_pool_zalloc(tdata->pool, + sizeof(pjsip_msg_body)); + status = pjsip_msg_body_clone(tdata->pool, + tdata->msg->body, + body); + if (status != PJ_SUCCESS) { + tdata->msg->body = NULL; + /* Ignore */ + return PJ_SUCCESS; + } + } + + return PJ_SUCCESS; +} + +/* + * Get subscription state from the value of Subscription-State header. + */ +static void get_hdr_state( pjsip_sub_state_hdr *sub_state, + pjsip_evsub_state *state, + pj_str_t **state_str ) +{ + if (pj_stricmp(&sub_state->sub_state, &STR_TERMINATED)==0) { + + *state = PJSIP_EVSUB_STATE_TERMINATED; + *state_str = NULL; + + } else if (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0) { + + *state = PJSIP_EVSUB_STATE_ACTIVE; + *state_str = NULL; + + } else if (pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0) { + + *state = PJSIP_EVSUB_STATE_PENDING; + *state_str = NULL; + + } else { + + *state = PJSIP_EVSUB_STATE_UNKNOWN; + *state_str = &sub_state->sub_state; + + } +} + +/* + * Transaction event processing by UAC, after subscription is sent. + */ +static void on_tsx_state_uac( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event ) +{ + + if (pjsip_method_cmp(&tsx->method, &sub->method)==0) { + + /* Received response to outgoing request that establishes/refresh + * subscription. + */ + + /* First time initial request is sent. */ + if (sub->state == PJSIP_EVSUB_STATE_NULL && + tsx->state == PJSIP_TSX_STATE_CALLING) + { + set_state(sub, PJSIP_EVSUB_STATE_SENT, NULL, event); + return; + } + + /* Only interested in final response */ + if (tsx->state != PJSIP_TSX_STATE_COMPLETED && + tsx->state != PJSIP_TSX_STATE_TERMINATED) + { + return; + } + + /* Handle authentication. */ + if (tsx->status_code==401 || tsx->status_code==407) { + pjsip_tx_data *tdata; + pj_status_t status; + + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + /* Previously failed transaction has terminated */ + return; + } + + status = pjsip_auth_clt_reinit_req(&sub->dlg->auth_sess, + event->body.tsx_state.src.rdata, + tsx->last_tx, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_dlg_send_request(sub->dlg, tdata, NULL); + + if (status != PJ_SUCCESS) { + /* Authentication failed! */ + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, + event); + return; + } + + return; + } + + if (tsx->status_code/100 == 2) { + + /* Successfull SUBSCRIBE request! + * This could be: + * - response to initial SUBSCRIBE request + * - response to subsequent refresh + * - response to unsubscription + */ + + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + /* Ignore; this transaction has been processed before */ + return; + } + + /* Update UAC refresh time, if response contains Expires header, + * only when we're not unsubscribing. + */ + if (sub->expires->ivalue != 0) { + pjsip_msg *msg; + pjsip_expires_hdr *expires; + + msg = event->body.tsx_state.src.rdata->msg_info.msg; + expires = pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); + if (expires) { + sub->expires->ivalue = expires->ivalue; + } + } + + /* Update time */ + update_expires(sub, sub->expires->ivalue); + + /* Start UAC refresh timer, only when we're not unsubscribing */ + if (sub->expires->ivalue != 0) { + unsigned timeout = (sub->expires->ivalue > TIME_UAC_REFRESH) ? + sub->expires->ivalue - TIME_UAC_REFRESH : sub->expires->ivalue; + + PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", + timeout)); + set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout); + + } else { + /* Otherwise set timer to terminate client subscription when + * NOTIFY to end subscription is not received. + */ + set_timer(sub, TIMER_TYPE_UAC_TERMINATE, TIME_UAC_TERMINATE); + } + + /* Set state, if necessary */ + pj_assert(sub->state != PJSIP_EVSUB_STATE_NULL); + if (sub->state == PJSIP_EVSUB_STATE_SENT) { + set_state(sub, PJSIP_EVSUB_STATE_ACCEPTED, NULL, event); + } + + } else { + + /* Failed SUBSCRIBE request! + * + * The RFC 3265 says that if outgoing SUBSCRIBE fails with status + * other than 481, the subscription is still considered valid for + * the duration of the last Expires. + * + * Since we send refresh about 5 seconds (TIME_UAC_REFRESH) before + * expiration, theoritically the expiration is still valid for the + * next 5 seconds even when we receive non-481 failed response. + * + * Ah, what the heck! + * + * Just terminate now! + * + */ + + if (sub->state == PJSIP_EVSUB_STATE_TERMINATED) { + /* Ignore, has been handled before */ + return; + } + + /* Kill any timer. */ + set_timer(sub, TIMER_TYPE_NONE, 0); + + /* Set state to TERMINATED */ + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, event); + + } + + } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) { + + /* Incoming NOTIFY. + * This can be the result of: + * - Initial subscription response + * - UAS updating the resource info. + * - Unsubscription response. + */ + int st_code = 200; + pj_str_t *st_text = NULL; + pjsip_hdr res_hdr; + pjsip_msg_body *body = NULL; + + pjsip_rx_data *rdata; + pjsip_msg *msg; + pjsip_sub_state_hdr *sub_state; + + pjsip_evsub_state new_state; + pj_str_t *new_state_str; + + pjsip_tx_data *tdata; + pj_status_t status; + int next_refresh; + + /* Only want to handle initial NOTIFY receive event. */ + if (tsx->state != PJSIP_TSX_STATE_TRYING) + return; + + + rdata = event->body.tsx_state.src.rdata; + msg = rdata->msg_info.msg; + + pj_list_init(&res_hdr); + + /* Get subscription state header. */ + sub_state = pjsip_msg_find_hdr_by_name(msg, &STR_SUB_STATE, NULL); + if (sub_state == NULL) { + + pjsip_warning_hdr *warn_hdr; + pj_str_t warn_text = { "Missing Subscription-State header", 33}; + + /* Bad request! Add warning header. */ + st_code = PJSIP_SC_BAD_REQUEST; + warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399, + pjsip_endpt_name(sub->endpt), + &warn_text); + pj_list_push_back(&res_hdr, warn_hdr); + } + + /* Call application registered callback to handle incoming NOTIFY, + * if any. + */ + if (st_code==200 && sub->user.on_rx_notify) { + (*sub->user.on_rx_notify)(sub, rdata, &st_code, &st_text, + &res_hdr, &body); + + /* Application MUST specify final response! */ + PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; }); + + /* Must be a valid status code */ + PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; }); + } + + + /* If non-2xx should be returned, then send the response. + * No need to update server subscription state. + */ + if (st_code >= 300) { + status = create_response(sub, rdata, st_code, st_text, &res_hdr, + body, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(sub->dlg, tsx, tdata); + } + + /* Start timer to terminate subscription, just in case server + * is not able to generate NOTIFY to our response. + */ + if (status == PJ_SUCCESS) { + unsigned timeout = TIME_UAC_WAIT_NOTIFY; + set_timer(sub, TIMER_TYPE_UAC_WAIT_NOTIFY, timeout); + } else { + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL); + } + + return; + } + + /* Update expiration from the value of expires param in + * Subscription-State header, but ONLY when subscription state + * is "active" or "pending", AND the header contains expires param. + */ + if (sub->expires->ivalue != 0 && + sub_state->expires_param >= 0 && + (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0 || + pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0)) + { + next_refresh = sub_state->expires_param; + + } else { + next_refresh = sub->expires->ivalue; + } + + /* Update time */ + update_expires(sub, next_refresh); + + /* Start UAC refresh timer, only when we're not unsubscribing */ + if (sub->expires->ivalue != 0) { + unsigned timeout = (next_refresh > TIME_UAC_REFRESH) ? + next_refresh - TIME_UAC_REFRESH : next_refresh; + + PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", timeout)); + set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout); + } + + /* Find out the state */ + get_hdr_state(sub_state, &new_state, &new_state_str); + + /* Send response. */ + status = create_response(sub, rdata, st_code, st_text, &res_hdr, + body, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_dlg_send_response(sub->dlg, tsx, tdata); + + /* Set the state */ + if (status == PJ_SUCCESS) { + set_state(sub, new_state, new_state_str, event); + } else { + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event); + } + + + } else { + + /* + * Unexpected method! + */ + PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s", + (int)tsx->method.name.slen, tsx->method.name.ptr)); + } +} + + +/* + * Transaction event processing by UAS, after subscription is accepted. + */ +static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event) +{ + + if (pjsip_method_cmp(&tsx->method, &sub->method) == 0) { + + /* + * Incoming request (e.g. SUBSCRIBE or REFER) to refresh subsciption. + * + */ + pjsip_rx_data *rdata; + pjsip_event_hdr *event_hdr; + pjsip_expires_hdr *expires; + pjsip_msg *msg; + pjsip_tx_data *tdata; + int st_code = 200; + pj_str_t *st_text = NULL; + pjsip_hdr res_hdr; + pjsip_msg_body *body = NULL; + pjsip_evsub_state old_state; + pj_str_t old_state_str; + pj_status_t status; + + + /* Only wants to handle the first event when the request is + * received. + */ + if (tsx->state != PJSIP_TSX_STATE_TRYING) + return; + + rdata = event->body.tsx_state.src.rdata; + msg = rdata->msg_info.msg; + + /* Set expiration time based on client request (in Expires header), + * or package default expiration time. + */ + event_hdr = pjsip_msg_find_hdr_by_name(msg, &STR_EVENT, NULL); + expires = pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); + if (event_hdr && expires) { + struct evpkg *evpkg; + + evpkg = find_pkg(&event_hdr->event_type); + if (evpkg) { + if (expires->ivalue < (pj_int32_t)evpkg->pkg_expires) + sub->expires->ivalue = expires->ivalue; + else + sub->expires->ivalue = evpkg->pkg_expires; + } + } + + /* Update time (before calling on_rx_refresh, since application + * will send NOTIFY. + */ + update_expires(sub, sub->expires->ivalue); + + + /* Save old state. + * If application respond with non-2xx, revert to old state. + */ + old_state = sub->state; + old_state_str = sub->state_str; + + if (sub->expires->ivalue == 0) { + sub->state = PJSIP_EVSUB_STATE_TERMINATED; + sub->state_str = evsub_state_names[sub->state]; + } else if (sub->state == PJSIP_EVSUB_STATE_NULL) { + sub->state = PJSIP_EVSUB_STATE_ACCEPTED; + sub->state_str = evsub_state_names[sub->state]; + } + + /* Call application's on_rx_refresh, just in case it wants to send + * response other than 200 (OK) + */ + pj_list_init(&res_hdr); + + (*sub->user.on_rx_refresh)(sub, rdata, &st_code, &st_text, + &res_hdr, &body); + + /* Application MUST specify final response! */ + PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; }); + + /* Must be a valid status code */ + PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; }); + + + /* Create and send response */ + status = create_response(sub, rdata, st_code, st_text, &res_hdr, + body, &tdata); + if (status == PJ_SUCCESS) { + /* Add expires header: */ + pjsip_msg_add_hdr( tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, + sub->expires)); + + /* Send */ + status = pjsip_dlg_send_response(sub->dlg, tsx, tdata); + } + + /* Update state or revert state */ + if (st_code/100==2) { + + if (sub->expires->ivalue == 0) { + set_state(sub, sub->state, NULL, event); + } else if (sub->state == PJSIP_EVSUB_STATE_NULL) { + set_state(sub, sub->state, NULL, event); + } + + /* Set UAS timeout timer, when state is not terminated. */ + if (sub->state != PJSIP_EVSUB_STATE_TERMINATED) { + PJ_LOG(5,(sub->obj_name, "UAS timeout in %d seconds", + sub->expires->ivalue)); + set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, + sub->expires->ivalue); + } + + } else { + sub->state = old_state; + sub->state_str = old_state_str; + } + + + } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0) { + + /* Handle authentication */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + (tsx->status_code==401 || tsx->status_code==407)) + { + pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_auth_clt_reinit_req( &sub->dlg->auth_sess, rdata, + tsx->last_tx, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_dlg_send_request( sub->dlg, tdata, NULL ); + + if (status != PJ_SUCCESS) { + /* Can't authenticate. Terminate session (?) */ + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL); + } + } + + } else { + + /* + * Unexpected method! + */ + PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s", + (int)tsx->method.name.slen, tsx->method.name.ptr)); + + } +} + + +/* + * Notification when transaction state has changed! + */ +static void mod_evsub_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event) +{ + pjsip_evsub *sub = pjsip_tsx_get_evsub(tsx); + + if (sub == NULL) { + sub = on_new_transaction(tsx, event); + if (sub == NULL) + return; + } + + + /* Call on_tsx_state callback, if any. */ + if (sub->user.on_tsx_state) + (*sub->user.on_tsx_state)(sub, tsx, event); + + + /* Process the event: */ + + if (sub->role == PJSIP_ROLE_UAC) { + on_tsx_state_uac(sub, tsx, event); + } else { + on_tsx_state_uas(sub, tsx, event); + } + + + /* Check transaction TERMINATE event */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + --sub->pending_tsx; + + if (sub->state == PJSIP_EVSUB_STATE_TERMINATED && + sub->pending_tsx == 0) + { + evsub_destroy(sub); + } + + } +} + + diff --git a/pjsip/src/pjsip-simple/event_notify_msg.c b/pjsip/src/pjsip-simple/evsub_msg.c index a5f946ef..7fd44d32 100644 --- a/pjsip/src/pjsip-simple/event_notify_msg.c +++ b/pjsip/src/pjsip-simple/evsub_msg.c @@ -16,13 +16,16 @@ * 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_msg.h> -#include <pjsip/print.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip/print_util.h> #include <pjsip/sip_parser.h> #include <pj/pool.h> #include <pj/string.h> #include <pj/except.h> +/* + * Event header. + */ static int pjsip_event_hdr_print( pjsip_event_hdr *hdr, char *buf, pj_size_t size); static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool, @@ -40,12 +43,15 @@ static pjsip_hdr_vptr event_hdr_vptr = 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)); + pjsip_event_hdr *hdr = pj_pool_zalloc(pool, sizeof(*hdr)); hdr->type = PJSIP_H_OTHER; - hdr->name = hdr->sname = event; + hdr->name.ptr = "Event"; + hdr->name.slen = 5; + hdr->sname.ptr = "o"; + hdr->sname.slen = 1; hdr->vptr = &event_hdr_vptr; pj_list_init(hdr); + pj_list_init(&hdr->other_param); return hdr; } @@ -62,8 +68,14 @@ static int pjsip_event_hdr_print( pjsip_event_hdr *hdr, 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); + + printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p, + &pjsip_PARAM_CHAR_SPEC, + &pjsip_PARAM_CHAR_SPEC, ';'); + if (printed < 0) + return printed; + + p += printed; return p - buf; } @@ -73,93 +85,43 @@ static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool, 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); + pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param); return hdr; } -static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool, - const pjsip_event_hdr *rhs ) +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)); + pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param); 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, -}; - - +/* + * Allow-Events header. + */ 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; + const pj_str_t STR_ALLOW_EVENTS = { "Allow-Events", 12}; + pjsip_allow_events_hdr *hdr; - copy_advance(p, hdr->name); - *p++ = ':'; - *p++ = ' '; + hdr = pjsip_generic_array_hdr_create(pool, &STR_ALLOW_EVENTS); - 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]); - } + if (hdr) { + hdr->sname.ptr = "u"; + hdr->sname.slen = 1; } - 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; } +/* + * Subscription-State header. + */ static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr, char *buf, pj_size_t size); static pjsip_sub_state_hdr* @@ -180,13 +142,14 @@ static pjsip_hdr_vptr sub_state_hdr_vptr = 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)); + pjsip_sub_state_hdr *hdr = pj_pool_zalloc(pool, 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); + pj_list_init(&hdr->other_param); return hdr; } @@ -215,8 +178,15 @@ static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr, printed = pj_utoa(hdr->retry_after, p); p += printed; } - if (hdr->other_param.slen) - copy_advance(p, hdr->other_param); + + printed = pjsip_param_print_on( &hdr->other_param, p, endbuf-p, + &pjsip_PARAM_CHAR_SPEC, + &pjsip_PARAM_CHAR_SPEC, + ';'); + if (printed < 0) + return printed; + + p += printed; return p - buf; } @@ -230,7 +200,7 @@ pjsip_sub_state_hdr_clone(pj_pool_t *pool, 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); + pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param); return hdr; } @@ -240,83 +210,87 @@ pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool, { pjsip_sub_state_hdr *hdr = pj_pool_alloc(pool, sizeof(*hdr)); pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param); return hdr; } -static pjsip_event_hdr *parse_hdr_event(pj_scanner *scanner, - pj_pool_t *pool) + +/* + * Parse Event header. + */ +static pjsip_hdr *parse_hdr_event(pjsip_parse_ctx *ctx) { - pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool); + pjsip_event_hdr *hdr = pjsip_event_hdr_create(ctx->pool); const pj_str_t id_param = { "id", 2 }; - pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->event_type); + pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->event_type); - while (*scanner->current == ';') { + while (*ctx->scanner->curptr == ';') { pj_str_t pname, pvalue; - pj_scan_get_char(scanner); - pjsip_parse_param_imp(scanner, &pname, &pvalue, 0); + + pj_scan_get_char(ctx->scanner); + pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0); + if (pj_stricmp(&pname, &id_param)==0) { hdr->id_param = pvalue; } else { - pjsip_concat_param_imp(&hdr->other_param, pool, &pname, &pvalue, ';'); + pjsip_param *param = pj_pool_alloc(ctx->pool, sizeof(pjsip_param)); + param->name = pname; + param->value = pvalue; + pj_list_push_back(&hdr->other_param, param); } } - pjsip_parse_end_hdr_imp( scanner ); - return hdr; + pjsip_parse_end_hdr_imp( ctx->scanner ); + return (pjsip_hdr*)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) +/* + * Parse Subscription-State header. + */ +static pjsip_hdr* parse_hdr_sub_state( pjsip_parse_ctx *ctx ) { - pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool); + pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(ctx->pool); const pj_str_t reason = { "reason", 6 }, expires = { "expires", 7 }, retry_after = { "retry-after", 11 }; - pj_scan_get(scanner, pjsip_TOKEN_SPEC, &hdr->sub_state); + pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->sub_state); - while (*scanner->current == ';') { + while (*ctx->scanner->curptr == ';') { pj_str_t pname, pvalue; - pj_scan_get_char(scanner); - pjsip_parse_param_imp(scanner, &pname, &pvalue, 0); + pj_scan_get_char(ctx->scanner); + pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0); + if (pj_stricmp(&pname, &reason) == 0) { hdr->reason_param = pvalue; + } else if (pj_stricmp(&pname, &expires) == 0) { hdr->expires_param = pj_strtoul(&pvalue); + } else if (pj_stricmp(&pname, &retry_after) == 0) { hdr->retry_after = pj_strtoul(&pvalue); + } else { - pjsip_concat_param_imp(&hdr->other_param, pool, &pname, &pvalue, ';'); + pjsip_param *param = pj_pool_alloc(ctx->pool, sizeof(pjsip_param)); + param->name = pname; + param->value = pvalue; + pj_list_push_back(&hdr->other_param, param); } } - pjsip_parse_end_hdr_imp( scanner ); - return hdr; + pjsip_parse_end_hdr_imp( ctx->scanner ); + return (pjsip_hdr*)hdr; } -PJ_DEF(void) pjsip_event_notify_init_parser(void) +/* + * Register header parsers. + */ +PJ_DEF(void) pjsip_evsub_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); + pjsip_register_hdr_parser( "Event", NULL, + &parse_hdr_event); + + pjsip_register_hdr_parser( "Subscription-State", NULL, + &parse_hdr_sub_state); } + diff --git a/pjsip/src/pjsip-simple/messaging.c b/pjsip/src/pjsip-simple/messaging.c deleted file mode 100644 index 6e08af68..00000000 --- a/pjsip/src/pjsip-simple/messaging.c +++ /dev/null @@ -1,352 +0,0 @@ -/* $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/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_util.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 index 7ababcad..acd20092 100644 --- a/pjsip/src/pjsip-simple/pidf.c +++ b/pjsip/src/pjsip-simple/pidf.c @@ -16,9 +16,11 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <pjsip_simple/pidf.h> +#include <pjsip-simple/pidf.h> #include <pj/string.h> #include <pj/pool.h> +#include <pj/assert.h> + struct pjpidf_op_desc pjpidf_op = { @@ -132,7 +134,7 @@ PJ_DEF(pjpidf_tuple*) pjpidf_pres_find_tuple(pjpidf_pres *pres, const pj_str_t * PJ_DEF(void) pjpidf_pres_remove_tuple(pjpidf_pres *pres, pjpidf_tuple *t) { - PJ_UNUSED_ARG(pres) + PJ_UNUSED_ARG(pres); pj_list_erase(t); } diff --git a/pjsip/src/pjsip-simple/presence.c b/pjsip/src/pjsip-simple/presence.c index a9cc6108..ca033f5b 100644 --- a/pjsip/src/pjsip-simple/presence.c +++ b/pjsip/src/pjsip-simple/presence.c @@ -16,384 +16,919 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <pjsip_simple/presence.h> -#include <pjsip/sip_transport.h> -#include <pj/pool.h> -#include <pj/string.h> +#include <pjsip-simple/presence.h> +#include <pjsip-simple/errno.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_dialog.h> +#include <pj/assert.h> #include <pj/guid.h> +#include <pj/log.h> #include <pj/os.h> -#include <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 } +#include <pj/pool.h> +#include <pj/string.h> + + +#define THIS_FILE "presence.c" +#define PRES_DEFAULT_EXPIRES 600 + +/* + * Presence module (mod-presence) + */ +static struct pjsip_module mod_presence = +{ + NULL, NULL, /* prev, next. */ + { "mod-presence", 12 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* User data. */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ }; -static pjsip_media_type accept_types[] = { - { - { "application", 11 }, - { "pidf+xml", 8 } - }, - { - { "application", 11 }, - { "xpidf+xml", 9 } - } + + +/* + * Presence message body type. + */ +typedef enum content_type +{ + CONTENT_TYPE_NONE, + CONTENT_TYPE_PIDF, + CONTENT_TYPE_XPIDF, +} content_type; + +/* + * This structure describe a presentity, for both subscriber and notifier. + */ +struct pjsip_pres +{ + pjsip_evsub *sub; /**< Event subscribtion record. */ + pjsip_dialog *dlg; /**< The dialog. */ + content_type content_type; /**< Content-Type. */ + pjsip_pres_status status; /**< Presence status. */ + pjsip_pres_status tmp_status; /**< Temp, before NOTIFY is answred.*/ + pjsip_evsub_user user_cb; /**< The user callback. */ }; -/* 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 }; +typedef struct pjsip_pres pjsip_pres; -/* 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. + * Forward decl for evsub callback. */ -PJ_DEF(void) pjsip_presence_init(const pjsip_presence_cb *pcb) +static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); +static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event); +static void pres_on_evsub_rx_refresh( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); +static void pres_on_evsub_rx_notify( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); +static void pres_on_evsub_client_refresh(pjsip_evsub *sub); +static void pres_on_evsub_server_timeout(pjsip_evsub *sub); + + +/* + * Event subscription callback for presence. + */ +static pjsip_evsub_user pres_user = { - pj_memcpy(&cb, pcb, sizeof(*pcb)); - pjsip_event_sub_register_pkg( &PRESENCE_EVENT, - sizeof(accept_names)/sizeof(accept_names[0]), - accept_names, - &pkg_cb); + &pres_on_evsub_state, + &pres_on_evsub_tsx_state, + &pres_on_evsub_rx_refresh, + &pres_on_evsub_rx_notify, + &pres_on_evsub_client_refresh, + &pres_on_evsub_server_timeout, +}; + + +/* + * Some static constants. + */ +const pj_str_t STR_EVENT = { "Event", 5 }; +const pj_str_t STR_PRESENCE = { "presence", 8 }; +const pj_str_t STR_APPLICATION = { "application", 11 }; +const pj_str_t STR_PIDF_XML = { "pidf+xml", 8}; +const pj_str_t STR_XPIDF_XML = { "xpidf+xml", 9}; +const pj_str_t STR_APP_PIDF_XML = { "application/pidf+xml", 20 }; +const pj_str_t STR_APP_XPIDF_XML = { "application/xpidf+xml", 21 }; + + +/* + * Init presence module. + */ +PJ_DEF(pj_status_t) pjsip_pres_init_module( pjsip_endpoint *endpt, + pjsip_module *mod_evsub) +{ + pj_status_t status; + pj_str_t accept[2]; + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL); + + /* Must have not been registered */ + PJ_ASSERT_RETURN(mod_presence.id == -1, PJ_EINVALIDOP); + + /* Register to endpoint */ + status = pjsip_endpt_register_module(endpt, &mod_presence); + if (status != PJ_SUCCESS) + return status; + + accept[0] = STR_APP_PIDF_XML; + accept[1] = STR_APP_XPIDF_XML; + + /* Register event package to event module. */ + status = pjsip_evsub_register_pkg( &mod_presence, &STR_PRESENCE, + PRES_DEFAULT_EXPIRES, + PJ_ARRAY_SIZE(accept), accept); + if (status != PJ_SUCCESS) { + pjsip_endpt_unregister_module(endpt, &mod_presence); + return status; + } + + return PJ_SUCCESS; } + +/* + * Get presence module instance. + */ +PJ_DEF(pjsip_module*) pjsip_pres_instance(void) +{ + return &mod_presence; +} + + /* - * Create presence subscription. + * Create client 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 ) +PJ_DEF(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_evsub **p_evsub ) { - pjsip_event_sub *sub; - pjsip_presentity *pres; + pj_status_t status; + pjsip_pres *pres; + pjsip_evsub *sub; - if (expires < 0) - expires = 300; + PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); /* 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; + status = pjsip_evsub_create_uac( dlg, &pres_user, &STR_PRESENCE, &sub); + if (status != PJ_SUCCESS) + goto on_return; - /* Allocate presence descriptor. */ - pres = pj_pool_calloc(sub->pool, 1, sizeof(*pres)); - pres->sub = sub; - pres->user_data = user_data; - sub->user_data = pres; + /* Create presence */ + pres = pj_pool_zalloc(dlg->pool, sizeof(pjsip_pres)); + pres->dlg = dlg; + if (user_cb) + pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_presence.id, pres); - return pres; + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; } + /* - * Send SUBSCRIBE. + * Create server subscription. */ -PJ_DEF(pj_status_t) pjsip_presence_subscribe( pjsip_presentity *pres ) +PJ_DEF(pj_status_t) pjsip_pres_create_uas( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_rx_data *rdata, + pjsip_evsub **p_evsub ) { - return pjsip_event_sub_subscribe( pres->sub ); + pjsip_accept_hdr *accept; + pjsip_event_hdr *event; + pjsip_expires_hdr *expires_hdr; + unsigned expires; + content_type content_type = CONTENT_TYPE_NONE; + pjsip_evsub *sub; + pjsip_pres *pres; + pj_status_t status; + + /* Check arguments */ + PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL); + + /* Must be request message */ + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Check that request is SUBSCRIBE */ + PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, + &pjsip_subscribe_method)==0, + PJSIP_SIMPLE_ENOTSUBSCRIBE); + + /* Check that Event header contains "presence" */ + event = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL); + if (!event) { + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST); + } + if (pj_stricmp(&event->event_type, &STR_PRESENCE) != 0) { + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT); + } + + /* Check that request contains compatible Accept header. */ + accept = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL); + if (accept) { + unsigned i; + for (i=0; i<accept->count; ++i) { + if (pj_stricmp(&accept->values[i], &STR_APP_PIDF_XML)==0) { + content_type = CONTENT_TYPE_PIDF; + break; + } else + if (pj_stricmp(&accept->values[i], &STR_APP_XPIDF_XML)==0) { + content_type = CONTENT_TYPE_XPIDF; + break; + } + } + + if (i==accept->count) { + /* Nothing is acceptable */ + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE); + } + + } else { + /* No Accept header. + * Treat as "application/pidf+xml" + */ + content_type = CONTENT_TYPE_PIDF; + } + + /* Check that expires is not too short. */ + expires_hdr=pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL); + if (expires_hdr) { + if (expires_hdr->ivalue < 5) { + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_INTERVAL_TOO_BRIEF); + } + + expires = expires_hdr->ivalue; + if (expires > PRES_DEFAULT_EXPIRES) + expires = PRES_DEFAULT_EXPIRES; + + } else { + expires = PRES_DEFAULT_EXPIRES; + } + + /* Lock dialog */ + pjsip_dlg_inc_lock(dlg); + + + /* Create server subscription */ + status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create server presence subscription */ + pres = pj_pool_zalloc(dlg->pool, sizeof(pjsip_pres)); + pres->dlg = dlg; + pres->sub = sub; + pres->content_type = content_type; + if (user_cb) + pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_presence.id, pres); + + /* Done: */ + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; } + /* - * Set credentials to be used for outgoing requests. + * Create SUBSCRIBE */ -PJ_DEF(pj_status_t) pjsip_presence_set_credentials( pjsip_presentity *pres, - int count, - const pjsip_cred_info cred[]) +PJ_DEF(pj_status_t) pjsip_pres_initiate( pjsip_evsub *sub, + pj_int32_t expires, + pjsip_tx_data **p_tdata) { - return pjsip_event_sub_set_credentials(pres->sub, count, cred); + return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires, + p_tdata); } + /* - * Set route-set. + * Accept incoming subscription. */ -PJ_DEF(pj_status_t) pjsip_presence_set_route_set( pjsip_presentity *pres, - const pjsip_route_hdr *hdr ) +PJ_DEF(pj_status_t) pjsip_pres_accept( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list ) { - return pjsip_event_sub_set_route_set( pres->sub, hdr ); + return pjsip_evsub_accept( sub, rdata, st_code, hdr_list ); } + /* - * Unsubscribe. + * Get presence status. */ -PJ_DEF(pj_status_t) pjsip_presence_unsubscribe( pjsip_presentity *pres ) +PJ_DEF(pj_status_t) pjsip_pres_get_status( pjsip_evsub *sub, + pjsip_pres_status *status ) { - return pjsip_event_sub_unsubscribe(pres->sub); + pjsip_pres *pres; + + PJ_ASSERT_RETURN(sub && status, PJ_EINVAL); + + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE); + + if (pres->tmp_status._is_valid) + pj_memcpy(status, &pres->tmp_status, sizeof(pjsip_pres_status)); + else + pj_memcpy(status, &pres->status, sizeof(pjsip_pres_status)); + + return PJ_SUCCESS; } + /* - * This is the pjsip_msg_body callback to print XML body. + * Set presence status. */ -static int print_xml(pjsip_msg_body *body, char *buf, pj_size_t size) +PJ_DEF(pj_status_t) pjsip_pres_set_status( pjsip_evsub *sub, + const pjsip_pres_status *status ) { - return pj_xml_print( body->data, buf, size, PJ_TRUE ); + unsigned i; + pjsip_pres *pres; + + PJ_ASSERT_RETURN(sub && status, PJ_EINVAL); + + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE); + + for (i=0; i<status->info_cnt; ++i) { + pres->status.info[i].basic_open = status->info[i].basic_open; + if (status->info[i].id.slen == 0) { + pj_create_unique_string(pres->dlg->pool, + &pres->status.info[i].id); + } else { + pj_strdup(pres->dlg->pool, + &pres->status.info[i].id, + &status->info[i].id); + } + pj_strdup(pres->dlg->pool, + &pres->status.info[i].contact, + &status->info[i].contact); + } + + pres->status.info_cnt = status->info_cnt; + + return PJ_SUCCESS; } + /* - * Create and initialize PIDF document and msg body (notifier only). + * Create PIDF document based on the presence info. */ -static pj_status_t init_presence_info( pjsip_presentity *pres ) +static pjpidf_pres* pres_create_pidf( pj_pool_t *pool, + pjsip_pres *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); + pjpidf_pres *pidf; + unsigned i; + pj_str_t entity; + + /* Get publisher URI */ + entity.ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); + entity.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + pres->dlg->local.info->uri, + entity.ptr, PJSIP_MAX_URL_SIZE); + if (entity.slen < 1) + return 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; + /* Create <presence>. */ + pidf = pjpidf_create(pool, &entity); - if (pres->pres_type == PJSIP_PRES_TYPE_PIDF) { - pj_str_t s; + /* Create <tuple> */ + for (i=0; i<pres->status.info_cnt; ++i) { - /* Create <presence>. */ - pres->uas_data.pidf = pjpidf_create(pool, &s); + pjpidf_tuple *pidf_tuple; + pjpidf_status *pidf_status; - /* Create <tuple> */ - pj_create_unique_string(pool, &s); - tuple = pjpidf_pres_add_tuple(pool, pres->uas_data.pidf, &s); + /* Add tuple id. */ + pidf_tuple = pjpidf_pres_add_tuple(pool, pidf, + &pres->status.info[i].id); /* 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); + if (pres->status.info[i].contact.slen) + pjpidf_tuple_set_contact(pool, pidf_tuple, + &pres->status.info[i].contact); - /* Content-Type */ - content_type = &accept_types[PJSIP_PRES_TYPE_PIDF]; - } else if (pres->pres_type == PJSIP_PRES_TYPE_XPIDF) { + /* Set basic status */ + pidf_status = pjpidf_tuple_get_status(pidf_tuple); + pjpidf_status_set_basic_open(pidf_status, + pres->status.info[i].basic_open); + } - /* Create XPIDF */ - pres->uas_data.xpidf = pjxpidf_create(pool, &uri); + return pidf; +} - /* 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; +/* + * Create XPIDF document based on the presence info. + */ +static pjxpidf_pres* pres_create_xpidf( pj_pool_t *pool, + pjsip_pres *pres ) +{ + /* Note: PJSIP implementation of XPIDF is not complete! + */ + pjxpidf_pres *xpidf; + pj_str_t publisher_uri; + + PJ_LOG(4,(THIS_FILE, "Warning: XPIDF format is not fully supported " + "by PJSIP")); + + publisher_uri.ptr = pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); + publisher_uri.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + pres->dlg->local.info->uri, + publisher_uri.ptr, + PJSIP_MAX_URL_SIZE); + if (publisher_uri.slen < 1) + return NULL; + + /* Create XPIDF document. */ + xpidf = pjxpidf_create(pool, &publisher_uri); - return 0; + /* Set basic status. */ + if (pres->status.info_cnt > 0) + pjxpidf_set_status( xpidf, pres->status.info[0].basic_open); + else + pjxpidf_set_status( xpidf, PJ_FALSE); + + return xpidf; } + /* - * Send NOTIFY and set subscription state. + * Function to print XML message body. */ -PJ_DEF(pj_status_t) pjsip_presence_notify( pjsip_presentity *pres, - pjsip_event_sub_state state, - pj_bool_t is_online ) +static int pres_print_body(struct pjsip_msg_body *msg_body, + char *buf, pj_size_t size) { - pj_str_t reason = { "", 0 }; + return pj_xml_print(msg_body->data, buf, size, PJ_TRUE); +} - 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); +/* + * Function to clone XML document. + */ +static void* xml_clone_data(pj_pool_t *pool, const void *data, unsigned len) +{ + PJ_UNUSED_ARG(len); + return pj_xml_clone( pool, data); +} + + +/* + * Create message body. + */ +static pj_status_t pres_create_msg_body( pjsip_pres *pres, + pjsip_tx_data *tdata) +{ + pjsip_msg_body *body; + + body = pj_pool_zalloc(tdata->pool, sizeof(pjsip_msg_body)); + + if (pres->content_type == CONTENT_TYPE_PIDF) { + + body->data = pres_create_pidf(tdata->pool, pres); + body->content_type.type = pj_str("application"); + body->content_type.subtype = pj_str("pidf+xml"); - } else if (pres->pres_type == PJSIP_PRES_TYPE_XPIDF) { - pjxpidf_set_status( pres->uas_data.xpidf, is_online ); + } else if (pres->content_type == CONTENT_TYPE_XPIDF) { + + body->data = pres_create_xpidf(tdata->pool, pres); + body->content_type.type = pj_str("application"); + body->content_type.subtype = pj_str("xpidf+xml"); } else { - pj_assert(0); + return PJSIP_SIMPLE_EBADCONTENT; } - /* Send notify. */ - return pjsip_event_sub_notify( pres->sub, state, &reason, pres->uas_body); + + body->print_body = &pres_print_body; + body->clone_data = &xml_clone_data; + + tdata->msg->body = body; + + return PJ_SUCCESS; } + /* - * Destroy subscription (can be called for both subscriber and notifier). + * Create NOTIFY */ -PJ_DEF(pj_status_t) pjsip_presence_destroy( pjsip_presentity *pres ) +PJ_DEF(pj_status_t) pjsip_pres_notify( pjsip_evsub *sub, + pjsip_evsub_state state, + const pj_str_t *state_str, + const pj_str_t *reason, + pjsip_tx_data **p_tdata) { - return pjsip_event_sub_destroy(pres->sub); + pjsip_pres *pres; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub, PJ_EINVAL); + + /* Get the presence object. */ + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE); + + /* Must have at least one presence info. */ + PJ_ASSERT_RETURN(pres->status.info_cnt > 0, PJSIP_SIMPLE_ENOPRESENCEINFO); + + + /* Lock object. */ + pjsip_dlg_inc_lock(pres->dlg); + + /* Create the NOTIFY request. */ + status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Create message body to reflect the presence status. */ + status = pres_create_msg_body( pres, tdata ); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Done. */ + *p_tdata = tdata; + + +on_return: + pjsip_dlg_dec_lock(pres->dlg); + return status; } + +/* + * Create NOTIFY that reflect current state. + */ +PJ_DEF(pj_status_t) pjsip_pres_current_notify( pjsip_evsub *sub, + pjsip_tx_data **p_tdata ) +{ + pjsip_pres *pres; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub, PJ_EINVAL); + + /* Get the presence object. */ + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE); + + /* Must have at least one presence info. */ + PJ_ASSERT_RETURN(pres->status.info_cnt > 0, PJSIP_SIMPLE_ENOPRESENCEINFO); + + + /* Lock object. */ + pjsip_dlg_inc_lock(pres->dlg); + + /* Create the NOTIFY request. */ + status = pjsip_evsub_current_notify( sub, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Create message body to reflect the presence status. */ + status = pres_create_msg_body( pres, tdata ); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Done. */ + *p_tdata = tdata; + + +on_return: + pjsip_dlg_dec_lock(pres->dlg); + return status; +} + + +/* + * Send request. + */ +PJ_DEF(pj_status_t) pjsip_pres_send_request( pjsip_evsub *sub, + pjsip_tx_data *tdata ) +{ + return pjsip_evsub_send_request(sub, tdata); +} + + +/* + * This callback is called by event subscription when subscription + * state has changed. + */ +static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsip_pres *pres; + + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); + + if (pres->user_cb.on_evsub_state) + (*pres->user_cb.on_evsub_state)(sub, event); +} + +/* + * Called when transaction state has changed. + */ +static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event) +{ + pjsip_pres *pres; + + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); + + if (pres->user_cb.on_tsx_state) + (*pres->user_cb.on_tsx_state)(sub, tsx, event); +} + + /* - * This callback is called by event framework to query whether we want to - * accept an incoming subscription. + * Called when SUBSCRIBE is received. */ -static void on_query_subscribe(pjsip_rx_data *rdata, int *status) +static void pres_on_evsub_rx_refresh( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body) { - if (cb.accept_presence) { - (*cb.accept_presence)(rdata, status); + pjsip_pres *pres; + + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); + + if (pres->user_cb.on_rx_refresh) { + (*pres->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text, + res_hdr, p_body); + + } else { + /* Implementors MUST send NOTIFY if it implements on_rx_refresh */ + pjsip_tx_data *tdata; + pj_str_t timeout = { "timeout", 7}; + pj_status_t status; + + if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) { + status = pjsip_pres_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, &timeout, &tdata); + } else { + status = pjsip_pres_current_notify(sub, &tdata); + } + + if (status == PJ_SUCCESS) + pjsip_pres_send_request(sub, tdata); } } /* - * This callback is called by event framework after we accept the incoming - * subscription, to notify about the new subscription instance. + * Parse PIDF to info. */ -static void on_subscribe(pjsip_event_sub *sub, pjsip_rx_data *rdata, - pjsip_event_sub_cb **set_sub_cb, int *expires) +static pj_status_t pres_parse_pidf( pjsip_pres *pres, + pjsip_rx_data *rdata, + pjsip_pres_status *pres_status) { - pjsip_presentity *pres; - pjsip_accept_hdr *accept; + pjpidf_pres *pidf; + pjpidf_tuple *pidf_tuple; - 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; + pidf = pjpidf_parse(rdata->tp_info.pool, + rdata->msg_info.msg->body->data, + rdata->msg_info.msg->body->len); + if (pidf == NULL) + return PJSIP_SIMPLE_EBADPIDF; - 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; - } - } + pres_status->info_cnt = 0; + + pidf_tuple = pjpidf_pres_get_first_tuple(pidf); + while (pidf_tuple) { + pjpidf_status *pidf_status; + + pj_strdup(pres->dlg->pool, + &pres_status->info[pres_status->info_cnt].id, + pjpidf_tuple_get_id(pidf_tuple)); + + pj_strdup(pres->dlg->pool, + &pres_status->info[pres_status->info_cnt].contact, + pjpidf_tuple_get_contact(pidf_tuple)); + + pidf_status = pjpidf_tuple_get_status(pidf_tuple); + if (pidf_status) { + pres_status->info[pres_status->info_cnt].basic_open = + pjpidf_status_is_basic_open(pidf_status); + } else { + pres_status->info[pres_status->info_cnt].basic_open = PJ_FALSE; } - pj_assert(found ); + + pidf_tuple = pjpidf_pres_get_next_tuple( pidf, pidf_tuple ); + pres_status->info_cnt++; } - (*cb.on_received_request)(pres, rdata, expires); + return PJ_SUCCESS; } /* - * This callback is called by event framework when the subscription is - * terminated. + * Parse XPIDF info. */ -static void on_sub_terminated(pjsip_event_sub *sub, const pj_str_t *reason) +static pj_status_t pres_parse_xpidf( pjsip_pres *pres, + pjsip_rx_data *rdata, + pjsip_pres_status *pres_status) { - pjsip_presentity *pres = sub->user_data; - if (cb.on_terminated) - (*cb.on_terminated)(pres, reason); + pjxpidf_pres *xpidf; + + xpidf = pjxpidf_parse(rdata->tp_info.pool, + rdata->msg_info.msg->body->data, + rdata->msg_info.msg->body->len); + if (xpidf == NULL) + return PJSIP_SIMPLE_EBADXPIDF; + + pres_status->info_cnt = 1; + + pj_strdup(pres->dlg->pool, + &pres_status->info[0].contact, + pjxpidf_get_uri(xpidf)); + pres_status->info[0].basic_open = pjxpidf_get_status(xpidf); + pres_status->info[0].id.slen = 0; + + return PJ_SUCCESS; } + /* - * This callback is called by event framework when it receives incoming - * SUBSCRIBE request to refresh the subscription. + * Called when NOTIFY is received. */ -static void on_sub_received_refresh(pjsip_event_sub *sub, pjsip_rx_data *rdata) +static void pres_on_evsub_rx_notify( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body) { - pjsip_presentity *pres = sub->user_data; - if (cb.on_received_refresh) - (*cb.on_received_refresh)(pres, rdata); + pjsip_ctype_hdr *ctype_hdr; + pjsip_pres *pres; + pj_status_t status; + + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); + + /* Check Content-Type and msg body are present. */ + ctype_hdr = rdata->msg_info.ctype; + + if (ctype_hdr==NULL || rdata->msg_info.msg->body==NULL) { + + pjsip_warning_hdr *warn_hdr; + pj_str_t warn_text; + + *p_st_code = PJSIP_SC_BAD_REQUEST; + + warn_text = pj_str("Message body is not present"); + warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399, + pjsip_endpt_name(pres->dlg->endpt), + &warn_text); + pj_list_push_back(res_hdr, warn_hdr); + + return; + } + + /* Parse content. */ + + if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 && + pj_stricmp(&ctype_hdr->media.subtype, &STR_PIDF_XML)==0) + { + status = pres_parse_pidf( pres, rdata, &pres->tmp_status); + } + else + if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 && + pj_stricmp(&ctype_hdr->media.subtype, &STR_XPIDF_XML)==0) + { + status = pres_parse_pidf( pres, rdata, &pres->tmp_status); + } + else + { + status = PJSIP_SIMPLE_EBADCONTENT; + } + + if (status != PJ_SUCCESS) { + /* Unsupported or bad Content-Type */ + pjsip_accept_hdr *accept_hdr; + pjsip_warning_hdr *warn_hdr; + + *p_st_code = PJSIP_SC_NOT_ACCEPTABLE_HERE; + + /* Add Accept header */ + accept_hdr = pjsip_accept_hdr_create(rdata->tp_info.pool); + accept_hdr->values[accept_hdr->count++] = STR_APP_PIDF_XML; + accept_hdr->values[accept_hdr->count++] = STR_APP_XPIDF_XML; + pj_list_push_back(res_hdr, accept_hdr); + + /* Add Warning header */ + warn_hdr = pjsip_warning_hdr_create_from_status( + rdata->tp_info.pool, + pjsip_endpt_name(pres->dlg->endpt), + status); + pj_list_push_back(res_hdr, warn_hdr); + + return; + } + + /* If application calls pres_get_status(), redirect the call to + * retrieve the temporary status. + */ + pres->tmp_status._is_valid = PJ_TRUE; + + /* Notify application. */ + if (pres->user_cb.on_rx_notify) { + (*pres->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text, + res_hdr, p_body); + } + + + /* If application responded NOTIFY with 2xx, copy temporary status + * to main status, and mark the temporary status as invalid. + */ + if ((*p_st_code)/100 == 2) { + pj_memcpy(&pres->status, &pres->tmp_status, sizeof(pjsip_pres_status)); + } + + pres->tmp_status._is_valid = PJ_FALSE; + + /* Done */ } /* - * This callback is called by event framework when it receives incoming - * NOTIFY request. + * Called when it's time to send SUBSCRIBE. */ -static void on_received_notify(pjsip_event_sub *sub, pjsip_rx_data *rdata) +static void pres_on_evsub_client_refresh(pjsip_evsub *sub) { - pjsip_presentity *pres = sub->user_data; + pjsip_pres *pres; - if (cb.on_received_update) { - pj_status_t is_open; - pjsip_msg_body *body; - int i; + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); - body = rdata->msg->body; - if (!body) - return; + if (pres->user_cb.on_client_refresh) { + (*pres->user_cb.on_client_refresh)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; - 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; - } - } + status = pjsip_pres_initiate(sub, -1, &tdata); + if (status == PJ_SUCCESS) + pjsip_pres_send_request(sub, tdata); + } +} - 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); +/* + * Called when no refresh is received after the interval. + */ +static void pres_on_evsub_server_timeout(pjsip_evsub *sub) +{ + pjsip_pres *pres; - } else { - return; - } + pres = pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); - (*cb.on_received_update)(pres, is_open); + if (pres->user_cb.on_server_timeout) { + (*pres->user_cb.on_server_timeout)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + pj_str_t reason = { "timeout", 7 }; + + status = pjsip_pres_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, &reason, &tdata); + if (status == PJ_SUCCESS) + pjsip_pres_send_request(sub, tdata); } } diff --git a/pjsip/src/pjsip-simple/xpidf.c b/pjsip/src/pjsip-simple/xpidf.c index 7cc377ba..12024827 100644 --- a/pjsip/src/pjsip-simple/xpidf.c +++ b/pjsip/src/pjsip-simple/xpidf.c @@ -16,10 +16,11 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <pjsip_simple/xpidf.h> +#include <pjsip-simple/xpidf.h> +#include <pj/assert.h> +#include <pj/guid.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 }; |