1
/* $Id: pjsua_pres.c 4186 2012-06-29 01:37:50Z ming $ */
3
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
#include <pjsua-lib/pjsua.h>
21
#include <pjsua-lib/pjsua_internal.h>
24
#define THIS_FILE "pjsua_pres.c"
27
static void subscribe_buddy_presence(pjsua_buddy_id buddy_id);
28
static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id);
34
static pjsua_buddy_id find_buddy(const pjsip_uri *uri)
36
const pjsip_sip_uri *sip_uri;
39
uri = (const pjsip_uri*) pjsip_uri_get_uri((pjsip_uri*)uri);
41
if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))
42
return PJSUA_INVALID_ID;
44
sip_uri = (const pjsip_sip_uri*) uri;
46
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
47
const pjsua_buddy *b = &pjsua_var.buddy[i];
49
if (!pjsua_buddy_is_valid(i))
52
if (pj_stricmp(&sip_uri->user, &b->name)==0 &&
53
pj_stricmp(&sip_uri->host, &b->host)==0 &&
54
(sip_uri->port==(int)b->port || (sip_uri->port==0 && b->port==5060)))
61
return PJSUA_INVALID_ID;
66
#define LOCK_ALL (LOCK_DIALOG | LOCK_PJSUA)
68
/* Buddy lock object */
76
/* Acquire lock to the specified buddy_id */
77
pj_status_t lock_buddy(const char *title,
78
pjsua_buddy_id buddy_id,
79
struct buddy_lock *lck,
82
enum { MAX_RETRY=50 };
83
pj_bool_t has_pjsua_lock = PJ_FALSE;
86
PJ_UNUSED_ARG(_unused_);
88
pj_bzero(lck, sizeof(*lck));
90
for (retry=0; retry<MAX_RETRY; ++retry) {
92
if (PJSUA_TRY_LOCK() != PJ_SUCCESS) {
93
pj_thread_sleep(retry/10);
97
has_pjsua_lock = PJ_TRUE;
98
lck->flag = LOCK_PJSUA;
99
lck->buddy = &pjsua_var.buddy[buddy_id];
101
if (lck->buddy->dlg == NULL)
104
if (pjsip_dlg_try_inc_lock(lck->buddy->dlg) != PJ_SUCCESS) {
107
has_pjsua_lock = PJ_FALSE;
109
pj_thread_sleep(retry/10);
113
lck->dlg = lck->buddy->dlg;
114
lck->flag = LOCK_DIALOG;
120
if (lck->flag == 0) {
121
if (has_pjsua_lock == PJ_FALSE)
122
PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire PJSUA mutex "
123
"(possibly system has deadlocked) in %s",
126
PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire dialog mutex "
127
"(possibly system has deadlocked) in %s",
135
/* Release buddy lock */
136
static void unlock_buddy(struct buddy_lock *lck)
138
if (lck->flag & LOCK_DIALOG)
139
pjsip_dlg_dec_lock(lck->dlg);
141
if (lck->flag & LOCK_PJSUA)
147
* Get total number of buddies.
149
PJ_DEF(unsigned) pjsua_get_buddy_count(void)
151
return pjsua_var.buddy_cnt;
158
PJ_DEF(pjsua_buddy_id) pjsua_buddy_find(const pj_str_t *uri_str)
163
pjsua_buddy_id buddy_id;
165
pool = pjsua_pool_create("buddyfind", 512, 512);
166
pj_strdup_with_null(pool, &input, uri_str);
168
uri = pjsip_parse_uri(pool, input.ptr, input.slen, 0);
170
buddy_id = PJSUA_INVALID_ID;
173
buddy_id = find_buddy(uri);
177
pj_pool_release(pool);
184
* Check if buddy ID is valid.
186
PJ_DEF(pj_bool_t) pjsua_buddy_is_valid(pjsua_buddy_id buddy_id)
188
return buddy_id>=0 && buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy) &&
189
pjsua_var.buddy[buddy_id].uri.slen != 0;
196
PJ_DEF(pj_status_t) pjsua_enum_buddies( pjsua_buddy_id ids[],
201
PJ_ASSERT_RETURN(ids && count, PJ_EINVAL);
205
for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
206
if (!pjsua_var.buddy[i].uri.slen)
221
* Get detailed buddy info.
223
PJ_DEF(pj_status_t) pjsua_buddy_get_info( pjsua_buddy_id buddy_id,
224
pjsua_buddy_info *info)
227
struct buddy_lock lck;
231
PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
233
pj_bzero(info, sizeof(pjsua_buddy_info));
235
status = lock_buddy("pjsua_buddy_get_info()", buddy_id, &lck, 0);
236
if (status != PJ_SUCCESS)
240
info->id = buddy->index;
241
if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
247
info->uri.ptr = info->buf_ + total;
248
pj_strncpy(&info->uri, &buddy->uri, sizeof(info->buf_)-total);
249
total += info->uri.slen;
252
info->contact.ptr = info->buf_ + total;
253
pj_strncpy(&info->contact, &buddy->contact, sizeof(info->buf_)-total);
254
total += info->contact.slen;
256
/* Presence status */
257
pj_memcpy(&info->pres_status, &buddy->status, sizeof(pjsip_pres_status));
259
/* status and status text */
260
if (buddy->sub == NULL || buddy->status.info_cnt==0) {
261
info->status = PJSUA_BUDDY_STATUS_UNKNOWN;
262
info->status_text = pj_str("?");
263
} else if (pjsua_var.buddy[buddy_id].status.info[0].basic_open) {
264
info->status = PJSUA_BUDDY_STATUS_ONLINE;
266
/* copy RPID information */
267
info->rpid = buddy->status.info[0].rpid;
269
if (info->rpid.note.slen)
270
info->status_text = info->rpid.note;
272
info->status_text = pj_str("Online");
275
info->status = PJSUA_BUDDY_STATUS_OFFLINE;
276
info->rpid = buddy->status.info[0].rpid;
278
if (info->rpid.note.slen)
279
info->status_text = info->rpid.note;
281
info->status_text = pj_str("Offline");
285
info->monitor_pres = buddy->monitor;
287
/* subscription state and termination reason */
288
info->sub_term_code = buddy->term_code;
290
info->sub_state = pjsip_evsub_get_state(buddy->sub);
291
info->sub_state_name = pjsip_evsub_get_state_name(buddy->sub);
292
if (info->sub_state == PJSIP_EVSUB_STATE_TERMINATED &&
293
total < sizeof(info->buf_))
295
info->sub_term_reason.ptr = info->buf_ + total;
296
pj_strncpy(&info->sub_term_reason,
297
pjsip_evsub_get_termination_reason(buddy->sub),
298
sizeof(info->buf_) - total);
299
total += info->sub_term_reason.slen;
301
info->sub_term_reason = pj_str("");
303
} else if (total < sizeof(info->buf_)) {
304
info->sub_state_name = "NULL";
305
info->sub_term_reason.ptr = info->buf_ + total;
306
pj_strncpy(&info->sub_term_reason, &buddy->term_reason,
307
sizeof(info->buf_) - total);
308
total += info->sub_term_reason.slen;
310
info->sub_state_name = "NULL";
311
info->sub_term_reason = pj_str("");
319
* Set the user data associated with the buddy object.
321
PJ_DEF(pj_status_t) pjsua_buddy_set_user_data( pjsua_buddy_id buddy_id,
324
struct buddy_lock lck;
327
PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
329
status = lock_buddy("pjsua_buddy_set_user_data()", buddy_id, &lck, 0);
330
if (status != PJ_SUCCESS)
333
pjsua_var.buddy[buddy_id].user_data = user_data;
342
* Get the user data associated with the budy object.
344
PJ_DEF(void*) pjsua_buddy_get_user_data(pjsua_buddy_id buddy_id)
346
struct buddy_lock lck;
350
PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), NULL);
352
status = lock_buddy("pjsua_buddy_get_user_data()", buddy_id, &lck, 0);
353
if (status != PJ_SUCCESS)
356
user_data = pjsua_var.buddy[buddy_id].user_data;
365
* Reset buddy descriptor.
367
static void reset_buddy(pjsua_buddy_id id)
369
pj_pool_t *pool = pjsua_var.buddy[id].pool;
370
pj_bzero(&pjsua_var.buddy[id], sizeof(pjsua_var.buddy[id]));
371
pjsua_var.buddy[id].pool = pool;
372
pjsua_var.buddy[id].index = id;
379
PJ_DEF(pj_status_t) pjsua_buddy_add( const pjsua_buddy_config *cfg,
380
pjsua_buddy_id *p_buddy_id)
382
pjsip_name_addr *url;
384
pjsip_sip_uri *sip_uri;
388
PJ_ASSERT_RETURN(pjsua_var.buddy_cnt <=
389
PJ_ARRAY_SIZE(pjsua_var.buddy),
392
PJ_LOG(4,(THIS_FILE, "Adding buddy: %.*s",
393
(int)cfg->uri.slen, cfg->uri.ptr));
394
pj_log_push_indent();
398
/* Find empty slot */
399
for (index=0; index<(int)PJ_ARRAY_SIZE(pjsua_var.buddy); ++index) {
400
if (pjsua_var.buddy[index].uri.slen == 0)
404
/* Expect to find an empty slot */
405
if (index == PJ_ARRAY_SIZE(pjsua_var.buddy)) {
407
/* This shouldn't happen */
408
pj_assert(!"index < PJ_ARRAY_SIZE(pjsua_var.buddy)");
413
buddy = &pjsua_var.buddy[index];
415
/* Create pool for this buddy */
417
pj_pool_reset(buddy->pool);
419
char name[PJ_MAX_OBJ_NAME];
420
pj_ansi_snprintf(name, sizeof(name), "buddy%03d", index);
421
buddy->pool = pjsua_pool_create(name, 512, 256);
424
/* Init buffers for presence subscription status */
425
buddy->term_reason.ptr = (char*)
426
pj_pool_alloc(buddy->pool,
427
PJSUA_BUDDY_SUB_TERM_REASON_LEN);
429
/* Get name and display name for buddy */
430
pj_strdup_with_null(buddy->pool, &tmp, &cfg->uri);
431
url = (pjsip_name_addr*)pjsip_parse_uri(buddy->pool, tmp.ptr, tmp.slen,
432
PJSIP_PARSE_URI_AS_NAMEADDR);
435
pjsua_perror(THIS_FILE, "Unable to add buddy", PJSIP_EINVALIDURI);
436
pj_pool_release(buddy->pool);
440
return PJSIP_EINVALIDURI;
443
/* Only support SIP schemes */
444
if (!PJSIP_URI_SCHEME_IS_SIP(url) && !PJSIP_URI_SCHEME_IS_SIPS(url)) {
445
pj_pool_release(buddy->pool);
449
return PJSIP_EINVALIDSCHEME;
452
/* Reset buddy, to make sure everything is cleared with default
458
pjsua_var.buddy[index].uri = tmp;
460
sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(url->uri);
461
pjsua_var.buddy[index].name = sip_uri->user;
462
pjsua_var.buddy[index].display = url->display;
463
pjsua_var.buddy[index].host = sip_uri->host;
464
pjsua_var.buddy[index].port = sip_uri->port;
465
pjsua_var.buddy[index].monitor = cfg->subscribe;
466
if (pjsua_var.buddy[index].port == 0)
467
pjsua_var.buddy[index].port = 5060;
470
pjsua_var.buddy[index].user_data = (void*)cfg->user_data;
475
pjsua_var.buddy_cnt++;
479
PJ_LOG(4,(THIS_FILE, "Buddy %d added.", index));
481
pjsua_buddy_subscribe_pres(index, cfg->subscribe);
491
PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id)
493
struct buddy_lock lck;
496
PJ_ASSERT_RETURN(buddy_id>=0 &&
497
buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
500
if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
504
status = lock_buddy("pjsua_buddy_del()", buddy_id, &lck, 0);
505
if (status != PJ_SUCCESS)
508
PJ_LOG(4,(THIS_FILE, "Buddy %d: deleting..", buddy_id));
509
pj_log_push_indent();
511
/* Unsubscribe presence */
512
pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE);
514
/* Not interested with further events for this buddy */
515
if (pjsua_var.buddy[buddy_id].sub) {
516
pjsip_evsub_set_mod_data(pjsua_var.buddy[buddy_id].sub,
517
pjsua_var.mod.id, NULL);
521
pjsua_var.buddy[buddy_id].uri.slen = 0;
522
pjsua_var.buddy_cnt--;
525
if (pjsua_var.buddy[buddy_id].timer.id) {
526
pjsua_cancel_timer(&pjsua_var.buddy[buddy_id].timer);
527
pjsua_var.buddy[buddy_id].timer.id = PJ_FALSE;
530
/* Reset buddy struct */
531
reset_buddy(buddy_id);
540
* Enable/disable buddy's presence monitoring.
542
PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id buddy_id,
545
struct buddy_lock lck;
548
PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
550
status = lock_buddy("pjsua_buddy_subscribe_pres()", buddy_id, &lck, 0);
551
if (status != PJ_SUCCESS)
554
PJ_LOG(4,(THIS_FILE, "Buddy %d: unsubscribing presence..", buddy_id));
555
pj_log_push_indent();
557
lck.buddy->monitor = subscribe;
559
pjsua_buddy_update_pres(buddy_id);
568
* Update buddy's presence.
570
PJ_DEF(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id)
572
struct buddy_lock lck;
575
PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
577
status = lock_buddy("pjsua_buddy_update_pres()", buddy_id, &lck, 0);
578
if (status != PJ_SUCCESS)
581
PJ_LOG(4,(THIS_FILE, "Buddy %d: updating presence..", buddy_id));
582
pj_log_push_indent();
584
/* Is this an unsubscribe request? */
585
if (!lck.buddy->monitor) {
586
unsubscribe_buddy_presence(buddy_id);
592
/* Ignore if presence is already active for the buddy */
593
if (lck.buddy->sub) {
599
/* Initiate presence subscription */
600
subscribe_buddy_presence(buddy_id);
609
* Dump presence subscriptions to log file.
611
PJ_DEF(void) pjsua_pres_dump(pj_bool_t verbose)
620
* When no detail is required, just dump number of server and client
623
if (verbose == PJ_FALSE) {
627
for (acc_id=0; acc_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
629
if (!pjsua_var.acc[acc_id].valid)
632
if (!pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) {
633
struct pjsua_srv_pres *uapres;
635
uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
636
while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
638
uapres = uapres->next;
643
PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d",
648
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
649
if (pjsua_var.buddy[i].uri.slen == 0)
651
if (pjsua_var.buddy[i].sub) {
656
PJ_LOG(3,(THIS_FILE, "Number of client/UAC subscriptions: %d",
664
* Dumping all server (UAS) subscriptions
666
PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
668
for (acc_id=0; acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
670
if (!pjsua_var.acc[acc_id].valid)
673
PJ_LOG(3,(THIS_FILE, " %.*s",
674
(int)pjsua_var.acc[acc_id].cfg.id.slen,
675
pjsua_var.acc[acc_id].cfg.id.ptr));
677
if (pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) {
679
PJ_LOG(3,(THIS_FILE, " - none - "));
682
struct pjsua_srv_pres *uapres;
684
uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
685
while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
687
PJ_LOG(3,(THIS_FILE, " %10s %s",
688
pjsip_evsub_get_state_name(uapres->sub),
691
uapres = uapres->next;
697
* Dumping all client (UAC) subscriptions
699
PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:"));
701
if (pjsua_var.buddy_cnt == 0) {
703
PJ_LOG(3,(THIS_FILE, " - no buddy list - "));
706
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
708
if (pjsua_var.buddy[i].uri.slen == 0)
711
if (pjsua_var.buddy[i].sub) {
712
PJ_LOG(3,(THIS_FILE, " %10s %.*s",
713
pjsip_evsub_get_state_name(pjsua_var.buddy[i].sub),
714
(int)pjsua_var.buddy[i].uri.slen,
715
pjsua_var.buddy[i].uri.ptr));
717
PJ_LOG(3,(THIS_FILE, " %10s %.*s",
719
(int)pjsua_var.buddy[i].uri.slen,
720
pjsua_var.buddy[i].uri.ptr));
729
/***************************************************************************
730
* Server subscription.
734
static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata);
736
/* The module instance. */
737
static pjsip_module mod_pjsua_pres =
739
NULL, NULL, /* prev, next. */
740
{ "mod-pjsua-pres", 14 }, /* Name. */
742
PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
747
&pres_on_rx_request, /* on_rx_request() */
748
NULL, /* on_rx_response() */
749
NULL, /* on_tx_request. */
750
NULL, /* on_tx_response() */
751
NULL, /* on_tsx_state() */
756
/* Callback called when *server* subscription state has changed. */
757
static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event)
759
pjsua_srv_pres *uapres;
761
PJ_UNUSED_ARG(event);
765
uapres = (pjsua_srv_pres*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
767
pjsip_evsub_state state;
769
PJ_LOG(4,(THIS_FILE, "Server subscription to %s is %s",
770
uapres->remote, pjsip_evsub_get_state_name(sub)));
771
pj_log_push_indent();
773
state = pjsip_evsub_get_state(sub);
775
if (pjsua_var.ua_cfg.cb.on_srv_subscribe_state) {
778
from = uapres->dlg->remote.info_str;
779
(*pjsua_var.ua_cfg.cb.on_srv_subscribe_state)(uapres->acc_id,
784
if (state == PJSIP_EVSUB_STATE_TERMINATED) {
785
pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
786
pj_list_erase(uapres);
794
/* This is called when request is received.
795
* We need to check for incoming SUBSCRIBE request.
797
static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
802
pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
803
pjsua_srv_pres *uapres;
805
pjsip_evsub_user pres_cb;
807
pjsip_status_code st_code;
809
pjsip_expires_hdr *expires_hdr;
810
pjsua_msg_data msg_data;
813
if (pjsip_method_cmp(req_method, pjsip_get_subscribe_method()) != 0)
816
/* Incoming SUBSCRIBE: */
818
/* Don't want to accept the request if shutdown is in progress */
819
if (pjsua_var.thread_quit_flag) {
820
pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
821
PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL,
828
/* Find which account for the incoming request. */
829
acc_id = pjsua_acc_find_for_incoming(rdata);
830
acc = &pjsua_var.acc[acc_id];
832
PJ_LOG(4,(THIS_FILE, "Creating server subscription, using account %d",
834
pj_log_push_indent();
836
/* Create suitable Contact header */
837
if (acc->contact.slen) {
838
contact = acc->contact;
840
status = pjsua_acc_create_uas_contact(rdata->tp_info.pool, &contact,
842
if (status != PJ_SUCCESS) {
843
pjsua_perror(THIS_FILE, "Unable to generate Contact header",
846
pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL,
853
/* Create UAS dialog: */
854
status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata,
856
if (status != PJ_SUCCESS) {
857
pjsua_perror(THIS_FILE,
858
"Unable to create UAS dialog for subscription",
861
pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL,
867
if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0)
868
pjsip_dlg_set_via_sent_by(dlg, &acc->via_addr, acc->via_tp);
870
/* Set credentials and preference. */
871
pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->cred_cnt, acc->cred);
872
pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref);
875
pj_bzero(&pres_cb, sizeof(pres_cb));
876
pres_cb.on_evsub_state = &pres_evsub_on_srv_state;
878
/* Create server presence subscription: */
879
status = pjsip_pres_create_uas( dlg, &pres_cb, rdata, &sub);
880
if (status != PJ_SUCCESS) {
881
int code = PJSIP_ERRNO_TO_SIP_STATUS(status);
882
pjsip_tx_data *tdata;
884
pjsua_perror(THIS_FILE, "Unable to create server subscription",
887
if (code==599 || code > 699 || code < 300) {
891
status = pjsip_dlg_create_response(dlg, rdata, code, NULL, &tdata);
892
if (status == PJ_SUCCESS) {
893
status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata),
902
/* If account is locked to specific transport, then lock dialog
903
* to this transport too.
905
if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
906
pjsip_tpselector tp_sel;
908
pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
909
pjsip_dlg_set_transport(dlg, &tp_sel);
912
/* Attach our data to the subscription: */
913
uapres = PJ_POOL_ALLOC_T(dlg->pool, pjsua_srv_pres);
915
uapres->remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE);
916
uapres->acc_id = acc_id;
918
status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri,
919
uapres->remote, PJSIP_MAX_URL_SIZE);
921
pj_ansi_strcpy(uapres->remote, "<-- url is too long-->");
923
uapres->remote[status] = '\0';
925
pjsip_evsub_add_header(sub, &acc->cfg.sub_hdr_list);
926
pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, uapres);
928
/* Add server subscription to the list: */
929
pj_list_push_back(&pjsua_var.acc[acc_id].pres_srv_list, uapres);
932
/* Capture the value of Expires header. */
933
expires_hdr = (pjsip_expires_hdr*)
934
pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES,
937
uapres->expires = expires_hdr->ivalue;
939
uapres->expires = -1;
941
st_code = (pjsip_status_code)200;
942
reason = pj_str("OK");
943
pjsua_msg_data_init(&msg_data);
945
/* Notify application callback, if any */
946
if (pjsua_var.ua_cfg.cb.on_incoming_subscribe) {
947
pjsua_buddy_id buddy_id;
949
buddy_id = find_buddy(rdata->msg_info.from->uri);
951
(*pjsua_var.ua_cfg.cb.on_incoming_subscribe)(acc_id, uapres, buddy_id,
952
&dlg->remote.info_str,
953
rdata, &st_code, &reason,
957
/* Handle rejection case */
958
if (st_code >= 300) {
959
pjsip_tx_data *tdata;
961
/* Create response */
962
status = pjsip_dlg_create_response(dlg, rdata, st_code,
964
if (status != PJ_SUCCESS) {
965
pjsua_perror(THIS_FILE, "Error creating response", status);
966
pj_list_erase(uapres);
967
pjsip_pres_terminate(sub, PJ_FALSE);
973
/* Add header list, if any */
974
pjsua_process_msg_data(tdata, &msg_data);
976
/* Send the response */
977
status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata),
979
if (status != PJ_SUCCESS) {
980
pjsua_perror(THIS_FILE, "Error sending response", status);
981
/* This is not fatal */
984
/* Terminate presence subscription */
985
pj_list_erase(uapres);
986
pjsip_pres_terminate(sub, PJ_FALSE);
992
/* Create and send 2xx response to the SUBSCRIBE request: */
993
status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list);
994
if (status != PJ_SUCCESS) {
995
pjsua_perror(THIS_FILE, "Unable to accept presence subscription",
997
pj_list_erase(uapres);
998
pjsip_pres_terminate(sub, PJ_FALSE);
1000
pj_log_pop_indent();
1004
/* If code is 200, send NOTIFY now */
1005
if (st_code == 200) {
1006
pjsua_pres_notify(acc_id, uapres, PJSIP_EVSUB_STATE_ACTIVE,
1007
NULL, NULL, PJ_TRUE, &msg_data);
1013
pj_log_pop_indent();
1021
PJ_DEF(pj_status_t) pjsua_pres_notify( pjsua_acc_id acc_id,
1022
pjsua_srv_pres *srv_pres,
1023
pjsip_evsub_state ev_state,
1024
const pj_str_t *state_str,
1025
const pj_str_t *reason,
1026
pj_bool_t with_body,
1027
const pjsua_msg_data *msg_data)
1030
pjsip_pres_status pres_status;
1031
pjsua_buddy_id buddy_id;
1032
pjsip_tx_data *tdata;
1035
/* Check parameters */
1036
PJ_ASSERT_RETURN(acc_id!=-1 && srv_pres, PJ_EINVAL);
1038
/* Check that account ID is valid */
1039
PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
1041
/* Check that account is valid */
1042
PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
1044
PJ_LOG(4,(THIS_FILE, "Acc %d: sending NOTIFY for srv_pres=0x%p..",
1045
acc_id, (int)(long)srv_pres));
1046
pj_log_push_indent();
1050
acc = &pjsua_var.acc[acc_id];
1052
/* Check that the server presence subscription is still valid */
1053
if (pj_list_find_node(&acc->pres_srv_list, srv_pres) == NULL) {
1054
/* Subscription has been terminated */
1056
pj_log_pop_indent();
1057
return PJ_EINVALIDOP;
1060
/* Set our online status: */
1061
pj_bzero(&pres_status, sizeof(pres_status));
1062
pres_status.info_cnt = 1;
1063
pres_status.info[0].basic_open = acc->online_status;
1064
pres_status.info[0].id = acc->cfg.pidf_tuple_id;
1065
//Both pjsua_var.local_uri and pjsua_var.contact_uri are enclosed in "<" and ">"
1066
//causing XML parsing to fail.
1067
//pres_status.info[0].contact = pjsua_var.local_uri;
1068
/* add RPID information */
1069
pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
1070
sizeof(pjrpid_element));
1072
pjsip_pres_set_status(srv_pres->sub, &pres_status);
1074
/* Check expires value. If it's zero, send our presense state but
1075
* set subscription state to TERMINATED.
1077
if (srv_pres->expires == 0)
1078
ev_state = PJSIP_EVSUB_STATE_TERMINATED;
1080
/* Create and send the NOTIFY to active subscription: */
1081
status = pjsip_pres_notify(srv_pres->sub, ev_state, state_str,
1083
if (status == PJ_SUCCESS) {
1084
/* Force removal of message body if msg_body==FALSE */
1086
tdata->msg->body = NULL;
1088
pjsua_process_msg_data(tdata, msg_data);
1089
status = pjsip_pres_send_request( srv_pres->sub, tdata);
1092
if (status != PJ_SUCCESS) {
1093
pjsua_perror(THIS_FILE, "Unable to create/send NOTIFY",
1095
pj_list_erase(srv_pres);
1096
pjsip_pres_terminate(srv_pres->sub, PJ_FALSE);
1098
pj_log_pop_indent();
1103
/* Subscribe to buddy's presence if we're not subscribed */
1104
buddy_id = find_buddy(srv_pres->dlg->remote.info->uri);
1105
if (buddy_id != PJSUA_INVALID_ID) {
1106
pjsua_buddy *b = &pjsua_var.buddy[buddy_id];
1107
if (b->monitor && b->sub == NULL) {
1108
PJ_LOG(4,(THIS_FILE, "Received SUBSCRIBE from buddy %d, "
1109
"activating outgoing subscription", buddy_id));
1110
subscribe_buddy_presence(buddy_id);
1115
pj_log_pop_indent();
1121
* Client presence publication callback.
1123
static void publish_cb(struct pjsip_publishc_cbparam *param)
1125
pjsua_acc *acc = (pjsua_acc*) param->token;
1127
if (param->code/100 != 2 || param->status != PJ_SUCCESS) {
1129
pjsip_publishc_destroy(param->pubc);
1130
acc->publish_sess = NULL;
1132
if (param->status != PJ_SUCCESS) {
1133
char errmsg[PJ_ERR_MSG_SIZE];
1135
pj_strerror(param->status, errmsg, sizeof(errmsg));
1136
PJ_LOG(1,(THIS_FILE,
1137
"Client publication (PUBLISH) failed, status=%d, msg=%s",
1138
param->status, errmsg));
1139
} else if (param->code == 412) {
1140
/* 412 (Conditional Request Failed)
1141
* The PUBLISH refresh has failed, retry with new one.
1143
pjsua_pres_init_publish_acc(acc->index);
1146
PJ_LOG(1,(THIS_FILE,
1147
"Client publication (PUBLISH) failed (%d/%.*s)",
1148
param->code, (int)param->reason.slen,
1149
param->reason.ptr));
1153
if (param->expiration < 1) {
1154
/* Could happen if server "forgot" to include Expires header
1155
* in the response. We will not renew, so destroy the pubc.
1157
pjsip_publishc_destroy(param->pubc);
1158
acc->publish_sess = NULL;
1165
* Send PUBLISH request.
1167
static pj_status_t send_publish(int acc_id, pj_bool_t active)
1169
pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
1170
pjsua_acc *acc = &pjsua_var.acc[acc_id];
1171
pjsip_pres_status pres_status;
1172
pjsip_tx_data *tdata;
1175
PJ_LOG(5,(THIS_FILE, "Acc %d: sending %sPUBLISH..",
1176
acc_id, (active ? "" : "un-")));
1177
pj_log_push_indent();
1179
/* Create PUBLISH request */
1184
status = pjsip_publishc_publish(acc->publish_sess, PJ_TRUE, &tdata);
1185
if (status != PJ_SUCCESS) {
1186
pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status);
1190
/* Set our online status: */
1191
pj_bzero(&pres_status, sizeof(pres_status));
1192
pres_status.info_cnt = 1;
1193
pres_status.info[0].basic_open = acc->online_status;
1194
pres_status.info[0].id = acc->cfg.pidf_tuple_id;
1195
/* .. including RPID information */
1196
pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
1197
sizeof(pjrpid_element));
1199
/* Be careful not to send PIDF with presence entity ID containing
1202
if ((bpos=pj_strchr(&acc_cfg->id, '<')) != NULL) {
1203
char *epos = pj_strchr(&acc_cfg->id, '>');
1204
if (epos - bpos < 2) {
1205
pj_assert(!"Unexpected invalid URI");
1206
status = PJSIP_EINVALIDURI;
1209
entity.ptr = bpos+1;
1210
entity.slen = epos - bpos - 1;
1212
entity = acc_cfg->id;
1215
/* Create and add PIDF message body */
1216
status = pjsip_pres_create_pidf(tdata->pool, &pres_status,
1217
&entity, &tdata->msg->body);
1218
if (status != PJ_SUCCESS) {
1219
pjsua_perror(THIS_FILE, "Error creating PIDF for PUBLISH request",
1221
pjsip_tx_data_dec_ref(tdata);
1225
status = pjsip_publishc_unpublish(acc->publish_sess, &tdata);
1226
if (status != PJ_SUCCESS) {
1227
pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status);
1232
/* Add headers etc */
1233
pjsua_process_msg_data(tdata, NULL);
1235
/* Set Via sent-by */
1236
if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
1237
pjsip_publishc_set_via_sent_by(acc->publish_sess, &acc->via_addr,
1241
/* Send the PUBLISH request */
1242
status = pjsip_publishc_send(acc->publish_sess, tdata);
1243
if (status == PJ_EPENDING) {
1244
PJ_LOG(3,(THIS_FILE, "Previous request is in progress, "
1245
"PUBLISH request is queued"));
1246
} else if (status != PJ_SUCCESS) {
1247
pjsua_perror(THIS_FILE, "Error sending PUBLISH request", status);
1251
acc->publish_state = acc->online_status;
1252
pj_log_pop_indent();
1256
if (acc->publish_sess) {
1257
pjsip_publishc_destroy(acc->publish_sess);
1258
acc->publish_sess = NULL;
1260
pj_log_pop_indent();
1265
/* Create client publish session */
1266
pj_status_t pjsua_pres_init_publish_acc(int acc_id)
1268
const pj_str_t STR_PRESENCE = { "presence", 8 };
1269
pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
1270
pjsua_acc *acc = &pjsua_var.acc[acc_id];
1273
/* Create and init client publication session */
1274
if (acc_cfg->publish_enabled) {
1276
/* Create client publication */
1277
status = pjsip_publishc_create(pjsua_var.endpt, &acc_cfg->publish_opt,
1279
&acc->publish_sess);
1280
if (status != PJ_SUCCESS) {
1281
acc->publish_sess = NULL;
1285
/* Initialize client publication */
1286
status = pjsip_publishc_init(acc->publish_sess, &STR_PRESENCE,
1287
&acc_cfg->id, &acc_cfg->id,
1289
PJSUA_PUBLISH_EXPIRATION);
1290
if (status != PJ_SUCCESS) {
1291
acc->publish_sess = NULL;
1295
/* Add credential for authentication */
1296
if (acc->cred_cnt) {
1297
pjsip_publishc_set_credentials(acc->publish_sess, acc->cred_cnt,
1302
pjsip_publishc_set_route_set(acc->publish_sess, &acc->route_set);
1304
/* Send initial PUBLISH request */
1305
if (acc->online_status != 0) {
1306
status = send_publish(acc_id, PJ_TRUE);
1307
if (status != PJ_SUCCESS)
1312
acc->publish_sess = NULL;
1319
/* Init presence for account */
1320
pj_status_t pjsua_pres_init_acc(int acc_id)
1322
pjsua_acc *acc = &pjsua_var.acc[acc_id];
1324
/* Init presence subscription */
1325
pj_list_init(&acc->pres_srv_list);
1331
/* Unpublish presence publication */
1332
void pjsua_pres_unpublish(pjsua_acc *acc, unsigned flags)
1334
if (acc->publish_sess) {
1335
pjsua_acc_config *acc_cfg = &acc->cfg;
1337
acc->online_status = PJ_FALSE;
1339
if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
1340
send_publish(acc->index, PJ_FALSE);
1343
/* By ticket #364, don't destroy the session yet (let the callback
1345
if (acc->publish_sess) {
1346
pjsip_publishc_destroy(acc->publish_sess);
1347
acc->publish_sess = NULL;
1350
acc_cfg->publish_enabled = PJ_FALSE;
1354
/* Terminate server subscription for the account */
1355
void pjsua_pres_delete_acc(int acc_id, unsigned flags)
1357
pjsua_acc *acc = &pjsua_var.acc[acc_id];
1358
pjsua_srv_pres *uapres;
1360
uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
1362
/* Notify all subscribers that we're no longer available */
1363
while (uapres != &acc->pres_srv_list) {
1365
pjsip_pres_status pres_status;
1366
pj_str_t reason = { "noresource", 10 };
1367
pjsua_srv_pres *next;
1368
pjsip_tx_data *tdata;
1370
next = uapres->next;
1372
pjsip_pres_get_status(uapres->sub, &pres_status);
1374
pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status;
1375
pjsip_pres_set_status(uapres->sub, &pres_status);
1377
if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
1378
if (pjsip_pres_notify(uapres->sub,
1379
PJSIP_EVSUB_STATE_TERMINATED, NULL,
1380
&reason, &tdata)==PJ_SUCCESS)
1382
pjsip_pres_send_request(uapres->sub, tdata);
1385
pjsip_pres_terminate(uapres->sub, PJ_FALSE);
1391
/* Clear server presence subscription list because account might be reused
1393
pj_list_init(&acc->pres_srv_list);
1395
/* Terminate presence publication, if any */
1396
pjsua_pres_unpublish(acc, flags);
1400
/* Update server subscription (e.g. when our online status has changed) */
1401
void pjsua_pres_update_acc(int acc_id, pj_bool_t force)
1403
pjsua_acc *acc = &pjsua_var.acc[acc_id];
1404
pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
1405
pjsua_srv_pres *uapres;
1407
uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
1409
while (uapres != &acc->pres_srv_list) {
1411
pjsip_pres_status pres_status;
1412
pjsip_tx_data *tdata;
1414
pjsip_pres_get_status(uapres->sub, &pres_status);
1416
/* Only send NOTIFY once subscription is active. Some subscriptions
1417
* may still be in NULL (when app is adding a new buddy while in the
1418
* on_incoming_subscribe() callback) or PENDING (when user approval is
1419
* being requested) state and we don't send NOTIFY to these subs until
1420
* the user accepted the request.
1422
if (pjsip_evsub_get_state(uapres->sub)==PJSIP_EVSUB_STATE_ACTIVE &&
1423
(force || pres_status.info[0].basic_open != acc->online_status))
1426
pres_status.info[0].basic_open = acc->online_status;
1427
pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
1428
sizeof(pjrpid_element));
1430
pjsip_pres_set_status(uapres->sub, &pres_status);
1432
if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS) {
1433
pjsua_process_msg_data(tdata, NULL);
1434
pjsip_pres_send_request(uapres->sub, tdata);
1438
uapres = uapres->next;
1441
/* Send PUBLISH if required. We only do this when we have a PUBLISH
1442
* session. If we don't have a PUBLISH session, then it could be
1443
* that we're waiting until registration has completed before we
1444
* send the first PUBLISH.
1446
if (acc_cfg->publish_enabled && acc->publish_sess) {
1447
if (force || acc->publish_state != acc->online_status) {
1448
send_publish(acc_id, PJ_TRUE);
1455
/***************************************************************************
1456
* Client subscription.
1459
static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry)
1461
pjsua_buddy *buddy = (pjsua_buddy*)entry->user_data;
1465
entry->id = PJ_FALSE;
1466
pjsua_buddy_update_pres(buddy->index);
1469
/* Reschedule subscription refresh timer or terminate the subscription
1470
* refresh timer for the specified buddy.
1472
static void buddy_resubscribe(pjsua_buddy *buddy, pj_bool_t resched,
1473
unsigned msec_interval)
1475
if (buddy->timer.id) {
1476
pjsua_cancel_timer(&buddy->timer);
1477
buddy->timer.id = PJ_FALSE;
1483
PJ_LOG(4,(THIS_FILE,
1484
"Resubscribing buddy id %u in %u ms (reason: %.*s)",
1485
buddy->index, msec_interval,
1486
(int)buddy->term_reason.slen,
1487
buddy->term_reason.ptr));
1489
pj_timer_entry_init(&buddy->timer, 0, buddy, &buddy_timer_cb);
1491
delay.msec = msec_interval;
1492
pj_time_val_normalize(&delay);
1494
if (pjsua_schedule_timer(&buddy->timer, &delay)==PJ_SUCCESS)
1495
buddy->timer.id = PJ_TRUE;
1499
/* Callback called when *client* subscription state has changed. */
1500
static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
1504
PJ_UNUSED_ARG(event);
1506
/* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
1507
* a dialog attached to it, lock_buddy() will use the dialog
1508
* lock, which we are currently holding!
1510
buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1512
PJ_LOG(4,(THIS_FILE,
1513
"Presence subscription to %.*s is %s",
1514
(int)pjsua_var.buddy[buddy->index].uri.slen,
1515
pjsua_var.buddy[buddy->index].uri.ptr,
1516
pjsip_evsub_get_state_name(sub)));
1517
pj_log_push_indent();
1519
if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
1520
int resub_delay = -1;
1522
if (buddy->term_reason.ptr == NULL) {
1523
buddy->term_reason.ptr = (char*)
1524
pj_pool_alloc(buddy->pool,
1525
PJSUA_BUDDY_SUB_TERM_REASON_LEN);
1527
pj_strncpy(&buddy->term_reason,
1528
pjsip_evsub_get_termination_reason(sub),
1529
PJSUA_BUDDY_SUB_TERM_REASON_LEN);
1531
buddy->term_code = 200;
1533
/* Determine whether to resubscribe automatically */
1534
if (event && event->type==PJSIP_EVENT_TSX_STATE) {
1535
const pjsip_transaction *tsx = event->body.tsx_state.tsx;
1536
if (pjsip_method_cmp(&tsx->method,
1537
&pjsip_subscribe_method)==0)
1539
buddy->term_code = tsx->status_code;
1540
switch (tsx->status_code) {
1541
case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST:
1542
/* 481: we refreshed too late? resubscribe
1545
/* But this must only happen when the 481 is received
1546
* on subscription refresh request. We MUST NOT try to
1547
* resubscribe automatically if the 481 is received
1548
* on the initial SUBSCRIBE (if server returns this
1549
* response for some reason).
1551
if (buddy->dlg->remote.contact)
1555
} else if (pjsip_method_cmp(&tsx->method,
1556
&pjsip_notify_method)==0)
1558
if (pj_stricmp2(&buddy->term_reason, "deactivated")==0 ||
1559
pj_stricmp2(&buddy->term_reason, "timeout")==0) {
1560
/* deactivated: The subscription has been terminated,
1561
* but the subscriber SHOULD retry immediately with
1562
* a new subscription.
1564
/* timeout: The subscription has been terminated
1565
* because it was not refreshed before it expired.
1566
* Clients MAY re-subscribe immediately. The
1567
* "retry-after" parameter has no semantics for
1572
else if (pj_stricmp2(&buddy->term_reason, "probation")==0||
1573
pj_stricmp2(&buddy->term_reason, "giveup")==0) {
1574
/* probation: The subscription has been terminated,
1575
* but the client SHOULD retry at some later time.
1576
* If a "retry-after" parameter is also present, the
1577
* client SHOULD wait at least the number of seconds
1578
* specified by that parameter before attempting to re-
1581
/* giveup: The subscription has been terminated because
1582
* the notifier could not obtain authorization in a
1583
* timely fashion. If a "retry-after" parameter is
1584
* also present, the client SHOULD wait at least the
1585
* number of seconds specified by that parameter before
1586
* attempting to re-subscribe; otherwise, the client
1587
* MAY retry immediately, but will likely get put back
1588
* into pending state.
1590
const pjsip_sub_state_hdr *sub_hdr;
1591
pj_str_t sub_state = { "Subscription-State", 18 };
1592
const pjsip_msg *msg;
1594
msg = event->body.tsx_state.src.rdata->msg_info.msg;
1595
sub_hdr = (const pjsip_sub_state_hdr*)
1596
pjsip_msg_find_hdr_by_name(msg, &sub_state,
1598
if (sub_hdr && sub_hdr->retry_after > 0)
1599
resub_delay = sub_hdr->retry_after * 1000;
1605
/* For other cases of subscription termination, if resubscribe
1606
* timer is not set, schedule with default expiration (plus minus
1607
* some random value, to avoid sending SUBSCRIBEs all at once)
1609
if (resub_delay == -1) {
1610
pj_assert(PJSUA_PRES_TIMER >= 3);
1611
resub_delay = PJSUA_PRES_TIMER*1000 - 2500 + (pj_rand()%5000);
1614
buddy_resubscribe(buddy, PJ_TRUE, resub_delay);
1617
/* This will clear the last termination code/reason */
1618
buddy->term_code = 0;
1619
buddy->term_reason.slen = 0;
1622
/* Call callbacks */
1623
if (pjsua_var.ua_cfg.cb.on_buddy_evsub_state)
1624
(*pjsua_var.ua_cfg.cb.on_buddy_evsub_state)(buddy->index, sub,
1627
if (pjsua_var.ua_cfg.cb.on_buddy_state)
1628
(*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index);
1630
/* Clear subscription */
1631
if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
1633
buddy->status.info_cnt = 0;
1635
pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
1638
pj_log_pop_indent();
1643
/* Callback when transaction state has changed. */
1644
static void pjsua_evsub_on_tsx_state(pjsip_evsub *sub,
1645
pjsip_transaction *tsx,
1649
pjsip_contact_hdr *contact_hdr;
1651
/* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
1652
* a dialog attached to it, lock_buddy() will use the dialog
1653
* lock, which we are currently holding!
1655
buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1660
/* We only use this to update buddy's Contact, when it's not
1663
if (buddy->contact.slen != 0) {
1664
/* Contact already set */
1668
/* Only care about 2xx response to outgoing SUBSCRIBE */
1669
if (tsx->status_code/100 != 2 ||
1670
tsx->role != PJSIP_UAC_ROLE ||
1671
event->type != PJSIP_EVENT_RX_MSG ||
1672
pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method())!=0)
1677
/* Find contact header. */
1678
contact_hdr = (pjsip_contact_hdr*)
1679
pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg,
1680
PJSIP_H_CONTACT, NULL);
1681
if (!contact_hdr || !contact_hdr->uri) {
1685
buddy->contact.ptr = (char*)
1686
pj_pool_alloc(buddy->pool, PJSIP_MAX_URL_SIZE);
1687
buddy->contact.slen = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR,
1690
PJSIP_MAX_URL_SIZE);
1691
if (buddy->contact.slen < 0)
1692
buddy->contact.slen = 0;
1696
/* Callback called when we receive NOTIFY */
1697
static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub,
1698
pjsip_rx_data *rdata,
1700
pj_str_t **p_st_text,
1702
pjsip_msg_body **p_body)
1706
/* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
1707
* a dialog attached to it, lock_buddy() will use the dialog
1708
* lock, which we are currently holding!
1710
buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1712
/* Update our info. */
1713
pjsip_pres_get_status(sub, &buddy->status);
1716
/* The default is to send 200 response to NOTIFY.
1717
* Just leave it there..
1719
PJ_UNUSED_ARG(rdata);
1720
PJ_UNUSED_ARG(p_st_code);
1721
PJ_UNUSED_ARG(p_st_text);
1722
PJ_UNUSED_ARG(res_hdr);
1723
PJ_UNUSED_ARG(p_body);
1727
/* It does what it says.. */
1728
static void subscribe_buddy_presence(pjsua_buddy_id buddy_id)
1730
pjsip_evsub_user pres_callback;
1731
pj_pool_t *tmp_pool = NULL;
1736
pjsip_tx_data *tdata;
1739
/* Event subscription callback. */
1740
pj_bzero(&pres_callback, sizeof(pres_callback));
1741
pres_callback.on_evsub_state = &pjsua_evsub_on_state;
1742
pres_callback.on_tsx_state = &pjsua_evsub_on_tsx_state;
1743
pres_callback.on_rx_notify = &pjsua_evsub_on_rx_notify;
1745
buddy = &pjsua_var.buddy[buddy_id];
1746
acc_id = pjsua_acc_find_for_outgoing(&buddy->uri);
1748
acc = &pjsua_var.acc[acc_id];
1750
PJ_LOG(4,(THIS_FILE, "Buddy %d: subscribing presence,using account %d..",
1752
pj_log_push_indent();
1754
/* Generate suitable Contact header unless one is already set in
1757
if (acc->contact.slen) {
1758
contact = acc->contact;
1760
tmp_pool = pjsua_pool_create("tmpbuddy", 512, 256);
1762
status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
1763
acc_id, &buddy->uri);
1764
if (status != PJ_SUCCESS) {
1765
pjsua_perror(THIS_FILE, "Unable to generate Contact header",
1767
pj_pool_release(tmp_pool);
1768
pj_log_pop_indent();
1773
/* Create UAC dialog */
1774
status = pjsip_dlg_create_uac( pjsip_ua_instance(),
1779
if (status != PJ_SUCCESS) {
1780
pjsua_perror(THIS_FILE, "Unable to create dialog",
1782
if (tmp_pool) pj_pool_release(tmp_pool);
1783
pj_log_pop_indent();
1787
/* Increment the dialog's lock otherwise when presence session creation
1788
* fails the dialog will be destroyed prematurely.
1790
pjsip_dlg_inc_lock(buddy->dlg);
1792
if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0)
1793
pjsip_dlg_set_via_sent_by(buddy->dlg, &acc->via_addr, acc->via_tp);
1795
status = pjsip_pres_create_uac( buddy->dlg, &pres_callback,
1796
PJSIP_EVSUB_NO_EVENT_ID, &buddy->sub);
1797
if (status != PJ_SUCCESS) {
1799
pjsua_perror(THIS_FILE, "Unable to create presence client",
1801
/* This should destroy the dialog since there's no session
1804
if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
1805
if (tmp_pool) pj_pool_release(tmp_pool);
1806
pj_log_pop_indent();
1810
/* If account is locked to specific transport, then lock dialog
1811
* to this transport too.
1813
if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
1814
pjsip_tpselector tp_sel;
1816
pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
1817
pjsip_dlg_set_transport(buddy->dlg, &tp_sel);
1821
if (!pj_list_empty(&acc->route_set)) {
1822
pjsip_dlg_set_route_set(buddy->dlg, &acc->route_set);
1825
/* Set credentials */
1826
if (acc->cred_cnt) {
1827
pjsip_auth_clt_set_credentials( &buddy->dlg->auth_sess,
1828
acc->cred_cnt, acc->cred);
1831
/* Set authentication preference */
1832
pjsip_auth_clt_set_prefs(&buddy->dlg->auth_sess, &acc->cfg.auth_pref);
1834
pjsip_evsub_set_mod_data(buddy->sub, pjsua_var.mod.id, buddy);
1836
status = pjsip_pres_initiate(buddy->sub, -1, &tdata);
1837
if (status != PJ_SUCCESS) {
1838
if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
1840
pjsip_pres_terminate(buddy->sub, PJ_FALSE);
1843
pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE",
1845
if (tmp_pool) pj_pool_release(tmp_pool);
1846
pj_log_pop_indent();
1850
pjsua_process_msg_data(tdata, NULL);
1852
status = pjsip_pres_send_request(buddy->sub, tdata);
1853
if (status != PJ_SUCCESS) {
1854
if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
1856
pjsip_pres_terminate(buddy->sub, PJ_FALSE);
1859
pjsua_perror(THIS_FILE, "Unable to send initial SUBSCRIBE",
1861
if (tmp_pool) pj_pool_release(tmp_pool);
1862
pj_log_pop_indent();
1866
pjsip_dlg_dec_lock(buddy->dlg);
1867
if (tmp_pool) pj_pool_release(tmp_pool);
1868
pj_log_pop_indent();
1872
/* It does what it says... */
1873
static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id)
1876
pjsip_tx_data *tdata;
1879
buddy = &pjsua_var.buddy[buddy_id];
1881
if (buddy->sub == NULL)
1884
if (pjsip_evsub_get_state(buddy->sub) == PJSIP_EVSUB_STATE_TERMINATED) {
1889
PJ_LOG(5,(THIS_FILE, "Buddy %d: unsubscribing..", buddy_id));
1890
pj_log_push_indent();
1892
status = pjsip_pres_initiate( buddy->sub, 0, &tdata);
1893
if (status == PJ_SUCCESS) {
1894
pjsua_process_msg_data(tdata, NULL);
1895
status = pjsip_pres_send_request( buddy->sub, tdata );
1898
if (status != PJ_SUCCESS && buddy->sub) {
1899
pjsip_pres_terminate(buddy->sub, PJ_FALSE);
1901
pjsua_perror(THIS_FILE, "Unable to unsubscribe presence",
1905
pj_log_pop_indent();
1908
/* It does what it says.. */
1909
static pj_status_t refresh_client_subscriptions(void)
1914
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
1915
struct buddy_lock lck;
1917
if (!pjsua_buddy_is_valid(i))
1920
status = lock_buddy("refresh_client_subscriptions()", i, &lck, 0);
1921
if (status != PJ_SUCCESS)
1924
if (pjsua_var.buddy[i].monitor && !pjsua_var.buddy[i].sub) {
1925
subscribe_buddy_presence(i);
1927
} else if (!pjsua_var.buddy[i].monitor && pjsua_var.buddy[i].sub) {
1928
unsubscribe_buddy_presence(i);
1938
/***************************************************************************
1941
/* Callback called when *client* subscription state has changed. */
1942
static void mwi_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
1946
PJ_UNUSED_ARG(event);
1948
/* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
1949
* a dialog attached to it, lock_buddy() will use the dialog
1950
* lock, which we are currently holding!
1952
acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1956
PJ_LOG(4,(THIS_FILE,
1957
"MWI subscription for %.*s is %s",
1958
(int)acc->cfg.id.slen, acc->cfg.id.ptr,
1959
pjsip_evsub_get_state_name(sub)));
1962
if (pjsua_var.ua_cfg.cb.on_mwi_state) {
1963
(*pjsua_var.ua_cfg.cb.on_mwi_state)(acc->index, sub);
1966
if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
1967
/* Clear subscription */
1968
acc->mwi_dlg = NULL;
1969
acc->mwi_sub = NULL;
1970
pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
1975
/* Callback called when we receive NOTIFY */
1976
static void mwi_evsub_on_rx_notify(pjsip_evsub *sub,
1977
pjsip_rx_data *rdata,
1979
pj_str_t **p_st_text,
1981
pjsip_msg_body **p_body)
1983
pjsua_mwi_info mwi_info;
1986
PJ_UNUSED_ARG(p_st_code);
1987
PJ_UNUSED_ARG(p_st_text);
1988
PJ_UNUSED_ARG(res_hdr);
1989
PJ_UNUSED_ARG(p_body);
1991
acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1995
/* Construct mwi_info */
1996
pj_bzero(&mwi_info, sizeof(mwi_info));
1997
mwi_info.evsub = sub;
1998
mwi_info.rdata = rdata;
2000
PJ_LOG(4,(THIS_FILE, "MWI got NOTIFY.."));
2001
pj_log_push_indent();
2004
if (pjsua_var.ua_cfg.cb.on_mwi_info) {
2005
(*pjsua_var.ua_cfg.cb.on_mwi_info)(acc->index, &mwi_info);
2008
pj_log_pop_indent();
2012
/* Event subscription callback. */
2013
static pjsip_evsub_user mwi_cb =
2015
&mwi_evsub_on_state,
2016
NULL, /* on_tsx_state: not interested */
2017
NULL, /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless
2018
* we want to authenticate
2021
&mwi_evsub_on_rx_notify,
2023
NULL, /* on_client_refresh: Use default behaviour, which is to
2024
* refresh client subscription. */
2026
NULL, /* on_server_timeout: Use default behaviour, which is to send
2027
* NOTIFY to terminate.
2031
pj_status_t pjsua_start_mwi(pjsua_acc_id acc_id, pj_bool_t force_renew)
2034
pj_pool_t *tmp_pool = NULL;
2036
pjsip_tx_data *tdata;
2037
pj_status_t status = PJ_SUCCESS;
2039
PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc)
2040
&& pjsua_var.acc[acc_id].valid, PJ_EINVAL);
2042
acc = &pjsua_var.acc[acc_id];
2044
if (!acc->cfg.mwi_enabled) {
2046
/* Terminate MWI subscription */
2047
pjsip_evsub *sub = acc->mwi_sub;
2049
/* Detach sub from this account */
2050
acc->mwi_sub = NULL;
2051
acc->mwi_dlg = NULL;
2052
pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
2055
status = pjsip_mwi_initiate(sub, 0, &tdata);
2056
if (status == PJ_SUCCESS) {
2057
status = pjsip_mwi_send_request(sub, tdata);
2063
/* Subscription is already active */
2068
/* Update MWI subscription */
2069
pj_assert(acc->mwi_dlg);
2070
pjsip_dlg_inc_lock(acc->mwi_dlg);
2072
status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata);
2073
if (status == PJ_SUCCESS) {
2074
pjsua_process_msg_data(tdata, NULL);
2075
status = pjsip_pres_send_request(acc->mwi_sub, tdata);
2078
pjsip_dlg_dec_lock(acc->mwi_dlg);
2082
PJ_LOG(4,(THIS_FILE, "Starting MWI subscription.."));
2083
pj_log_push_indent();
2085
/* Generate suitable Contact header unless one is already set in
2088
if (acc->contact.slen) {
2089
contact = acc->contact;
2091
tmp_pool = pjsua_pool_create("tmpmwi", 512, 256);
2092
status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
2093
acc->index, &acc->cfg.id);
2094
if (status != PJ_SUCCESS) {
2095
pjsua_perror(THIS_FILE, "Unable to generate Contact header",
2101
/* Create UAC dialog */
2102
status = pjsip_dlg_create_uac( pjsip_ua_instance(),
2106
NULL, &acc->mwi_dlg);
2107
if (status != PJ_SUCCESS) {
2108
pjsua_perror(THIS_FILE, "Unable to create dialog", status);
2112
/* Increment the dialog's lock otherwise when presence session creation
2113
* fails the dialog will be destroyed prematurely.
2115
pjsip_dlg_inc_lock(acc->mwi_dlg);
2117
if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0)
2118
pjsip_dlg_set_via_sent_by(acc->mwi_dlg, &acc->via_addr, acc->via_tp);
2120
/* Create UAC subscription */
2121
status = pjsip_mwi_create_uac(acc->mwi_dlg, &mwi_cb,
2122
PJSIP_EVSUB_NO_EVENT_ID, &acc->mwi_sub);
2123
if (status != PJ_SUCCESS) {
2124
pjsua_perror(THIS_FILE, "Error creating MWI subscription", status);
2125
if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
2129
/* If account is locked to specific transport, then lock dialog
2130
* to this transport too.
2132
if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
2133
pjsip_tpselector tp_sel;
2135
pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
2136
pjsip_dlg_set_transport(acc->mwi_dlg, &tp_sel);
2140
if (!pj_list_empty(&acc->route_set)) {
2141
pjsip_dlg_set_route_set(acc->mwi_dlg, &acc->route_set);
2144
/* Set credentials */
2145
if (acc->cred_cnt) {
2146
pjsip_auth_clt_set_credentials( &acc->mwi_dlg->auth_sess,
2147
acc->cred_cnt, acc->cred);
2150
/* Set authentication preference */
2151
pjsip_auth_clt_set_prefs(&acc->mwi_dlg->auth_sess, &acc->cfg.auth_pref);
2153
pjsip_evsub_set_mod_data(acc->mwi_sub, pjsua_var.mod.id, acc);
2155
status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata);
2156
if (status != PJ_SUCCESS) {
2157
if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
2159
pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE);
2161
acc->mwi_sub = NULL;
2162
acc->mwi_dlg = NULL;
2163
pjsua_perror(THIS_FILE, "Unable to create initial MWI SUBSCRIBE",
2168
pjsua_process_msg_data(tdata, NULL);
2170
status = pjsip_pres_send_request(acc->mwi_sub, tdata);
2171
if (status != PJ_SUCCESS) {
2172
if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
2174
pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE);
2176
acc->mwi_sub = NULL;
2177
acc->mwi_dlg = NULL;
2178
pjsua_perror(THIS_FILE, "Unable to send initial MWI SUBSCRIBE",
2183
pjsip_dlg_dec_lock(acc->mwi_dlg);
2186
if (tmp_pool) pj_pool_release(tmp_pool);
2188
pj_log_pop_indent();
2193
/***************************************************************************
2196
static pj_bool_t unsolicited_mwi_on_rx_request(pjsip_rx_data *rdata)
2198
pjsip_msg *msg = rdata->msg_info.msg;
2199
pj_str_t EVENT_HDR = { "Event", 5 };
2200
pj_str_t MWI = { "message-summary", 15 };
2201
pjsip_event_hdr *eh;
2203
if (pjsip_method_cmp(&msg->line.req.method, &pjsip_notify_method)!=0) {
2204
/* Only interested with NOTIFY request */
2208
eh = (pjsip_event_hdr*) pjsip_msg_find_hdr_by_name(msg, &EVENT_HDR, NULL);
2210
/* Something wrong with the request, it has no Event hdr */
2214
if (pj_stricmp(&eh->event_type, &MWI) != 0) {
2219
PJ_LOG(4,(THIS_FILE, "Got unsolicited NOTIFY from %s:%d..",
2220
rdata->pkt_info.src_name, rdata->pkt_info.src_port));
2221
pj_log_push_indent();
2223
/* Got unsolicited MWI request, respond with 200/OK first */
2224
pjsip_endpt_respond(pjsua_get_pjsip_endpt(), NULL, rdata, 200, NULL,
2229
if (pjsua_var.ua_cfg.cb.on_mwi_info) {
2230
pjsua_acc_id acc_id;
2231
pjsua_mwi_info mwi_info;
2233
acc_id = pjsua_acc_find_for_incoming(rdata);
2235
pj_bzero(&mwi_info, sizeof(mwi_info));
2236
mwi_info.rdata = rdata;
2238
(*pjsua_var.ua_cfg.cb.on_mwi_info)(acc_id, &mwi_info);
2241
pj_log_pop_indent();
2245
/* The module instance. */
2246
static pjsip_module pjsua_unsolicited_mwi_mod =
2248
NULL, NULL, /* prev, next. */
2249
{ "mod-unsolicited-mwi", 19 }, /* Name. */
2251
PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
2255
NULL, /* unload() */
2256
&unsolicited_mwi_on_rx_request, /* on_rx_request() */
2257
NULL, /* on_rx_response() */
2258
NULL, /* on_tx_request. */
2259
NULL, /* on_tx_response() */
2260
NULL, /* on_tsx_state() */
2263
static pj_status_t enable_unsolicited_mwi(void)
2267
status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(),
2268
&pjsua_unsolicited_mwi_mod);
2269
if (status != PJ_SUCCESS)
2270
pjsua_perror(THIS_FILE, "Error registering unsolicited MWI module",
2278
/***************************************************************************/
2280
/* Timer callback to re-create client subscription */
2281
static void pres_timer_cb(pj_timer_heap_t *th,
2282
pj_timer_entry *entry)
2285
pj_time_val delay = { PJSUA_PRES_TIMER, 0 };
2287
entry->id = PJ_FALSE;
2289
/* Retry failed PUBLISH and MWI SUBSCRIBE requests */
2290
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
2291
pjsua_acc *acc = &pjsua_var.acc[i];
2293
/* Acc may not be ready yet, otherwise assertion will happen */
2294
if (!pjsua_acc_is_valid(i))
2298
if (acc->cfg.publish_enabled && acc->publish_sess==NULL)
2299
pjsua_pres_init_publish_acc(acc->index);
2301
/* Re-subscribe MWI subscription if it's terminated prematurely */
2302
if (acc->cfg.mwi_enabled && !acc->mwi_sub)
2303
pjsua_start_mwi(acc->index, PJ_FALSE);
2306
/* #937: No need to do bulk client refresh, as buddies have their
2307
* own individual timer now.
2309
//refresh_client_subscriptions();
2311
pjsip_endpt_schedule_timer(pjsua_var.endpt, entry, &delay);
2312
entry->id = PJ_TRUE;
2321
pj_status_t pjsua_pres_init()
2326
status = pjsip_endpt_register_module( pjsua_var.endpt, &mod_pjsua_pres);
2327
if (status != PJ_SUCCESS) {
2328
pjsua_perror(THIS_FILE, "Unable to register pjsua presence module",
2332
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
2341
* Start presence subsystem.
2343
pj_status_t pjsua_pres_start(void)
2345
/* Start presence timer to re-subscribe to buddy's presence when
2346
* subscription has failed.
2348
if (pjsua_var.pres_timer.id == PJ_FALSE) {
2349
pj_time_val pres_interval = {PJSUA_PRES_TIMER, 0};
2351
pjsua_var.pres_timer.cb = &pres_timer_cb;
2352
pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.pres_timer,
2354
pjsua_var.pres_timer.id = PJ_TRUE;
2357
if (pjsua_var.ua_cfg.enable_unsolicited_mwi) {
2358
pj_status_t status = enable_unsolicited_mwi();
2359
if (status != PJ_SUCCESS)
2368
* Shutdown presence.
2370
void pjsua_pres_shutdown(unsigned flags)
2374
PJ_LOG(4,(THIS_FILE, "Shutting down presence.."));
2375
pj_log_push_indent();
2377
if (pjsua_var.pres_timer.id != 0) {
2378
pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.pres_timer);
2379
pjsua_var.pres_timer.id = PJ_FALSE;
2382
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
2383
if (!pjsua_var.acc[i].valid)
2385
pjsua_pres_delete_acc(i, flags);
2388
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
2389
pjsua_var.buddy[i].monitor = 0;
2392
if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
2393
refresh_client_subscriptions();
2395
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
2396
if (pjsua_var.acc[i].valid)
2397
pjsua_pres_update_acc(i, PJ_FALSE);
2401
pj_log_pop_indent();