summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip-simple/evsub.c
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-02-19 01:38:06 +0000
committerBenny Prijono <bennylp@teluu.com>2006-02-19 01:38:06 +0000
commit49a3b60593925562cbeb836a5885e034d2f78997 (patch)
tree87ae5502ec663309e0c78ad97cb22fd776896fc9 /pjsip/src/pjsip-simple/evsub.c
parent4e0f563feccb847c57739e48c91b0f5190938e9d (diff)
Initial SIMPLE implementation
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@197 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src/pjsip-simple/evsub.c')
-rw-r--r--pjsip/src/pjsip-simple/evsub.c1785
1 files changed, 1785 insertions, 0 deletions
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);
+ }
+
+ }
+}
+
+