diff options
Diffstat (limited to 'pjsip/src/pjsip-simple/evsub.c')
-rw-r--r-- | pjsip/src/pjsip-simple/evsub.c | 1785 |
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); + } + + } +} + + |