sippresence.cpp 14.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/*
 *  Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
 *
 *  Author: Patrick Keroulas  <patrick.keroulas@savoirfairelinux.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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *  Additional permission under GNU GPL version 3 section 7:
 *
 *  If you modify this program, 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, Savoir-Faire Linux Inc.
 *  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.
 */


33
#include "sippresence.h"
34 35 36 37
#include "logger.h"
#include "manager.h"
#include "client/client.h"
#include "client/callmanager.h"
38
#include "client/presencemanager.h"
39
#include "sipaccount.h"
40
#include "sip_utils.h"
41 42
#include "pres_sub_server.h"
#include "pres_sub_client.h"
43 44
#include "sipvoiplink.h"

45 46
#define MAX_N_PRES_SUB_SERVER 20
#define MAX_N_PRES_SUB_CLIENT 20
47 48

SIPPresence::SIPPresence(SIPAccount *acc)
49
    : publish_sess_()
50
    , pres_status_data_()
Tristan Matthews's avatar
Tristan Matthews committed
51
    , enabled_(true)
52
    , acc_(acc)
Tristan Matthews's avatar
Tristan Matthews committed
53 54
    , pres_sub_server_list_()  //IP2IP context
    , pres_sub_client_list_()
55 56 57 58
    , mutex_()
    , mutex_nesting_level_()
    , mutex_owner_()
    , cp_()
59
    , pool_()
60
{
61
    /* init default status */
Tristan Matthews's avatar
Tristan Matthews committed
62
    updateStatus(true, "Available");
63

64 65 66 67
    /* init pool */
    pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0);
    pool_ = pj_pool_create(&cp_.factory, "pres", 1000, 1000, NULL);

68
    /* Create mutex */
Tristan Matthews's avatar
Tristan Matthews committed
69 70
    if (pj_mutex_create_recursive(pool_, "pres", &mutex_) != PJ_SUCCESS)
        ERROR("Unable to create mutex");
71 72 73
}


Tristan Matthews's avatar
Tristan Matthews committed
74 75
SIPPresence::~SIPPresence()
{
76
    /* Flush the lists */
77
    for (const auto & c : pres_sub_client_list_)
78
        removePresSubClient(c) ;
Tristan Matthews's avatar
Tristan Matthews committed
79

80
    for (const auto & s : pres_sub_server_list_)
81
        removePresSubServer(s);
82
}
83

Tristan Matthews's avatar
Tristan Matthews committed
84 85
SIPAccount * SIPPresence::getAccount() const
{
86 87 88
    return acc_;
}

Tristan Matthews's avatar
Tristan Matthews committed
89 90
pjsip_pres_status * SIPPresence::getStatus()
{
91
    return &pres_status_data_;
92 93
}

Tristan Matthews's avatar
Tristan Matthews committed
94 95 96
int SIPPresence::getModId() const
{
    return ((SIPVoIPLink*)(acc_->getVoIPLink()))->getModId();
97 98
}

Tristan Matthews's avatar
Tristan Matthews committed
99 100
pj_pool_t*  SIPPresence::getPool() const
{
101 102 103
    return pool_;
}

104
void SIPPresence::enable(bool flag)
Tristan Matthews's avatar
Tristan Matthews committed
105 106
{
    enabled_ = flag;
107 108
}

109
void SIPPresence::updateStatus(bool status, const std::string &note)
Tristan Matthews's avatar
Tristan Matthews committed
110
{
111 112
    //char* pj_note  = (char*) pj_pool_alloc(pool_, "50");

113
    pjrpid_element rpid = {
Tristan Matthews's avatar
Tristan Matthews committed
114 115 116 117 118
        PJRPID_ELEMENT_TYPE_PERSON,
        pj_str("20"),
        PJRPID_ACTIVITY_UNKNOWN,
        pj_str((char *) note.c_str())
    };
119 120

    /* fill activity if user not available. */
Tristan Matthews's avatar
Tristan Matthews committed
121
    if (note == "away")
122
        rpid.activity = PJRPID_ACTIVITY_AWAY;
Tristan Matthews's avatar
Tristan Matthews committed
123
    else if (note == "busy")
124
        rpid.activity = PJRPID_ACTIVITY_BUSY;
125
    else // TODO: is there any other possibilities
126 127
        DEBUG("Presence : no activity");

128 129 130 131 132
    pj_bzero(&pres_status_data_, sizeof(pres_status_data_));
    pres_status_data_.info_cnt = 1;
    pres_status_data_.info[0].basic_open = status;
    pres_status_data_.info[0].id = pj_str("0"); /* todo: tuplie_id*/
    pj_memcpy(&pres_status_data_.info[0].rpid, &rpid, sizeof(pjrpid_element));
133 134 135
    /* "contact" field is optionnal */
}

136
void SIPPresence::sendPresence(bool status, const std::string &note)
Tristan Matthews's avatar
Tristan Matthews committed
137 138 139 140 141 142 143 144 145 146
{
    updateStatus(status, note);

    if (not enabled_)
        return;

    if (acc_->isIP2IP())
        notifyPresSubServer(); // to each subscribers
    else
        pres_publish(this); // to the PBX server
147 148 149
}


Tristan Matthews's avatar
Tristan Matthews committed
150 151
void SIPPresence::reportPresSubClientNotification(const std::string& uri, pjsip_pres_status * status)
{
152 153
    /* Update our info. See pjsua_buddy_get_info() for additionnal ideas*/
    const std::string basic(status->info[0].basic_open ? "open" : "closed");
Tristan Matthews's avatar
Tristan Matthews committed
154 155
    const std::string note(status->info[0].rpid.note.ptr, status->info[0].rpid.note.slen);
    DEBUG(" Received status of PresSubClient  %s: status=%s note=%s", uri.c_str(), (status->info[0].basic_open ? "open" : "closed"), note.c_str());
156
    /* report status to client signal */
157
    Manager::instance().getClient()->getPresenceManager()->newBuddySubscription(uri, status->info[0].basic_open, note);
158 159
}

160
void SIPPresence::subscribeClient(const std::string& uri, bool flag)
Tristan Matthews's avatar
Tristan Matthews committed
161
{
162
    /* Check if the buddy was already subscribed */
163
    for (const auto & c : pres_sub_client_list_) {
Tristan Matthews's avatar
Tristan Matthews committed
164 165
        if (c->getURI() == uri) {
            DEBUG("-PresSubClient:%s exists in the list. Replace it.", uri.c_str());
166 167
            delete c;
            removePresSubClient(c);
168 169
            break;
        }
170
    }
171

Tristan Matthews's avatar
Tristan Matthews committed
172
    if (pres_sub_client_list_.size() >= MAX_N_PRES_SUB_CLIENT) {
173 174 175 176
        WARN("Can't add PresSubClient, max number reached.");
        return;
    }

Tristan Matthews's avatar
Tristan Matthews committed
177 178 179 180
    if (flag) {
        PresSubClient *c = new PresSubClient(uri, this);

        if (!(c->subscribe())) {
181
            WARN("Failed send subscribe.");
182
            delete c;
183
        }
Tristan Matthews's avatar
Tristan Matthews committed
184

185 186
        // the buddy has to be accepted before being added in the list
    }
187 188
}

Tristan Matthews's avatar
Tristan Matthews committed
189 190 191
void SIPPresence::addPresSubClient(PresSubClient *c)
{
    if (pres_sub_client_list_.size() < MAX_N_PRES_SUB_CLIENT) {
192
        pres_sub_client_list_.push_back(c);
Tristan Matthews's avatar
Tristan Matthews committed
193 194
        DEBUG("New Presence_subscription_client client added in the list[l=%i].", pres_sub_client_list_.size());
    } else {
195
        WARN("Max Presence_subscription_client is reach.");
196
        // let the client alive //delete c;
197
    }
198
}
199

Tristan Matthews's avatar
Tristan Matthews committed
200 201
void SIPPresence::removePresSubClient(PresSubClient *c)
{
202
    DEBUG("Presence_subscription_client removed from the buddy list.");
203
    pres_sub_client_list_.remove(c);
204 205
}

206

Tristan Matthews's avatar
Tristan Matthews committed
207 208
void SIPPresence::reportnewServerSubscriptionRequest(PresSubServer *s)
{
209
    Manager::instance().getClient()->getPresenceManager()->newServerSubscriptionRequest(s->remote);
210 211
}

212
void SIPPresence::approvePresSubServer(const std::string& uri, bool flag)
Tristan Matthews's avatar
Tristan Matthews committed
213
{
214
    for (const auto & s : pres_sub_server_list_) {
Tristan Matthews's avatar
Tristan Matthews committed
215 216 217 218 219
        if (s->matches((char *) uri.c_str())) {
            DEBUG("Approve Presence_subscription_server for %s: %s.", s->remote, flag ? "true" : "false");
            s->approve(flag);
            // return; // 'return' would prevent multiple-time subscribers from spam
        }
220
    }
221 222 223
}


Tristan Matthews's avatar
Tristan Matthews committed
224 225 226 227
void SIPPresence::addPresSubServer(PresSubServer *s)
{
    if (pres_sub_server_list_.size() < MAX_N_PRES_SUB_SERVER) {
        DEBUG("Presence_subscription_server added: %s.", s->remote);
228
        pres_sub_server_list_.push_back(s);
Tristan Matthews's avatar
Tristan Matthews committed
229
    } else {
230
        WARN("Max Presence_subscription_server is reach.");
231
        // let de server alive // delete s;
232
    }
233 234
}

Tristan Matthews's avatar
Tristan Matthews committed
235 236
void SIPPresence::removePresSubServer(PresSubServer *s)
{
237
    pres_sub_server_list_.remove(s);
238
    DEBUG("Presence_subscription_server removed");
239 240
}

Tristan Matthews's avatar
Tristan Matthews committed
241 242
void SIPPresence::notifyPresSubServer()
{
243
    DEBUG("Iterating through Presence_subscription_server:");
Tristan Matthews's avatar
Tristan Matthews committed
244

245
    for (const auto & s : pres_sub_server_list_)
246
        s->notify();
247
}
248 249 250 251 252 253 254 255 256 257 258

void SIPPresence::lock()
{
    pj_mutex_lock(mutex_);
    mutex_owner_ = pj_thread_this();
    ++mutex_nesting_level_;
}

void SIPPresence::unlock()
{
    if (--mutex_nesting_level_ == 0)
Tristan Matthews's avatar
Tristan Matthews committed
259 260
        mutex_owner_ = NULL;

261 262 263
    pj_mutex_unlock(mutex_);
}

264 265 266 267 268 269 270 271 272 273 274
void SIPPresence::fillDoc(pjsip_tx_data *tdata, const pres_msg_data *msg_data)
{

    if (tdata->msg->type == PJSIP_REQUEST_MSG) {
        const pj_str_t STR_USER_AGENT = pj_str("User-Agent");
        std::string useragent(acc_->getUserAgentName());
        pj_str_t pJuseragent = pj_str((char*) useragent.c_str());
        pjsip_hdr *h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_USER_AGENT, &pJuseragent);
        pjsip_msg_add_hdr(tdata->msg, h);
    }

Tristan Matthews's avatar
Tristan Matthews committed
275
    if (msg_data == NULL)
276 277 278 279
        return;

    const pjsip_hdr *hdr;
    hdr = msg_data->hdr_list.next;
Tristan Matthews's avatar
Tristan Matthews committed
280

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
    while (hdr && hdr != &msg_data->hdr_list) {
        pjsip_hdr *new_hdr;
        new_hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr);
        DEBUG("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_msg_body *body;
        pj_str_t type = pj_str("application");
        pj_str_t subtype = pj_str("pidf+xml");
        body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &msg_data->msg_body);
        tdata->msg->body = body;
    }
}
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483

static const pjsip_publishc_opt my_publish_opt = {true}; // this is queue_request

/*
 * Client presence publication callback.
 */
void
SIPPresence::pres_publish_cb(struct pjsip_publishc_cbparam *param)
{
    SIPPresence *pres = (SIPPresence*) param->token;

    if (param->code / 100 != 2 || param->status != PJ_SUCCESS) {

        pjsip_publishc_destroy(param->pubc);
        pres->publish_sess_ = NULL;

        if (param->status != PJ_SUCCESS) {
            char errmsg[PJ_ERR_MSG_SIZE];

            pj_strerror(param->status, errmsg, sizeof(errmsg));
            ERROR("Client publication (PUBLISH) failed, status=%d, msg=%s", param->status, errmsg);
        } else if (param->code == 412) {
            /* 412 (Conditional Request Failed)
             * The PUBLISH refresh has failed, retry with new one.
             */
            WARN("Publish retry.");
            pres_publish(pres);
        } else {
            ERROR("Client publication (PUBLISH) failed (%u/%.*s)",
                  param->code, param->reason.slen, param->reason.ptr);
        }

    } else {
        if (param->expiration < 1) {
            /* Could happen if server "forgot" to include Expires header
             * in the response. We will not renew, so destroy the pubc.
             */
            pjsip_publishc_destroy(param->pubc);
            pres->publish_sess_ = NULL;
        }
    }
}

/*
 * Send PUBLISH request.
 */
pj_status_t
SIPPresence::pres_send_publish(SIPPresence * pres, pj_bool_t active)
{
    pjsip_tx_data *tdata;
    pj_status_t status;

    DEBUG("Send presence %sPUBLISH..", (active ? "" : "un-"));

    SIPAccount * acc = pres->getAccount();
    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 contact = pj_str(strdup(contactWithoutAngles.c_str()));
//    pj_memcpy(&pres_status_data.info[0].contact, &contt, sizeof(pj_str_t));;

    /* Create PUBLISH request */
    if (active) {
        char *bpos;
        pj_str_t entity;

        status = pjsip_publishc_publish(pres->publish_sess_, PJ_TRUE, &tdata);

        if (status != PJ_SUCCESS) {
            ERROR("Error creating PUBLISH request", status);
            goto on_error;
        }

        pj_str_t from = pj_str(strdup(acc->getFromUri().c_str()));

        if ((bpos = pj_strchr(&from, '<')) != NULL) {
            char *epos = pj_strchr(&from, '>');

            if (epos - bpos < 2) {
                pj_assert(!"Unexpected invalid URI");
                status = PJSIP_EINVALIDURI;
                goto on_error;
            }

            entity.ptr = bpos + 1;
            entity.slen = epos - bpos - 1;
        } else {
            entity = from;
        }

        /* Create and add PIDF message body */
        status = pjsip_pres_create_pidf(tdata->pool, pres->getStatus(),
                                        &entity, &tdata->msg->body);

        if (status != PJ_SUCCESS) {
            ERROR("Error creating PIDF for PUBLISH request");
            pjsip_tx_data_dec_ref(tdata);
            goto on_error;
        }

    } else {
        WARN("Unpublish is not implemented.");
    }


    pres_msg_data 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);

    pres->fillDoc(tdata, &msg_data);

    /* Send the PUBLISH request */
    status = pjsip_publishc_send(pres->publish_sess_, tdata);

    if (status == PJ_EPENDING) {
        WARN("Previous request is in progress, ");
    } else if (status != PJ_SUCCESS) {
        ERROR("Error sending PUBLISH request");
        goto on_error;
    }

    return PJ_SUCCESS;

on_error:

    if (pres->publish_sess_) {
        pjsip_publishc_destroy(pres->publish_sess_);
        pres->publish_sess_ = NULL;
    }

    return status;
}


/* Create client publish session */
pj_status_t
SIPPresence::pres_publish(SIPPresence *pres)
{
    pj_status_t status;
    const pj_str_t STR_PRESENCE = pj_str("presence");
    SIPAccount * acc = pres->getAccount();
    pjsip_endpoint *endpt = ((SIPVoIPLink*) acc->getVoIPLink())->getEndpoint();

    /* Create and init client publication session */

    /* Create client publication */
    status = pjsip_publishc_create(endpt, &my_publish_opt,
                                   pres, &pres_publish_cb,
                                   &pres->publish_sess_);

    if (status != PJ_SUCCESS) {
        pres->publish_sess_ = NULL;
        ERROR("Failed to create a publish seesion.");
        return status;
    }

    /* Initialize client publication */
    pj_str_t from = pj_str(strdup(acc->getFromUri().c_str()));
    status = pjsip_publishc_init(pres->publish_sess_, &STR_PRESENCE, &from, &from, &from, 0xFFFF);

    if (status != PJ_SUCCESS) {
        ERROR("Failed to init a publish session");
        pres->publish_sess_ = NULL;
        return status;
    }

    /* Add credential for authentication */
    if (acc->hasCredentials() and pjsip_publishc_set_credentials(pres->publish_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()));

    /* Send initial PUBLISH request */
    status = pres_send_publish(pres, PJ_TRUE);

    if (status != PJ_SUCCESS)
        return status;

    return PJ_SUCCESS;
}