summaryrefslogtreecommitdiffstats
path: root/src/presence
diff options
context:
space:
mode:
Diffstat (limited to 'src/presence')
-rw-r--r--src/presence/Makefile.am17
-rw-r--r--src/presence/buddy.cpp574
-rw-r--r--src/presence/buddy.h341
-rw-r--r--src/presence/pidf_body.cpp221
-rw-r--r--src/presence/pidf_body.h104
-rw-r--r--src/presence/presence_dialog.cpp35
-rw-r--r--src/presence/presence_dialog.h47
-rw-r--r--src/presence/presence_epa.cpp71
-rw-r--r--src/presence/presence_epa.h67
-rw-r--r--src/presence/presence_state.cpp100
-rw-r--r--src/presence/presence_state.h94
-rw-r--r--src/presence/presence_subscription.cpp144
-rw-r--r--src/presence/presence_subscription.h51
13 files changed, 1866 insertions, 0 deletions
diff --git a/src/presence/Makefile.am b/src/presence/Makefile.am
new file mode 100644
index 0000000..17caf93
--- /dev/null
+++ b/src/presence/Makefile.am
@@ -0,0 +1,17 @@
+AM_CPPFLAGS = -Wall $(CCRTP_CFLAGS) $(XML2_CFLAGS) -I$(top_srcdir)/src
+
+noinst_LIBRARIES = libpresence.a
+
+libpresence_a_SOURCES =\
+ buddy.cpp\
+ pidf_body.cpp\
+ presence_dialog.cpp\
+ presence_epa.cpp\
+ presence_state.cpp\
+ presence_subscription.cpp\
+ buddy.h\
+ pidf_body.h\
+ presence_dialog.h\
+ presence_epa.h\
+ presence_state.h\
+ presence_subscription.h
diff --git a/src/presence/buddy.cpp b/src/presence/buddy.cpp
new file mode 100644
index 0000000..075626a
--- /dev/null
+++ b/src/presence/buddy.cpp
@@ -0,0 +1,574 @@
+/*
+ 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 "buddy.h"
+
+#include <cassert>
+
+#include "log.h"
+#include "phone.h"
+#include "phone_user.h"
+#include "userintf.h"
+#include "audits/memman.h"
+
+extern t_phone *phone;
+extern t_event_queue *evq_timekeeper;
+
+/** Buddy */
+
+void t_buddy::cleanup_presence_dialog(void) {
+ assert(phone_user);
+
+ if (presence_dialog && presence_dialog->get_subscription_state() == SS_TERMINATED) {
+ string reason_termination = presence_dialog->get_reason_termination();
+ bool may_resubscribe = presence_dialog->get_may_resubscribe();
+ unsigned long dur_resubscribe = presence_dialog->get_resubscribe_after();
+
+ MEMMAN_DELETE(presence_dialog);
+ delete presence_dialog;
+ presence_dialog = NULL;
+ phone_user->stun_binding_inuse_presence--;
+ phone_user->cleanup_stun_data();
+ phone_user->cleanup_nat_keepalive();
+
+ if (presence_auto_resubscribe) {
+ if (may_resubscribe) {
+ if (dur_resubscribe > 0) {
+ start_resubscribe_presence_timer(dur_resubscribe * 1000);
+ } else {
+ subscribe_presence();
+ }
+ } else if (reason_termination.empty()) {
+ start_resubscribe_presence_timer(DUR_PRESENCE_FAILURE * 1000);
+ }
+ }
+ }
+}
+
+t_buddy::t_buddy() :
+ phone_user(NULL),
+ may_subscribe_presence(false),
+ presence_state(this),
+ presence_dialog(NULL),
+ subscribe_after_stun(false),
+ presence_auto_resubscribe(false),
+ delete_after_presence_terminated(false),
+ id_resubscribe_presence(0)
+{
+}
+
+t_buddy::t_buddy(t_phone_user *_phone_user) :
+ phone_user(_phone_user),
+ may_subscribe_presence(false),
+ presence_state(this),
+ presence_dialog(NULL),
+ subscribe_after_stun(false),
+ presence_auto_resubscribe(false),
+ delete_after_presence_terminated(false),
+ id_resubscribe_presence(0)
+{
+}
+
+t_buddy::t_buddy(t_phone_user *_phone_user, const string _name, const string &_sip_address) :
+ phone_user(_phone_user),
+ name(_name),
+ sip_address(_sip_address),
+ may_subscribe_presence(false),
+ presence_state(this),
+ presence_dialog(NULL),
+ subscribe_after_stun(false),
+ presence_auto_resubscribe(false),
+ delete_after_presence_terminated(false),
+ id_resubscribe_presence(0)
+{
+}
+
+t_buddy::t_buddy(const t_buddy &other) :
+ phone_user(other.phone_user),
+ name(other.name),
+ sip_address(other.sip_address),
+ may_subscribe_presence(other.may_subscribe_presence),
+ presence_state(this),
+ presence_dialog(NULL),
+ subscribe_after_stun(false),
+ presence_auto_resubscribe(false),
+ delete_after_presence_terminated(false),
+ id_resubscribe_presence(0)
+{}
+
+t_buddy::~t_buddy() {
+ if (presence_dialog) {
+ MEMMAN_DELETE(presence_dialog);
+ delete presence_dialog;
+ }
+}
+
+string t_buddy::get_name(void) const {
+ return name;
+}
+
+string t_buddy::get_sip_address(void) const {
+ return sip_address;
+}
+
+bool t_buddy::get_may_subscribe_presence(void) const {
+ return may_subscribe_presence;
+}
+
+const t_presence_state *t_buddy::get_presence_state(void) const {
+ return &presence_state;
+}
+
+t_user *t_buddy::get_user_profile(void) {
+ assert(phone_user);
+ return phone_user->get_user_profile();
+}
+
+t_buddy_list *t_buddy::get_buddy_list(void) {
+ assert(phone_user);
+ return phone_user->get_buddy_list();
+}
+
+void t_buddy::set_phone_user(t_phone_user *_phone_user) {
+ phone_user = _phone_user;
+}
+
+void t_buddy::set_name(const string &_name) {
+ name = _name;
+ notify();
+}
+
+void t_buddy::set_sip_address(const string &_sip_address) {
+ sip_address = _sip_address;
+ notify();
+}
+
+void t_buddy::set_may_subscribe_presence(bool _may_subscribe_presence) {
+ may_subscribe_presence = _may_subscribe_presence;
+ notify();
+}
+
+bool t_buddy::match_response(t_response *r, t_tuid tuid) const {
+ return (presence_dialog && presence_dialog->match_response(r, tuid));
+}
+
+bool t_buddy::match_request(t_request *r) const {
+ if (!presence_dialog) return false;
+
+ bool partial_match = false;
+ bool match = presence_dialog->match_request(r, partial_match);
+
+ if (match) return true;
+
+ if (partial_match && presence_dialog->get_remote_tag().empty()) {
+ // A NOTIFY may be received before a 2XX on SUBSCRIBE.
+ // In this case the NOTIFY will establish the dialog.
+ return true;
+ }
+
+ return false;
+}
+
+bool t_buddy::match_timer(t_subscribe_timer timer, t_object_id id_timer) const {
+ if (presence_dialog && presence_dialog->match_timer(timer, id_timer)) {
+ return true;
+ }
+
+ return id_timer == id_resubscribe_presence;
+}
+
+void t_buddy::timeout(t_subscribe_timer timer, t_object_id id_timer) {
+ switch (timer) {
+ case STMR_SUBSCRIPTION:
+ if (presence_dialog && presence_dialog->match_timer(timer, id_timer)) {
+ (void)presence_dialog->timeout(timer);
+ cleanup_presence_dialog();
+ } else if (id_timer == id_resubscribe_presence) {
+ // Try to subscribe to presence
+ id_resubscribe_presence = 0;
+ subscribe_presence();
+ }
+ break;
+ default:
+ assert(false);
+ }
+}
+
+void t_buddy::recvd_response(t_response *r, t_tuid tuid, t_tid tid) {
+ if (presence_dialog) {
+ presence_dialog->recvd_response(r, tuid, tid);
+ cleanup_presence_dialog();
+ }
+}
+
+void t_buddy::recvd_request(t_request *r, t_tuid tuid, t_tid tid) {
+ if (presence_dialog) {
+ presence_dialog->recvd_request(r, tuid, tid);
+ cleanup_presence_dialog();
+ }
+}
+
+void t_buddy::start_resubscribe_presence_timer(unsigned long duration) {
+ t_tmr_subscribe *t;
+ t = new t_tmr_subscribe(duration, STMR_SUBSCRIPTION, 0, 0, SIP_EVENT_PRESENCE, "");
+ MEMMAN_NEW(t);
+ id_resubscribe_presence = t->get_object_id();
+
+ evq_timekeeper->push_start_timer(t);
+ MEMMAN_DELETE(t);
+ delete t;
+}
+
+void t_buddy::stop_resubscribe_presence_timer(void) {
+ if (id_resubscribe_presence != 0) {
+ evq_timekeeper->push_stop_timer(id_resubscribe_presence);
+ id_resubscribe_presence = 0;
+ }
+}
+
+void t_buddy::stun_completed(void) {
+ if (subscribe_after_stun) {
+ subscribe_after_stun = false;
+ subscribe_presence();
+ }
+}
+
+void t_buddy::stun_failed(void) {
+ if (subscribe_after_stun) {
+ subscribe_after_stun = false;
+ start_resubscribe_presence_timer(DUR_PRESENCE_FAILURE * 1000);
+ }
+}
+
+void t_buddy::subscribe_presence(void) {
+ assert(phone_user);
+ t_user *user_config = phone_user->get_user_profile();
+
+ if (!may_subscribe_presence) return;
+
+ presence_auto_resubscribe = true;
+
+ if (presence_dialog) {
+ // Already subscribed.
+ log_file->write_header("t_buddy::subscribe_presence", LOG_NORMAL, LOG_DEBUG);
+ log_file->write_raw("Already subscribed to presence: ");
+ log_file->write_raw(name);
+ log_file->write_raw(", ");
+ log_file->write_raw(sip_address);
+ log_file->write_endl();
+ log_file->write_footer();
+ return;
+ }
+
+ // If STUN is enabled, then do a STUN query before registering to
+ // determine the public IP address.
+ if (phone_user->use_stun) {
+ if (phone_user->stun_public_ip_sip == 0)
+ {
+ phone_user->send_stun_request();
+ phone_user->presence_subscribe_after_stun = true;
+ subscribe_after_stun = true;
+ return;
+ }
+ phone_user->stun_binding_inuse_presence++;
+ }
+
+ presence_dialog = new t_presence_dialog(phone_user, &presence_state);
+ MEMMAN_NEW(presence_dialog);
+
+ string dest = ui->expand_destination(user_config, sip_address);
+ t_url dest_url(dest);
+ if (!dest_url.is_valid()) {
+ log_file->write_header("t_buddy::subscribe_presence", LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("Invalid SIP address: ");
+ log_file->write_raw(sip_address);
+ log_file->write_endl();
+ log_file->write_footer();
+ return;
+ }
+
+ presence_dialog->subscribe(DUR_PRESENCE(user_config), dest_url, dest_url, "");
+
+ // Start sending NAT keepalive packets when STUN is used
+ // (or in case of symmetric firewall)
+ if (phone_user->use_nat_keepalive && phone_user->id_nat_keepalive == 0) {
+ // Just start the NAT keepalive timer. The SUBSCRIBE
+ // message will create the NAT binding. So there is
+ // no need to send a NAT keep alive packet now.
+ phone->start_timer(PTMR_NAT_KEEPALIVE, phone_user);
+ }
+
+ cleanup_presence_dialog();
+}
+
+void t_buddy::unsubscribe_presence(bool remove) {
+ presence_auto_resubscribe = false;
+ stop_resubscribe_presence_timer();
+ presence_state.set_basic_state(t_presence_state::ST_BASIC_UNKNOWN);
+ delete_after_presence_terminated = remove;
+
+ if (presence_dialog) {
+ presence_dialog->unsubscribe();
+ cleanup_presence_dialog();
+ }
+}
+
+bool t_buddy::create_file_record(vector<string> &v) const {
+ if (delete_after_presence_terminated) return false;
+
+ v.clear();
+ v.push_back(name);
+ v.push_back(sip_address);
+ v.push_back((may_subscribe_presence ? "y" : "n"));
+
+ return true;
+}
+
+bool t_buddy::populate_from_file_record(const vector<string> &v) {
+ if (v.size() !=3 ) return false;
+
+ name = v[0];
+ sip_address = v[1];
+ may_subscribe_presence = (v[2] == "y");
+
+ return true;
+}
+
+bool t_buddy::operator==(const t_buddy &other) const {
+ return (name == other.name && sip_address == other.sip_address);
+}
+
+void t_buddy::clear_presence(void) {
+ if (id_resubscribe_presence) stop_resubscribe_presence_timer();
+
+ if (presence_dialog) {
+ MEMMAN_DELETE(presence_dialog);
+ delete presence_dialog;
+ presence_dialog = NULL;
+ }
+
+ presence_state.set_basic_state(t_presence_state::ST_BASIC_UNKNOWN);
+}
+
+bool t_buddy::is_presence_terminated(void) const {
+ return presence_dialog == NULL;
+}
+
+bool t_buddy::must_delete_now(void) const {
+ return delete_after_presence_terminated && is_presence_terminated();
+}
+
+/** Buddy list */
+
+void t_buddy_list::add_record(const t_buddy &record) {
+ t_buddy r(record);
+ r.set_phone_user(phone_user);
+ utils::t_record_file<t_buddy>::add_record(r);
+}
+
+t_buddy_list::t_buddy_list(t_phone_user *_phone_user) :
+ phone_user(_phone_user),
+ is_subscribed(false)
+{
+ t_user *user_config = phone_user->get_user_profile();
+
+ set_header("name|sip_address|subscribe");
+ set_separator('|');
+
+ string filename = user_config->get_profile_name() + BUDDY_FILE_EXT;
+ string f = user_config->expand_filename(filename);
+ set_filename(f);
+}
+
+t_user *t_buddy_list::get_user_profile(void) {
+ return phone_user->get_user_profile();
+}
+
+t_buddy *t_buddy_list::add_buddy(const t_buddy &buddy) {
+ t_buddy *b = NULL;
+
+ mtx_records.lock();
+ add_record(buddy);
+
+ // KLUDGE: this code assumes that the buddy is added at the end.
+ b = &records.back();
+ mtx_records.unlock();
+
+ log_file->write_header("t_buddy_list::add_buddy");
+ log_file->write_raw("Added buddy: ");
+ log_file->write_raw(b->get_name());
+ log_file->write_raw(", ");
+ log_file->write_raw(b->get_sip_address());
+ log_file->write_endl();
+ log_file->write_footer();
+
+ return b;
+}
+
+void t_buddy_list::del_buddy(const t_buddy &buddy) {
+ mtx_records.lock();
+
+ list<t_buddy>::iterator it = find(records.begin(), records.end(), buddy);
+
+ if (it == records.end()) {
+ mtx_records.unlock();
+ return;
+ }
+
+ log_file->write_header("t_buddy_list::del_buddy");
+ log_file->write_raw("Delete buddy: ");
+ log_file->write_raw(buddy.get_name());
+ log_file->write_raw(", ");
+ log_file->write_raw(buddy.get_sip_address());
+ log_file->write_endl();
+ log_file->write_footer();
+
+ records.erase(it);
+
+ mtx_records.unlock();
+}
+
+bool t_buddy_list::match_response(t_response *r, t_tuid tuid, t_buddy **buddy) {
+ *buddy = NULL;
+
+ mtx_records.lock();
+
+ for (list<t_buddy>::iterator it = records.begin(); it != records.end(); ++it) {
+ if (it->match_response(r, tuid)) {
+ *buddy = &(*it);
+ break;
+ }
+ }
+
+ mtx_records.unlock();
+ return *buddy != NULL;
+}
+
+bool t_buddy_list::match_request(t_request *r, t_buddy **buddy) {
+ *buddy = NULL;
+
+ mtx_records.lock();
+
+ for (list<t_buddy>::iterator it = records.begin(); it != records.end(); ++it) {
+ if (it->match_request(r)) {
+ *buddy = &(*it);
+ break;
+ }
+ }
+
+ mtx_records.unlock();
+ return *buddy != NULL;
+}
+
+bool t_buddy_list::match_timer(t_subscribe_timer timer, t_object_id id_timer, t_buddy **buddy) {
+ *buddy = NULL;
+
+ mtx_records.lock();
+
+ for (list<t_buddy>::iterator it = records.begin(); it != records.end(); ++it) {
+ if (it->match_timer(timer, id_timer)) {
+ *buddy = &(*it);
+ break;
+ }
+ }
+
+ mtx_records.unlock();
+ return *buddy != NULL;
+}
+
+void t_buddy_list::stun_completed(void) {
+ mtx_records.lock();
+
+ for (list<t_buddy>::iterator it = records.begin(); it != records.end(); ++it) {
+ it->stun_completed();
+ }
+
+ mtx_records.unlock();
+}
+
+void t_buddy_list::stun_failed(void) {
+ mtx_records.lock();
+
+ for (list<t_buddy>::iterator it = records.begin(); it != records.end(); ++it) {
+ it->stun_failed();
+ }
+
+ mtx_records.unlock();
+}
+
+void t_buddy_list::subscribe_presence(void) {
+ mtx_records.lock();
+
+ for (list<t_buddy>::iterator it = records.begin(); it != records.end(); ++it) {
+ it->subscribe_presence();
+ }
+
+ is_subscribed = true;
+
+ mtx_records.unlock();
+}
+
+void t_buddy_list::unsubscribe_presence(void) {
+ mtx_records.lock();
+
+ for (list<t_buddy>::iterator it = records.begin(); it != records.end(); ++it) {
+ it->unsubscribe_presence();
+ }
+
+ is_subscribed = false;
+
+ mtx_records.unlock();
+}
+
+bool t_buddy_list::get_is_subscribed() const {
+ bool result;
+
+ mtx_records.lock();
+ result = is_subscribed;
+ mtx_records.unlock();
+
+ return result;
+}
+
+void t_buddy_list::clear_presence(void) {
+ mtx_records.lock();
+
+ for (list<t_buddy>::iterator it = records.begin(); it != records.end(); ++it) {
+ it->clear_presence();
+ }
+
+ is_subscribed = false;
+
+ mtx_records.unlock();
+}
+
+bool t_buddy_list::is_presence_terminated(void) const {
+ bool result = true;
+ mtx_records.lock();
+
+ for (list<t_buddy>::const_iterator it = records.begin(); it != records.end(); ++it) {
+ if (!it->is_presence_terminated()) {
+ result = false;
+ break;
+ }
+ }
+
+ mtx_records.unlock();
+
+ return result;
+}
diff --git a/src/presence/buddy.h b/src/presence/buddy.h
new file mode 100644
index 0000000..b7852de
--- /dev/null
+++ b/src/presence/buddy.h
@@ -0,0 +1,341 @@
+/*
+ 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
+*/
+
+/**
+ * @file
+ * Buddy list
+ */
+
+#ifndef _BUDDY_H
+#define _BUDDY_H
+
+#include <string>
+#include <set>
+
+#include "presence_state.h"
+#include "presence_dialog.h"
+
+#include "sockets/url.h"
+#include "utils/record_file.h"
+#include "patterns/observer.h"
+
+// Forward declaration
+class t_phone_user;
+class t_buddy_list;
+
+#define BUDDY_FILE_EXT ".bud"
+
+using namespace std;
+
+/** Buddy */
+class t_buddy : public utils::t_record, public patterns::t_subject {
+private:
+ /** Phone user owning this buddy. */
+ t_phone_user *phone_user;
+
+ /** Name of buddy (for display only) */
+ string name;
+
+ /** SIP address of the buddy. */
+ string sip_address;
+
+ /** Indicates if the user may subscribe to the presence state of the buddy. */
+ bool may_subscribe_presence;
+
+ /** Presence state. */
+ t_presence_state presence_state;
+
+ /** Presence subscription dialog. */
+ t_presence_dialog *presence_dialog;
+
+ /** Subscribe to presence after STUN transaction completed. */
+ bool subscribe_after_stun;
+
+ /**
+ * Indicates if presence must be automatically resubscribed to, if the
+ * subscription terminates with a reason telling that resubscription
+ * is possible.
+ */
+ bool presence_auto_resubscribe;
+
+ /**
+ * Indicates if the buddy must be deleted after the presence subscription
+ * has been terminated.
+ */
+ bool delete_after_presence_terminated;
+
+ /** Handle presence dialog termination. */
+ void cleanup_presence_dialog(void);
+
+public:
+ /** Interval before trying to resubscribe to presence after a failure. */
+ t_object_id id_resubscribe_presence;
+
+ /** Constructor. */
+ t_buddy();
+
+ /** Constructor. */
+ t_buddy(t_phone_user *_phone_user);
+
+ /** Constructor. */
+ t_buddy(t_phone_user *_phone_user, const string _name, const string &_sip_address);
+
+ /** Copy constructor. */
+ t_buddy(const t_buddy &other);
+
+ /** Destructor. */
+ virtual ~t_buddy();
+
+ /** @name Getters */
+ //@{
+ string get_name(void) const;
+ string get_sip_address(void) const;
+ bool get_may_subscribe_presence(void) const;
+ const t_presence_state *get_presence_state(void) const;
+ //@}
+
+ /**
+ * Get user profile for the user owning this buddy.
+ * @return User profile.
+ */
+ t_user *get_user_profile(void);
+
+ /**
+ * Get the buddy list containing this buddy.
+ * @return Buddy list
+ */
+ t_buddy_list *get_buddy_list(void);
+
+ /** @name Setters */
+ //@{
+ void set_phone_user(t_phone_user *_phone_user);
+ void set_name(const string &_name);
+ void set_sip_address(const string &_sip_address);
+ void set_may_subscribe_presence(bool _may_subscribe_presence);
+ //@}
+
+ /**
+ * Match response with a buddy. It matches if the buddy
+ * has a presence dialog that matches with the response.
+ * @param r [in] The response.
+ * @param tuid [in] Transaction user id.
+ * @return True if the response matches, otherwise false.
+ */
+ bool match_response(t_response *r, t_tuid tuid) const;
+
+ /**
+ * Match request with buddy list. It matches if a buddy in the list
+ * has a presence dialog that matches with the request.
+ * @param r [in] The request.
+ * @return True if the request matches, otherwise false.
+ */
+ bool match_request(t_request *r) const;
+
+ /**
+ * Match a timer id with a running timer.
+ * @param timer [in] The running timer.
+ * @param id_timer [in] The timer id.
+ * @return true, if timer id matches with timer.
+ * @return false, otherwise.
+ */
+ bool match_timer(t_subscribe_timer timer, t_object_id id_timer) const;
+
+ /**
+ * Process timeout.
+ * @param timer [in] The timer that expired.
+ * @param id_timer [in] The timer id.
+ */
+ void timeout(t_subscribe_timer timer, t_object_id id_timer);
+
+ /**
+ * Handle received response.
+ * @param r [in] The response.
+ * @param tuid [in] Transaction user id.
+ * @param tid [in] Transaction id.
+ */
+ void recvd_response(t_response *r, t_tuid tuid, t_tid tid);
+
+ /**
+ * Handle received request.
+ * @param r [in] The request.
+ * @param tuid [in] Transaction user id.
+ * @param tid [in] Transaction id.
+ */
+ void recvd_request(t_request *r, t_tuid tuid, t_tid tid);
+
+ /**
+ * Start the re-subscribe timer after a presence subscription failure.
+ * @param duration [in] Duration before trying a re-subscribe (s)
+ */
+ void start_resubscribe_presence_timer(unsigned long duration);
+
+ /** Stop presence re-subscribe timer. */
+ void stop_resubscribe_presence_timer(void);
+
+ /**
+ * By calling this method, succesful STUN completion is signalled to
+ * the buddy. It will subscribe to presence if it was waiting for STUN.
+ */
+ void stun_completed(void);
+
+ /**
+ * By calling this method, a STUN failure is signalled to
+ * the buddy. It will reschedule a presence subscription if it is
+ * waiting for STUN to complete.
+ */
+ void stun_failed(void);
+
+ /** Subscribe to presence of the buddy if we may do so. */
+ void subscribe_presence(void);
+
+ /** Unsubscribe to presence.
+ * @param remove [in] Indicates if the buddy must be deleted after unsubscription.
+ */
+ void unsubscribe_presence(bool remove = false);
+
+ virtual bool create_file_record(vector<string> &v) const;
+ virtual bool populate_from_file_record(const vector<string> &v);
+
+ /** Compare 2 buddies for equality (same SIP address) */
+ bool operator==(const t_buddy &other) const;
+
+ /** Clear presence state. */
+ void clear_presence(void);
+
+ /**
+ * Check if presence subscription is terminated.
+ * @return true, if presence subscriptions is terminated.
+ * @return false, otherwise
+ */
+ bool is_presence_terminated(void) const;
+
+ /**
+ * Check if buddy must be deleted.
+ * @return true, if the buddy must be deleted immediately.
+ * @return false, otherwise.
+ */
+ bool must_delete_now(void) const;
+};
+
+/** List of buddies for a particular account. */
+class t_buddy_list : public utils::t_record_file<t_buddy> {
+private:
+ /** Phone user owning this buddy list. */
+ t_phone_user *phone_user;
+
+ /**
+ * Indicates if subscribe is done. This indicator will be set to false
+ * when you call unsubscribe.
+ */
+ bool is_subscribed;
+
+protected:
+ virtual void add_record(const t_buddy &record);
+
+public:
+ /** Constructor. */
+ t_buddy_list(t_phone_user *_phone_user);
+
+ /**
+ * Get the user profile for this buddy list.
+ * @return User profile.
+ */
+ t_user *get_user_profile(void);
+
+ /**
+ * Add a buddy.
+ * @param buddy [in] Buddy to add.
+ * @return Pointer to added buddy.
+ * @note This method adds a copy of the buddy. It returns a pointer to this copy.
+ */
+ t_buddy *add_buddy(const t_buddy &buddy);
+
+ /**
+ * Delete a buddy.
+ * @param buddy [in] Buddy to delete.
+ */
+ void del_buddy(const t_buddy &buddy);
+
+ /**
+ * Match response with buddy list. It matches if a buddy in the list
+ * has a presence dialog that matches with the response.
+ * @param r [in] The response.
+ * @param tuid [in] Transaction user id.
+ * @param buddy [out] On a match, this parameter contains the matching buddy.
+ * @return True if the response matches, otherwise false.
+ */
+ bool match_response(t_response *r, t_tuid tuid, t_buddy **buddy);
+
+ /**
+ * Match request with buddy list. It matches if a buddy in the list
+ * has a presence dialog that matches with the request.
+ * @param r [in] The request.
+ * @param buddy [out] On a match, this parameter contains the matching buddy.
+ * @return True if the request matches, otherwise false.
+ */
+ bool match_request(t_request *r, t_buddy **buddy);
+
+ /**
+ * Match a timer id with a running timer. A timer id matches with the
+ * buddy list if it matches with one of the buddies in the list.
+ * @param timer [in] The running timer.
+ * @param id_timer [in] The timer id.
+ * @param buddy [out] On a match, this parameter contains the matching buddy.
+ * @return true, if timer id matches with timer.
+ * @return false, otherwise.
+ */
+ bool match_timer(t_subscribe_timer timer, t_object_id id_timer, t_buddy **buddy);
+
+ /**
+ * By calling this method, succesful STUN completion is signalled to the buddy
+ * list. The buddy list will now start presence subscriptions that were waiting
+ * for STUN to complete.
+ */
+ void stun_completed(void);
+
+ /**
+ * By calling this method, a STUN failure is signalled to the buddy list.
+ * The buddy list will reschedule presence subscriptions that were waiting
+ * for STUN to complete.
+ */
+ void stun_failed(void);
+
+ /** Subscribe to presence of all buddies in the list. */
+ void subscribe_presence(void);
+
+ /** Unsubscribe to presence of all buddies in the list. */
+ void unsubscribe_presence(void);
+
+ /**
+ * Check if user is subcribed to buddy list presence.
+ * @return True if subscribed, otherwise false.
+ */
+ bool get_is_subscribed() const;
+
+ /** Clear presence state of all buddies. */
+ void clear_presence(void);
+
+ /**
+ * Check if all presence subscriptions are terminated.
+ * @return true, if all presence subscriptions are terminated.
+ * @return false, otherwise
+ */
+ bool is_presence_terminated(void) const;
+};
+
+#endif
diff --git a/src/presence/pidf_body.cpp b/src/presence/pidf_body.cpp
new file mode 100644
index 0000000..aa67401
--- /dev/null
+++ b/src/presence/pidf_body.cpp
@@ -0,0 +1,221 @@
+/*
+ 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 "pidf_body.h"
+
+#include <cassert>
+#include <libxml/parser.h>
+
+#include "log.h"
+#include "util.h"
+#include "audits/memman.h"
+
+#define PIDF_XML_VERSION "1.0"
+#define PIDF_NAMESPACE "urn:ietf:params:xml:ns:pidf"
+
+#define IS_PIDF_TAG(node, tag) IS_XML_TAG(node, tag, PIDF_NAMESPACE)
+
+#define IS_PIDF_ATTR(attr, attr_name) IS_XML_ATTR(attr, attr_name, PIDF_NAMESPACE)
+
+bool t_pidf_xml_body::extract_status(void) {
+ assert(xml_doc);
+
+ xmlNode *root_element = NULL;
+
+ // Get root
+ root_element = xmlDocGetRootElement(xml_doc);
+ if (!root_element) {
+ log_file->write_report("PIDF document has no root element.",
+ "t_pidf_xml_body::extract_status",
+ LOG_NORMAL, LOG_WARNING);
+ return false;
+ }
+
+ // Check if root is <presence>
+ if (!IS_PIDF_TAG(root_element, "presence")) {
+ log_file->write_report("PIDF document has invalid root element.",
+ "t_pidf_xml_body::extract_status",
+ LOG_NORMAL, LOG_WARNING);
+ return false;
+ }
+
+ pres_entity.clear();
+ tuple_id.clear();
+ basic_status.clear();
+
+ // Get presence entity
+ xmlChar *prop_entity = xmlGetProp(root_element, BAD_CAST "entity");
+ if (prop_entity) {
+ pres_entity = (char *)prop_entity;
+ } else {
+ log_file->write_report("Presence entity is missing.",
+ "t_pidf_xml_body::extract_status",
+ LOG_NORMAL, LOG_WARNING);
+ }
+
+ xmlNode *child = root_element->children;
+
+ // Process children of root.
+ for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) {
+ // Process tuple
+ if (IS_PIDF_TAG(cur_node, "tuple")) {
+ process_pidf_tuple(cur_node);
+
+ // Process the first tuple and then stop.
+ // Currently there is no support for multiple tuples
+ // or additional elements.
+ break;
+ }
+ }
+
+ return true;
+}
+
+void t_pidf_xml_body::process_pidf_tuple(xmlNode *tuple) {
+ assert(tuple);
+
+ // Get tuple id.
+ xmlChar *id = xmlGetProp(tuple, BAD_CAST "id");
+ if (id) {
+ tuple_id = (char *)id;
+ } else {
+ log_file->write_report("Tuple id is missing.",
+ "t_pidf_xml_body::process_pidf_tuple",
+ LOG_NORMAL, LOG_WARNING);
+ }
+
+ // Find status element
+ xmlNode *child = tuple->children;
+ for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) {
+ // Process status
+ if (IS_PIDF_TAG(cur_node, "status")) {
+ process_pidf_status(cur_node);
+ break;
+ }
+ }
+}
+
+void t_pidf_xml_body::process_pidf_status(xmlNode *status) {
+ assert(status);
+
+ xmlNode *child = status->children;
+ for (xmlNode *cur_node = child; cur_node; cur_node = cur_node->next) {
+ // Process status
+ if (IS_PIDF_TAG(cur_node, "basic")) {
+ process_pidf_basic(cur_node);
+ break;
+ }
+ }
+}
+
+void t_pidf_xml_body::process_pidf_basic(xmlNode *basic) {
+ assert(basic);
+
+ xmlNode *child = basic->children;
+ if (child && child->type == XML_TEXT_NODE) {
+ basic_status = tolower((char*)child->content);
+ } else {
+ log_file->write_report("<basic> element has no content.",
+ "t_pidf_xml_body::process_pidf_basic",
+ LOG_NORMAL, LOG_WARNING);
+ }
+}
+
+void t_pidf_xml_body::create_xml_doc(const string &xml_version, const string &charset) {
+ t_sip_body_xml::create_xml_doc(xml_version, charset);
+
+ // presence
+ xmlNode *node_presence = xmlNewNode(NULL, BAD_CAST "presence");
+ xmlNs *ns_pidf = xmlNewNs(node_presence, BAD_CAST PIDF_NAMESPACE, NULL);
+ xmlNewProp(node_presence, BAD_CAST "entity", BAD_CAST pres_entity.c_str());
+ xmlDocSetRootElement(xml_doc, node_presence);
+
+ // tuple
+ xmlNode *node_tuple = xmlNewChild(node_presence, ns_pidf, BAD_CAST "tuple", NULL);
+ xmlNewProp(node_tuple, BAD_CAST "id", BAD_CAST tuple_id.c_str());
+
+ // status
+ xmlNode *node_status = xmlNewChild(node_tuple, ns_pidf, BAD_CAST "status", NULL);
+
+ // basic
+ xmlNewChild(node_status, ns_pidf,
+ BAD_CAST "basic", BAD_CAST basic_status.c_str());
+}
+
+t_pidf_xml_body::t_pidf_xml_body() : t_sip_body_xml ()
+{}
+
+t_sip_body *t_pidf_xml_body::copy(void) const {
+ t_pidf_xml_body *body = new t_pidf_xml_body(*this);
+ MEMMAN_NEW(body);
+
+ // Clear the xml_doc pointer in the new body, as a copy of the
+ // XML document must be copied to the body.
+ body->xml_doc = NULL;
+
+ copy_xml_doc(body);
+
+ return body;
+}
+
+t_body_type t_pidf_xml_body::get_type(void) const {
+ return BODY_PIDF_XML;
+}
+
+t_media t_pidf_xml_body::get_media(void) const {
+ return t_media("application", "pidf+xml");
+}
+
+string t_pidf_xml_body::get_pres_entity(void) const {
+ return pres_entity;
+}
+
+string t_pidf_xml_body::get_tuple_id(void) const {
+ return tuple_id;
+}
+
+string t_pidf_xml_body::get_basic_status(void) const {
+ return basic_status;
+}
+
+void t_pidf_xml_body::set_pres_entity(const string &_pres_entity) {
+ clear_xml_doc();
+ pres_entity = _pres_entity;
+}
+
+void t_pidf_xml_body::set_tuple_id(const string &_tuple_id) {
+ clear_xml_doc();
+ tuple_id = _tuple_id;
+}
+
+void t_pidf_xml_body::set_basic_status(const string &_basic_status) {
+ clear_xml_doc();
+ basic_status = _basic_status;
+}
+
+bool t_pidf_xml_body::parse(const string &s) {
+ if (t_sip_body_xml::parse(s)) {
+ if (!extract_status()) {
+ MEMMAN_DELETE(xml_doc);
+ xmlFreeDoc(xml_doc);
+ xml_doc = NULL;
+ }
+ }
+
+ return (xml_doc != NULL);
+}
diff --git a/src/presence/pidf_body.h b/src/presence/pidf_body.h
new file mode 100644
index 0000000..445d531
--- /dev/null
+++ b/src/presence/pidf_body.h
@@ -0,0 +1,104 @@
+/*
+ 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
+*/
+
+/**
+ * @file
+ * RFC 3863 pidf+xml body
+ */
+
+#ifndef _PIDF_BODY_H
+#define _PIDF_BODY_H
+
+#include <string>
+#include <libxml/tree.h>
+#include "parser/sip_body.h"
+
+#define PIDF_STATUS_BASIC_OPEN "open"
+#define PIDF_STATUS_BASIC_CLOSED "closed"
+
+/** RFC 3863 pidf+xml body */
+class t_pidf_xml_body : public t_sip_body_xml {
+private:
+ string pres_entity; /**< Presence entity */
+ string tuple_id; /**< Id of tuple containing the basic status. */
+ string basic_status; /**< Value of basic-tag */
+
+ /**
+ * Extract the status information from a PIDF document.
+ * This will populate the state attributes.
+ * @return True if PIDF document is valid, otherwise false.
+ * @pre The @ref pidf_doc should contain a valid PIDF document.
+ */
+ bool extract_status(void);
+
+ /**
+ * Process tuple element.
+ * @param tuple [in] tuple element.
+ */
+ void process_pidf_tuple(xmlNode *tuple);
+
+ /**
+ * Process status element.
+ * @param status [in] status element.
+ */
+ void process_pidf_status(xmlNode *status);
+
+ /**
+ * Process basic element.
+ * @param basic [in] basic element.
+ */
+ void process_pidf_basic(xmlNode *basic);
+
+protected:
+ /**
+ * Create a pidf document from the values stored in the attributes.
+ */
+ virtual void create_xml_doc(const string &xml_version = "1.0", const string &charset = "UTF-8");
+
+public:
+ /** Constructor */
+ t_pidf_xml_body();
+
+ virtual t_sip_body *copy(void) const;
+ virtual t_body_type get_type(void) const;
+ virtual t_media get_media(void) const;
+
+ /** @name Getters */
+ //@{
+ string get_pres_entity(void) const;
+ string get_tuple_id(void) const;
+ string get_basic_status(void) const;
+ //@}
+
+ /** @name Setters */
+ //@{
+ void set_pres_entity(const string &_pres_entity);
+ void set_tuple_id(const string &_tuple_id);
+ void set_basic_status(const string &_basic_status);;
+ //@}
+
+ /**
+ * Parse a text representation of the body.
+ * If parsing succeeds, then the state is extracted.
+ * @param s [in] Text to parse.
+ * @return True if parsing and state extracting succeeded, false otherwise.
+ */
+ virtual bool parse(const string &s);
+};
+
+#endif
diff --git a/src/presence/presence_dialog.cpp b/src/presence/presence_dialog.cpp
new file mode 100644
index 0000000..f281493
--- /dev/null
+++ b/src/presence/presence_dialog.cpp
@@ -0,0 +1,35 @@
+/*
+ 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 "presence_dialog.h"
+
+#include "presence_subscription.h"
+#include "phone_user.h"
+#include "audits/memman.h"
+
+t_presence_dialog::t_presence_dialog(t_phone_user *_phone_user, t_presence_state *presence_state) :
+ t_subscription_dialog(_phone_user)
+{
+ subscription = new t_presence_subscription(this, presence_state);
+ MEMMAN_NEW(subscription);
+}
+
+t_presence_dialog *t_presence_dialog::copy(void) {
+ // Copy is not needed.
+ assert(false);
+}
diff --git a/src/presence/presence_dialog.h b/src/presence/presence_dialog.h
new file mode 100644
index 0000000..10513bd
--- /dev/null
+++ b/src/presence/presence_dialog.h
@@ -0,0 +1,47 @@
+/*
+ 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
+*/
+
+/**
+ * @file
+ * Dialog for presence subscription (RFC 3856)
+ */
+
+#ifndef _PRESENCE_DIALOG_H
+#define _PRESENCE_DIALOG_H
+
+#include "subscription_dialog.h"
+#include "presence_state.h"
+
+// Forward declaration
+class t_phone_user;
+
+/** Dialog for presence subscription (RFC 3856) */
+class t_presence_dialog : public t_subscription_dialog {
+public:
+ /**
+ * Constructor.
+ * @param _phone_user [in] Phone user owning the dialog.
+ * @param presence_state [in] Presence state that is updated by notification
+ * on this dialog
+ */
+ t_presence_dialog(t_phone_user *_phone_user, t_presence_state *presence_state);
+
+ virtual t_presence_dialog *copy(void);
+};
+
+#endif
diff --git a/src/presence/presence_epa.cpp b/src/presence/presence_epa.cpp
new file mode 100644
index 0000000..0d4071f
--- /dev/null
+++ b/src/presence/presence_epa.cpp
@@ -0,0 +1,71 @@
+/*
+ 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 "presence_epa.h"
+
+#include "pidf_body.h"
+#include "audits/memman.h"
+#include "parser/hdr_event.h"
+
+t_presence_epa::t_presence_epa(t_phone_user *pu) :
+ t_epa(pu, SIP_EVENT_PRESENCE, t_url(pu->get_user_profile()->create_user_uri(false))),
+ basic_state(t_presence_state::ST_BASIC_CLOSED),
+ tuple_id(NEW_PIDF_TUPLE_ID)
+{}
+
+t_presence_state::t_basic_state t_presence_epa::get_basic_state(void) const {
+ return basic_state;
+}
+
+bool t_presence_epa::recv_response(t_response *r, t_tuid tuid, t_tid tid) {
+ t_epa::recv_response(r, tuid, tid);
+
+ // Notify observers so they can get the latest publication state.
+ notify();
+
+ return true;
+}
+
+void t_presence_epa::publish_presence(t_presence_state::t_basic_state _basic_state) {
+ if (_basic_state != t_presence_state::ST_BASIC_CLOSED &&
+ _basic_state != t_presence_state::ST_BASIC_OPEN)
+ {
+ // Cannot publish internal states.
+ return;
+ }
+
+ t_user *user_config = phone_user->get_user_profile();
+ basic_state = _basic_state;
+
+ // Create PIDF document
+ t_pidf_xml_body *pidf = new t_pidf_xml_body();
+ MEMMAN_NEW(pidf);
+ pidf->set_pres_entity(user_config->create_user_uri(false));
+ pidf->set_tuple_id(tuple_id);
+ pidf->set_basic_status(t_presence_state::basic_state2pidf_str(_basic_state));
+
+ publish(user_config->get_pres_publication_time(), pidf);
+
+ // NOTE: the observers will be notified of the state change, when the
+ // PUBLISH response is received.
+}
+
+void t_presence_epa::clear(void) {
+ t_epa::clear();
+ basic_state = t_presence_state::ST_BASIC_CLOSED;
+}
diff --git a/src/presence/presence_epa.h b/src/presence/presence_epa.h
new file mode 100644
index 0000000..f224426
--- /dev/null
+++ b/src/presence/presence_epa.h
@@ -0,0 +1,67 @@
+/*
+ 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
+*/
+
+/**
+ * @file
+ * Presence Event Publication Agent (EPA) [RFC 3903]
+ */
+
+#ifndef _PRESENCE_EPA_H
+#define _PRESENCE_EPA_H
+
+#include <string>
+
+#include "epa.h"
+#include "presence_state.h"
+#include "patterns/observer.h"
+
+using namespace std;
+
+/** Presence Event Publication Agent (EPA) [RFC 3903] */
+class t_presence_epa : public t_epa, public patterns::t_subject {
+private:
+ /** Basic presence state. */
+ t_presence_state::t_basic_state basic_state;
+
+ /** Tuple id to be put in the PIDF documents. */
+ string tuple_id;
+
+public:
+ /** Constructor */
+ t_presence_epa(t_phone_user *pu);
+
+ /** @name Getters */
+ //@{
+ t_presence_state::t_basic_state get_basic_state(void) const;
+ //@}
+
+ virtual bool recv_response(t_response *r, t_tuid tuid, t_tid tid);
+
+ /**
+ * Publish presence state.
+ * @param _basic_state [in] The basic presence state.
+ * @pre _basic_state must be one of the following values:
+ * - @ref ST_BASIC_CLOSED
+ * - @ref ST_BASIC_OPEN
+ */
+ void publish_presence(t_presence_state::t_basic_state _basic_state);
+
+ virtual void clear(void);
+};
+
+#endif
diff --git a/src/presence/presence_state.cpp b/src/presence/presence_state.cpp
new file mode 100644
index 0000000..91c9ba8
--- /dev/null
+++ b/src/presence/presence_state.cpp
@@ -0,0 +1,100 @@
+/*
+ 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 "presence_state.h"
+
+#include <cassert>
+#include "buddy.h"
+#include "pidf_body.h"
+#include "log.h"
+
+string t_presence_state::basic_state2str(t_presence_state::t_basic_state state) {
+ switch (state) {
+ case ST_BASIC_UNKNOWN:
+ return "unknown";
+ case ST_BASIC_CLOSED:
+ return "closed";
+ case ST_BASIC_OPEN:
+ return "open";
+ case ST_BASIC_FAILED:
+ return "failed";
+ case ST_BASIC_REJECTED:
+ return "rejeceted";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+string t_presence_state::basic_state2pidf_str(t_presence_state::t_basic_state state) {
+ if (state == ST_BASIC_OPEN) {
+ return PIDF_STATUS_BASIC_OPEN;
+ }
+
+ // Convert all other states to "closed".
+ return PIDF_STATUS_BASIC_CLOSED;
+}
+
+t_presence_state::t_presence_state() {
+ assert(false);
+}
+
+t_presence_state::t_presence_state(t_buddy *_buddy) :
+ buddy(_buddy),
+ basic_state(ST_BASIC_UNKNOWN)
+{
+}
+
+t_presence_state::t_basic_state t_presence_state::get_basic_state(void) const {
+ t_basic_state result;
+ mtx_state.lock();
+ result = basic_state;
+ mtx_state.unlock();
+ return result;
+}
+
+string t_presence_state::get_failure_msg(void) const {
+ string result;
+ mtx_state.lock();
+ result = failure_msg;
+ mtx_state.unlock();
+ return result;
+}
+
+void t_presence_state::set_basic_state(t_presence_state::t_basic_state state) {
+ mtx_state.lock();
+ basic_state = state;
+
+ log_file->write_header("t_presence_state::set_basic_state", LOG_NORMAL, LOG_DEBUG);
+ log_file->write_raw("Presence state changed to: ");
+ log_file->write_raw(basic_state2str(basic_state));
+ log_file->write_endl();
+ log_file->write_raw(buddy->get_sip_address());
+ log_file->write_endl();
+ log_file->write_footer();
+
+ mtx_state.unlock();
+
+ // Notify the stat change to all observers of the buddy.
+ buddy->notify();
+}
+
+void t_presence_state::set_failure_msg(const string &msg) {
+ mtx_state.lock();
+ failure_msg = msg;
+ mtx_state.unlock();
+}
diff --git a/src/presence/presence_state.h b/src/presence/presence_state.h
new file mode 100644
index 0000000..f86082e
--- /dev/null
+++ b/src/presence/presence_state.h
@@ -0,0 +1,94 @@
+/*
+ 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
+*/
+
+/**
+ * @file
+ * Presence state (RFC 3863)
+ */
+
+#ifndef _PRESENCE_STATE_H
+#define _PRESENCE_STATE_H
+
+#include <string>
+#include "threads/mutex.h"
+
+using namespace std;
+
+// Forward declaration
+class t_buddy;
+
+/** Presence state */
+class t_presence_state {
+public:
+ /** Basic state (RFC 3863 4.1.4) */
+ enum t_basic_state {
+ ST_BASIC_UNKNOWN, /**< Presence state is unknown. */
+ ST_BASIC_CLOSED, /**< Unable to accept communication. */
+ ST_BASIC_OPEN, /**< Ready to accept communication. */
+ ST_BASIC_FAILED, /**< Failed to determine basic state. */
+ ST_BASIC_REJECTED,/**< Subscription has been rejected. */
+ };
+
+ /**
+ * Convert a basic state to a string representation for internal usage.
+ * @param state [in] A basic state value.
+ * @return String representation of the basic state.
+ */
+ static string basic_state2str(t_basic_state state);
+
+ /**
+ * Convert a basic state to a PIDF string representation.
+ * @param state [in] A basic state value.
+ * @return PIDF representation of the basic state.
+ */
+ static string basic_state2pidf_str(t_basic_state state);
+
+private:
+ /** Mutex for concurrent access to the presence state. */
+ mutable t_mutex mtx_state;
+
+ /** Buddy owning this state. */
+ t_buddy *buddy;
+
+ /** Basic presence state. */
+ t_basic_state basic_state;
+
+ /** Detailed failure message */
+ string failure_msg;
+
+ /** Protect the default constructor from being used. */
+ t_presence_state();
+
+public:
+ /** Constructor. */
+ t_presence_state(t_buddy *_buddy);
+
+ /** @name Getters */
+ //@{
+ t_basic_state get_basic_state(void) const;
+ string get_failure_msg(void) const;
+ //@}
+
+ /** @name Setters */
+ //@{
+ void set_basic_state(t_basic_state state);
+ void set_failure_msg(const string &msg);
+ //@}
+};
+
+#endif
diff --git a/src/presence/presence_subscription.cpp b/src/presence/presence_subscription.cpp
new file mode 100644
index 0000000..af1514e
--- /dev/null
+++ b/src/presence/presence_subscription.cpp
@@ -0,0 +1,144 @@
+/*
+ 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 "presence_subscription.h"
+
+#include <cassert>
+
+#include "pidf_body.h"
+
+#include "log.h"
+#include "util.h"
+#include "parser/hdr_event.h"
+#include "audits/memman.h"
+
+t_request *t_presence_subscription::create_subscribe(unsigned long expires) const {
+ t_request *r = t_subscription::create_subscribe(expires);
+ SET_PRESENCE_HDR_ACCEPT(r->hdr_accept);
+
+ return r;
+}
+
+t_presence_subscription::t_presence_subscription(t_presence_dialog *_dialog, t_presence_state *_state) :
+ t_subscription(_dialog, SR_SUBSCRIBER, SIP_EVENT_PRESENCE),
+ presence_state(_state)
+{
+}
+
+bool t_presence_subscription::recv_notify(t_request *r, t_tuid tuid, t_tid tid) {
+ if (t_subscription::recv_notify(r, tuid, tid)) return true;
+
+ bool unsupported_body = false;
+
+ // NOTE: if the subscription is still pending (RFC 3265 3.2.4), then the
+ // information in the body has no meaning.
+ // NOTE: a NOTIFY request may have no body (RFC 3856 6.6.2)
+ if (r->body && r->body->get_type() == BODY_PIDF_XML &&
+ !is_pending())
+ {
+ t_pidf_xml_body *body = dynamic_cast<t_pidf_xml_body *>(r->body);
+ assert(body);
+
+ string basic = body->get_basic_status();
+ if (basic == PIDF_STATUS_BASIC_OPEN) {
+ presence_state->set_basic_state(t_presence_state::ST_BASIC_OPEN);
+ } else if (basic == PIDF_STATUS_BASIC_CLOSED) {
+ presence_state->set_basic_state(t_presence_state::ST_BASIC_CLOSED);
+ } else {
+ log_file->write_header("t_presence_subscription::recv_notify",
+ LOG_NORMAL, LOG_WARNING);
+ log_file->write_raw("Unknown basic status in pidf: ");
+ log_file->write_raw(basic);
+ log_file->write_endl();
+ log_file->write_footer();
+
+ presence_state->set_basic_state(t_presence_state::ST_BASIC_UNKNOWN);
+ }
+ }
+
+ // Verify if there is an usupported body.
+ if (r->body && r->body->get_type() != BODY_PIDF_XML) {
+ unsupported_body = true;
+ }
+
+ if (state == SS_TERMINATED) {
+ if (may_resubscribe) {
+ presence_state->set_basic_state(t_presence_state::ST_BASIC_UNKNOWN);
+ log_file->write_report("Presence subscription terminated.",
+ "t_presence_subscription::recv_notify");
+ } else {
+ if (reason_termination == EV_REASON_REJECTED) {
+ presence_state->set_basic_state(t_presence_state::ST_BASIC_REJECTED);
+
+ log_file->write_report("Presence agent rejected the subscription.",
+ "t_presence_subscription::recv_notify");
+ } else {
+ // The PA ended the subscription and indicated
+ // that resubscription is not possible. So no presence status
+ // can be retrieved anymore. This should not happen.
+ // Show it as a failure to the user.
+ presence_state->set_failure_msg(reason_termination);
+ presence_state->set_basic_state(t_presence_state::ST_BASIC_FAILED);
+
+ log_file->write_report(
+ "Presence agent permanently terminated the subscription.",
+ "t_presence_subscription::recv_notify");
+ }
+ }
+ }
+
+ t_response *resp;
+ if (unsupported_body) {
+ resp = r->create_response(R_415_UNSUPPORTED_MEDIA_TYPE);
+ SET_PRESENCE_HDR_ACCEPT(r->hdr_accept);
+ } else {
+ resp = r->create_response(R_200_OK);
+ }
+ send_response(user_config, resp, 0, tid);
+ MEMMAN_DELETE(resp);
+ delete resp;
+
+ return true;
+}
+
+bool t_presence_subscription::recv_subscribe_response(t_response *r, t_tuid tuid, t_tid tid) {
+ // Parent handles the SUBSCRIBE response
+ (void)t_subscription::recv_subscribe_response(r, tuid, tid);
+
+ // If the subscription is terminated after the SUBSCRIBE response, it means
+ // that subscription failed.
+ if (state == SS_TERMINATED) {
+ if (r->code == R_403_FORBIDDEN || r->code == R_603_DECLINE) {
+ presence_state->set_basic_state(t_presence_state::ST_BASIC_REJECTED);
+
+ log_file->write_report("Presence subscription rejected.",
+ "t_presence_subscription::recv_subscribe_response");
+ } else {
+ string failure = int2str(r->code);
+ failure += ' ';
+ failure += r->reason;
+ presence_state->set_failure_msg(failure);
+ presence_state->set_basic_state(t_presence_state::ST_BASIC_FAILED);
+
+ log_file->write_report("Presence subscription failed.",
+ "t_presence_subscription::recv_subscribe_response");
+ }
+ }
+
+ return true;
+}
diff --git a/src/presence/presence_subscription.h b/src/presence/presence_subscription.h
new file mode 100644
index 0000000..09fb6ba
--- /dev/null
+++ b/src/presence/presence_subscription.h
@@ -0,0 +1,51 @@
+/*
+ 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
+*/
+
+/**
+ * @file
+ * Presence subscription (RFC 3856)
+ */
+
+#ifndef _PRESENCE_SUBSCRIPTION_H
+#define _PRESENCE_SUBSCRIPTION_H
+
+#include "presence_state.h"
+#include "presence_dialog.h"
+#include "subscription.h"
+
+/** Subscription to the presence event (RFC 3856) */
+class t_presence_subscription : public t_subscription {
+private:
+ t_presence_state *presence_state;
+
+protected:
+ virtual t_request *create_subscribe(unsigned long expires) const;
+
+public:
+ /**
+ * Constructor.
+ * @param _dialog [in] Dialog for the presence subscription.
+ * @param _state [in] Current presence state.
+ */
+ t_presence_subscription(t_presence_dialog *_dialog, t_presence_state *_state);
+
+ virtual bool recv_notify(t_request *r, t_tuid tuid, t_tid tid);
+ virtual bool recv_subscribe_response(t_response *r, t_tuid tuid, t_tid tid);
+};
+
+#endif