From 32b524a5a9aebb00287118dbd6ffa1a946c579e6 Mon Sep 17 00:00:00 2001 From: Eloi BAIL <eloi.bail@savoirfairelinux.com> Date: Wed, 3 Jul 2013 17:02:04 -0400 Subject: [PATCH] Presence : missing files Some files were not added in the previous commit --- daemon/src/sip/sipbuddy.cpp | 554 ++++++++++++++++++++++++++++++++ daemon/src/sip/sipbuddy.h | 120 +++++++ daemon/src/sip/sipvoip_pres.cpp | 341 ++++++++++++++++++++ daemon/src/sip/sipvoip_pres.h | 101 ++++++ 4 files changed, 1116 insertions(+) create mode 100644 daemon/src/sip/sipbuddy.cpp create mode 100644 daemon/src/sip/sipbuddy.h create mode 100644 daemon/src/sip/sipvoip_pres.cpp create mode 100644 daemon/src/sip/sipvoip_pres.h diff --git a/daemon/src/sip/sipbuddy.cpp b/daemon/src/sip/sipbuddy.cpp new file mode 100644 index 0000000000..6ed316e1c8 --- /dev/null +++ b/daemon/src/sip/sipbuddy.cpp @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2012, 2013 LOTES TM LLC + * Author : Andrey Loukhnov <aol.nnov@gmail.com> + * + * This file is a part of pult5-voip + * + * pult5-voip 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 3 of the License, or + * (at your option) any later version. + * + * pult5-voip 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 programm. If not, see <http://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify pult5-voip, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <pj/log.h> +#include <pj/rand.h> +#include <pj/log.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_types.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_dialog.h> +#include <pjsip/sip_endpoint.h> +#include <string> +#include <pj/pool.h> +#include <pjsip/sip_ua_layer.h> +#include <pjsip-simple/evsub.h> + +#include "sipbuddy.h" +#include "sipaccount.h" +#include "sipvoiplink.h" + +#define PJSUA_BUDDY_SUB_TERM_REASON_LEN 32 +#define PJSUA_PRES_TIMER 300 +#define THIS_FILE "sipbuddy.cpp" + +#include "logger.h" +//extern pjsip_module mod_ua_; +//extern pjsip_endpoint *endpt_; + +int modId; +static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry) { + (void) th; + SIPBuddy *b = (SIPBuddy *) entry->user_data; + b->updatePresence(); +} + +/* Callback called when *client* subscription state has changed. */ +static void sflphoned_evsub_on_state(pjsip_evsub *sub, pjsip_event *event) { + SIPBuddy *buddy; + + PJ_UNUSED_ARG(event); + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + + buddy = (SIPBuddy *) pjsip_evsub_get_mod_data(sub, modId); + if (buddy) { + buddy->incLock(); + PJ_LOG(4, + (THIS_FILE, "Presence subscription to '%s' is '%s'", buddy->getURI().c_str(), pjsip_evsub_get_state_name(sub)?pjsip_evsub_get_state_name(sub):"null")); +// pj_log_push_indent(); + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + int resub_delay = -1; + +// const pj_str_t *pjTermReason = pjsip_evsub_get_termination_reason(sub); +// std::string termReason(pjTermReason->ptr, +// pjTermReason->slen > PJSUA_BUDDY_SUB_TERM_REASON_LEN? +// PJSUA_BUDDY_SUB_TERM_REASON_LEN: +// pjTermReason->slen +// ); + pj_strdup_with_null(buddy->pool, &buddy->term_reason, pjsip_evsub_get_termination_reason(sub)); +// buddy->setTermReason(termReason); +// buddy->setTermCode(200); + buddy->term_code = 200; + + /* Determine whether to resubscribe automatically */ + if (event && event->type == PJSIP_EVENT_TSX_STATE) { + const pjsip_transaction *tsx = event->body.tsx_state.tsx; + if (pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0) { +// buddy->setTermCode(tsx->status_code); + buddy->term_code = tsx->status_code; + switch (tsx->status_code) { + case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST: + /* 481: we refreshed too late? resubscribe + * immediately. + */ + /* But this must only happen when the 481 is received + * on subscription refresh request. We MUST NOT try to + * resubscribe automatically if the 481 is received + * on the initial SUBSCRIBE (if server returns this + * response for some reason). + */ + if (buddy->dlg->remote.contact) + resub_delay = 500; + break; + } + } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) { + if (buddy->isTermReason("deactivated") || buddy->isTermReason("timeout")) { + /* deactivated: The subscription has been terminated, + * but the subscriber SHOULD retry immediately with + * a new subscription. + */ + /* timeout: The subscription has been terminated + * because it was not refreshed before it expired. + * Clients MAY re-subscribe immediately. The + * "retry-after" parameter has no semantics for + * "timeout". + */ + resub_delay = 500; + } else if (buddy->isTermReason("probation") || buddy->isTermReason("giveup")) { + /* probation: The subscription has been terminated, + * but the client SHOULD retry at some later time. + * If a "retry-after" parameter is also present, the + * client SHOULD wait at least the number of seconds + * specified by that parameter before attempting to re- + * subscribe. + */ + /* giveup: The subscription has been terminated because + * the notifier could not obtain authorization in a + * timely fashion. If a "retry-after" parameter is + * also present, the client SHOULD wait at least the + * number of seconds specified by that parameter before + * attempting to re-subscribe; otherwise, the client + * MAY retry immediately, but will likely get put back + * into pending state. + */ + const pjsip_sub_state_hdr *sub_hdr; + pj_str_t sub_state = { + "Subscription-State", + 18 }; + const pjsip_msg *msg; + + msg = event->body.tsx_state.src.rdata->msg_info.msg; + sub_hdr = (const pjsip_sub_state_hdr*) pjsip_msg_find_hdr_by_name(msg, &sub_state, NULL); + if (sub_hdr && sub_hdr->retry_after > 0) + resub_delay = sub_hdr->retry_after * 1000; + } + + } + } + + /* For other cases of subscription termination, if resubscribe + * timer is not set, schedule with default expiration (plus minus + * some random value, to avoid sending SUBSCRIBEs all at once) + */ + if (resub_delay == -1) { +// pj_assert(PJSUA_PRES_TIMER >= 3); + resub_delay = PJSUA_PRES_TIMER * 1000;// - 2500 + (pj_rand() % 5000); + } + buddy->sub = sub; + buddy->rescheduleTimer(PJ_TRUE, resub_delay); + }/* else { + This will clear the last termination code/reason + buddy->term_code = 0; + buddy->term_reason.ptr = NULL; + }*/ + + /* Clear subscription */ + /* if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_terminate(buddy->sub, PJ_FALSE); // = NULL; + buddy->status.info_cnt = 0; + buddy->dlg = NULL; + buddy->rescheduleTimer(PJ_FALSE, 0); +// pjsip_evsub_set_mod_data(sub, modId, NULL); + }*/ + +// pj_log_pop_indent(); + buddy->decLock(); + } +} + +/* Callback when transaction state has changed. */ +static void sflphoned_evsub_on_tsx_state(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) { + + SIPBuddy *buddy; + pjsip_contact_hdr *contact_hdr; + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (SIPBuddy *) pjsip_evsub_get_mod_data(sub, modId); + if (!buddy) { + return; + } + buddy->incLock(); + + /* We only use this to update buddy's Contact, when it's not + * set. + */ + if (buddy->contact.slen != 0) { + /* Contact already set */ + buddy->decLock(); + return; + } + + /* Only care about 2xx response to outgoing SUBSCRIBE */ + if (tsx->status_code / 100 != 2 || tsx->role != PJSIP_UAC_ROLE || event->type != PJSIP_EVENT_RX_MSG + || pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method()) != 0) { + buddy->decLock(); + return; + } + + /* Find contact header. */ + contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg, PJSIP_H_CONTACT, + NULL); + if (!contact_hdr || !contact_hdr->uri) { + buddy->decLock(); + return; + } + + buddy->contact.ptr = (char*) pj_pool_alloc(buddy->pool, PJSIP_MAX_URL_SIZE); + buddy->contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, contact_hdr->uri, buddy->contact.ptr, + PJSIP_MAX_URL_SIZE); + if (buddy->contact.slen < 0) + buddy->contact.slen = 0; + + buddy->decLock(); +} + +/* Callback called when we receive NOTIFY */ +static void sflphoned_evsub_on_rx_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, + pjsip_hdr *res_hdr, pjsip_msg_body **p_body) { + SIPBuddy *buddy; + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (SIPBuddy *) pjsip_evsub_get_mod_data(sub, modId); + if (!buddy) { + return; + } + buddy->incLock(); + /* Update our info. */ + pjsip_pres_get_status(sub, &buddy->status); +// if (buddy->status.info[0] != NULL ) { +#if 0 // ELOI no existing chanState element in info + std::string chanState(buddy->status.info[0].chanState.ptr, buddy->status.info[0].chanState.slen); +#endif + std::string basic(buddy->status.info[0].basic_open ? "open" : "closed"); +//ELOI Call here the callback for presence changement + ERROR("\n-----------------\n presenceStateChange for %s to %s \n-----------------\n", buddy->getURI().c_str(),basic.c_str()); +// } + + /* The default is to send 200 response to NOTIFY. + * Just leave it there.. + */ + PJ_UNUSED_ARG(rdata); + PJ_UNUSED_ARG(p_st_code); + PJ_UNUSED_ARG(p_st_text); + PJ_UNUSED_ARG(res_hdr); + PJ_UNUSED_ARG(p_body); + + buddy->decLock(); +} + +SIPBuddy::SIPBuddy(const std::string& uri_, SIPAccount *acc_) : + acc(acc_), + uri(pj_str(strdup(uri_.c_str()))), + //buddy_id(-1) + contact(pj_str(strdup(acc->getFromUri().c_str()))), + display(), + dlg(NULL), +// host () + monitor(false), + name(), + cp_(), + pool(0), +// port(0) + status(), + sub(NULL), + term_code(0), + term_reason(), + timer(), + user_data(NULL), + lock_count(0) { + pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0); + pool = pj_pool_create(&cp_.factory, "buddy", 512, 512, NULL); +} + +SIPBuddy::~SIPBuddy() { + while(lock_count >0) { + usleep(200); + } + PJ_LOG(4, ("Destroying buddy object with uri %s", uri.ptr)); + monitor = false; + rescheduleTimer(PJ_FALSE, 0); + updatePresence(); + + pj_pool_release(pool); +} + +bool SIPBuddy::isSubscribed() { + return this->monitor; +} + +std::string SIPBuddy::getURI() { + std::string buddyURI(uri.ptr, uri.slen); + return buddyURI; +} + +bool SIPBuddy::isTermReason(std::string reason) { + std::string myReason(term_reason.ptr, term_reason.slen); + return !myReason.compare(reason); +} + +void SIPBuddy::rescheduleTimer(bool reschedule, unsigned msec) { + if (timer.id) { + // pjsua_cancel_timer(&timer); + pjsip_endpt_cancel_timer(((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint(), &timer); + timer.id = PJ_FALSE; + } + + if (reschedule) { + pj_time_val delay; + + PJ_LOG(4, + (THIS_FILE, "Resubscribing buddy %.*s in %u ms (reason: %.*s)", uri.slen, uri.ptr, msec, (int) term_reason.slen, term_reason.ptr)); + monitor = PJ_TRUE; + pj_timer_entry_init(&timer, 0, this, &buddy_timer_cb); + delay.sec = 0; + delay.msec = msec; + pj_time_val_normalize(&delay); + + // if (pjsua_schedule_timer(&timer, &delay)==PJ_SUCCESS) + + if (pjsip_endpt_schedule_timer(((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint(), &timer, &delay) == PJ_SUCCESS) { +// timer.id = PJ_TRUE; + } + } +} + +pj_status_t SIPBuddy::updatePresence() { + + if (!monitor) { + /* unsubscribe */ + pjsip_tx_data *tdata; + pj_status_t retStatus; + + if (sub == NULL) { + return PJ_SUCCESS; + } + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL; + return PJ_SUCCESS; + } + + PJ_LOG(5, (THIS_FILE, "Buddy %s: unsubscribing..", uri.ptr)); + + retStatus = pjsip_pres_initiate(sub, 300, &tdata); + if (retStatus == PJ_SUCCESS) { + // pjsua_process_msg_data(tdata, NULL); + if (/*pjsua_var.ua_cfg.user_agent.slen &&*/ + tdata->msg->type == PJSIP_REQUEST_MSG) { + const pj_str_t STR_USER_AGENT = { + "User-Agent", + 10 }; + pj_str_t ua = pj_str(strdup(acc->getUserAgentName().c_str())); + pjsip_hdr *h; + h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_USER_AGENT, &ua); + pjsip_msg_add_hdr(tdata->msg, h); + } + retStatus = pjsip_pres_send_request(sub, tdata); + } + + if (retStatus != PJ_SUCCESS && sub) { + pjsip_pres_terminate(sub, PJ_FALSE); + pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL; + PJ_LOG(4, (THIS_FILE, "Unable to unsubscribe presence", status)); + } +// pjsip_evsub_set_mod_data(sub, modId, NULL); + return PJ_SUCCESS; + } + +#if 0 + if (sub && sub->dlg) { //do not bother if already subscribed +// PJ_LOG(4, (THIS_FILE, "Buddy %s: already subscribed", uri.ptr)); +// return PJ_SUCCESS; + pjsip_evsub_terminate(sub, PJ_FALSE); + } +#endif + + //subscribe + pjsip_evsub_user pres_callback; +// pj_pool_t *tmp_pool = NULL; + + pjsip_tx_data *tdata; + pj_status_t status; + + /* Event subscription callback. */ + pj_bzero(&pres_callback, sizeof(pres_callback)); + pres_callback.on_evsub_state = &sflphoned_evsub_on_state; + pres_callback.on_tsx_state = &sflphoned_evsub_on_tsx_state; + pres_callback.on_rx_notify = &sflphoned_evsub_on_rx_notify; + + PJ_LOG(4, (THIS_FILE, "Buddy %s: subscribing presence,using account %s..", uri.ptr, acc->getAccountID().c_str())); + + /* Generate suitable Contact header unless one is already set in + * the account + */ +#if 0 + if (acc->contact.slen) { + contact = acc->contact; + } else { + tmp_pool = pjsua_pool_create("tmpbuddy", 512, 256); + + status = pjsua_acc_create_uac_contact(tmp_pool, &contact, + acc_id, &buddy->uri); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + } +#endif + /* Create UAC dialog */ + pj_str_t from = pj_str(strdup(acc->getFromUri().c_str())); + status = pjsip_dlg_create_uac(pjsip_ua_instance(), &from, &contact, &uri, NULL, &dlg); +#if 1 + if (status != PJ_SUCCESS) { + //pjsua_perror(THIS_FILE, "Unable to create dialog", + // status); + ERROR("Unable to create dialog \n"); + //if (tmp_pool) pj_pool_release(tmp_pool); + //pj_log_pop_indent(); + return PJ_FALSE; + } +#endif + //ELOI add credential for auth - otherwise subscription was failing + if (acc->hasCredentials() and pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->getCredentialCount(), acc->getCredInfo()) != PJ_SUCCESS) { + ERROR("Could not initialize credentials for invite session authentication"); + } + // E.B add credential for auth + + /* Increment the dialog's lock otherwise when presence session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(dlg); + DEBUG("ELOI > pjsip_pres_create_uac "); + + status = pjsip_pres_create_uac(dlg, &pres_callback, PJSIP_EVSUB_NO_EVENT_ID, &sub); + if (status != PJ_SUCCESS) { + DEBUG("ELOI > pjsip_pres_create_uac : KO"); + pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL; + PJ_LOG(4, (THIS_FILE, "Unable to create presence client", status)); + /* This should destroy the dialog since there's no session + * referencing it + */ + if (dlg) { + pjsip_dlg_dec_lock(dlg); + } +// if (tmp_pool) pj_pool_release(tmp_pool); +// pj_log_pop_indent(); + return PJ_SUCCESS; + } + DEBUG("ELOI > pjsip_pres_create_uac : OK"); +#if 0 + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(buddy->dlg, &tp_sel); + } + + /* Set route-set */ + if (!pj_list_empty(&acc->route_set)) { + pjsip_dlg_set_route_set(buddy->dlg, &acc->route_set); + } + + /* Set credentials */ + if (acc->cred_cnt) { + pjsip_auth_clt_set_credentials(&buddy->dlg->auth_sess, + acc->cred_cnt, acc->cred); + } + + /* Set authentication preference */ + pjsip_auth_clt_set_prefs(&buddy->dlg->auth_sess, &acc->cfg.auth_pref); +#endif + modId = ((SIPVoIPLink*) acc->getVoIPLink())->getModId(); + pjsip_evsub_set_mod_data(sub, modId, this); + + status = pjsip_pres_initiate(sub, -1, &tdata); + if (status != PJ_SUCCESS) { + if (dlg) + pjsip_dlg_dec_lock(dlg); + if (sub) { + pjsip_pres_terminate(sub, PJ_FALSE); + } + pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL; + PJ_LOG(4, (THIS_FILE, "Unable to create initial SUBSCRIBE", status)); +// if (tmp_pool) pj_pool_release(tmp_pool); +// pj_log_pop_indent(); + return PJ_SUCCESS; + } + +// pjsua_process_msg_data(tdata, NULL); + + status = pjsip_pres_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + if (dlg) + pjsip_dlg_dec_lock(dlg); + if (sub) { + pjsip_pres_terminate(sub, PJ_FALSE); + sub = NULL; + } + + PJ_LOG(4, (THIS_FILE, "Unable to send initial SUBSCRIBE", status)); +// if (tmp_pool) pj_pool_release(tmp_pool); +// pj_log_pop_indent(); + return PJ_SUCCESS; + } + + pjsip_dlg_dec_lock(dlg); +// if (tmp_pool) pj_pool_release(tmp_pool); + return PJ_SUCCESS; +} + +void SIPBuddy::subscribe() { + monitor = true; + updatePresence(); +} + +void SIPBuddy::unsubscribe() { + monitor = false; + updatePresence(); +} diff --git a/daemon/src/sip/sipbuddy.h b/daemon/src/sip/sipbuddy.h new file mode 100644 index 0000000000..b1b2798dfe --- /dev/null +++ b/daemon/src/sip/sipbuddy.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012, 2013 LOTES TM LLC + * Author : Andrey Loukhnov <aol.nnov@gmail.com> + * + * This file is a part of pult5-voip + * + * pult5-voip 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 3 of the License, or + * (at your option) any later version. + * + * pult5-voip 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 programm. If not, see <http://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify pult5-voip, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SIPBUDDY_H +#define SIPBUDDY_H + +//#define THIS_FILE sipbuddy.cpp +#include <pjsip-simple/presence.h> +#include <pj/timer.h> +#include <pj/pool.h> +#include <string> +// ELOI #include "misc/noncopyable.h" + +#include <pjsip-simple/evsub.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_transport.h> + + +class SIPAccount; + +static void sflphoned_evsub_on_state(pjsip_evsub *sub, pjsip_event *event); +static void sflphoned_evsub_on_tsx_state(pjsip_evsub *sub, + pjsip_transaction *tsx, + pjsip_event *event); +static void sflphoned_evsub_on_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); +static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry); + +class SIPBuddy { +public: + SIPBuddy(const std::string &uri, SIPAccount *acc); + ~SIPBuddy(); + void subscribe(); + void unsubscribe(); + bool isSubscribed(); + std::string getURI(); + + friend void sflphoned_evsub_on_state( pjsip_evsub *sub, pjsip_event *event); + friend void sflphoned_evsub_on_tsx_state(pjsip_evsub *sub, + pjsip_transaction *tsx, + pjsip_event *event); + friend void sflphoned_evsub_on_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); + friend void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry); + void incLock() { + lock_count++; + } + void decLock() { + lock_count--; + } + +private: + //ELOI FIXME NON_COPYABLE(SIPBuddy); + + void rescheduleTimer(bool reschedule, unsigned msec); + + pj_status_t updatePresence(); + + bool isTermReason(std::string); + unsigned getTermCode(); + + SIPAccount *acc; + pj_str_t uri; /**< Buddy URI. */ +// unsigned buddy_id; /**< Buddy index. */ + pj_str_t contact; /**< Contact learned from subscrp. */ + pj_str_t display; /**< Buddy display name. */ + pjsip_dialog *dlg; /**< The underlying dialog. */ +// pj_str_t host; /**< Buddy host. */ + pj_bool_t monitor; /**< Should we monitor? */ + pj_str_t name; /**< Buddy name. */ + pj_caching_pool cp_; + pj_pool_t *pool; /**< Pool for this buddy. */ +// unsigned port; /**< Buddy port. */ + pjsip_pres_status status; /**< Buddy presence status. */ + pjsip_evsub *sub; /**< Buddy presence subscription */ + unsigned term_code; /**< Subscription termination code */ + pj_str_t term_reason;/**< Subscription termination reason */ + pj_timer_entry timer; /**< Resubscription timer */ + void *user_data; /**< Application data. */ + int lock_count; +}; + +#endif /* SIPBUDDY_H */ diff --git a/daemon/src/sip/sipvoip_pres.cpp b/daemon/src/sip/sipvoip_pres.cpp new file mode 100644 index 0000000000..a203a6a8b5 --- /dev/null +++ b/daemon/src/sip/sipvoip_pres.cpp @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2012б 2013 LOTES TM LLC + * Author : Andrey Loukhnov <aol.nnov@gmail.com> + * + * This file is a part of pult5-voip + * + * pult5-voip 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 3 of the License, or + * (at your option) any later version. + * + * pult5-voip 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 programm. If not, see <http://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify pult5-voip, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string> +#include <bits/stringfwd.h> +#include <bits/basic_string.h> + +#define THIS_FILE "sipvoip_pres.cpp" +#include "pjsip/sip_dialog.h" +#include "pjsip/sip_msg.h" +#include "pjsip/sip_ua_layer.h" +#include "pjsip/sip_transaction.h" + +#include"pjsip-simple/evsub.h" +#include"pjsip-simple/presence.h" +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/compat/string.h> + +#include "sipvoip_pres.h" +#include "logger.h" +#include "sipaccount.h" +#include "sipcall.h" +#include "sipvoiplink.h" +#include "manager.h" + + +/* Callback called when *server* subscription state has changed. */ +void pres_evsub_on_srv_state(pjsip_evsub *sub, pjsip_event *event) { + /*****************/ +#if 0 // ELOI : useless code ? + pjsip_rx_data *rdata = event->body.rx_msg.rdata; + if(!rdata) { + PJ_LOG(4, (THIS_FILE, "no rdata in presence")); + ERROR("no rdata in presence"); + return; + } +// std::string name(rdata->msg_info.to->name.ptr, rdata->msg_info.to->name.slen); +// std::string servername(rdata->msg_info.from->name.ptr, rdata->msg_info.from->name.slen); +#if 0 //ELOI modify account management + std::string accountId = "IP2IP"; //Manager::instance().getAccountIdFromNameAndServer(name, servername); + SIPAccount *acc = (SIPAccount *) Phone::instance().getAccountById(accountId); +#else + std::string accountId = "IP2IP";//ELOI TODO is it proper accountId ? + SIPAccount *acc = Manager::instance().getSipAccount(accountId); +#endif + /******************/ + PJ_UNUSED_ARG(event); +//#if 0 + ServerPresenceSub *server; +// PJSUA_LOCK(); + server = (ServerPresenceSub *) pjsip_evsub_get_mod_data(sub, + ((SIPVoIPLink*) (acc->getVoIPLink()))->getModId() /*my_mod_pres.id*/); + PJ_LOG(4, (THIS_FILE, "Server subscription to %s is %s", server->remote, pjsip_evsub_get_state_name(sub))); + + if (server) { + pjsip_evsub_state state; + + PJ_LOG(4, (THIS_FILE, "Server subscription to %s is %s", server->remote, pjsip_evsub_get_state_name(sub))); + + state = pjsip_evsub_get_state(sub); + +#if 0 + if (false pjsua_var.ua_cfg.cb.on_srv_subscribe_state) { + pj_str_t from; + + from = server->dlg->remote.info_str; + (*pjsua_var.ua_cfg.cb.on_srv_subscribe_state)(uapres->acc_id, + uapres, &from, + state, event); + } +#endif + + if (state == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_set_mod_data(sub, ((SIPVoIPLink*) (acc->getVoIPLink()))->getModId(), NULL); +// pj_list_erase(uapres); +#if 0 //ELOI modify account management + ((SIPAccount*) (Phone::instance().getAccountById(server->accId)))->removerServerSubscription(server); +#else + // ELOI ((SIPAccount*) (Manager::instance().getSipAccount(server->accId)))->removerServerSubscription(server); +#endif + } + } +// PJSUA_UNLOCK(); +#endif +} + +pj_bool_t my_pres_on_rx_request(pjsip_rx_data *rdata) { + return PJ_FALSE; + +#if 0 // ELOI useless code ? + pjsip_method *method = &rdata->msg_info.msg->line.req.method; + pj_str_t *str = &method->name; + std::string request(str->ptr, str->slen); + DEBUG("MY PRESENCE: %s ", request.c_str()); + DEBUG("\n----------------------BUFFER--------------- \n %s \n ------------------------------------- ",rdata->msg_info.msg_buf); + PJ_LOG(1, (THIS_FILE, "> my_pres_on_rx_request" )); + pj_str_t contact; + pj_status_t status; + pjsip_dialog *dlg; + pjsip_evsub *sub; + pjsip_evsub_user pres_cb; + pjsip_pres_status pres_status; + pjsip_tx_data *tdata; + pjsip_expires_hdr *expires_hdr; +// int expires; + pjsip_status_code st_code; + pj_str_t reason; + pjsua_msg_data msg_data; + pjsip_evsub_state ev_state; + + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method()) != 0){ + ERROR("pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method()) != 0)" ); + ERROR("Method id = %d",rdata->msg_info.msg->line.req.method.id); + ERROR("Method name = %s",rdata->msg_info.msg->line.req.method.name); + return PJ_FALSE; + } + + std::string name(rdata->msg_info.to->name.ptr, rdata->msg_info.to->name.slen); + std::string server(rdata->msg_info.from->name.ptr, rdata->msg_info.from->name.slen); + + //std::string accountId = "IP2IP"; //Manager::instance().getAccountIdFromNameAndServer(name, server); + std::string accountId = "Account:1371589892"; //Manager::instance().getAccountIdFromNameAndServer(name, server); +#if 0 //ELOI modify account management + SIPAccount *acc = (SIPAccount *) Phone::instance().getAccountById(accountId); +#else + SIPAccount *acc = (SIPAccount *) Manager::instance().getSipAccount(accountId); +#endif + pjsip_endpoint *endpt = ((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint(); + + contact = pj_str(strdup(acc->getContactHeader().c_str())); + + /* Create UAS dialog: */ + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, &contact, &dlg); + if (status != PJ_SUCCESS) { + + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(1, (THIS_FILE, "Unable to create UAS dialog for subscription: %s [status=%d]", errmsg, status)); + // PJSUA_UNLOCK(); + pjsip_endpt_respond_stateless(endpt, rdata, 400, NULL, NULL, NULL); + return PJ_TRUE; + } + + /* Init callback: */ + pj_bzero(&pres_cb, sizeof(pres_cb)); + pres_cb.on_evsub_state = &pres_evsub_on_srv_state; + + /* Create server presence subscription: */ + status = pjsip_pres_create_uas(dlg, &pres_cb, rdata, &sub); + if (status != PJ_SUCCESS) { + int code = PJSIP_ERRNO_TO_SIP_STATUS(status); + pjsip_tx_data *tdata; + + PJ_LOG(1, (THIS_FILE, "Unable to create server subscription %d", status)); + + if (code == 599 || code > 699 || code < 300) { + code = 400; + } + + status = pjsip_dlg_create_response(dlg, rdata, code, NULL, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata); + } + +// PJSUA_UNLOCK(); + return PJ_TRUE; + } + + /* Attach our data to the subscription: */ + +// uapres = PJ_POOL_ALLOC_T(dlg->pool, pjsua_srv_pres); +// uapres->sub = sub; +// uapres-> + char* remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE); +// uapres->acc_id = acc_id; +// uapres->dlg = dlg; + status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri, remote, PJSIP_MAX_URL_SIZE); + + //ACHTUNG !! + pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, dlg->local.info->uri, contact.ptr, PJSIP_MAX_URL_SIZE); + ServerPresenceSub *serverSub = new ServerPresenceSub(sub, remote, accountId, dlg); + + if (status < 1) + pj_ansi_strcpy(remote, "<-- url is too long-->"); + else + remote[status] = '\0'; + +// pjsip_evsub_add_header(sub, &acc->cfg.sub_hdr_list); + int modId = ((SIPVoIPLink*) (acc->getVoIPLink()))->getModId(); + pjsip_evsub_set_mod_data(sub, modId/*my_mod_pres.id*/, serverSub); + PJ_LOG(1, (THIS_FILE, "addServerSubscription" )); + // ELOI acc->addServerSubscription(serverSub); + /* Add server subscription to the list: */ +// pj_list_push_back(&pjsua_var.acc[acc_id].pres_srv_list, uapres); + /* Capture the value of Expires header. */ + expires_hdr = (pjsip_expires_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL); + if (expires_hdr) + serverSub->setExpires(expires_hdr->ivalue); + else + serverSub->setExpires(-1); + + st_code = (pjsip_status_code) 200; + reason = pj_str("OK"); +// pjsua_msg_data_init(&msg_data); + pj_bzero(&msg_data, sizeof(msg_data)); + pj_list_init(&msg_data.hdr_list); + pjsip_media_type_init(&msg_data.multipart_ctype, NULL, NULL); + pj_list_init(&msg_data.multipart_parts); + + /* Create and send 2xx response to the SUBSCRIBE request: */ + status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list); + if (status != PJ_SUCCESS) { + PJ_LOG(1, (THIS_FILE, "Unable to accept presence subscription %d", status)); +// pj_list_erase(uapres); + pjsip_pres_terminate(sub, PJ_FALSE); +// PJSUA_UNLOCK(); + return PJ_FALSE; + } +//TODO: handle rejection case pjsua_pers.c:956 + + /* If code is 200, send NOTIFY now */ +#if 0 + if (st_code == 200) { + pjsua_pres_notify(acc_id, uapres, PJSIP_EVSUB_STATE_ACTIVE, + NULL, NULL, PJ_TRUE, &msg_data); + } +#endif + pj_bzero(&pres_status, sizeof(pres_status)); + pres_status.info_cnt = 1; + pres_status.info[0].basic_open = true; + /*std::string currState = ((SIPVoIPLink*) acc->getVoIPLink())->getChannelState(); + pres_status.info[0].chanState = pj_str(strdup(currState.c_str()));*/ + + std::string contactWithAngles = acc->getFromUri(); + contactWithAngles.erase(contactWithAngles.find('>')); + + int semicolon = contactWithAngles.find_first_of(":"); + std::string contactWithoutAngles = contactWithAngles.substr(semicolon + 1); + pj_str_t contt = pj_str(strdup(contactWithoutAngles.c_str())); +// acc->get + + pj_memcpy(&pres_status.info[0].contact, &contt, sizeof(pj_str_t)); + +// pres_status.info[0].id = pj_str("000005"); //TODO: tuple id + + pjsip_pres_set_status(sub, &pres_status); + + ev_state = PJSIP_EVSUB_STATE_ACTIVE; + if (serverSub->expires == 0) + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + + /* Create and send the NOTIFY to active subscription: */ + pj_str_t stateStr = pj_str(""); + tdata = NULL; + status = pjsip_pres_notify(sub, ev_state, &stateStr, &reason, &tdata); + if (status == PJ_SUCCESS) { + /* Force removal of message body if msg_body==FALSE */ + + //pjsua_process_msg_data(tdata, msg_data); + if (tdata->msg->type == PJSIP_REQUEST_MSG) { + const pj_str_t STR_USER_AGENT = { + "User-Agent", + 10 }; + pjsip_hdr *h; + pj_str_t ua = pj_str("SFLPhone"); + h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_USER_AGENT, &ua); + pjsip_msg_add_hdr(tdata->msg, h); + } + + const pjsip_hdr *hdr; + hdr = msg_data.hdr_list.next; + while (hdr && hdr != &msg_data.hdr_list) { + pjsip_hdr *new_hdr; + + new_hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr); + PJ_LOG(1, ("adding header", new_hdr->name.ptr)); + pjsip_msg_add_hdr(tdata->msg, new_hdr); + + hdr = hdr->next; + } + if (msg_data.content_type.slen && msg_data.msg_body.slen) { +// pjsip_media_type ctype; + pjsip_msg_body *body; + pj_str_t type = pj_str("application"); + pj_str_t subtype = pj_str("pidf+xml"); + +// pjsua_parse_media_type(tdata->pool, &msg_data->content_type, &ctype); + body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &msg_data.msg_body); + tdata->msg->body = body; + } + // process_msg_data + + status = pjsip_pres_send_request(sub, tdata); + } + + if (status != PJ_SUCCESS) { + PJ_LOG(1, (THIS_FILE, "Unable to create/send NOTIFY %d", status)); +// pj_list_erase(srv_pres); + pjsip_pres_terminate(sub, PJ_FALSE); +// PJSUA_UNLOCK(); + return status; + } + + return PJ_TRUE; +#endif +} diff --git a/daemon/src/sip/sipvoip_pres.h b/daemon/src/sip/sipvoip_pres.h new file mode 100644 index 0000000000..537c2667c4 --- /dev/null +++ b/daemon/src/sip/sipvoip_pres.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2012б 2013 LOTES TM LLC + * Author : Andrey Loukhnov <aol.nnov@gmail.com> + * + * This file is a part of pult5-voip + * + * pult5-voip 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 3 of the License, or + * (at your option) any later version. + * + * pult5-voip 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 programm. If not, see <http://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify pult5-voip, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SIPVOIP_PRES_H +#define SIPVOIP_PRES_H +#include "pjsip/sip_types.h" +#include "pjsip/sip_module.h" +#include "pjsip/sip_msg.h" +#include "pjsip/sip_multipart.h" + +//PJ_BEGIN_DECL + + + extern pj_bool_t my_pres_on_rx_request(pjsip_rx_data *rdata); + + static pjsip_module my_mod_pres = { + NULL, NULL, /* prev, next. */ + { "mod-lotes-presence", 18}, /* Name. */ + -1, /* Id */ + // PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + PJSIP_MOD_PRIORITY_DIALOG_USAGE, + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &my_pres_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + struct pjsua_msg_data +{ + /** + * Additional message headers as linked list. Application can add + * headers to the list by creating the header, either from the heap/pool + * or from temporary local variable, and add the header using + * linked list operation. See pjsip_apps.c for some sample codes. + */ + pjsip_hdr hdr_list; + + /** + * MIME type of optional message body. + */ + pj_str_t content_type; + + /** + * Optional message body to be added to the message, only when the + * message doesn't have a body. + */ + pj_str_t msg_body; + + /** + * Content type of the multipart body. If application wants to send + * multipart message bodies, it puts the parts in \a parts and set + * the content type in \a multipart_ctype. If the message already + * contains a body, the body will be added to the multipart bodies. + */ + pjsip_media_type multipart_ctype; + + /** + * List of multipart parts. If application wants to send multipart + * message bodies, it puts the parts in \a parts and set the content + * type in \a multipart_ctype. If the message already contains a body, + * the body will be added to the multipart bodies. + */ + pjsip_multipart_part multipart_parts; +}; + +//PJ_END_DECL +#endif /* SIPVOIP_PRES_H */ -- GitLab