diff options
author | Michal Kubecek <mkubecek@suse.cz> | 2015-04-13 09:21:39 +0200 |
---|---|---|
committer | Michal Kubecek <mkubecek@suse.cz> | 2015-04-13 09:21:39 +0200 |
commit | e2bc6f4153813cc570ae814c8ddb74628009b488 (patch) | |
tree | a40b171be1d859c2232ccc94f758010f9ae54d3c /src/subscription.cpp | |
download | twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar.gz twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar.lz twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar.xz twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.zip |
initial checkin
Check in contents of upstream 1.4.2 tarball, exclude generated files.
Diffstat (limited to 'src/subscription.cpp')
-rw-r--r-- | src/subscription.cpp | 695 |
1 files changed, 695 insertions, 0 deletions
diff --git a/src/subscription.cpp b/src/subscription.cpp new file mode 100644 index 0000000..8429618 --- /dev/null +++ b/src/subscription.cpp @@ -0,0 +1,695 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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 "subscription.h" + +#include "dialog.h" +#include "line.h" +#include "log.h" +#include "phone_user.h" +#include "audits/memman.h" +#include "parser/hdr_event.h" + +extern t_event_queue *evq_trans_mgr; +extern t_event_queue *evq_timekeeper; +extern t_timekeeper *timekeeper; + +string t_subscription_state2str(t_subscription_state state) { + switch (state) { + case SS_NULL: return "SS_NULL"; + case SS_ESTABLISHED: return "SS_ESTABLISHED"; + case SS_UNSUBSCRIBING: return "SS_UNSUBSCRIBING"; + case SS_UNSUBSCRIBED: return "SS_UNSUBSCRIBED"; + case SS_TERMINATED: return "SS_TERMINATED"; + } + + return "UNKNOWN"; +} + +///////////// +// PROTECTED +///////////// + +void t_subscription::log_event() const { + log_file->write_raw("Event: "); + log_file->write_raw(event_type); + log_file->write_endl(); + log_file->write_raw("Event id: "); + log_file->write_raw(event_id); + log_file->write_endl(); +} + +void t_subscription::remove_client_request(t_client_request **cr) { + if ((*cr)->dec_ref_count() == 0) { + MEMMAN_DELETE(*cr); + delete *cr; + } + + *cr = NULL; +} + +t_request *t_subscription::create_subscribe(unsigned long expires) const { + // RFC 3265 3.1.4 + t_request *r = dialog->create_request(SUBSCRIBE); + r->hdr_expires.set_time(expires); + r->hdr_event.set_event_type(event_type); + if (event_id.size() > 0) r->hdr_event.set_id(event_id); + + // Re-calculate the destination as the event type may + // influence the route to be taken. + // The destination has been calculated already at the + // dialog level. + r->calc_destinations(*user_config); + + return r; +} + +t_request *t_subscription::create_notify(const string &sub_state, + const string &reason) const +{ + // RFC 3265 3.2.2 + t_request *r = dialog->create_request(NOTIFY); + r->hdr_event.set_event_type(event_type); + if (event_id.size() > 0) r->hdr_event.set_id(event_id); + r->hdr_subscription_state.set_substate(sub_state); + + // Subscription state specific parameters + if (sub_state == SUBSTATE_ACTIVE || sub_state == SUBSTATE_PENDING) { + // Add expires parameter with remaining time + if (id_subscription_timeout) { + long remaining = timekeeper-> + get_remaining_time(id_subscription_timeout); + r->hdr_subscription_state.set_expires(remaining / 1000); + } + } else if (sub_state == SUBSTATE_TERMINATED) { + // Add reason parameter + if (reason.size() > 0) { + r->hdr_subscription_state.set_reason(reason); + } + } + + return r; +} + +void t_subscription::send_request(t_user *user_config, t_request *r, t_tuid tuid) const { + evq_trans_mgr->push_user(user_config, (t_sip_message *)r, tuid, 0); +} + +void t_subscription::send_response(t_user *user_config, t_response *r, + t_tuid tuid, t_tid tid) const +{ + evq_trans_mgr->push_user(user_config, (t_sip_message *)r, tuid, tid); +} + +void t_subscription::start_timer(t_subscribe_timer timer, long duration) { + t_tmr_subscribe *t; + t_object_id oid_line = 0; + + switch(timer) { + case STMR_SUBSCRIPTION: + if (dynamic_cast<t_dialog *>(dialog) != NULL) { + oid_line = dynamic_cast<t_dialog *>(dialog)->get_line()->get_object_id(); + } + t = new t_tmr_subscribe(duration, timer, oid_line, + dialog->get_object_id(), event_type, event_id); + MEMMAN_NEW(t); + id_subscription_timeout = t->get_object_id(); + break; + default: + assert(false); + } + + evq_timekeeper->push_start_timer(t); + MEMMAN_DELETE(t); + delete t; +} + +void t_subscription::stop_timer(t_subscribe_timer timer) { + unsigned short *id; + + switch(timer) { + case STMR_SUBSCRIPTION: + id = &id_subscription_timeout; + break; + default: + assert(false); + } + + if (*id != 0) evq_timekeeper->push_stop_timer(*id); + *id = 0; +} + +////////// +// PUBLIC +////////// + +t_subscription::t_subscription(t_abstract_dialog *_dialog, t_subscription_role _role, + const string &_event_type) +{ + dialog = _dialog; + + user_config = dialog->get_user(); + assert(user_config); + + role = _role; + state = SS_NULL; + resubscribe_after = 0; + may_resubscribe = false; + pending = true; + id_subscription_timeout = 0; + req_out = NULL; + event_type = _event_type; + auto_refresh = true; + subscription_expiry = 3600; + default_duration = 3600; +} + +t_subscription::t_subscription(t_abstract_dialog *_dialog, t_subscription_role _role, + const string &_event_type, const string &_event_id) +{ + dialog = _dialog; + + user_config = dialog->get_user(); + assert(user_config); + + role = _role; + state = SS_NULL; + resubscribe_after = 0; + may_resubscribe = false; + pending = true; + id_subscription_timeout = 0; + req_out = NULL; + event_type = _event_type; + event_id = _event_id; + auto_refresh = true; + subscription_expiry = 3600; + default_duration = 3600; +} + +t_subscription::~t_subscription() { + if (req_out) remove_client_request(&req_out); + if (id_subscription_timeout) stop_timer(STMR_SUBSCRIPTION); + + // Cleanup list of unsent NOTIFY messages + while (!queue_notify.empty()) { + t_request *r = queue_notify.front(); + queue_notify.pop(); + MEMMAN_DELETE(r); + delete r; + } +} + +t_subscription_role t_subscription::get_role(void) const { + return role; +} + +t_subscription_state t_subscription::get_state(void) const { + return state; +} + +string t_subscription::get_reason_termination(void) const { + return reason_termination; +} + +unsigned long t_subscription::get_resubscribe_after(void) const { + return resubscribe_after; +} + +bool t_subscription::get_may_resubscribe(void) const { + return may_resubscribe; +} + +string t_subscription::get_event_type(void) const { + return event_type; +} + +string t_subscription::get_event_id(void) const { + return event_id; +} + +unsigned long t_subscription::get_expiry(void) const { + return subscription_expiry; +} + +bool t_subscription::recv_subscribe(t_request *r, t_tuid tuid, t_tid tid) { + if (role != SR_NOTIFIER) { + // Reject a SUBSCRIBE coming in for a SUBSCRIBER + // TODO: is this ok?? + log_file->write_header("t_subscription::recv_subscribe", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("SUBSCRIBER receives SUBSCRIBE.\n"); + log_event(); + log_file->write_endl(); + log_file->write_raw(r->encode()); + log_file->write_endl(); + log_file->write_footer(); + + t_response *resp = r->create_response(R_603_DECLINE); + send_response(user_config, resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return true; + } + + // If the subscription is already in the terminated state + // then a SUBSCRIBE is not allowed anymore. + if (state == SS_TERMINATED) { + t_response *resp = r->create_response(R_481_TRANSACTION_NOT_EXIST, + REASON_481_SUBSCRIPTION_NOT_EXIST); + send_response(user_config, resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return true; + } + + if (state == SS_NULL) state = SS_ESTABLISHED; + + // If there is no expires header, then the implementation of + // the event specific package must deal with this subscribe + if (!r->hdr_expires.is_populated()) { + return false; + } + + // An expiry time of 0 is an unsubscribe + if (r->hdr_expires.time == 0) { + stop_timer(STMR_SUBSCRIPTION); + state = SS_TERMINATED; + return false; + } + + // Check if the requested expiry is not too small + if (r->hdr_expires.time < MIN_DUR_SUBSCRIPTION) { + t_response *resp = r->create_response( + R_423_INTERVAL_TOO_BRIEF); + resp->hdr_min_expires.set_time(MIN_DUR_SUBSCRIPTION); + send_response(user_config, resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return true; + } + + // Restart subscription timer + stop_timer(STMR_SUBSCRIPTION); + start_timer(STMR_SUBSCRIPTION, r->hdr_expires.time * 1000); + return false; +} + +bool t_subscription::recv_notify(t_request *r, t_tuid tuid, t_tid tid) { + if (role != SR_SUBSCRIBER) { + // Reject a NOTIFY coming in for a NOTIFIER + // TODO: is this ok?? + log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("NOTIFIER receives NOTIFY.\n"); + log_event(); + log_file->write_endl(); + log_file->write_raw(r->encode()); + log_file->write_endl(); + log_file->write_footer(); + + t_response *resp = r->create_response(R_603_DECLINE); + send_response(user_config, resp, 0, tid); + MEMMAN_DELETE(resp); + delete resp; + return true; + } + + if (state == SS_NULL) { + log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("NOTIFY establishes subscription.\n"); + log_event(); + log_file->write_footer(); + + state = SS_ESTABLISHED; + } + + if (r->hdr_subscription_state.substate == SUBSTATE_ACTIVE && pending) { + log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("NOTIFY ends pending state.\n"); + log_event(); + log_file->write_footer(); + + pending = false; + } + + if (r->hdr_subscription_state.substate == SUBSTATE_TERMINATED) { + stop_timer(STMR_SUBSCRIPTION); + state = SS_TERMINATED; + reason_termination = r->hdr_subscription_state.reason; + resubscribe_after = r->hdr_subscription_state.retry_after; + + // RFC 3264 3.2.4 + if (resubscribe_after > 0) { + may_resubscribe = true; + } else { + if (reason_termination == EV_REASON_DEACTIVATED || + reason_termination == EV_REASON_TIMEOUT) + { + may_resubscribe = true; + } else if (reason_termination == EV_REASON_PROBATION || + reason_termination == EV_REASON_GIVEUP) + { + may_resubscribe = true; + resubscribe_after = DUR_RESUBSCRIBE; + } + } + + log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("NOTIFY terminates subscription.\n"); + log_event(); + log_file->write_raw("Termination reason: "); + log_file->write_raw(reason_termination); + log_file->write_endl(); + log_file->write_raw("May resubscribe: "); + log_file->write_bool(may_resubscribe); + log_file->write_endl(); + log_file->write_raw("Resubscribe after: "); + log_file->write_raw(resubscribe_after); + log_file->write_endl(); + log_file->write_footer(); + } + + if (r->hdr_subscription_state.expires > 0 && state == SS_ESTABLISHED) { + log_file->write_header("t_subscription::recv_notify", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Received NOTIFY on established subscription.\n"); + log_event(); + log_file->write_footer(); + + unsigned long dur = r->hdr_subscription_state.expires; + if (auto_refresh) { + if (!id_subscription_timeout || + timekeeper->get_remaining_time(id_subscription_timeout) >= + dur * 1000) + { + // Adjust timer to expiry duration indicated + // in NOTIFY. + dur -= dur / 10; + stop_timer(STMR_SUBSCRIPTION); + start_timer(STMR_SUBSCRIPTION, dur * 1000); + } + } else { + if (!id_subscription_timeout || + timekeeper->get_remaining_time(id_subscription_timeout) < + dur * 1000) + { + // Adjust timer to expiry duration indicated + // in NOTIFY. + dur += dur / 10; + stop_timer(STMR_SUBSCRIPTION); + start_timer(STMR_SUBSCRIPTION, dur * 1000); + } + } + } + + return false; +} + +bool t_subscription::recv_response(t_response *r, t_tuid tuid, t_tid tid) { + switch (r->hdr_cseq.method) { + case NOTIFY: + return recv_notify_response(r, tuid, tid); + break; + case SUBSCRIBE: + return recv_subscribe_response(r, tuid, tid); + break; + default: + break; + } + + return false; +} + +bool t_subscription::recv_notify_response(t_response *r, t_tuid tuid, t_tid tid) { + // Discard response if it does not match a pending request + if (!req_out) return true; + if (r->hdr_cseq.method != req_out->get_request()->method) return true; + + // Ignore provisional responses + if (r->is_provisional()) return true; + + // Successful response + if (r->is_success()) { + if (req_out->get_request()->hdr_subscription_state.substate == + SUBSTATE_TERMINATED) + { + // This is a 2XX respsone on a NOTIFY that terminates the + // subscription. + state = SS_TERMINATED; + + log_file->write_header("t_subscription::recv_notify_response", + LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Subscription terminated by 2XX NOTIFY.\n"); + log_file->write_raw(r->code); + log_file->write_raw(" " + r->reason + "\n"); + log_event(); + log_file->write_footer(); + } + remove_client_request(&req_out); + } else { + // RFC 3265 3.2.2 + // NOTIFY failed, terminate subscription + remove_client_request(&req_out); + state = SS_TERMINATED; + + log_file->write_header("t_subscription::recv_notify_response", + LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Subscription terminated by NOTIFY failure response.\n"); + log_file->write_raw(r->code); + log_file->write_raw(" " + r->reason + "\n"); + log_event(); + log_file->write_footer(); + return true; + } + + // If there is a NOTIFY in the queue, then send it + if (!queue_notify.empty()) { + log_file->write_header("t_subscription::recv_notify_response", + LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Get NOTIFY from queue."); + log_event(); + log_file->write_footer(); + + t_request *notify = queue_notify.front(); + queue_notify.pop(); + req_out = new t_client_request(user_config, notify,0); + MEMMAN_NEW(req_out); + send_request(user_config, notify, req_out->get_tuid()); + MEMMAN_DELETE(notify); + delete notify; + } + + return true; +} + +bool t_subscription::recv_subscribe_response(t_response *r, t_tuid tuid, t_tid tid) { + // Discard response if it does not match a pending request + if (!req_out) return true; + if (r->hdr_cseq.method != req_out->get_request()->method) return true; + + // Ignore provisional responses + if (r->is_provisional()) return true; + + // Successful response + if (r->is_success()) { + if (state == SS_NULL) { + log_file->write_header("t_subscription::recv_subscribe_response", + LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Subscription established by 2XX SUBSCRIBE.\n"); + log_file->write_raw(r->code); + log_file->write_raw(" " + r->reason + "\n"); + log_event(); + log_file->write_footer(); + + state = SS_ESTABLISHED; + } + + // RFC 3265 7.1, 7.2 says that the Expires header is mandatory + // in a 2XX response. Some SIP servers do not include this + // however. To interoperate with such servers, assume that + // the granted expiry time equals the requested expiry time. + if (!r->hdr_expires.is_populated()) { + r->hdr_expires.set_time( + req_out->get_request()->hdr_expires.time); + + log_file->write_header( + "t_subscription::recv_subscribe_response", + LOG_NORMAL, LOG_WARNING); + log_file->write_raw("Mandatory Expires header missing.\n"); + log_file->write_raw("Assuming expires = "); + log_file->write_raw(r->hdr_expires.time); + log_file->write_endl(); + log_event(); + log_file->write_footer(); + } + + // If some faulty server sends a non-zero expiry time in + // a response on an unsubscribe request, then ignore + // the expiry time. + if (r->hdr_expires.time == 0 || state == SS_UNSUBSCRIBING) { + // Unsubscription succeeded. + stop_timer(STMR_SUBSCRIPTION); + + // Start the unsubscribe guard. If the triggered + // NOTIFY is never received, this guard assures, that + // the subscription will be cleaned up at timeout. + start_timer(STMR_SUBSCRIPTION, DUR_UNSUBSCRIBE_GUARD); + + // The subscription will only + // terminate after a NOTIFY triggered by the unsubscribe + // has been received or this guard timer expires. + state = SS_UNSUBSCRIBED; + auto_refresh = false; + + log_file->write_header("t_subscription::recv_subscribe_response", + LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Unsubcription succesful.\n"); + log_event(); + log_file->write_footer(); + } else { + // Start/refresh subscribe timer + stop_timer(STMR_SUBSCRIPTION); + unsigned long dur = r->hdr_expires.time; + if (auto_refresh) { + dur -= dur / 10; + } else { + dur += dur / 10; + } + start_timer(STMR_SUBSCRIPTION, dur * 1000); + } + + remove_client_request(&req_out); + return true; + } + + // RFC 3265 3.1.4.1 + // SUBSCRIBE failed, terminate subscription + // NOTE: redirection and authentication responses should have + // been handled already (eg. on line or phone user level). + remove_client_request(&req_out); + state = SS_TERMINATED; + + log_file->write_header("t_subscription::recv_subscribe_response", + LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Subscription terminated by SUBSCRIBE failure response.\n"); + log_file->write_raw(r->code); + log_file->write_raw(" " + r->reason + "\n"); + log_event(); + log_file->write_footer(); + + return true; +} + +bool t_subscription::timeout(t_subscribe_timer timer) { + switch (timer) { + case STMR_SUBSCRIPTION: + id_subscription_timeout = 0; + + if (role == SR_SUBSCRIBER) { + log_file->write_header("t_subcription::timeout"); + log_file->write_raw("Subscriber timed out.\n"); + log_event(); + log_file->write_footer(); + + if (auto_refresh) { + // Refresh subscription + refresh_subscribe(); + } else { + // The cause for timeout may be temporary. + // Allow resubscription to overcome a transient problem. + may_resubscribe = true; + state = SS_TERMINATED; + } + + return true; + } + break; + default: + assert(false); + } + + return false; +} + +bool t_subscription::match_timer(t_subscribe_timer timer, t_object_id id_timer) const { + return id_timer == id_subscription_timeout; +} + +bool t_subscription::match(t_request *r) const { + if (!r->hdr_event.is_populated()) return false; + + // If the subscription has been terminated, then do not match + // any request. + if (state == SS_TERMINATED) return false; + + return (r->hdr_event.event_type == event_type && + r->hdr_event.id == event_id); +} + +bool t_subscription::is_pending(void) const { + return pending; +} + +void t_subscription::subscribe(unsigned long expires) { + if (req_out) { + // Delete previous outgoing request + MEMMAN_DELETE(req_out); + delete req_out; + } + + if (expires > 0) { + subscription_expiry = expires; + } else { + subscription_expiry = default_duration; + } + + t_request *r = create_subscribe(subscription_expiry); + req_out = new t_client_request(user_config, r ,0); + MEMMAN_NEW(req_out); + send_request(user_config, r, req_out->get_tuid()); + MEMMAN_DELETE(r); + delete r; +} + +void t_subscription::unsubscribe(void) { + if (state != SS_ESTABLISHED) { + state = SS_TERMINATED; + return; + } + + if (req_out) { + // Delete previous outgoing request + MEMMAN_DELETE(req_out); + delete req_out; + } + + stop_timer(STMR_SUBSCRIPTION); + + t_request *r = create_subscribe(0); + req_out = new t_client_request(user_config, r ,0); + MEMMAN_NEW(req_out); + send_request(user_config, r, req_out->get_tuid()); + MEMMAN_DELETE(r); + delete r; + + // NOTE: the subscription is only ended when the response is received. + state = SS_UNSUBSCRIBING; +} + +void t_subscription::refresh_subscribe(void) { + if (state != SS_ESTABLISHED) return; + stop_timer(STMR_SUBSCRIPTION); + subscribe(subscription_expiry); +} |