/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 }; PJ_DEF_DATA(const pjsip_method) pjsip_subscribe_method = { (pjsip_method_e) PJSIP_SUBSCRIBE_METHOD, { "SUBSCRIBE", 9 } }; PJ_DEF_DATA(const pjsip_method) pjsip_notify_method = { (pjsip_method_e) PJSIP_NOTIFY_METHOD, { "NOTIFY", 6 } }; /** * SUBSCRIBE method constant. */ PJ_DEF(const pjsip_method*) pjsip_get_subscribe_method() { return &pjsip_subscribe_method; } /** * NOTIFY method constant. */ PJ_DEF(const pjsip_method*) pjsip_get_notify_method() { return &pjsip_notify_method; } /* * 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 PJSIP_EVSUB_TIME_UAC_REFRESH /* Time to wait for the final NOTIFY after sending unsubscription */ #define TIME_UAC_TERMINATE PJSIP_EVSUB_TIME_UAC_TERMINATE /* 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 PJSIP_EVSUB_TIME_UAC_WAIT_NOTIFY /* * 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, /* Max nb of timer types. */ TIMER_TYPE_MAX }; static const char *timer_names[] = { "None", "UAC_REFRESH", "UAS_TIMEOUT", "UAC_TERMINATE", "UAC_WAIT_NOTIFY", "INVALID_TIMER" }; /* * 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_DIALOG_USAGE, /* Priority */ 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. */ unsigned option; /**< Options. */ pjsip_evsub_user user; /**< Callback. */ pj_bool_t call_cb; /**< Notify 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. */ pj_str_t term_reason; /**< Termination reason. */ 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. */ pjsip_hdr sub_hdr_list; /**< User-defined header. */ pj_time_val refresh_time; /**< Time to refresh. */ pj_timer_entry timer; /**< Internal timer. */ int pending_tsx; /**< Number of pending transactions.*/ pjsip_transaction *pending_sub; /**< Pending UAC SUBSCRIBE tsx. */ pj_timer_entry *pending_sub_timer; /**< Stop pending sub timer. */ pj_grp_lock_t *grp_lock; /* Session group lock */ 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_EVENT_S = { "o", 1 }; 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}; static const pj_str_t STR_RETRY_AFTER = { "Retry-After", 11 }; /* * 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; } /* Proto for pjsipsimple_strerror(). * Defined in errno.c */ PJ_DECL(pj_str_t) pjsipsimple_strerror( pj_status_t statcode, char *buf, pj_size_t bufsize ); /* * Init and register module. */ PJ_DEF(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt) { pj_status_t status; pj_str_t method_tags[] = { { "SUBSCRIBE", 9}, { "NOTIFY", 6} }; status = pj_register_strerror(PJSIP_SIMPLE_ERRNO_START, PJ_ERRNO_SPACE_SIZE, &pjsipsimple_strerror); pj_assert(status == PJ_SUCCESS); 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", 512, 512); 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(); /* Register new methods SUBSCRIBE and NOTIFY in Allow-ed header */ pjsip_endpt_add_capability(endpt, &mod_evsub.mod, PJSIP_H_ALLOW, NULL, 2, method_tags); /* Done. */ 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 (pjsip_evsub*) 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); /* Make sure accept_cnt < PJ_ARRAY_SIZE(pkg->pkg_accept->values) */ PJ_ASSERT_RETURN(accept_cnt <= PJSIP_GENERIC_ARRAY_MAX_COUNT, PJ_ETOOMANY); /* Make sure evsub module has been initialized */ PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, PJ_EINVALIDOP); /* 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_T(mod_evsub.pool, 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; ipkg_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; } /* Add to endpoint's Accept header */ pjsip_endpt_add_capability(mod_evsub.endpt, &mod_evsub.mod, PJSIP_H_ACCEPT, NULL, pkg->pkg_accept->count, pkg->pkg_accept->values); /* 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; } /* * Retrieve Allow-Events header */ PJ_DEF(const pjsip_hdr*) pjsip_evsub_get_allow_events_hdr(pjsip_module *m) { struct mod_evsub *mod; if (m == NULL) m = pjsip_evsub_instance(); mod = (struct mod_evsub*)m; return (pjsip_hdr*) mod->allow_events_hdr; } /* * 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_uint32_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 && seconds != PJSIP_EXPIRES_NOT_SPECIFIED) { pj_time_val timeout; PJ_ASSERT_ON_FAIL(timer_id>TIMER_TYPE_NONE && timer_idendpt), &sub->timer, &timeout, timer_id, sub->grp_lock); PJ_LOG(5,(sub->obj_name, "Timer %s scheduled in %d seconds", timer_names[sub->timer.id], timeout.sec)); } } /* * Set event subscription UAS timout. */ PJ_DEF(void) pjsip_evsub_uas_set_timeout(pjsip_evsub *sub, pj_uint32_t seconds) { set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, (pj_int32_t)seconds); } /* * Destructor. */ static void evsub_on_destroy(void *obj) { pjsip_evsub *sub = (pjsip_evsub*)obj; /* Decrement dialog's session */ pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod); } /* * 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); /* Kill timer for stopping pending sub (see ticket #1807) */ if (sub->pending_sub_timer && sub->pending_sub_timer->id == 1) { pjsip_endpt_cancel_timer(sub->endpt, sub->pending_sub_timer); sub->pending_sub_timer->id = 0; sub->pending_sub_timer = NULL; } /* Remove this session from dialog's list of subscription */ dlgsub_head = (struct dlgsub *) 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; } pj_grp_lock_dec_ref(sub->grp_lock); } /* * Set subscription session state. */ static void set_state( pjsip_evsub *sub, pjsip_evsub_state state, const pj_str_t *state_str, pjsip_event *event, const pj_str_t *reason) { pjsip_evsub_state prev_state = sub->state; pj_str_t old_state_str = sub->state_str; pjsip_event dummy_event; 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]; if (reason && sub->term_reason.slen==0) pj_strdup(sub->pool, &sub->term_reason, reason); 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)); pj_log_push_indent(); /* don't call the callback with NULL event, it may crash the app! */ if (!event) { PJSIP_EVENT_INIT_USER(dummy_event, 0, 0, 0, 0); event = &dummy_event; } if (sub->user.on_evsub_state && sub->call_cb) (*sub->user.on_evsub_state)(sub, event); if (state == PJSIP_EVSUB_STATE_TERMINATED && prev_state != PJSIP_EVSUB_STATE_TERMINATED) { /* Kill any timer. */ set_timer(sub, TIMER_TYPE_NONE, 0); if (sub->pending_tsx == 0) { evsub_destroy(sub); } } pj_log_pop_indent(); } /* * 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 = (pjsip_evsub*) entry->user_data; pjsip_dlg_inc_lock(sub->dlg); /* If this timer entry has just been rescheduled or cancelled * while waiting for dialog mutex, just return (see #1885 scenario 1). */ if (pj_timer_entry_running(entry) || entry->id == TIMER_TYPE_NONE) { pjsip_dlg_dec_lock(sub->dlg); return; } 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->call_cb) { (*sub->user.on_client_refresh)(sub); } else { pjsip_tx_data *tdata; pj_status_t status; PJ_LOG(5,(sub->obj_name, "Refreshing subscription.")); pj_log_push_indent(); status = pjsip_evsub_initiate(sub, NULL, sub->expires->ivalue, &tdata); if (status == PJ_SUCCESS) pjsip_evsub_send_request(sub, tdata); pj_log_pop_indent(); } break; case TIMER_TYPE_UAS_TIMEOUT: /* Refresh from UAC has not been received */ if (sub->user.on_server_timeout && sub->call_cb) { (*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.")); pj_log_push_indent(); status = pjsip_evsub_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, &STR_TIMEOUT, &tdata); if (status == PJ_SUCCESS) pjsip_evsub_send_request(sub, tdata); pj_log_pop_indent(); } break; case TIMER_TYPE_UAC_TERMINATE: { pj_str_t timeout = {"timeout", 7}; PJ_LOG(5,(sub->obj_name, "Timeout waiting for final NOTIFY. " "Terminating..")); pj_log_push_indent(); set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, &timeout); pj_log_pop_indent(); } 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..")); pj_log_push_indent(); status = pjsip_evsub_initiate( sub, NULL, 0, &tdata); if (status == PJ_SUCCESS) pjsip_evsub_send_request(sub, tdata); pj_log_pop_indent(); } 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, unsigned option, 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; /* Must lock dialog before using pool etc. */ pjsip_dlg_inc_lock(dlg); /* Init attributes: */ sub = PJ_POOL_ZALLOC_T(dlg->pool, struct pjsip_evsub); sub->pool = dlg->pool; sub->endpt = dlg->endpt; sub->dlg = dlg; sub->pkg = pkg; sub->role = role; sub->call_cb = PJ_TRUE; sub->option = option; 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_accept_hdr*) pjsip_hdr_clone(sub->pool, pkg->pkg_accept); pj_list_init(&sub->sub_hdr_list); sub->timer.user_data = sub; sub->timer.cb = &on_timer; /* Set name. */ pj_ansi_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); /* Check if another subscription has been registered to the dialog. In * that case, just add ourselves to the subscription list, otherwise * create and register a new subscription list. */ if (pjsip_dlg_has_usage(dlg, &mod_evsub.mod)) { dlgsub_head = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id]; dlgsub = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub); dlgsub->sub = sub; pj_list_push_back(dlgsub_head, dlgsub); } else { dlgsub_head = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub); dlgsub = PJ_POOL_ALLOC_T(sub->pool, 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) { pjsip_dlg_dec_lock(dlg); 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; pjsip_dlg_dec_lock(dlg); return PJ_SUCCESS; } /* * Increment the event subscription's group lock. */ PJ_DEF(pj_status_t) pjsip_evsub_add_ref(pjsip_evsub *sub) { return pj_grp_lock_add_ref(sub->grp_lock); } /* * Decrement the event subscription's group lock. */ PJ_DEF(pj_status_t) pjsip_evsub_dec_ref(pjsip_evsub *sub) { return pj_grp_lock_dec_ref(sub->grp_lock); } /* * 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, unsigned option, 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, option, &sub); if (status != PJ_SUCCESS) goto on_return; /* Add unique Id to Event header, only when PJSIP_EVSUB_NO_EVENT_ID * is not specified. */ if ((option & PJSIP_EVSUB_NO_EVENT_ID) == 0) { pj_create_unique_string(sub->pool, &sub->event->id_param); } /* Increment dlg session. */ pjsip_dlg_inc_session(sub->dlg, &mod_evsub.mod); /* Init group lock */ status = pj_grp_lock_create(dlg->pool, NULL, &sub->grp_lock); if (status != PJ_SUCCESS) { pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod); goto on_return; } pj_grp_lock_add_ref(sub->grp_lock); pj_grp_lock_add_handler(sub->grp_lock, dlg->pool, sub, &evsub_on_destroy); /* 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, unsigned option, 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. We need the Event header to get * the package name (don't want to add more arguments in the function). */ event_hdr = (pjsip_event_hdr*) pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &STR_EVENT, &STR_EVENT_S, 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, option, &sub); if (status != PJ_SUCCESS) goto on_return; /* Just duplicate Event header from the request */ sub->event = (pjsip_event_hdr*) 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_accept_hdr*)pjsip_hdr_clone(sub->pool,accept_hdr); /* Increment dlg session. */ pjsip_dlg_inc_session(dlg, &mod_evsub.mod); /* Init group lock */ status = pj_grp_lock_create(dlg->pool, NULL, &sub->grp_lock); if (status != PJ_SUCCESS) { pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod); goto on_return; } pj_grp_lock_add_ref(sub->grp_lock); pj_grp_lock_add_handler(sub->grp_lock, dlg->pool, sub, &evsub_on_destroy); /* We can start the session: */ sub->pending_tsx++; tsx->mod_data[mod_evsub.mod.id] = sub; /* Done. */ *p_evsub = sub; on_return: pjsip_dlg_dec_lock(dlg); return status; } /* * Forcefully destroy subscription. */ PJ_DEF(pj_status_t) pjsip_evsub_terminate( pjsip_evsub *sub, pj_bool_t notify ) { PJ_ASSERT_RETURN(sub, PJ_EINVAL); pjsip_dlg_inc_lock(sub->dlg); /* I think it's pretty safe to disable this check. if (sub->pending_tsx) { pj_assert(!"Unable to terminate when there's pending tsx"); pjsip_dlg_dec_lock(sub->dlg); return PJ_EINVALIDOP; } */ sub->call_cb = notify; set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, NULL); pjsip_dlg_dec_lock(sub->dlg); return PJ_SUCCESS; } /* * 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; } /* * Get termination reason. */ PJ_DEF(const pj_str_t*) pjsip_evsub_get_termination_reason(pjsip_evsub *sub) { return &sub->term_reason; } /* * Initiate client subscription */ PJ_DEF(pj_status_t) pjsip_evsub_initiate( pjsip_evsub *sub, const pjsip_method *method, pj_uint32_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*) pjsip_hdr_shallow_clone(tdata->pool, sub->event)); /* Update and add expires header: */ if (expires != PJSIP_EXPIRES_NOT_SPECIFIED) sub->expires->ivalue = expires; pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) pjsip_hdr_shallow_clone(tdata->pool, sub->expires)); /* Add Supported header (it's optional in RFC 3265, but some event package * RFC may bring this requirement to SHOULD strength - e.g. RFC 5373) */ { const pjsip_hdr *hdr = pjsip_endpt_get_capability(sub->endpt, PJSIP_H_SUPPORTED, NULL); if (hdr) { pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) pjsip_hdr_shallow_clone(tdata->pool, hdr)); } } /* Add Accept header: */ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) pjsip_hdr_shallow_clone(tdata->pool, sub->accept)); /* Add Allow-Events header: */ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) pjsip_hdr_shallow_clone(tdata->pool, mod_evsub.allow_events_hdr)); /* Add custom headers */ { const pjsip_hdr *hdr = sub->sub_hdr_list.next; while (hdr != &sub->sub_hdr_list) { pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) pjsip_hdr_shallow_clone(tdata->pool, hdr)); hdr = hdr->next; } } *p_tdata = tdata; on_return: pjsip_dlg_dec_lock(sub->dlg); return status; } /* * Add custom headers. */ PJ_DEF(pj_status_t) pjsip_evsub_add_header( pjsip_evsub *sub, const pjsip_hdr *hdr_list ) { const pjsip_hdr *hdr; PJ_ASSERT_RETURN(sub && hdr_list, PJ_EINVAL); hdr = hdr_list->next; while (hdr != hdr_list) { pj_list_push_back(&sub->sub_hdr_list, (pjsip_hdr*) pjsip_hdr_clone(sub->pool, hdr)); hdr = hdr->next; } return PJ_SUCCESS; } /* * 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*) pjsip_hdr_shallow_clone(tdata->pool, sub->expires)); /* Add additional header, if any. */ if (hdr_list) { const pjsip_hdr *hdr = hdr_list->next; while (hdr != hdr_list) { pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr)); hdr = hdr->next; } } /* Send the response: */ status = pjsip_dlg_send_response( sub->dlg, tsx, tdata ); if (status != PJ_SUCCESS) goto on_return; /* Set UAS timeout timer, when status code is 2xx and state is not * terminated. */ if (st_code/100 == 2 && 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); } 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_NULL: case PJSIP_EVSUB_STATE_SENT: pj_assert(!"Invalid state!"); /* Treat as pending */ case PJSIP_EVSUB_STATE_ACCEPTED: 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_get_notify_method(), -1, &tdata); if (status != PJ_SUCCESS) goto on_return; /* Add Event header */ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) 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*) pjsip_hdr_shallow_clone(tdata->pool, mod_evsub.allow_events_hdr)); /* Add Authentication headers. */ pjsip_auth_clt_init_req( &sub->dlg->auth_sess, tdata ); /* Update reason */ if (reason) pj_strdup(sub->dlg->pool, &sub->term_reason, reason); /* 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, -1, 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, NULL); sub->dst_state = PJSIP_EVSUB_STATE_NULL; sub->dst_state_str.slen = 0; } on_return: pjsip_dlg_dec_lock(sub->dlg); return status; } /* Callback to be called to terminate transaction. */ static void terminate_timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) { pj_str_t *key; pjsip_transaction *tsx; PJ_UNUSED_ARG(timer_heap); /* Clear timer ID */ entry->id = 0; key = (pj_str_t*)entry->user_data; tsx = pjsip_tsx_layer_find_tsx(key, PJ_FALSE); /* Chance of race condition here */ if (tsx) { pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_UPDATED); } } /* * 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) { //Note: // this transaction can be other transaction in the dialog. // The assertion below probably only valid for dialog that // only has one event subscription usage. //pj_assert(!"First transaction event is not TX or RX!"); return NULL; } event_hdr = (pjsip_event_hdr*) pjsip_msg_find_hdr_by_names(msg, &STR_EVENT, &STR_EVENT_S, 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 = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id]; if (dlgsub_head == NULL) { dlgsub_head = PJ_POOL_ALLOC_T(dlg->pool, struct dlgsub); pj_list_init(dlgsub_head); dlg->mod_data[mod_evsub.mod.id] = dlgsub_head; } dlgsub = dlgsub_head->next; while (dlgsub != dlgsub_head) { if (pj_stricmp(&dlgsub->sub->event->event_type, &event_hdr->event_type)==0) { /* Event type matched. * Check if event ID matched too. */ if (pj_strcmp(&dlgsub->sub->event->id_param, &event_hdr->id_param)==0) { /* Skip this subscription if it has no event ID and has been * terminated (see ticket #1647). */ if ((dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID) && (pjsip_evsub_get_state(dlgsub->sub)== PJSIP_EVSUB_STATE_TERMINATED)) { dlgsub = dlgsub->next; continue; } else { break; } } /* * Otherwise if it is an UAC subscription, AND * PJSIP_EVSUB_NO_EVENT_ID flag is set, AND * the session's event id is NULL, AND * the incoming request is NOTIFY with event ID, then * we consider it as a match, and update the * session's event id. */ else if (dlgsub->sub->role == PJSIP_ROLE_UAC && (dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID)!=0 && dlgsub->sub->event->id_param.slen==0 && !pjsip_method_cmp(&tsx->method, &pjsip_notify_method)) { /* Update session's event id. */ pj_strdup(dlgsub->sub->pool, &dlgsub->sub->event->id_param, &event_hdr->id_param); break; } } dlgsub = dlgsub->next; } /* Note: * the second condition is for http://trac.pjsip.org/repos/ticket/911 */ if (dlgsub == dlgsub_head || (dlgsub->sub && pjsip_evsub_get_state(dlgsub->sub)==PJSIP_EVSUB_STATE_TERMINATED)) { const char *reason_msg = (dlgsub == dlgsub_head ? "Subscription Does Not Exist" : "Subscription already terminated"); /* This could be incoming request to create new subscription */ PJ_LOG(4,(THIS_FILE, "%s for %.*s, event=%.*s;id=%.*s", reason_msg, (int)tsx->method.name.slen, tsx->method.name.ptr, (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; pjsip_tx_data *tdata; pj_status_t status; pj_cstr(&reason, reason_msg); 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++; /* Special case for outgoing/UAC SUBSCRIBE/REFER transaction. * We can only have one pending UAC SUBSCRIBE/REFER, so if another * transaction is started while previous one still alive, terminate * the older one. * * Sample scenario: * - subscribe sent to destination that doesn't exist, transaction * is still retransmitting request, then unsubscribe is sent. */ if (tsx->role == PJSIP_ROLE_UAC && tsx->state == PJSIP_TSX_STATE_CALLING && (pjsip_method_cmp(&tsx->method, &sub->method) == 0 || pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0)) { if (sub->pending_sub && sub->pending_sub->state < PJSIP_TSX_STATE_COMPLETED) { pj_timer_entry *timer; pj_str_t *key; pj_time_val timeout = {0, 0}; PJ_LOG(4,(sub->obj_name, "Cancelling pending subscription request")); /* By convention, we use 490 (Request Updated) status code. * When transaction handler (below) see this status code, it * will ignore the transaction. */ /* This unfortunately may cause deadlock, because at the moment * we are holding dialog's mutex. If a response to this * transaction is in progress in another thread, that thread * will deadlock when trying to acquire dialog mutex, because * it is holding the transaction mutex. * * So the solution is to register timer to kill this transaction. */ //pjsip_tsx_terminate(sub->pending_sub, PJSIP_SC_REQUEST_UPDATED); timer = PJ_POOL_ZALLOC_T(dlg->pool, pj_timer_entry); key = PJ_POOL_ALLOC_T(dlg->pool, pj_str_t); pj_strdup(dlg->pool, key, &sub->pending_sub->transaction_key); timer->cb = &terminate_timer_cb; timer->user_data = key; timer->id = 1; sub->pending_sub_timer = timer; pjsip_endpt_schedule_timer(dlg->endpt, timer, &timeout); } sub->pending_sub = 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*) pjsip_hdr_clone(tdata->pool, hdr)); hdr = hdr->next; } /* Add msg body, if any */ if (body) { tdata->msg->body = pjsip_msg_body_clone(tdata->pool, body); if (tdata->msg->body == NULL) { PJ_LOG(4,(THIS_FILE, "Error: unable to clone msg body")); /* 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 || pjsip_method_cmp(&tsx->method, &pjsip_subscribe_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, NULL); return; } /* Only interested in final response */ if (tsx->state != PJSIP_TSX_STATE_COMPLETED && tsx->state != PJSIP_TSX_STATE_TERMINATED) { return; } /* Clear pending subscription */ if (tsx == sub->pending_sub) { sub->pending_sub = NULL; } else if (sub->pending_sub != NULL) { /* This SUBSCRIBE transaction has been "renewed" with another * SUBSCRIBE, so we can just ignore this. For example, user * sent SUBSCRIBE followed immediately with UN-SUBSCRIBE. */ 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, -1, NULL); if (status != PJ_SUCCESS) { /* Authentication failed! */ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event, &tsx->status_text); 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_expires_hdr*) 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; /* Reduce timeout by about 1 - 10 secs (randomized) */ if (timeout > 10) timeout += -10 + (pj_rand() % 10); 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, NULL); } } 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; } /* Ignore 490 (Request Updated) status. * This happens when application sends SUBSCRIBE/REFER while * another one is still in progress. */ if (tsx->status_code == PJSIP_SC_REQUEST_UPDATED) { return; } /* Set state to TERMINATED */ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event, &tsx->status_text); } } 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; /* 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_sub_state_hdr*) 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->call_cb) { (*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 { char errmsg[PJ_ERR_MSG_SIZE]; pj_str_t reason; reason = pj_strerror(status, errmsg, sizeof(errmsg)); set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, &reason); } 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 != PJSIP_EXPIRES_NOT_SPECIFIED && (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0 || pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0)) { unsigned next_refresh = sub_state->expires_param; unsigned timeout; update_expires(sub, next_refresh); /* Start UAC refresh timer, only when we're not unsubscribing */ 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, &sub_state->reason_param); } else { char errmsg[PJ_ERR_MSG_SIZE]; pj_str_t reason; reason = pj_strerror(status, errmsg, sizeof(errmsg)); set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event, &reason); } } 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 || pjsip_method_cmp(&tsx->method, &pjsip_subscribe_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_str_t reason = { NULL, 0 }; 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_event_hdr*) pjsip_msg_find_hdr_by_names(msg, &STR_EVENT, &STR_EVENT, NULL); expires = (pjsip_expires_hdr*) 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 < 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); if (sub->user.on_rx_refresh && sub->call_cb) { (*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*) 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, &reason); } else if (sub->state == PJSIP_EVSUB_STATE_NULL) { set_state(sub, sub->state, NULL, event, &reason); } /* 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_tx_data *tdata; pj_status_t status; pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; /* Handled by other module already (e.g: invite module) */ if (tsx->last_tx->auth_retry) return; 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, -1, NULL); if (status != PJ_SUCCESS) { /* Can't authenticate. Terminate session (?) */ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, &tsx->status_text); } return; } if (sub->state == PJSIP_EVSUB_STATE_TERMINATED) return; /* NOTIFY failure check */ if (tsx->status_code/100 != 2) { pj_bool_t should_terminate_sub = PJ_FALSE; if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { if (tsx->status_code == 481) { should_terminate_sub = PJ_TRUE; } else { pjsip_retry_after_hdr *retry_after; pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; pjsip_msg *msg = rdata->msg_info.msg; retry_after = (pjsip_retry_after_hdr*) pjsip_msg_find_hdr_by_name(msg, &STR_RETRY_AFTER, NULL); if (!retry_after) { should_terminate_sub = PJ_TRUE; } } } else if (event->body.tsx_state.type == PJSIP_EVENT_TIMER) { if (tsx->status_code == 408) { should_terminate_sub = PJ_TRUE; } } /* * Terminate event usage if we receive non 2xx without retry_after * parameter, 481, 408 responses. */ if (should_terminate_sub) { set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event, &tsx->status_text); return; } } } 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->call_cb) (*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); } } }