summaryrefslogtreecommitdiffstats
path: root/src/epa.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/epa.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/epa.cpp')
-rw-r--r--src/epa.cpp516
1 files changed, 516 insertions, 0 deletions
diff --git a/src/epa.cpp b/src/epa.cpp
new file mode 100644
index 0000000..857100d
--- /dev/null
+++ b/src/epa.cpp
@@ -0,0 +1,516 @@
+/*
+ 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 "epa.h"
+
+#include "log.h"
+#include "phone.h"
+#include "timekeeper.h"
+#include "util.h"
+#include "audits/memman.h"
+
+extern t_phone *phone;
+extern t_event_queue *evq_timekeeper;
+extern string local_hostname;
+
+/////////////
+// PRIVATE
+/////////////
+
+void t_epa::enqueue_request(t_request *r) {
+ log_file->write_header("t_epa::enqueue_request", LOG_NORMAL, LOG_DEBUG);
+ log_file->write_raw("Enqueue request\n");
+ log_publication();
+ log_file->write_footer();
+
+ queue_publish.push(r);
+}
+
+
+/////////////
+// PROTECTED
+/////////////
+
+void t_epa::log_publication() const {
+ log_file->write_raw("Event: ");
+ log_file->write_raw(event_type);
+ log_file->write_raw(", URI: ");
+ log_file->write_raw(request_uri.encode());
+ log_file->write_raw(", SIP-ETag: ");
+ log_file->write_raw(etag);
+ log_file->write_endl();
+}
+
+void t_epa::remove_client_request(t_client_request **cr) {
+ if ((*cr)->dec_ref_count() == 0) {
+ MEMMAN_DELETE(*cr);
+ delete *cr;
+ }
+
+ *cr = NULL;
+}
+
+t_request *t_epa::create_publish(unsigned long expires, t_sip_body *body) const {
+ t_user *user_config = phone_user->get_user_profile();
+ t_request *r = phone_user->create_request(PUBLISH, request_uri);
+
+ // Call-ID
+ r->hdr_call_id.set_call_id(NEW_CALL_ID(user_config));
+
+ // CSeq
+ r->hdr_cseq.set_method(PUBLISH);
+ r->hdr_cseq.set_seqnr(NEW_SEQNR);
+
+ // To
+ r->hdr_to.set_uri(user_config->create_user_uri(false));
+ r->hdr_to.set_display(user_config->get_display(false));
+
+ // RFC 3903 4 Expires
+ r->hdr_expires.set_time(expires);
+
+ // RFC 3903 4 Event
+ r->hdr_event.set_event_type(event_type);
+
+ // SIP-If-Match
+ if (!etag.empty()) {
+ r->hdr_sip_if_match.set_etag(etag);
+ }
+
+ // Body
+ if (body) {
+ r->body = body;
+ r->hdr_content_type.set_media(body->get_media());
+ }
+
+ return r;
+}
+
+void t_epa::send_request(t_request *r, t_tuid tuid) const {
+ phone->send_request(phone_user->get_user_profile(), r, tuid);
+}
+
+void t_epa::send_publish_from_queue(void) {
+ // If there is a PUBLISH in the queue, then send it
+ while (!queue_publish.empty()) {
+ log_file->write_header("t_epa::send_publish_from_queue", LOG_NORMAL, LOG_DEBUG);
+ log_file->write_raw("Get PUBLISH from queue.\n");
+ log_publication();
+ log_file->write_footer();
+
+ t_request *req = queue_publish.front();
+ queue_publish.pop();
+
+ // Update the SIP-If-Match header to the current entity tag
+ if (!etag.empty()) {
+ req->hdr_sip_if_match.set_etag(etag);
+ } else {
+ req->hdr_sip_if_match.clear();
+ }
+
+ if (req->hdr_expires.time == 0) {
+ if (epa_state != EPA_PUBLISHED) {
+ log_file->write_header("t_epa::send_publish_from_queue", LOG_NORMAL, LOG_DEBUG);
+ log_file->write_raw("Nothing published, discard unpublish\n");
+ log_publication();
+ log_file->write_footer();
+
+ MEMMAN_DELETE(req);
+ delete req;
+ continue;
+ }
+ is_unpublishing = true;
+ } else {
+ is_unpublishing = false;
+ }
+
+ stop_timer(PUBLISH_TMR_PUBLICATION);
+
+ req_out = new t_client_request(phone_user->get_user_profile(), req, 0);
+ MEMMAN_NEW(req_out);
+ send_request(req, req_out->get_tuid());
+ MEMMAN_DELETE(req);
+ delete req;
+
+ break;
+ }
+}
+
+void t_epa::start_timer(t_publish_timer timer, long duration) {
+ t_tmr_publish *t = NULL;
+
+ switch(timer) {
+ case PUBLISH_TMR_PUBLICATION:
+ t = new t_tmr_publish(duration, timer, event_type);
+ MEMMAN_NEW(t);
+ id_publication_timeout = t->get_object_id();
+ break;
+ default:
+ assert(false);
+ }
+
+ evq_timekeeper->push_start_timer(t);
+ MEMMAN_DELETE(t);
+ delete t;
+}
+
+void t_epa::stop_timer(t_publish_timer timer) {
+ unsigned short *id;
+
+ switch(timer) {
+ case PUBLISH_TMR_PUBLICATION:
+ id = &id_publication_timeout;
+ break;
+ default:
+ assert(false);
+ }
+
+ if (*id != 0) evq_timekeeper->push_stop_timer(*id);
+ *id = 0;
+}
+
+//////////
+// PUBLIC
+//////////
+
+t_epa::t_epa(t_phone_user *pu, const string &_event_type, const t_url _request_uri) :
+ phone_user(pu),
+ epa_state(EPA_UNPUBLISHED),
+ event_type(_event_type),
+ request_uri(_request_uri),
+ id_publication_timeout(0),
+ publication_expiry(3600),
+ default_duration(3600),
+ is_unpublishing(false),
+ cached_body(NULL),
+ req_out(NULL)
+{}
+
+t_epa::~t_epa() {
+ clear();
+}
+
+t_epa::t_epa_state t_epa::get_epa_state(void) const {
+ return epa_state;
+}
+
+string t_epa::get_failure_msg(void) const {
+ return failure_msg;
+}
+
+t_phone_user *t_epa::get_phone_user(void) const {
+ return phone_user;
+}
+
+t_user *t_epa::get_user_profile(void) const {
+ return phone_user->get_user_profile();
+}
+
+bool t_epa::recv_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;
+ t_request *req = req_out->get_request();
+ if (r->hdr_cseq.method != req->method) return true;
+
+ // Ignore provisional responses
+ if (r->is_provisional()) return true;
+
+ if (r->is_success()) {
+ // RFC 3903 11.3
+ // A 2XX response must contain a SIP-ETag header
+ if (r->hdr_sip_etag.is_populated()) {
+ etag = r->hdr_sip_etag.etag;
+ } else {
+ log_file->write_report("SIP-ETag header missing from PUBLISH 2XX response.",
+ "t_epa::recv_response", LOG_NORMAL, LOG_WARNING);
+ etag.clear();
+ }
+
+ // RFC 3903 1.1.1 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->hdr_expires.time);
+
+ log_file->write_header("t_epa::recv_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_publication();
+ 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 || is_unpublishing) {
+ // Unpublish succeeded.
+ stop_timer(PUBLISH_TMR_PUBLICATION);
+ etag.clear();
+ is_unpublishing = false;
+ epa_state = EPA_UNPUBLISHED;
+
+ log_file->write_header("t_epa::recv_response",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("Unpublish successful.\n");
+ log_publication();
+ log_file->write_footer();
+ } else {
+ log_file->write_header("t_epa::recv_response");
+ log_file->write_raw("Publication sucessful.\n");
+ log_publication();
+ log_file->write_footer();
+
+ // Start/refresh publish timer
+ stop_timer(PUBLISH_TMR_PUBLICATION);
+ unsigned long dur = r->hdr_expires.time;
+ dur -= dur / 10;
+ start_timer(PUBLISH_TMR_PUBLICATION, dur * 1000);
+ epa_state = EPA_PUBLISHED;
+ }
+
+ remove_client_request(&req_out);
+ send_publish_from_queue();
+ return true;
+ }
+
+ // Authentication
+ if (r->must_authenticate()) {
+ if (phone_user->authorize(req, r)) {
+ phone_user->resend_request(req, req_out);
+ return true;
+ }
+
+ // Authentication failed
+ // Handle the 401/407 as a normal failure response
+ }
+
+ // PUBLISH failed
+
+ if (is_unpublishing) {
+ // Unpublish failed.
+ // There is nothing we can do about that. Just clear
+ // the internal publication.
+ stop_timer(PUBLISH_TMR_PUBLICATION);
+ etag.clear();
+ is_unpublishing = false;
+ epa_state = EPA_UNPUBLISHED;
+
+ log_file->write_header("t_epa::recv_response",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("Unpublish failed.\n");
+ log_publication();
+ log_file->write_footer();
+
+ remove_client_request(&req_out);
+ return true;
+ }
+
+ if (r->code == R_423_INTERVAL_TOO_BRIEF) {
+ if (!r->hdr_min_expires.is_populated()) {
+ // Violation of RFC 3261 10.3 item 7
+ log_file->write_report("Min-Expires header missing from 423 response.",
+ "t_epa::recv_response",
+ LOG_NORMAL, LOG_WARNING);
+ } else if (r->hdr_min_expires.time <= publication_expiry) {
+ // Wrong Min-Expires time
+ string s = "Min-Expires (";
+ s += ulong2str(r->hdr_min_expires.time);
+ s += ") is smaller than the requested ";
+ s += "time (";
+ s += ulong2str(publication_expiry);
+ s += ")";
+ log_file->write_report(s, "t_epa::recv_response",
+ LOG_NORMAL, LOG_WARNING);
+ } else {
+ // Publish with the advised interval
+ remove_client_request(&req_out);
+ if (etag.empty()) {
+ // Initial publication.
+ publish(r->hdr_min_expires.time, cached_body);
+ } else {
+ publication_expiry = r->hdr_min_expires.time;
+ refresh_publication();
+ }
+
+ return true;
+ }
+ } else if (r->code == R_412_CONDITIONAL_REQUEST_FAILED) {
+ log_file->write_header("t_epa::recv_response");
+ log_file->write_raw("SIP-ETag mismatch, retry with initial publication.\n");
+ log_publication();
+ log_file->write_endl();
+ log_file->write_footer();
+
+ // The state seems to be gone from the presence agent. Clear
+ // the internal pubication state.
+ remove_client_request(&req_out);
+ etag.clear();
+ epa_state = EPA_UNPUBLISHED;
+
+ // Retry to publish state
+ publish(publication_expiry, cached_body);
+ return true;
+ }
+
+ remove_client_request(&req_out);
+ epa_state = EPA_FAILED;
+ failure_msg = int2str(r->code);
+ failure_msg += ' ';
+ failure_msg += r->reason;
+
+ log_file->write_header("t_epa::recv_response",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("PUBLISH failure response.\n");
+ log_file->write_raw(r->code);
+ log_file->write_raw(" " + r->reason + "\n");
+ log_publication();
+ log_file->write_footer();
+
+ send_publish_from_queue();
+ return true;
+}
+
+bool t_epa::match_response(t_response *r, t_tuid tuid) const {
+ return (req_out && req_out->get_tuid() == tuid);
+}
+
+bool t_epa::timeout(t_publish_timer timer) {
+ switch (timer) {
+ case PUBLISH_TMR_PUBLICATION:
+ id_publication_timeout = 0;
+
+ log_file->write_header("t_epa::timeout");
+ log_file->write_raw("Publication timed out.\n");
+ log_publication();
+ log_file->write_footer();
+
+ refresh_publication();
+ return true;
+ default:
+ assert(false);
+ }
+
+ return false;
+}
+
+bool t_epa::match_timer(t_publish_timer timer, t_object_id id_timer) const {
+ return id_timer == id_publication_timeout;
+}
+
+void t_epa::publish(unsigned long expires, t_sip_body *body) {
+ t_request *r = create_publish(expires, body);
+
+ if (req_out) {
+ // A PUBLISH request is pending, queue this one.
+ // Only 1 PUBLISH at a time may be sent.
+ // RFC 3903 4
+ enqueue_request(r);
+ return;
+ }
+
+ // If the body equals the cached body, then do not
+ // delete the cached_body as that will delete the body!
+ if (cached_body && body && body != cached_body) {
+ MEMMAN_DELETE(cached_body);
+ delete cached_body;
+ cached_body = NULL;
+ }
+
+ if (body) {
+ cached_body = body->copy();
+ }
+
+ if (expires > 0) {
+ publication_expiry = expires;
+ } else {
+ publication_expiry = default_duration;
+ }
+
+ is_unpublishing = false;
+
+ stop_timer(PUBLISH_TMR_PUBLICATION);
+
+ req_out = new t_client_request(phone_user->get_user_profile(), r, 0);
+ MEMMAN_NEW(req_out);
+ send_request(r, req_out->get_tuid());
+ MEMMAN_DELETE(r);
+ delete r;
+}
+
+void t_epa::unpublish(void) {
+ if (!req_out && epa_state != EPA_PUBLISHED) {
+ log_file->write_header("t_epa::unpublish", LOG_NORMAL, LOG_DEBUG);
+ log_file->write_raw("Nothing published, discard unpublish\n");
+ log_publication();
+ log_file->write_footer();
+ return;
+ }
+
+ t_request *r = create_publish(0, NULL);
+
+ if (req_out) {
+ // A PUBLISH request is pending, queue this one.
+ // Only 1 PUBLISH at a time may be sent.
+ // RFC 3903 4
+ enqueue_request(r);
+ return;
+ }
+
+ if (cached_body) {
+ MEMMAN_DELETE(cached_body);
+ delete cached_body;
+ cached_body = NULL;
+ }
+
+ is_unpublishing = true;
+
+ stop_timer(PUBLISH_TMR_PUBLICATION);
+
+ req_out = new t_client_request(phone_user->get_user_profile(), r, 0);
+ MEMMAN_NEW(req_out);
+ send_request(r, req_out->get_tuid());
+ MEMMAN_DELETE(r);
+ delete r;
+}
+
+void t_epa::refresh_publication(void) {
+ publish(publication_expiry, NULL);
+}
+
+void t_epa::clear(void) {
+ if (req_out) remove_client_request(&req_out);
+ if (id_publication_timeout) stop_timer(PUBLISH_TMR_PUBLICATION);
+
+ if (cached_body) {
+ MEMMAN_DELETE(cached_body);
+ delete cached_body;
+ cached_body = NULL;
+ }
+
+ // Cleanup list of unsent PUBLISH messages
+ while (!queue_publish.empty()) {
+ t_request *r = queue_publish.front();
+ queue_publish.pop();
+ MEMMAN_DELETE(r);
+ delete r;
+ }
+}