Skip to content
Snippets Groups Projects
Select Git revision
  • master
  • release/201811
  • release/201812
  • release/201901
  • release/201902
  • release/201903
  • release/201904
  • release/201905
  • release/201906
  • release/201908
  • release/201912
  • release/202001
  • release/202005
  • release/windows-test/201910
  • release/201808
  • wip/smartlist_refacto
  • wip/patches_poly_2017/JimmyHamel/MathieuGirouxHuppe
17 results

AboutPage.xaml

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    pres_sub_client.cpp 17.78 KiB
    /*
     *  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 <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 <unistd.h>
    
    #include "pres_sub_client.h"
    #include "sipaccount.h"
    #include "sippresence.h"
    #include "sipvoiplink.h"
    #include "sip_utils.h"
    #include "manager.h"
    
    #include "logger.h"
    
    #define PRES_TIMER 300 // 5min
    
    int modId; // used to extract data structure from event_subscription
    
    void pres_client_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry) {
        //(void) th;
        /* TODO : clean*/
        PresSubClient *c = (PresSubClient *) entry->user_data;
        DEBUG("timout for %s",c->getURI().c_str());
        //c->reportPresence();
    }
    
    /* Callback called when *client* subscription state has changed. */
    void pres_client_evsub_on_state(pjsip_evsub *sub, pjsip_event *event) {
        PJ_UNUSED_ARG(event);
    
        /* Note: #937: no need to acuire PJSUA_LOCK here. Since the pres_client has
         *   a dialog attached to it, lock_pres_client() will use the dialog
         *   lock, which we are currently holding!
         */
    
        PresSubClient *pres_client = (PresSubClient *) pjsip_evsub_get_mod_data(sub, modId);
        if (pres_client) {
            pres_client->incLock();
            DEBUG("pres_client  '%s' is '%s'", pres_client->getURI().c_str(),
                pjsip_evsub_get_state_name(sub) ? pjsip_evsub_get_state_name(sub) : "null");
    
            pjsip_evsub_state state = pjsip_evsub_get_state(sub);
            if(state == PJSIP_EVSUB_STATE_ACCEPTED){
                DEBUG("pres_client accepted.");
                pres_client->accept();
            }
            else if (state == PJSIP_EVSUB_STATE_TERMINATED) {
                int resub_delay = -1;
                pj_strdup_with_null(pres_client->pool, &pres_client->term_reason, pjsip_evsub_get_termination_reason(sub));
                pres_client->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) {
                        pres_client->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 (pres_client->dlg->remote.contact)
                                    resub_delay = 500;
                                break;
                        }
                    } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) {
                        if (pres_client->isTermReason("deactivated") || pres_client->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 (pres_client->isTermReason("probation") || pres_client->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) {
                    resub_delay = PRES_TIMER * 1000;            }
                pres_client->sub = sub;
                pres_client->rescheduleTimer(PJ_TRUE, resub_delay);
            } else { //state==ACTIVE ......
                //This will clear the last termination code/reason
                pres_client->term_code = 0;
                pres_client->term_reason.ptr = NULL;
            }
    
            /* Clear subscription */
            if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
                pjsip_evsub_terminate(pres_client->sub, PJ_FALSE); // = NULL;
                pres_client->status.info_cnt = 0;
                pres_client->dlg = NULL;
                pres_client->rescheduleTimer(PJ_FALSE, 0);
                pjsip_evsub_set_mod_data(sub, modId, NULL);
            }
    
            pres_client->decLock();
        }
    }
    
    /* Callback when transaction state has changed. */
    void pres_client_evsub_on_tsx_state(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) {
    
        PresSubClient *pres_client;
        pjsip_contact_hdr *contact_hdr;
    
        /* Note: #937: no need to acuire PJSUA_LOCK here. Since the pres_client has
         *   a dialog attached to it, lock_pres_client() will use the dialog
         *   lock, which we are currently holding!
         */
        pres_client = (PresSubClient *) pjsip_evsub_get_mod_data(sub, modId);
        if (!pres_client) {
            return;
        }
        pres_client->incLock();
    
        /* We only use this to update pres_client's Contact, when it's not
         * set.
         */
        if (pres_client->contact.slen != 0) {
            /* Contact already set */
            pres_client->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) {
            pres_client->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) {
            pres_client->decLock();
            return;
        }
    
        pres_client->contact.ptr = (char*) pj_pool_alloc(pres_client->pool, PJSIP_MAX_URL_SIZE);
        pres_client->contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, contact_hdr->uri, pres_client->contact.ptr,
                PJSIP_MAX_URL_SIZE);
        if (pres_client->contact.slen < 0)
            pres_client->contact.slen = 0;
    
        pres_client->decLock();
    }
    
    /* Callback called when we receive NOTIFY */
    static void pres_client_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) {
    
        /* Note: #937: no need to acuire PJSUA_LOCK here. Since the pres_client has
         *   a dialog attached to it, lock_pres_client() will use the dialog
         *   lock, which we are currently holding!
         */
        PresSubClient *pres_client = (PresSubClient *) pjsip_evsub_get_mod_data(sub, modId);
        if (!pres_client){
            WARN ("Couldn't extract pres_client from ev_sub.");
            return;
        }
    
        pjsip_pres_get_status(sub, &pres_client->status);
        pres_client->reportPresence();
    
        /* 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);
    
        pres_client->decLock();
    }
    
    PresSubClient::PresSubClient(const std::string& uri_, SIPPresence *pres_) :
            pres(pres_),
            uri(pj_str(strdup(uri_.c_str()))),
            contact(pj_str(strdup(pres->getAccount()->getFromUri().c_str()))),
            display(),
            dlg(NULL),
            monitor(false),
            name(),
            cp_(),
            pool(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, "Pres_sub_client", 512, 512, NULL);
    }
    
    PresSubClient::~PresSubClient() {
        while(lock_count >0) {
            usleep(200);
        }
        DEBUG("Destroying pres_client object with uri %s", uri.ptr);
        rescheduleTimer(PJ_FALSE, 0);
        unsubscribe();
    
        pj_pool_release(pool);
    }
    
    bool PresSubClient::isSubscribed() {
        return this->monitor;
    }
    
    std::string PresSubClient::getURI() {
        std::string res(uri.ptr, uri.slen);
        return res;
    }
    
    bool PresSubClient::isPresent() {
        return status.info[0].basic_open;
    }
    
    std::string PresSubClient::getLineStatus() {
        return std::string(status.info[0].rpid.note.ptr,status.info[0].rpid.note.slen);
    }
    
    bool PresSubClient::isTermReason(std::string reason) {
        std::string myReason(term_reason.ptr, term_reason.slen);
        return !myReason.compare(reason);
    }
    
    void PresSubClient::rescheduleTimer(bool reschedule, unsigned msec) {
        SIPAccount * acc = pres->getAccount();
        if (timer.id) {
            pjsip_endpt_cancel_timer(((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint(), &timer);
            timer.id = PJ_FALSE;
        }
    
        if (reschedule) {
            pj_time_val delay;
    
            WARN("pres_client  %.*s will resubscribe 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, &pres_client_timer_cb);
            delay.sec = 0;
            delay.msec = msec;
            pj_time_val_normalize(&delay);
    
            if (pjsip_endpt_schedule_timer(((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint(), &timer, &delay) == PJ_SUCCESS) {
                timer.id = PJ_TRUE;
            }
        }
    }
    
    void PresSubClient::accept() {
        pres->addPresSubClient(this);
    }
    
    void PresSubClient::reportPresence() {
        /* callback*/
        pres->reportPresSubClientNotification(getURI(),&status);
    }
    
    
    pj_status_t PresSubClient::updateSubscription() {
    
        if (!monitor) {
            /* unsubscribe */
            pjsip_tx_data *tdata;
            pj_status_t retStatus;
    
            if (sub == NULL) {
                WARN("PresSubClient already unsubscribed sub=NULL.");
                return PJ_SUCCESS;
            }
    
            if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
                WARN("pres_client already unsubscribed sub=TERMINATED.");
                //pjsip_evsub_terminate(sub, PJ_FALSE); //
                sub = NULL;
                return PJ_SUCCESS;
            }
    
            WARN("pres_client %s: unsubscribing..", uri.ptr);
            retStatus = pjsip_pres_initiate(sub, 0, &tdata);
            if (retStatus == PJ_SUCCESS) {
                pres->fillDoc(tdata, NULL);
                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;
                WARN("Unable to unsubscribe presence", status);
            }
    
            pjsip_evsub_set_mod_data(sub, modId, NULL);   // Not interested with further events
    
            return PJ_SUCCESS;
        }
    
        if (sub && dlg) { //do not bother if already subscribed
            pjsip_evsub_terminate(sub, PJ_FALSE);
            DEBUG("Terminate existing sub.");
        }
    
        //subscribe
        pjsip_evsub_user pres_callback;
        pjsip_tx_data *tdata;
        pj_status_t status;
    
        /* Event subscription callback. */
        pj_bzero(&pres_callback, sizeof(pres_callback));
        pres_callback.on_evsub_state = &pres_client_evsub_on_state;
        pres_callback.on_tsx_state = &pres_client_evsub_on_tsx_state;
        pres_callback.on_rx_notify = &pres_client_evsub_on_rx_notify;
    
        SIPAccount * acc = pres->getAccount();
        DEBUG("PresSubClient %s: subscribing presence,using %s..",
              uri.ptr, acc->getAccountID().c_str());
    
    
        /* 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 (status != PJ_SUCCESS) {
            ERROR("Unable to create dialog \n");
            return PJ_FALSE;
        }
        /* Add credential for auth. */
        if (acc->hasCredentials() and pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->getCredentialCount(), acc->getCredInfo()) != PJ_SUCCESS) {
          ERROR("Could not initialize credentials for subscribe session authentication");
        }
    
        /* Increment the dialog's lock otherwise when presence session creation
         * fails the dialog will be destroyed prematurely.
         */
        pjsip_dlg_inc_lock(dlg);
    
        status = pjsip_pres_create_uac(dlg, &pres_callback, PJSIP_EVSUB_NO_EVENT_ID, &sub);
        if (status != PJ_SUCCESS) {
            pjsip_evsub_terminate(sub, PJ_FALSE); // = NULL;
            WARN("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);
            }
            return PJ_SUCCESS;
        }
    
        /* Add credential for authentication */
        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");
            return status;
        }
    
        /* Set route-set */
        if (acc->hasServiceRoute())
            pjsip_regc_set_route_set(
                    acc->getRegistrationInfo(),
                    sip_utils::createRouteSet(acc->getServiceRoute(),
                    pres->getPool()));
    
    
        /* FIXME : not sure this is acceptable */
        modId = pres->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;
            WARN("Unable to create initial SUBSCRIBE", status);
            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;
            }
    
            WARN("Unable to send initial SUBSCRIBE", status);
            return PJ_SUCCESS;
        }
    
        pjsip_dlg_dec_lock(dlg);
        return PJ_SUCCESS;
    }
    
    bool PresSubClient::subscribe() {
        monitor = true;
        return ((updateSubscription() == PJ_SUCCESS))? true : false;
    }
    
    bool PresSubClient::unsubscribe() {
        monitor = false;
        return ((updateSubscription() == PJ_SUCCESS))? true : false;
    }
    
    bool PresSubClient::match(PresSubClient *b){
        //return !(strcmp(b->getURI(),getURI()));
        return (b->getURI()==getURI());
    }