summaryrefslogtreecommitdiffstats
path: root/src/subscription.cpp
diff options
context:
space:
mode:
authorMichal Kubecek <mkubecek@suse.cz>2015-04-13 09:21:39 +0200
committerMichal Kubecek <mkubecek@suse.cz>2015-04-13 09:21:39 +0200
commite2bc6f4153813cc570ae814c8ddb74628009b488 (patch)
treea40b171be1d859c2232ccc94f758010f9ae54d3c /src/subscription.cpp
downloadtwinkle-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.cpp695
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);
+}