1 /* $Id$ */
2 /*
3  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4  * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5  *
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.
10  *
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.
15  *
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
19  */
20 #include <pjsua-lib/pjsua.h>
21 #include <pjsua-lib/pjsua_internal.h>
22 
23 
24 #define THIS_FILE   "pjsua_pres.c"
25 
26 
27 static void subscribe_buddy_presence(pjsua_buddy_id buddy_id);
28 static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id);
29 
30 
31 /*
32  * Find buddy.
33  */
find_buddy(const pjsip_uri * uri)34 static pjsua_buddy_id find_buddy(const pjsip_uri *uri)
35 {
36     const pjsip_sip_uri *sip_uri;
37     unsigned i;
38 
39     uri = (const pjsip_uri*) pjsip_uri_get_uri((pjsip_uri*)uri);
40 
41     if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))
42 	return PJSUA_INVALID_ID;
43 
44     sip_uri = (const pjsip_sip_uri*) uri;
45 
46     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
47 	const pjsua_buddy *b = &pjsua_var.buddy[i];
48 
49 	if (!pjsua_buddy_is_valid(i))
50 	    continue;
51 
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)))
55 	{
56 	    /* Match */
57 	    return i;
58 	}
59     }
60 
61     return PJSUA_INVALID_ID;
62 }
63 
64 #define LOCK_DIALOG	1
65 #define LOCK_PJSUA	2
66 #define LOCK_ALL	(LOCK_DIALOG | LOCK_PJSUA)
67 
68 /* Buddy lock object */
69 struct buddy_lock
70 {
71     pjsua_buddy	    *buddy;
72     pjsip_dialog    *dlg;
73     pj_uint8_t	     flag;
74 };
75 
76 /* Acquire lock to the specified buddy_id */
lock_buddy(const char * title,pjsua_buddy_id buddy_id,struct buddy_lock * lck,unsigned _unused_)77 static pj_status_t lock_buddy(const char *title,
78 			      pjsua_buddy_id buddy_id,
79 			      struct buddy_lock *lck,
80 			      unsigned _unused_)
81 {
82     enum { MAX_RETRY=50 };
83     pj_bool_t has_pjsua_lock = PJ_FALSE;
84     unsigned retry;
85 
86     PJ_UNUSED_ARG(_unused_);
87 
88     pj_bzero(lck, sizeof(*lck));
89 
90     for (retry=0; retry<MAX_RETRY; ++retry) {
91 
92 	if (PJSUA_TRY_LOCK() != PJ_SUCCESS) {
93 	    pj_thread_sleep(retry/10);
94 	    continue;
95 	}
96 
97 	has_pjsua_lock = PJ_TRUE;
98 	lck->flag = LOCK_PJSUA;
99 	lck->buddy = &pjsua_var.buddy[buddy_id];
100 
101 	if (lck->buddy->dlg == NULL)
102 	    return PJ_SUCCESS;
103 
104 	if (pjsip_dlg_try_inc_lock(lck->buddy->dlg) != PJ_SUCCESS) {
105 	    lck->flag = 0;
106 	    lck->buddy = NULL;
107 	    has_pjsua_lock = PJ_FALSE;
108 	    PJSUA_UNLOCK();
109 	    pj_thread_sleep(retry/10);
110 	    continue;
111 	}
112 
113 	lck->dlg = lck->buddy->dlg;
114 	lck->flag = LOCK_DIALOG;
115 	PJSUA_UNLOCK();
116 
117 	break;
118     }
119 
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",
124 				 title));
125 	else
126 	    PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire dialog mutex "
127 				 "(possibly system has deadlocked) in %s",
128 				 title));
129 	return PJ_ETIMEDOUT;
130     }
131 
132     return PJ_SUCCESS;
133 }
134 
135 /* Release buddy lock */
unlock_buddy(struct buddy_lock * lck)136 static void unlock_buddy(struct buddy_lock *lck)
137 {
138     if (lck->flag & LOCK_DIALOG)
139 	pjsip_dlg_dec_lock(lck->dlg);
140 
141     if (lck->flag & LOCK_PJSUA)
142 	PJSUA_UNLOCK();
143 }
144 
145 
146 /*
147  * Get total number of buddies.
148  */
pjsua_get_buddy_count(void)149 PJ_DEF(unsigned) pjsua_get_buddy_count(void)
150 {
151     return pjsua_var.buddy_cnt;
152 }
153 
154 
155 /*
156  * Find buddy.
157  */
pjsua_buddy_find(const pj_str_t * uri_str)158 PJ_DEF(pjsua_buddy_id) pjsua_buddy_find(const pj_str_t *uri_str)
159 {
160     pj_str_t input;
161     pj_pool_t *pool;
162     pjsip_uri *uri;
163     pjsua_buddy_id buddy_id;
164 
165     pool = pjsua_pool_create("buddyfind", 512, 512);
166     pj_strdup_with_null(pool, &input, uri_str);
167 
168     uri = pjsip_parse_uri(pool, input.ptr, input.slen, 0);
169     if (!uri)
170 	buddy_id = PJSUA_INVALID_ID;
171     else {
172 	PJSUA_LOCK();
173 	buddy_id = find_buddy(uri);
174 	PJSUA_UNLOCK();
175     }
176 
177     pj_pool_release(pool);
178 
179     return buddy_id;
180 }
181 
182 
183 /*
184  * Check if buddy ID is valid.
185  */
pjsua_buddy_is_valid(pjsua_buddy_id buddy_id)186 PJ_DEF(pj_bool_t) pjsua_buddy_is_valid(pjsua_buddy_id buddy_id)
187 {
188     return buddy_id>=0 && buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy) &&
189 	   pjsua_var.buddy[buddy_id].uri.slen != 0;
190 }
191 
192 
193 /*
194  * Enum buddy IDs.
195  */
pjsua_enum_buddies(pjsua_buddy_id ids[],unsigned * count)196 PJ_DEF(pj_status_t) pjsua_enum_buddies( pjsua_buddy_id ids[],
197 					unsigned *count)
198 {
199     unsigned i, c;
200 
201     PJ_ASSERT_RETURN(ids && count, PJ_EINVAL);
202 
203     PJSUA_LOCK();
204 
205     for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
206 	if (!pjsua_var.buddy[i].uri.slen)
207 	    continue;
208 	ids[c] = i;
209 	++c;
210     }
211 
212     *count = c;
213 
214     PJSUA_UNLOCK();
215 
216     return PJ_SUCCESS;
217 }
218 
219 
220 /*
221  * Get detailed buddy info.
222  */
pjsua_buddy_get_info(pjsua_buddy_id buddy_id,pjsua_buddy_info * info)223 PJ_DEF(pj_status_t) pjsua_buddy_get_info( pjsua_buddy_id buddy_id,
224 					  pjsua_buddy_info *info)
225 {
226     pj_size_t total=0;
227     struct buddy_lock lck;
228     pjsua_buddy *buddy;
229     pj_status_t status;
230 
231     PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id),  PJ_EINVAL);
232 
233     pj_bzero(info, sizeof(pjsua_buddy_info));
234 
235     status = lock_buddy("pjsua_buddy_get_info()", buddy_id, &lck, 0);
236     if (status != PJ_SUCCESS)
237 	return status;
238 
239     buddy = lck.buddy;
240     info->id = buddy->index;
241     if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
242 	unlock_buddy(&lck);
243 	return PJ_SUCCESS;
244     }
245 
246     /* uri */
247     info->uri.ptr = info->buf_ + total;
248     pj_strncpy(&info->uri, &buddy->uri, sizeof(info->buf_)-total);
249     total += info->uri.slen;
250 
251     /* contact */
252     if (total < sizeof(info->buf_)) {
253         info->contact.ptr = info->buf_ + total;
254         pj_strncpy(&info->contact, &buddy->contact, sizeof(info->buf_) - total);
255         total += info->contact.slen;
256     } else {
257         info->contact = pj_str("");
258     }
259 
260     /* Presence status */
261     pj_memcpy(&info->pres_status, &buddy->status, sizeof(pjsip_pres_status));
262 
263     /* status and status text */
264     if (buddy->sub == NULL || buddy->status.info_cnt==0) {
265 	info->status = PJSUA_BUDDY_STATUS_UNKNOWN;
266 	info->status_text = pj_str("?");
267     } else if (pjsua_var.buddy[buddy_id].status.info[0].basic_open) {
268 	info->status = PJSUA_BUDDY_STATUS_ONLINE;
269 
270 	/* copy RPID information */
271 	info->rpid = buddy->status.info[0].rpid;
272 
273 	if (info->rpid.note.slen)
274 	    info->status_text = info->rpid.note;
275 	else
276 	    info->status_text = pj_str("Online");
277 
278     } else {
279 	info->status = PJSUA_BUDDY_STATUS_OFFLINE;
280 	info->rpid = buddy->status.info[0].rpid;
281 
282 	if (info->rpid.note.slen)
283 	    info->status_text = info->rpid.note;
284 	else
285 	    info->status_text = pj_str("Offline");
286     }
287 
288     /* monitor pres */
289     info->monitor_pres = buddy->monitor;
290 
291     /* subscription state and termination reason */
292     info->sub_term_code = buddy->term_code;
293     if (buddy->sub) {
294 	info->sub_state = pjsip_evsub_get_state(buddy->sub);
295 	info->sub_state_name = pjsip_evsub_get_state_name(buddy->sub);
296 	if (info->sub_state == PJSIP_EVSUB_STATE_TERMINATED &&
297 	    total < sizeof(info->buf_))
298 	{
299 	    info->sub_term_reason.ptr = info->buf_ + total;
300 	    pj_strncpy(&info->sub_term_reason,
301 		       pjsip_evsub_get_termination_reason(buddy->sub),
302 		       sizeof(info->buf_) - total);
303 	    total += info->sub_term_reason.slen;
304 	} else {
305 	    info->sub_term_reason = pj_str("");
306 	}
307     } else if (total < sizeof(info->buf_)) {
308 	info->sub_state_name = "NULL";
309 	info->sub_term_reason.ptr = info->buf_ + total;
310 	pj_strncpy(&info->sub_term_reason, &buddy->term_reason,
311 		   sizeof(info->buf_) - total);
312 	total += info->sub_term_reason.slen;
313     } else {
314 	info->sub_state_name = "NULL";
315 	info->sub_term_reason = pj_str("");
316     }
317 
318     unlock_buddy(&lck);
319     return PJ_SUCCESS;
320 }
321 
322 /*
323  * Set the user data associated with the buddy object.
324  */
pjsua_buddy_set_user_data(pjsua_buddy_id buddy_id,void * user_data)325 PJ_DEF(pj_status_t) pjsua_buddy_set_user_data( pjsua_buddy_id buddy_id,
326 					       void *user_data)
327 {
328     struct buddy_lock lck;
329     pj_status_t status;
330 
331     PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
332 
333     status = lock_buddy("pjsua_buddy_set_user_data()", buddy_id, &lck, 0);
334     if (status != PJ_SUCCESS)
335 	return status;
336 
337     pjsua_var.buddy[buddy_id].user_data = user_data;
338 
339     unlock_buddy(&lck);
340 
341     return PJ_SUCCESS;
342 }
343 
344 
345 /*
346  * Get the user data associated with the budy object.
347  */
pjsua_buddy_get_user_data(pjsua_buddy_id buddy_id)348 PJ_DEF(void*) pjsua_buddy_get_user_data(pjsua_buddy_id buddy_id)
349 {
350     struct buddy_lock lck;
351     pj_status_t status;
352     void *user_data;
353 
354     PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), NULL);
355 
356     status = lock_buddy("pjsua_buddy_get_user_data()", buddy_id, &lck, 0);
357     if (status != PJ_SUCCESS)
358 	return NULL;
359 
360     user_data = pjsua_var.buddy[buddy_id].user_data;
361 
362     unlock_buddy(&lck);
363 
364     return user_data;
365 }
366 
367 
368 /*
369  * Reset buddy descriptor.
370  */
reset_buddy(pjsua_buddy_id id)371 static void reset_buddy(pjsua_buddy_id id)
372 {
373     pj_pool_t *pool = pjsua_var.buddy[id].pool;
374     pj_bzero(&pjsua_var.buddy[id], sizeof(pjsua_var.buddy[id]));
375     pjsua_var.buddy[id].pool = pool;
376     pjsua_var.buddy[id].index = id;
377 }
378 
379 
380 /*
381  * Add new buddy.
382  */
pjsua_buddy_add(const pjsua_buddy_config * cfg,pjsua_buddy_id * p_buddy_id)383 PJ_DEF(pj_status_t) pjsua_buddy_add( const pjsua_buddy_config *cfg,
384 				     pjsua_buddy_id *p_buddy_id)
385 {
386     pjsip_name_addr *url;
387     pjsua_buddy *buddy;
388     pjsip_sip_uri *sip_uri;
389     int index;
390     pj_str_t tmp;
391 
392     PJ_ASSERT_RETURN(pjsua_var.buddy_cnt <=
393 			PJ_ARRAY_SIZE(pjsua_var.buddy),
394 		     PJ_ETOOMANY);
395 
396     PJ_LOG(4,(THIS_FILE, "Adding buddy: %.*s",
397 	      (int)cfg->uri.slen, cfg->uri.ptr));
398     pj_log_push_indent();
399 
400     PJSUA_LOCK();
401 
402     /* Find empty slot */
403     for (index=0; index<(int)PJ_ARRAY_SIZE(pjsua_var.buddy); ++index) {
404 	if (pjsua_var.buddy[index].uri.slen == 0)
405 	    break;
406     }
407 
408     /* Expect to find an empty slot */
409     if (index == PJ_ARRAY_SIZE(pjsua_var.buddy)) {
410 	PJSUA_UNLOCK();
411 	/* This shouldn't happen */
412 	pj_assert(!"index < PJ_ARRAY_SIZE(pjsua_var.buddy)");
413 	pj_log_pop_indent();
414 	return PJ_ETOOMANY;
415     }
416 
417     buddy = &pjsua_var.buddy[index];
418 
419     /* Create pool for this buddy */
420     if (buddy->pool) {
421 	pj_pool_reset(buddy->pool);
422     } else {
423 	char name[PJ_MAX_OBJ_NAME];
424 	pj_ansi_snprintf(name, sizeof(name), "buddy%03d", index);
425 	buddy->pool = pjsua_pool_create(name, 512, 256);
426     }
427 
428     /* Init buffers for presence subscription status */
429     buddy->term_reason.ptr = (char*)
430 			     pj_pool_alloc(buddy->pool,
431 					   PJSUA_BUDDY_SUB_TERM_REASON_LEN);
432 
433     /* Get name and display name for buddy */
434     pj_strdup_with_null(buddy->pool, &tmp, &cfg->uri);
435     url = (pjsip_name_addr*)pjsip_parse_uri(buddy->pool, tmp.ptr, tmp.slen,
436 					    PJSIP_PARSE_URI_AS_NAMEADDR);
437 
438     if (url == NULL) {
439 	pjsua_perror(THIS_FILE, "Unable to add buddy", PJSIP_EINVALIDURI);
440 	pj_pool_release(buddy->pool);
441 	buddy->pool = NULL;
442 	PJSUA_UNLOCK();
443 	pj_log_pop_indent();
444 	return PJSIP_EINVALIDURI;
445     }
446 
447     /* Only support SIP schemes */
448     if (!PJSIP_URI_SCHEME_IS_SIP(url) && !PJSIP_URI_SCHEME_IS_SIPS(url)) {
449 	pj_pool_release(buddy->pool);
450 	buddy->pool = NULL;
451 	PJSUA_UNLOCK();
452 	pj_log_pop_indent();
453 	return PJSIP_EINVALIDSCHEME;
454     }
455 
456     /* Reset buddy, to make sure everything is cleared with default
457      * values
458      */
459     reset_buddy(index);
460 
461     /* Save URI */
462     pjsua_var.buddy[index].uri = tmp;
463 
464     sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(url->uri);
465     pjsua_var.buddy[index].name = sip_uri->user;
466     pjsua_var.buddy[index].display = url->display;
467     pjsua_var.buddy[index].host = sip_uri->host;
468     pjsua_var.buddy[index].port = sip_uri->port;
469     pjsua_var.buddy[index].monitor = cfg->subscribe;
470     if (pjsua_var.buddy[index].port == 0)
471 	pjsua_var.buddy[index].port = 5060;
472 
473     /* Save user data */
474     pjsua_var.buddy[index].user_data = (void*)cfg->user_data;
475 
476     if (p_buddy_id)
477 	*p_buddy_id = index;
478 
479     pjsua_var.buddy_cnt++;
480 
481     PJSUA_UNLOCK();
482 
483     PJ_LOG(4,(THIS_FILE, "Buddy %d added.", index));
484 
485     pjsua_buddy_subscribe_pres(index, cfg->subscribe);
486 
487     pj_log_pop_indent();
488     return PJ_SUCCESS;
489 }
490 
491 
492 /*
493  * Delete buddy.
494  */
pjsua_buddy_del(pjsua_buddy_id buddy_id)495 PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id)
496 {
497     struct buddy_lock lck;
498     pj_status_t status;
499 
500     PJ_ASSERT_RETURN(buddy_id>=0 &&
501 			buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
502 		     PJ_EINVAL);
503 
504     if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
505 	return PJ_SUCCESS;
506     }
507 
508     status = lock_buddy("pjsua_buddy_del()", buddy_id, &lck, 0);
509     if (status != PJ_SUCCESS)
510 	return status;
511 
512     PJ_LOG(4,(THIS_FILE, "Buddy %d: deleting..", buddy_id));
513     pj_log_push_indent();
514 
515     /* Unsubscribe presence */
516     pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE);
517 
518     /* Not interested with further events for this buddy */
519     if (pjsua_var.buddy[buddy_id].sub) {
520 	pjsip_evsub_set_mod_data(pjsua_var.buddy[buddy_id].sub,
521 				 pjsua_var.mod.id, NULL);
522     }
523 
524     /* Remove buddy */
525     pjsua_var.buddy[buddy_id].uri.slen = 0;
526     pjsua_var.buddy_cnt--;
527 
528     /* Clear timer */
529     if (pjsua_var.buddy[buddy_id].timer.id) {
530 	pjsua_cancel_timer(&pjsua_var.buddy[buddy_id].timer);
531 	pjsua_var.buddy[buddy_id].timer.id = PJ_FALSE;
532     }
533 
534     /* Reset buddy struct */
535     reset_buddy(buddy_id);
536 
537     unlock_buddy(&lck);
538     pj_log_pop_indent();
539     return PJ_SUCCESS;
540 }
541 
542 
543 /*
544  * Enable/disable buddy's presence monitoring.
545  */
pjsua_buddy_subscribe_pres(pjsua_buddy_id buddy_id,pj_bool_t subscribe)546 PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id buddy_id,
547 						pj_bool_t subscribe)
548 {
549     struct buddy_lock lck;
550     pj_status_t status;
551 
552     PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
553 
554     status = lock_buddy("pjsua_buddy_subscribe_pres()", buddy_id, &lck, 0);
555     if (status != PJ_SUCCESS)
556 	return status;
557 
558     pj_log_push_indent();
559 
560     lck.buddy->monitor = subscribe;
561 
562     pjsua_buddy_update_pres(buddy_id);
563 
564     unlock_buddy(&lck);
565     pj_log_pop_indent();
566     return PJ_SUCCESS;
567 }
568 
569 
570 /*
571  * Update buddy's presence.
572  */
pjsua_buddy_update_pres(pjsua_buddy_id buddy_id)573 PJ_DEF(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id)
574 {
575     struct buddy_lock lck;
576     pj_status_t status;
577 
578     PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL);
579 
580     status = lock_buddy("pjsua_buddy_update_pres()", buddy_id, &lck, 0);
581     if (status != PJ_SUCCESS)
582 	return status;
583 
584     PJ_LOG(4,(THIS_FILE, "Buddy %d: updating presence..", buddy_id));
585     pj_log_push_indent();
586 
587     /* Is this an unsubscribe request? */
588     if (!lck.buddy->monitor) {
589 	unsubscribe_buddy_presence(buddy_id);
590 	unlock_buddy(&lck);
591 	pj_log_pop_indent();
592 	return PJ_SUCCESS;
593     }
594 
595     /* Ignore if presence is already active for the buddy */
596     if (lck.buddy->sub) {
597 	unlock_buddy(&lck);
598 	pj_log_pop_indent();
599 	return PJ_SUCCESS;
600     }
601 
602     /* Initiate presence subscription */
603     subscribe_buddy_presence(buddy_id);
604 
605     unlock_buddy(&lck);
606     pj_log_pop_indent();
607     return PJ_SUCCESS;
608 }
609 
610 
611 /*
612  * Dump presence subscriptions to log file.
613  */
pjsua_pres_dump(pj_bool_t verbose)614 PJ_DEF(void) pjsua_pres_dump(pj_bool_t verbose)
615 {
616     unsigned acc_id;
617     unsigned i;
618 
619 
620     PJSUA_LOCK();
621 
622     /*
623      * When no detail is required, just dump number of server and client
624      * subscriptions.
625      */
626     if (verbose == PJ_FALSE) {
627 
628 	int count = 0;
629 
630 	for (acc_id=0; acc_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
631 
632 	    if (!pjsua_var.acc[acc_id].valid)
633 		continue;
634 
635 	    if (!pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) {
636 		struct pjsua_srv_pres *uapres;
637 
638 		uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
639 		while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
640 		    ++count;
641 		    uapres = uapres->next;
642 		}
643 	    }
644 	}
645 
646 	PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d",
647 		  count));
648 
649 	count = 0;
650 
651 	for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
652 	    if (pjsua_var.buddy[i].uri.slen == 0)
653 		continue;
654 	    if (pjsua_var.buddy[i].sub) {
655 		++count;
656 	    }
657 	}
658 
659 	PJ_LOG(3,(THIS_FILE, "Number of client/UAC subscriptions: %d",
660 		  count));
661 	PJSUA_UNLOCK();
662 	return;
663     }
664 
665 
666     /*
667      * Dumping all server (UAS) subscriptions
668      */
669     PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
670 
671     for (acc_id=0; acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
672 
673 	if (!pjsua_var.acc[acc_id].valid)
674 	    continue;
675 
676 	PJ_LOG(3,(THIS_FILE, "  %.*s",
677 		  (int)pjsua_var.acc[acc_id].cfg.id.slen,
678 		  pjsua_var.acc[acc_id].cfg.id.ptr));
679 
680 	if (pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) {
681 
682 	    PJ_LOG(3,(THIS_FILE, "  - none - "));
683 
684 	} else {
685 	    struct pjsua_srv_pres *uapres;
686 
687 	    uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
688 	    while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
689 
690 		PJ_LOG(3,(THIS_FILE, "    %10s %s",
691 			  pjsip_evsub_get_state_name(uapres->sub),
692 			  uapres->remote));
693 
694 		uapres = uapres->next;
695 	    }
696 	}
697     }
698 
699     /*
700      * Dumping all client (UAC) subscriptions
701      */
702     PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:"));
703 
704     if (pjsua_var.buddy_cnt == 0) {
705 
706 	PJ_LOG(3,(THIS_FILE, "  - no buddy list - "));
707 
708     } else {
709 	for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
710 
711 	    if (pjsua_var.buddy[i].uri.slen == 0)
712 		continue;
713 
714 	    if (pjsua_var.buddy[i].sub) {
715 		PJ_LOG(3,(THIS_FILE, "  %10s %.*s",
716 			  pjsip_evsub_get_state_name(pjsua_var.buddy[i].sub),
717 			  (int)pjsua_var.buddy[i].uri.slen,
718 			  pjsua_var.buddy[i].uri.ptr));
719 	    } else {
720 		PJ_LOG(3,(THIS_FILE, "  %10s %.*s",
721 			  "(null)",
722 			  (int)pjsua_var.buddy[i].uri.slen,
723 			  pjsua_var.buddy[i].uri.ptr));
724 	    }
725 	}
726     }
727 
728     PJSUA_UNLOCK();
729 }
730 
731 
732 /***************************************************************************
733  * Server subscription.
734  */
735 
736 /* Proto */
737 static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata);
738 
739 /* The module instance. */
740 static pjsip_module mod_pjsua_pres =
741 {
742     NULL, NULL,				/* prev, next.		*/
743     { "mod-pjsua-pres", 14 },		/* Name.		*/
744     -1,					/* Id			*/
745     PJSIP_MOD_PRIORITY_APPLICATION,	/* Priority	        */
746     NULL,				/* load()		*/
747     NULL,				/* start()		*/
748     NULL,				/* stop()		*/
749     NULL,				/* unload()		*/
750     &pres_on_rx_request,		/* on_rx_request()	*/
751     NULL,				/* on_rx_response()	*/
752     NULL,				/* on_tx_request.	*/
753     NULL,				/* on_tx_response()	*/
754     NULL,				/* on_tsx_state()	*/
755 
756 };
757 
758 
759 /* Callback called when *server* subscription state has changed. */
pres_evsub_on_srv_state(pjsip_evsub * sub,pjsip_event * event)760 static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event)
761 {
762     pjsua_srv_pres *uapres;
763 
764     PJ_UNUSED_ARG(event);
765 
766     PJSUA_LOCK();
767 
768     uapres = (pjsua_srv_pres*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
769     if (uapres) {
770 	pjsip_evsub_state state;
771 
772 	PJ_LOG(4,(THIS_FILE, "Server subscription to %s is %s",
773 		  uapres->remote, pjsip_evsub_get_state_name(sub)));
774 	pj_log_push_indent();
775 
776 	state = pjsip_evsub_get_state(sub);
777 
778 	if (pjsua_var.ua_cfg.cb.on_srv_subscribe_state) {
779 	    pj_str_t from;
780 
781 	    from = uapres->dlg->remote.info_str;
782 	    (*pjsua_var.ua_cfg.cb.on_srv_subscribe_state)(uapres->acc_id,
783 							  uapres, &from,
784 							  state, event);
785 	}
786 
787 	if (state == PJSIP_EVSUB_STATE_TERMINATED) {
788 	    pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
789 	    pj_list_erase(uapres);
790 	}
791 	pj_log_pop_indent();
792     }
793 
794     PJSUA_UNLOCK();
795 }
796 
797 /* This is called when request is received.
798  * We need to check for incoming SUBSCRIBE request.
799  */
pres_on_rx_request(pjsip_rx_data * rdata)800 static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
801 {
802     int acc_id;
803     pjsua_acc *acc;
804     pj_str_t contact;
805     pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
806     pjsua_srv_pres *uapres;
807     pjsip_evsub *sub;
808     pjsip_evsub_user pres_cb;
809     pjsip_dialog *dlg;
810     pjsip_status_code st_code;
811     pj_str_t reason;
812     pjsip_expires_hdr *expires_hdr;
813     pjsua_msg_data msg_data;
814     pj_status_t status;
815 
816     if (pjsip_method_cmp(req_method, pjsip_get_subscribe_method()) != 0)
817 	return PJ_FALSE;
818 
819     /* Incoming SUBSCRIBE: */
820 
821     /* Don't want to accept the request if shutdown is in progress */
822     if (pjsua_var.thread_quit_flag) {
823 	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
824 				      PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL,
825 				      NULL, NULL);
826 	return PJ_TRUE;
827     }
828 
829     PJSUA_LOCK();
830 
831     /* Find which account for the incoming request. */
832     acc_id = pjsua_acc_find_for_incoming(rdata);
833     if (acc_id == PJSUA_INVALID_ID) {
834 	PJ_LOG(2, (THIS_FILE,
835 		   "Unable to process incoming message %s "
836 		   "due to no available account",
837 		   pjsip_rx_data_get_info(rdata)));
838 
839 	PJSUA_UNLOCK();
840 	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
841 				      PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL,
842 				      NULL, NULL);
843 	pj_log_pop_indent();
844 	return PJ_TRUE;
845     }
846     acc = &pjsua_var.acc[acc_id];
847 
848     PJ_LOG(4,(THIS_FILE, "Creating server subscription, using account %d",
849 	      acc_id));
850     pj_log_push_indent();
851 
852     /* Create suitable Contact header */
853     if (acc->contact.slen) {
854 	contact = acc->contact;
855     } else {
856 	status = pjsua_acc_create_uas_contact(rdata->tp_info.pool, &contact,
857 					      acc_id, rdata);
858 	if (status != PJ_SUCCESS) {
859 	    pjsua_perror(THIS_FILE, "Unable to generate Contact header",
860 			 status);
861 	    PJSUA_UNLOCK();
862 	    pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL,
863 					  NULL, NULL);
864 	    pj_log_pop_indent();
865 	    return PJ_TRUE;
866 	}
867     }
868 
869     /* Create UAS dialog: */
870     status = pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(), rdata,
871 					       &contact, &dlg);
872     if (status != PJ_SUCCESS) {
873 	pjsua_perror(THIS_FILE,
874 		     "Unable to create UAS dialog for subscription",
875 		     status);
876 	PJSUA_UNLOCK();
877 	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL,
878 				      NULL, NULL);
879 	pj_log_pop_indent();
880 	return PJ_TRUE;
881     }
882 
883     if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
884         pjsip_dlg_set_via_sent_by(dlg, &acc->via_addr, acc->via_tp);
885     } else if (!pjsua_sip_acc_is_using_stun(acc_id)) {
886 	/* Choose local interface to use in Via if acc is not using
887 	 * STUN. See https://trac.pjsip.org/repos/ticket/1412
888 	 */
889 	char target_buf[PJSIP_MAX_URL_SIZE];
890 	pj_str_t target;
891 	pjsip_host_port via_addr;
892 	const void *via_tp;
893 
894 	target.ptr = target_buf;
895 	target.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
896 	                              dlg->target,
897 	                              target_buf, sizeof(target_buf));
898 	if (target.slen < 0) target.slen = 0;
899 
900 	if (pjsua_acc_get_uac_addr(acc_id, dlg->pool, &target,
901 				   &via_addr, NULL, NULL,
902 				   &via_tp) == PJ_SUCCESS)
903 	{
904 	    pjsip_dlg_set_via_sent_by(dlg, &via_addr,
905 				      (pjsip_transport*)via_tp);
906 	}
907     }
908 
909     /* Set credentials and preference. */
910     pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->cred_cnt, acc->cred);
911     pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref);
912 
913     /* Init callback: */
914     pj_bzero(&pres_cb, sizeof(pres_cb));
915     pres_cb.on_evsub_state = &pres_evsub_on_srv_state;
916 
917     /* Create server presence subscription: */
918     status = pjsip_pres_create_uas( dlg, &pres_cb, rdata, &sub);
919     if (status != PJ_SUCCESS) {
920 	int code = PJSIP_ERRNO_TO_SIP_STATUS(status);
921 	pjsip_tx_data *tdata;
922 
923 	pjsua_perror(THIS_FILE, "Unable to create server subscription",
924 		     status);
925 
926 	if (code==599 || code > 699 || code < 300) {
927 	    code = 400;
928 	}
929 
930 	status = pjsip_dlg_create_response(dlg, rdata, code, NULL, &tdata);
931 	if (status == PJ_SUCCESS) {
932 	    status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata),
933 					     tdata);
934 	}
935 
936 	pjsip_dlg_dec_lock(dlg);
937 	PJSUA_UNLOCK();
938 	pj_log_pop_indent();
939 	return PJ_TRUE;
940     }
941 
942     /* Subscription has been created, decrement & release dlg lock */
943     pjsip_dlg_dec_lock(dlg);
944 
945     /* If account is locked to specific transport, then lock dialog
946      * to this transport too.
947      */
948     if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
949 	pjsip_tpselector tp_sel;
950 
951 	pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
952 	pjsip_dlg_set_transport(dlg, &tp_sel);
953     }
954 
955     /* Attach our data to the subscription: */
956     uapres = PJ_POOL_ALLOC_T(dlg->pool, pjsua_srv_pres);
957     uapres->sub = sub;
958     uapres->remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE);
959     uapres->acc_id = acc_id;
960     uapres->dlg = dlg;
961     status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri,
962 			     uapres->remote, PJSIP_MAX_URL_SIZE);
963     if (status < 1)
964 	pj_ansi_strcpy(uapres->remote, "<-- url is too long-->");
965     else
966 	uapres->remote[status] = '\0';
967 
968     pjsip_evsub_add_header(sub, &acc->cfg.sub_hdr_list);
969     pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, uapres);
970 
971     /* Add server subscription to the list: */
972     pj_list_push_back(&pjsua_var.acc[acc_id].pres_srv_list, uapres);
973 
974 
975     /* Capture the value of Expires header. */
976     expires_hdr = (pjsip_expires_hdr*)
977     		  pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES,
978 				     NULL);
979     if (expires_hdr)
980 	uapres->expires = expires_hdr->ivalue;
981     else
982 	uapres->expires = PJSIP_EXPIRES_NOT_SPECIFIED;
983 
984     st_code = (pjsip_status_code)200;
985     reason = pj_str("OK");
986     pjsua_msg_data_init(&msg_data);
987 
988     /* Notify application callback, if any */
989     if (pjsua_var.ua_cfg.cb.on_incoming_subscribe) {
990 	pjsua_buddy_id buddy_id;
991 
992 	buddy_id = find_buddy(rdata->msg_info.from->uri);
993 
994 	(*pjsua_var.ua_cfg.cb.on_incoming_subscribe)(acc_id, uapres, buddy_id,
995 						     &dlg->remote.info_str,
996 						     rdata, &st_code, &reason,
997 						     &msg_data);
998     }
999 
1000     /* Handle rejection case */
1001     if (st_code >= 300) {
1002 	pjsip_tx_data *tdata;
1003 
1004 	/* Create response */
1005 	status = pjsip_dlg_create_response(dlg, rdata, st_code,
1006 					   &reason, &tdata);
1007 	if (status != PJ_SUCCESS) {
1008 	    pjsua_perror(THIS_FILE, "Error creating response",  status);
1009 	    pj_list_erase(uapres);
1010 	    pjsip_pres_terminate(sub, PJ_FALSE);
1011 	    PJSUA_UNLOCK();
1012 	    pj_log_pop_indent();
1013 	    return PJ_FALSE;
1014 	}
1015 
1016 	/* Add header list, if any */
1017 	pjsua_process_msg_data(tdata, &msg_data);
1018 
1019 	/* Send the response */
1020 	status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata),
1021 					 tdata);
1022 	if (status != PJ_SUCCESS) {
1023 	    pjsua_perror(THIS_FILE, "Error sending response",  status);
1024 	    /* This is not fatal */
1025 	}
1026 
1027 	/* Terminate presence subscription */
1028 	pj_list_erase(uapres);
1029 	pjsip_pres_terminate(sub, PJ_FALSE);
1030 	PJSUA_UNLOCK();
1031 	pj_log_pop_indent();
1032 	return PJ_TRUE;
1033     }
1034 
1035     /* Create and send 2xx response to the SUBSCRIBE request: */
1036     status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list);
1037     if (status != PJ_SUCCESS) {
1038 	pjsua_perror(THIS_FILE, "Unable to accept presence subscription",
1039 		     status);
1040 	pj_list_erase(uapres);
1041 	pjsip_pres_terminate(sub, PJ_FALSE);
1042 	PJSUA_UNLOCK();
1043 	pj_log_pop_indent();
1044 	return PJ_FALSE;
1045     }
1046 
1047     /* If code is 200, send NOTIFY now */
1048     if (st_code == 200) {
1049 	pjsua_pres_notify(acc_id, uapres, PJSIP_EVSUB_STATE_ACTIVE,
1050 			  NULL, NULL, PJ_TRUE, &msg_data);
1051     }
1052 
1053     /* Done: */
1054     PJSUA_UNLOCK();
1055     pj_log_pop_indent();
1056     return PJ_TRUE;
1057 }
1058 
1059 
1060 /*
1061  * Send NOTIFY.
1062  */
pjsua_pres_notify(pjsua_acc_id acc_id,pjsua_srv_pres * srv_pres,pjsip_evsub_state ev_state,const pj_str_t * state_str,const pj_str_t * reason,pj_bool_t with_body,const pjsua_msg_data * msg_data)1063 PJ_DEF(pj_status_t) pjsua_pres_notify( pjsua_acc_id acc_id,
1064 				       pjsua_srv_pres *srv_pres,
1065 				       pjsip_evsub_state ev_state,
1066 				       const pj_str_t *state_str,
1067 				       const pj_str_t *reason,
1068 				       pj_bool_t with_body,
1069 				       const pjsua_msg_data *msg_data)
1070 {
1071     pjsua_acc *acc;
1072     pjsip_pres_status pres_status;
1073     pjsua_buddy_id buddy_id;
1074     pjsip_tx_data *tdata;
1075     pj_status_t status;
1076 
1077     /* Check parameters */
1078     PJ_ASSERT_RETURN(acc_id!=-1 && srv_pres, PJ_EINVAL);
1079 
1080     /* Check that account ID is valid */
1081     PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
1082 		     PJ_EINVAL);
1083     /* Check that account is valid */
1084     PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
1085 
1086     PJ_LOG(4,(THIS_FILE, "Acc %d: sending NOTIFY for srv_pres=0x%p..",
1087 	      acc_id, (int)(pj_ssize_t)srv_pres));
1088     pj_log_push_indent();
1089 
1090     PJSUA_LOCK();
1091 
1092     acc = &pjsua_var.acc[acc_id];
1093 
1094     /* Check that the server presence subscription is still valid */
1095     if (pj_list_find_node(&acc->pres_srv_list, srv_pres) == NULL) {
1096 	/* Subscription has been terminated */
1097 	PJSUA_UNLOCK();
1098 	pj_log_pop_indent();
1099 	return PJ_EINVALIDOP;
1100     }
1101 
1102     /* Set our online status: */
1103     pj_bzero(&pres_status, sizeof(pres_status));
1104     pres_status.info_cnt = 1;
1105     pres_status.info[0].basic_open = acc->online_status;
1106     pres_status.info[0].id = acc->cfg.pidf_tuple_id;
1107     //Both pjsua_var.local_uri and pjsua_var.contact_uri are enclosed in "<" and ">"
1108     //causing XML parsing to fail.
1109     //pres_status.info[0].contact = pjsua_var.local_uri;
1110     /* add RPID information */
1111     pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
1112 	      sizeof(pjrpid_element));
1113 
1114     pjsip_pres_set_status(srv_pres->sub, &pres_status);
1115 
1116     /* Check expires value. If it's zero, send our presense state but
1117      * set subscription state to TERMINATED.
1118      */
1119     if (srv_pres->expires == 0)
1120 	ev_state = PJSIP_EVSUB_STATE_TERMINATED;
1121 
1122     /* Create and send the NOTIFY to active subscription: */
1123     status = pjsip_pres_notify(srv_pres->sub, ev_state, state_str,
1124 			       reason, &tdata);
1125     if (status == PJ_SUCCESS) {
1126 	/* Force removal of message body if msg_body==FALSE */
1127 	if (!with_body) {
1128 	    tdata->msg->body = NULL;
1129 	}
1130 	pjsua_process_msg_data(tdata, msg_data);
1131 	status = pjsip_pres_send_request( srv_pres->sub, tdata);
1132     }
1133 
1134     if (status != PJ_SUCCESS) {
1135 	pjsua_perror(THIS_FILE, "Unable to create/send NOTIFY",
1136 		     status);
1137 	pj_list_erase(srv_pres);
1138 	pjsip_pres_terminate(srv_pres->sub, PJ_FALSE);
1139 	PJSUA_UNLOCK();
1140 	pj_log_pop_indent();
1141 	return status;
1142     }
1143 
1144 
1145     /* Subscribe to buddy's presence if we're not subscribed */
1146     buddy_id = find_buddy(srv_pres->dlg->remote.info->uri);
1147     if (buddy_id != PJSUA_INVALID_ID) {
1148 	pjsua_buddy *b = &pjsua_var.buddy[buddy_id];
1149 	if (b->monitor && b->sub == NULL) {
1150 	    PJ_LOG(4,(THIS_FILE, "Received SUBSCRIBE from buddy %d, "
1151 		      "activating outgoing subscription", buddy_id));
1152 	    subscribe_buddy_presence(buddy_id);
1153 	}
1154     }
1155 
1156     PJSUA_UNLOCK();
1157     pj_log_pop_indent();
1158     return PJ_SUCCESS;
1159 }
1160 
1161 
1162 /*
1163  * Client presence publication callback.
1164  */
publish_cb(struct pjsip_publishc_cbparam * param)1165 static void publish_cb(struct pjsip_publishc_cbparam *param)
1166 {
1167     pjsua_acc *acc = (pjsua_acc*) param->token;
1168 
1169     if (param->code/100 != 2 || param->status != PJ_SUCCESS) {
1170 
1171 	pjsip_publishc_destroy(param->pubc);
1172 	acc->publish_sess = NULL;
1173 
1174 	if (param->status != PJ_SUCCESS) {
1175 	    char errmsg[PJ_ERR_MSG_SIZE];
1176 
1177 	    pj_strerror(param->status, errmsg, sizeof(errmsg));
1178 	    PJ_LOG(1,(THIS_FILE,
1179 		      "Client publication (PUBLISH) failed, status=%d, msg=%s",
1180 		       param->status, errmsg));
1181 	} else if (param->code == 412) {
1182 	    /* 412 (Conditional Request Failed)
1183 	     * The PUBLISH refresh has failed, retry with new one.
1184 	     */
1185 	    pjsua_pres_init_publish_acc(acc->index);
1186 
1187 	} else {
1188 	    PJ_LOG(1,(THIS_FILE,
1189 		      "Client publication (PUBLISH) failed (%d/%.*s)",
1190 		       param->code, (int)param->reason.slen,
1191 		       param->reason.ptr));
1192 	}
1193 
1194     } else {
1195 	if (param->expiration < 1) {
1196 	    /* Could happen if server "forgot" to include Expires header
1197 	     * in the response. We will not renew, so destroy the pubc.
1198 	     */
1199 	    pjsip_publishc_destroy(param->pubc);
1200 	    acc->publish_sess = NULL;
1201 	}
1202     }
1203 }
1204 
1205 
1206 /*
1207  * Send PUBLISH request.
1208  */
send_publish(int acc_id,pj_bool_t active)1209 static pj_status_t send_publish(int acc_id, pj_bool_t active)
1210 {
1211     pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
1212     pjsua_acc *acc = &pjsua_var.acc[acc_id];
1213     pjsip_pres_status pres_status;
1214     pjsip_tx_data *tdata;
1215     pj_status_t status;
1216 
1217     PJ_LOG(5,(THIS_FILE, "Acc %d: sending %sPUBLISH..",
1218 	      acc_id, (active ? "" : "un-")));
1219     pj_log_push_indent();
1220 
1221     /* Create PUBLISH request */
1222     if (active) {
1223 	char *bpos;
1224 	pj_str_t entity;
1225 
1226 	status = pjsip_publishc_publish(acc->publish_sess, PJ_TRUE, &tdata);
1227 	if (status != PJ_SUCCESS) {
1228 	    pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status);
1229 	    goto on_error;
1230 	}
1231 
1232 	/* Set our online status: */
1233 	pj_bzero(&pres_status, sizeof(pres_status));
1234 	pres_status.info_cnt = 1;
1235 	pres_status.info[0].basic_open = acc->online_status;
1236 	pres_status.info[0].id = acc->cfg.pidf_tuple_id;
1237 	/* .. including RPID information */
1238 	pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
1239 		  sizeof(pjrpid_element));
1240 
1241 	/* Be careful not to send PIDF with presence entity ID containing
1242 	 * "<" character.
1243 	 */
1244 	if ((bpos=pj_strchr(&acc_cfg->id, '<')) != NULL) {
1245 	    char *epos = pj_strchr(&acc_cfg->id, '>');
1246 	    if (epos - bpos < 2) {
1247 		pj_assert(!"Unexpected invalid URI");
1248 		status = PJSIP_EINVALIDURI;
1249 		goto on_error;
1250 	    }
1251 	    entity.ptr = bpos+1;
1252 	    entity.slen = epos - bpos - 1;
1253 	} else {
1254 	    entity = acc_cfg->id;
1255 	}
1256 
1257 	/* Create and add PIDF message body */
1258 	status = pjsip_pres_create_pidf(tdata->pool, &pres_status,
1259 					&entity, &tdata->msg->body);
1260 	if (status != PJ_SUCCESS) {
1261 	    pjsua_perror(THIS_FILE, "Error creating PIDF for PUBLISH request",
1262 			 status);
1263 	    pjsip_tx_data_dec_ref(tdata);
1264 	    goto on_error;
1265 	}
1266     } else {
1267 	status = pjsip_publishc_unpublish(acc->publish_sess, &tdata);
1268 	if (status != PJ_SUCCESS) {
1269 	    pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status);
1270 	    goto on_error;
1271 	}
1272     }
1273 
1274     /* Add headers etc */
1275     pjsua_process_msg_data(tdata, NULL);
1276 
1277     /* Set Via sent-by */
1278     if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
1279         pjsip_publishc_set_via_sent_by(acc->publish_sess, &acc->via_addr,
1280                                        acc->via_tp);
1281     } else if (!pjsua_sip_acc_is_using_stun(acc_id)) {
1282 	/* Choose local interface to use in Via if acc is not using
1283 	 * STUN. See https://trac.pjsip.org/repos/ticket/1412
1284 	 */
1285 	pjsip_host_port via_addr;
1286 	const void *via_tp;
1287 
1288 	if (pjsua_acc_get_uac_addr(acc_id, acc->pool, &acc_cfg->id,
1289 				   &via_addr, NULL, NULL,
1290 				   &via_tp) == PJ_SUCCESS)
1291         {
1292 	    pjsip_publishc_set_via_sent_by(acc->publish_sess, &via_addr,
1293 	                                   (pjsip_transport*)via_tp);
1294         }
1295     }
1296 
1297     /* Send the PUBLISH request */
1298     status = pjsip_publishc_send(acc->publish_sess, tdata);
1299     if (status == PJ_EPENDING) {
1300 	PJ_LOG(3,(THIS_FILE, "Previous request is in progress, "
1301 		  "PUBLISH request is queued"));
1302     } else if (status != PJ_SUCCESS) {
1303 	pjsua_perror(THIS_FILE, "Error sending PUBLISH request", status);
1304 	goto on_error;
1305     }
1306 
1307     acc->publish_state = acc->online_status;
1308     pj_log_pop_indent();
1309     return PJ_SUCCESS;
1310 
1311 on_error:
1312     if (acc->publish_sess) {
1313 	pjsip_publishc_destroy(acc->publish_sess);
1314 	acc->publish_sess = NULL;
1315     }
1316     pj_log_pop_indent();
1317     return status;
1318 }
1319 
1320 
1321 /* Create client publish session */
pjsua_pres_init_publish_acc(int acc_id)1322 pj_status_t pjsua_pres_init_publish_acc(int acc_id)
1323 {
1324     const pj_str_t STR_PRESENCE = { "presence", 8 };
1325     pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
1326     pjsua_acc *acc = &pjsua_var.acc[acc_id];
1327     pj_status_t status;
1328 
1329     /* Create and init client publication session */
1330     if (acc_cfg->publish_enabled) {
1331 
1332 	/* Create client publication */
1333 	status = pjsip_publishc_create(pjsua_var.endpt, &acc_cfg->publish_opt,
1334 				       acc, &publish_cb,
1335 				       &acc->publish_sess);
1336 	if (status != PJ_SUCCESS) {
1337 	    acc->publish_sess = NULL;
1338 	    return status;
1339 	}
1340 
1341 	/* Initialize client publication */
1342 	status = pjsip_publishc_init(acc->publish_sess, &STR_PRESENCE,
1343 				     &acc_cfg->id, &acc_cfg->id,
1344 				     &acc_cfg->id,
1345 				     PJSUA_PUBLISH_EXPIRATION);
1346 	if (status != PJ_SUCCESS) {
1347 	    acc->publish_sess = NULL;
1348 	    return status;
1349 	}
1350 
1351 	/* Add credential for authentication */
1352 	if (acc->cred_cnt) {
1353 	    pjsip_publishc_set_credentials(acc->publish_sess, acc->cred_cnt,
1354 					   acc->cred);
1355 	}
1356 
1357 	/* Set route-set */
1358 	pjsip_publishc_set_route_set(acc->publish_sess, &acc->route_set);
1359 
1360 	/* Send initial PUBLISH request */
1361 	if (acc->online_status != 0) {
1362 	    status = send_publish(acc_id, PJ_TRUE);
1363 	    if (status != PJ_SUCCESS)
1364 		return status;
1365 	}
1366 
1367     } else {
1368 	acc->publish_sess = NULL;
1369     }
1370 
1371     return PJ_SUCCESS;
1372 }
1373 
1374 
1375 /* Init presence for account */
pjsua_pres_init_acc(int acc_id)1376 pj_status_t pjsua_pres_init_acc(int acc_id)
1377 {
1378     pjsua_acc *acc = &pjsua_var.acc[acc_id];
1379 
1380     /* Init presence subscription */
1381     pj_list_init(&acc->pres_srv_list);
1382 
1383     return PJ_SUCCESS;
1384 }
1385 
1386 
1387 /* Unpublish presence publication */
pjsua_pres_unpublish(pjsua_acc * acc,unsigned flags)1388 void pjsua_pres_unpublish(pjsua_acc *acc, unsigned flags)
1389 {
1390     if (acc->publish_sess) {
1391 	pjsua_acc_config *acc_cfg = &acc->cfg;
1392 
1393 	acc->online_status = PJ_FALSE;
1394 	acc_cfg->publish_enabled = PJ_FALSE;
1395 
1396 	if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
1397 	    send_publish(acc->index, PJ_FALSE);
1398 	}
1399 
1400 	/* By ticket #364, don't destroy the session yet (let the callback
1401 	   destroy it)
1402 	if (acc->publish_sess) {
1403 	    pjsip_publishc_destroy(acc->publish_sess);
1404 	    acc->publish_sess = NULL;
1405 	}
1406 	*/
1407     }
1408 }
1409 
1410 /* Terminate server subscription for the account */
pjsua_pres_delete_acc(int acc_id,unsigned flags)1411 void pjsua_pres_delete_acc(int acc_id, unsigned flags)
1412 {
1413     pjsua_acc *acc = &pjsua_var.acc[acc_id];
1414     pjsua_srv_pres *uapres;
1415 
1416     uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
1417 
1418     /* Notify all subscribers that we're no longer available */
1419     while (uapres != &acc->pres_srv_list) {
1420 
1421 	pjsip_pres_status pres_status;
1422 	pj_str_t reason = { "noresource", 10 };
1423 	pjsua_srv_pres *next;
1424 	pjsip_tx_data *tdata;
1425 
1426 	next = uapres->next;
1427 
1428 	pjsip_pres_get_status(uapres->sub, &pres_status);
1429 
1430 	pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status;
1431 	pjsip_pres_set_status(uapres->sub, &pres_status);
1432 
1433 	if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
1434 	    if (pjsip_pres_notify(uapres->sub,
1435 				  PJSIP_EVSUB_STATE_TERMINATED, NULL,
1436 				  &reason, &tdata)==PJ_SUCCESS)
1437 	    {
1438 		pjsip_pres_send_request(uapres->sub, tdata);
1439 	    }
1440 	} else {
1441 	    pjsip_pres_terminate(uapres->sub, PJ_FALSE);
1442 	}
1443 
1444 	uapres = next;
1445     }
1446 
1447     /* Clear server presence subscription list because account might be reused
1448      * later. */
1449     pj_list_init(&acc->pres_srv_list);
1450 
1451     /* Terminate presence publication, if any */
1452     if (acc->cfg.publish_enabled)
1453     	pjsua_pres_unpublish(acc, flags);
1454 }
1455 
1456 
1457 /* Update server subscription (e.g. when our online status has changed) */
pjsua_pres_update_acc(int acc_id,pj_bool_t force)1458 void pjsua_pres_update_acc(int acc_id, pj_bool_t force)
1459 {
1460     pjsua_acc *acc = &pjsua_var.acc[acc_id];
1461     pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
1462     pjsua_srv_pres *uapres;
1463 
1464     uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
1465 
1466     while (uapres != &acc->pres_srv_list) {
1467 
1468 	pjsip_pres_status pres_status;
1469 	pjsip_tx_data *tdata;
1470 
1471 	pjsip_pres_get_status(uapres->sub, &pres_status);
1472 
1473 	/* Only send NOTIFY once subscription is active. Some subscriptions
1474 	 * may still be in NULL (when app is adding a new buddy while in the
1475 	 * on_incoming_subscribe() callback) or PENDING (when user approval is
1476 	 * being requested) state and we don't send NOTIFY to these subs until
1477 	 * the user accepted the request.
1478 	 */
1479 	if (pjsip_evsub_get_state(uapres->sub)==PJSIP_EVSUB_STATE_ACTIVE &&
1480 	    (force || pres_status.info[0].basic_open != acc->online_status))
1481 	{
1482 
1483 	    pres_status.info[0].basic_open = acc->online_status;
1484 	    pj_memcpy(&pres_status.info[0].rpid, &acc->rpid,
1485 		      sizeof(pjrpid_element));
1486 
1487 	    pjsip_pres_set_status(uapres->sub, &pres_status);
1488 
1489 	    if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS) {
1490 		pjsua_process_msg_data(tdata, NULL);
1491 		pjsip_pres_send_request(uapres->sub, tdata);
1492 	    }
1493 	}
1494 
1495 	uapres = uapres->next;
1496     }
1497 
1498     /* Send PUBLISH if required. We only do this when we have a PUBLISH
1499      * session. If we don't have a PUBLISH session, then it could be
1500      * that we're waiting until registration has completed before we
1501      * send the first PUBLISH.
1502      */
1503     if (acc_cfg->publish_enabled && acc->publish_sess) {
1504 	if (force || acc->publish_state != acc->online_status) {
1505 	    send_publish(acc_id, PJ_TRUE);
1506 	}
1507     }
1508 }
1509 
1510 
1511 
1512 /***************************************************************************
1513  * Client subscription.
1514  */
1515 
buddy_timer_cb(pj_timer_heap_t * th,pj_timer_entry * entry)1516 static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry)
1517 {
1518     pjsua_buddy *buddy = (pjsua_buddy*)entry->user_data;
1519 
1520     PJ_UNUSED_ARG(th);
1521 
1522     entry->id = PJ_FALSE;
1523     pjsua_buddy_update_pres(buddy->index);
1524 }
1525 
1526 /* Reschedule subscription refresh timer or terminate the subscription
1527  * refresh timer for the specified buddy.
1528  */
buddy_resubscribe(pjsua_buddy * buddy,pj_bool_t resched,unsigned msec_interval)1529 static void buddy_resubscribe(pjsua_buddy *buddy, pj_bool_t resched,
1530 			      unsigned msec_interval)
1531 {
1532     if (buddy->timer.id) {
1533 	pjsua_cancel_timer(&buddy->timer);
1534 	buddy->timer.id = PJ_FALSE;
1535     }
1536 
1537     if (resched) {
1538 	pj_time_val delay;
1539 
1540 	PJ_LOG(4,(THIS_FILE,
1541 	          "Resubscribing buddy id %u in %u ms (reason: %.*s)",
1542 		  buddy->index, msec_interval,
1543 		  (int)buddy->term_reason.slen,
1544 		  buddy->term_reason.ptr));
1545 
1546 	pj_timer_entry_init(&buddy->timer, 0, buddy, &buddy_timer_cb);
1547 	delay.sec = 0;
1548 	delay.msec = msec_interval;
1549 	pj_time_val_normalize(&delay);
1550 
1551 	if (pjsua_schedule_timer(&buddy->timer, &delay)==PJ_SUCCESS)
1552 	    buddy->timer.id = PJ_TRUE;
1553     }
1554 }
1555 
1556 /* Callback called when *client* subscription state has changed. */
pjsua_evsub_on_state(pjsip_evsub * sub,pjsip_event * event)1557 static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
1558 {
1559     pjsua_buddy *buddy;
1560 
1561     PJ_UNUSED_ARG(event);
1562 
1563     /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
1564      *   a dialog attached to it, lock_buddy() will use the dialog
1565      *   lock, which we are currently holding!
1566      */
1567     buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1568     if (buddy) {
1569 	PJ_LOG(4,(THIS_FILE,
1570 		  "Presence subscription to %.*s is %s",
1571 		  (int)pjsua_var.buddy[buddy->index].uri.slen,
1572 		  pjsua_var.buddy[buddy->index].uri.ptr,
1573 		  pjsip_evsub_get_state_name(sub)));
1574 	pj_log_push_indent();
1575 
1576 	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
1577 	    int resub_delay = -1;
1578 
1579 	    if (buddy->term_reason.ptr == NULL) {
1580 		buddy->term_reason.ptr = (char*)
1581 					 pj_pool_alloc(buddy->pool,
1582 					   PJSUA_BUDDY_SUB_TERM_REASON_LEN);
1583 	    }
1584 	    pj_strncpy(&buddy->term_reason,
1585 		       pjsip_evsub_get_termination_reason(sub),
1586 		       PJSUA_BUDDY_SUB_TERM_REASON_LEN);
1587 
1588 	    buddy->term_code = 200;
1589 
1590 	    /* Determine whether to resubscribe automatically */
1591 	    if (event && event->type==PJSIP_EVENT_TSX_STATE) {
1592 		const pjsip_transaction *tsx = event->body.tsx_state.tsx;
1593 		if (pjsip_method_cmp(&tsx->method,
1594 				     pjsip_get_subscribe_method())==0)
1595 		{
1596 		    buddy->term_code = tsx->status_code;
1597 		    switch (tsx->status_code) {
1598 		    case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST:
1599 			/* 481: we refreshed too late? resubscribe
1600 			 * immediately.
1601 			 */
1602 			/* But this must only happen when the 481 is received
1603 			 * on subscription refresh request. We MUST NOT try to
1604 			 * resubscribe automatically if the 481 is received
1605 			 * on the initial SUBSCRIBE (if server returns this
1606 			 * response for some reason).
1607 			 */
1608 			if (buddy->dlg->remote.contact)
1609 			    resub_delay = 500;
1610 			break;
1611 		    }
1612 		} else if (pjsip_method_cmp(&tsx->method,
1613 					    pjsip_get_notify_method())==0)
1614 		{
1615 		    if (pj_stricmp2(&buddy->term_reason, "deactivated")==0 ||
1616 			pj_stricmp2(&buddy->term_reason, "timeout")==0) {
1617 			/* deactivated: The subscription has been terminated,
1618 			 * but the subscriber SHOULD retry immediately with
1619 			 * a new subscription.
1620 			 */
1621 			/* timeout: The subscription has been terminated
1622 			 * because it was not refreshed before it expired.
1623 			 * Clients MAY re-subscribe immediately. The
1624 			 * "retry-after" parameter has no semantics for
1625 			 * "timeout".
1626 			 */
1627 			resub_delay = 500;
1628 		    }
1629 		    else if (pj_stricmp2(&buddy->term_reason, "probation")==0||
1630 			     pj_stricmp2(&buddy->term_reason, "giveup")==0) {
1631 			/* probation: The subscription has been terminated,
1632 			 * but the client SHOULD retry at some later time.
1633 			 * If a "retry-after" parameter is also present, the
1634 			 * client SHOULD wait at least the number of seconds
1635 			 * specified by that parameter before attempting to re-
1636 			 * subscribe.
1637 			 */
1638 			/* giveup: The subscription has been terminated because
1639 			 * the notifier could not obtain authorization in a
1640 			 * timely fashion.  If a "retry-after" parameter is
1641 			 * also present, the client SHOULD wait at least the
1642 			 * number of seconds specified by that parameter before
1643 			 * attempting to re-subscribe; otherwise, the client
1644 			 * MAY retry immediately, but will likely get put back
1645 			 * into pending state.
1646 			 */
1647 			const pjsip_sub_state_hdr *sub_hdr;
1648 			pj_str_t sub_state = { "Subscription-State", 18 };
1649 			const pjsip_msg *msg;
1650 
1651 			msg = event->body.tsx_state.src.rdata->msg_info.msg;
1652 			sub_hdr = (const pjsip_sub_state_hdr*)
1653 				  pjsip_msg_find_hdr_by_name(msg, &sub_state,
1654 							     NULL);
1655 			if (sub_hdr && sub_hdr->retry_after > 0)
1656 			    resub_delay = sub_hdr->retry_after * 1000;
1657 		    }
1658 
1659 		}
1660 	    }
1661 
1662 	    /* For other cases of subscription termination, if resubscribe
1663 	     * timer is not set, schedule with default expiration (plus minus
1664 	     * some random value, to avoid sending SUBSCRIBEs all at once)
1665 	     */
1666 	    if (resub_delay == -1) {
1667 		pj_assert(PJSUA_PRES_TIMER >= 3);
1668 		resub_delay = PJSUA_PRES_TIMER*1000 - 2500 + (pj_rand()%5000);
1669 	    }
1670 
1671 	    buddy_resubscribe(buddy, PJ_TRUE, resub_delay);
1672 
1673 	} else {
1674 	    /* This will clear the last termination code/reason */
1675 	    buddy->term_code = 0;
1676 	    buddy->term_reason.slen = 0;
1677 	}
1678 
1679 	/* Call callbacks */
1680 	if (pjsua_var.ua_cfg.cb.on_buddy_evsub_state)
1681 	    (*pjsua_var.ua_cfg.cb.on_buddy_evsub_state)(buddy->index, sub,
1682 							event);
1683 
1684 	if (pjsua_var.ua_cfg.cb.on_buddy_state)
1685 	    (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index);
1686 
1687 	/* Clear subscription */
1688 	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
1689 	    buddy->sub = NULL;
1690 	    buddy->status.info_cnt = 0;
1691 	    buddy->dlg = NULL;
1692 	    pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
1693 	}
1694 
1695 	pj_log_pop_indent();
1696     }
1697 }
1698 
1699 
1700 /* Callback when transaction state has changed. */
pjsua_evsub_on_tsx_state(pjsip_evsub * sub,pjsip_transaction * tsx,pjsip_event * event)1701 static void pjsua_evsub_on_tsx_state(pjsip_evsub *sub,
1702 				     pjsip_transaction *tsx,
1703 				     pjsip_event *event)
1704 {
1705     pjsua_buddy *buddy;
1706     pjsip_contact_hdr *contact_hdr;
1707 
1708     /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
1709      *   a dialog attached to it, lock_buddy() will use the dialog
1710      *   lock, which we are currently holding!
1711      */
1712     buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1713     if (!buddy) {
1714 	return;
1715     }
1716 
1717     /* We only use this to update buddy's Contact, when it's not
1718      * set.
1719      */
1720     if (buddy->contact.slen != 0) {
1721 	/* Contact already set */
1722 	return;
1723     }
1724 
1725     /* Only care about 2xx response to outgoing SUBSCRIBE */
1726     if (tsx->status_code/100 != 2 ||
1727 	tsx->role != PJSIP_UAC_ROLE ||
1728 	event->type != PJSIP_EVENT_RX_MSG ||
1729 	pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method())!=0)
1730     {
1731 	return;
1732     }
1733 
1734     /* Find contact header. */
1735     contact_hdr = (pjsip_contact_hdr*)
1736 		  pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg,
1737 				     PJSIP_H_CONTACT, NULL);
1738     if (!contact_hdr || !contact_hdr->uri) {
1739 	return;
1740     }
1741 
1742     buddy->contact.ptr = (char*)
1743 			 pj_pool_alloc(buddy->pool, PJSIP_MAX_URL_SIZE);
1744     buddy->contact.slen = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR,
1745 					   contact_hdr->uri,
1746 					   buddy->contact.ptr,
1747 					   PJSIP_MAX_URL_SIZE);
1748     if (buddy->contact.slen < 0)
1749 	buddy->contact.slen = 0;
1750 }
1751 
1752 
1753 /* Callback called when we receive NOTIFY */
pjsua_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)1754 static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub,
1755 				     pjsip_rx_data *rdata,
1756 				     int *p_st_code,
1757 				     pj_str_t **p_st_text,
1758 				     pjsip_hdr *res_hdr,
1759 				     pjsip_msg_body **p_body)
1760 {
1761     pjsua_buddy *buddy;
1762 
1763     /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
1764      *   a dialog attached to it, lock_buddy() will use the dialog
1765      *   lock, which we are currently holding!
1766      */
1767     buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
1768     if (buddy) {
1769 	/* Update our info. */
1770 	pjsip_pres_get_status(sub, &buddy->status);
1771     }
1772 
1773     /* The default is to send 200 response to NOTIFY.
1774      * Just leave it there..
1775      */
1776     PJ_UNUSED_ARG(rdata);
1777     PJ_UNUSED_ARG(p_st_code);
1778     PJ_UNUSED_ARG(p_st_text);
1779     PJ_UNUSED_ARG(res_hdr);
1780     PJ_UNUSED_ARG(p_body);
1781 }
1782 
1783 
1784 /* It does what it says.. */
subscribe_buddy_presence(pjsua_buddy_id buddy_id)1785 static void subscribe_buddy_presence(pjsua_buddy_id buddy_id)
1786 {
1787     pjsip_evsub_user pres_callback;
1788     pj_pool_t *tmp_pool = NULL;
1789     pjsua_buddy *buddy;
1790     int acc_id;
1791     pjsua_acc *acc;
1792     pj_str_t contact;
1793     pjsip_tx_data *tdata;
1794     pj_status_t status;
1795 
1796     /* Event subscription callback. */
1797     pj_bzero(&pres_callback, sizeof(pres_callback));
1798     pres_callback.on_evsub_state = &pjsua_evsub_on_state;
1799     pres_callback.on_tsx_state = &pjsua_evsub_on_tsx_state;
1800     pres_callback.on_rx_notify = &pjsua_evsub_on_rx_notify;
1801 
1802     buddy = &pjsua_var.buddy[buddy_id];
1803     acc_id = pjsua_acc_find_for_outgoing(&buddy->uri);
1804 
1805     acc = &pjsua_var.acc[acc_id];
1806 
1807     PJ_LOG(4,(THIS_FILE, "Buddy %d: subscribing presence,using account %d..",
1808 	      buddy_id, acc_id));
1809     pj_log_push_indent();
1810 
1811     /* Generate suitable Contact header unless one is already set in
1812      * the account
1813      */
1814     if (acc->contact.slen) {
1815 	contact = acc->contact;
1816     } else {
1817 	tmp_pool = pjsua_pool_create("tmpbuddy", 512, 256);
1818 
1819 	status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
1820 					      acc_id, &buddy->uri);
1821 	if (status != PJ_SUCCESS) {
1822 	    pjsua_perror(THIS_FILE, "Unable to generate Contact header",
1823 		         status);
1824 	    pj_pool_release(tmp_pool);
1825 	    pj_log_pop_indent();
1826 	    return;
1827 	}
1828     }
1829 
1830     /* Create UAC dialog */
1831     status = pjsip_dlg_create_uac( pjsip_ua_instance(),
1832 				   &acc->cfg.id,
1833 				   &contact,
1834 				   &buddy->uri,
1835 				   NULL, &buddy->dlg);
1836     if (status != PJ_SUCCESS) {
1837 	pjsua_perror(THIS_FILE, "Unable to create dialog",
1838 		     status);
1839 	if (tmp_pool) pj_pool_release(tmp_pool);
1840 	pj_log_pop_indent();
1841 	return;
1842     }
1843 
1844     /* Increment the dialog's lock otherwise when presence session creation
1845      * fails the dialog will be destroyed prematurely.
1846      */
1847     pjsip_dlg_inc_lock(buddy->dlg);
1848 
1849     if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
1850         pjsip_dlg_set_via_sent_by(buddy->dlg, &acc->via_addr, acc->via_tp);
1851     } else if (!pjsua_sip_acc_is_using_stun(acc_id)) {
1852 	/* Choose local interface to use in Via if acc is not using
1853 	 * STUN. See https://trac.pjsip.org/repos/ticket/1412
1854 	 */
1855 	pjsip_host_port via_addr;
1856 	const void *via_tp;
1857 
1858 	if (pjsua_acc_get_uac_addr(acc_id, buddy->dlg->pool, &buddy->uri,
1859 				   &via_addr, NULL, NULL,
1860 				   &via_tp) == PJ_SUCCESS)
1861         {
1862 	    pjsip_dlg_set_via_sent_by(buddy->dlg, &via_addr,
1863 				      (pjsip_transport*)via_tp);
1864         }
1865     }
1866 
1867 
1868     status = pjsip_pres_create_uac( buddy->dlg, &pres_callback,
1869 				    PJSIP_EVSUB_NO_EVENT_ID, &buddy->sub);
1870     if (status != PJ_SUCCESS) {
1871 	buddy->sub = NULL;
1872 	pjsua_perror(THIS_FILE, "Unable to create presence client",
1873 		     status);
1874 	/* This should destroy the dialog since there's no session
1875 	 * referencing it
1876 	 */
1877 	if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
1878 	if (tmp_pool) pj_pool_release(tmp_pool);
1879 	pj_log_pop_indent();
1880 	return;
1881     }
1882 
1883     /* If account is locked to specific transport, then lock dialog
1884      * to this transport too.
1885      */
1886     if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
1887 	pjsip_tpselector tp_sel;
1888 
1889 	pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
1890 	pjsip_dlg_set_transport(buddy->dlg, &tp_sel);
1891     }
1892 
1893     /* Set route-set */
1894     if (!pj_list_empty(&acc->route_set)) {
1895 	pjsip_dlg_set_route_set(buddy->dlg, &acc->route_set);
1896     }
1897 
1898     /* Set credentials */
1899     if (acc->cred_cnt) {
1900 	pjsip_auth_clt_set_credentials( &buddy->dlg->auth_sess,
1901 					acc->cred_cnt, acc->cred);
1902     }
1903 
1904     /* Set authentication preference */
1905     pjsip_auth_clt_set_prefs(&buddy->dlg->auth_sess, &acc->cfg.auth_pref);
1906 
1907     pjsip_evsub_add_header(buddy->sub, &acc->cfg.sub_hdr_list);
1908     pjsip_evsub_set_mod_data(buddy->sub, pjsua_var.mod.id, buddy);
1909 
1910     status = pjsip_pres_initiate(buddy->sub, PJSIP_EXPIRES_NOT_SPECIFIED,
1911     				 &tdata);
1912     if (status != PJ_SUCCESS) {
1913 	if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
1914 	if (buddy->sub) {
1915 	    pjsip_pres_terminate(buddy->sub, PJ_FALSE);
1916 	}
1917 	buddy->sub = NULL;
1918 	pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE",
1919 		     status);
1920 	if (tmp_pool) pj_pool_release(tmp_pool);
1921 	pj_log_pop_indent();
1922 	return;
1923     }
1924 
1925     pjsua_process_msg_data(tdata, NULL);
1926 
1927     status = pjsip_pres_send_request(buddy->sub, tdata);
1928     if (status != PJ_SUCCESS) {
1929 	if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg);
1930 	if (buddy->sub) {
1931 	    pjsip_pres_terminate(buddy->sub, PJ_FALSE);
1932 	}
1933 	buddy->sub = NULL;
1934 	pjsua_perror(THIS_FILE, "Unable to send initial SUBSCRIBE",
1935 		     status);
1936 	if (tmp_pool) pj_pool_release(tmp_pool);
1937 	pj_log_pop_indent();
1938 	return;
1939     }
1940 
1941     pjsip_dlg_dec_lock(buddy->dlg);
1942     if (tmp_pool) pj_pool_release(tmp_pool);
1943     pj_log_pop_indent();
1944 }
1945 
1946 
1947 /* It does what it says... */
unsubscribe_buddy_presence(pjsua_buddy_id buddy_id)1948 static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id)
1949 {
1950     pjsua_buddy *buddy;
1951     pjsip_tx_data *tdata;
1952     pj_status_t status;
1953 
1954     buddy = &pjsua_var.buddy[buddy_id];
1955 
1956     if (buddy->sub == NULL)
1957 	return;
1958 
1959     if (pjsip_evsub_get_state(buddy->sub) == PJSIP_EVSUB_STATE_TERMINATED) {
1960 	buddy->sub = NULL;
1961 	return;
1962     }
1963 
1964     PJ_LOG(5,(THIS_FILE, "Buddy %d: unsubscribing..", buddy_id));
1965     pj_log_push_indent();
1966 
1967     status = pjsip_pres_initiate( buddy->sub, 0, &tdata);
1968     if (status == PJ_SUCCESS) {
1969 	pjsua_process_msg_data(tdata, NULL);
1970 	status = pjsip_pres_send_request( buddy->sub, tdata );
1971     }
1972 
1973     if (status != PJ_SUCCESS && buddy->sub) {
1974 	pjsip_pres_terminate(buddy->sub, PJ_FALSE);
1975 	buddy->sub = NULL;
1976 	pjsua_perror(THIS_FILE, "Unable to unsubscribe presence",
1977 		     status);
1978     }
1979 
1980     pj_log_pop_indent();
1981 }
1982 
1983 /* It does what it says.. */
refresh_client_subscriptions(void)1984 static pj_status_t refresh_client_subscriptions(void)
1985 {
1986     unsigned i;
1987     pj_status_t status;
1988 
1989     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
1990 	struct buddy_lock lck;
1991 
1992 	if (!pjsua_buddy_is_valid(i))
1993 	    continue;
1994 
1995 	status = lock_buddy("refresh_client_subscriptions()", i, &lck, 0);
1996 	if (status != PJ_SUCCESS)
1997 	    return status;
1998 
1999 	if (pjsua_var.buddy[i].monitor && !pjsua_var.buddy[i].sub) {
2000 	    subscribe_buddy_presence(i);
2001 
2002 	} else if (!pjsua_var.buddy[i].monitor && pjsua_var.buddy[i].sub) {
2003 	    unsubscribe_buddy_presence(i);
2004 
2005 	}
2006 
2007 	unlock_buddy(&lck);
2008     }
2009 
2010     return PJ_SUCCESS;
2011 }
2012 
2013 /***************************************************************************
2014  * MWI
2015  */
2016 /* Callback called when *client* subscription state has changed. */
mwi_evsub_on_state(pjsip_evsub * sub,pjsip_event * event)2017 static void mwi_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
2018 {
2019     pjsua_acc *acc;
2020 
2021     PJ_UNUSED_ARG(event);
2022 
2023     /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has
2024      *   a dialog attached to it, lock_buddy() will use the dialog
2025      *   lock, which we are currently holding!
2026      */
2027     acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
2028     if (!acc)
2029 	return;
2030 
2031     PJ_LOG(4,(THIS_FILE,
2032 	      "MWI subscription for %.*s is %s",
2033 	      (int)acc->cfg.id.slen, acc->cfg.id.ptr,
2034 	      pjsip_evsub_get_state_name(sub)));
2035 
2036     /* Call callback */
2037     if (pjsua_var.ua_cfg.cb.on_mwi_state) {
2038 	(*pjsua_var.ua_cfg.cb.on_mwi_state)(acc->index, sub);
2039     }
2040 
2041     if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
2042 	/* Clear subscription */
2043 	acc->mwi_dlg = NULL;
2044 	acc->mwi_sub = NULL;
2045 	pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
2046 
2047     }
2048 }
2049 
2050 /* Callback called when we receive NOTIFY */
mwi_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)2051 static void mwi_evsub_on_rx_notify(pjsip_evsub *sub,
2052 				   pjsip_rx_data *rdata,
2053 				   int *p_st_code,
2054 				   pj_str_t **p_st_text,
2055 				   pjsip_hdr *res_hdr,
2056 				   pjsip_msg_body **p_body)
2057 {
2058     pjsua_mwi_info mwi_info;
2059     pjsua_acc *acc;
2060 
2061     PJ_UNUSED_ARG(p_st_code);
2062     PJ_UNUSED_ARG(p_st_text);
2063     PJ_UNUSED_ARG(res_hdr);
2064     PJ_UNUSED_ARG(p_body);
2065 
2066     acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
2067     if (!acc)
2068 	return;
2069 
2070     /* Construct mwi_info */
2071     pj_bzero(&mwi_info, sizeof(mwi_info));
2072     mwi_info.evsub = sub;
2073     mwi_info.rdata = rdata;
2074 
2075     PJ_LOG(4,(THIS_FILE, "MWI got NOTIFY.."));
2076     pj_log_push_indent();
2077 
2078     /* Call callback */
2079     if (pjsua_var.ua_cfg.cb.on_mwi_info) {
2080 	(*pjsua_var.ua_cfg.cb.on_mwi_info)(acc->index, &mwi_info);
2081     }
2082 
2083     pj_log_pop_indent();
2084 }
2085 
2086 
2087 /* Event subscription callback. */
2088 static pjsip_evsub_user mwi_cb =
2089 {
2090     &mwi_evsub_on_state,
2091     NULL,   /* on_tsx_state: not interested */
2092     NULL,   /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless
2093 	     * we want to authenticate
2094 	     */
2095 
2096     &mwi_evsub_on_rx_notify,
2097 
2098     NULL,   /* on_client_refresh: Use default behaviour, which is to
2099 	     * refresh client subscription. */
2100 
2101     NULL,   /* on_server_timeout: Use default behaviour, which is to send
2102 	     * NOTIFY to terminate.
2103 	     */
2104 };
2105 
pjsua_start_mwi(pjsua_acc_id acc_id,pj_bool_t force_renew)2106 pj_status_t pjsua_start_mwi(pjsua_acc_id acc_id, pj_bool_t force_renew)
2107 {
2108     pjsua_acc *acc;
2109     pj_pool_t *tmp_pool = NULL;
2110     pj_str_t contact;
2111     pjsip_tx_data *tdata;
2112     pj_status_t status = PJ_SUCCESS;
2113 
2114     PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc)
2115                      && pjsua_var.acc[acc_id].valid, PJ_EINVAL);
2116 
2117     acc = &pjsua_var.acc[acc_id];
2118 
2119     if (!acc->cfg.mwi_enabled || !acc->regc) {
2120 	if (acc->mwi_sub) {
2121 	    /* Terminate MWI subscription */
2122 	    pjsip_evsub *sub = acc->mwi_sub;
2123 
2124 	    /* Detach sub from this account */
2125 	    acc->mwi_sub = NULL;
2126 	    acc->mwi_dlg = NULL;
2127 	    pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
2128 
2129 	    /* Unsubscribe */
2130 	    status = pjsip_mwi_initiate(sub, 0, &tdata);
2131 	    if (status == PJ_SUCCESS) {
2132 		status = pjsip_mwi_send_request(sub, tdata);
2133 	    }
2134 	}
2135 	return status;
2136     }
2137 
2138     /* Subscription is already active */
2139     if (acc->mwi_sub) {
2140 	if (!force_renew)
2141 	    return PJ_SUCCESS;
2142 
2143 	/* Update MWI subscription */
2144 	pj_assert(acc->mwi_dlg);
2145 	pjsip_dlg_inc_lock(acc->mwi_dlg);
2146 
2147 	status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata);
2148 	if (status == PJ_SUCCESS) {
2149 	    pjsua_process_msg_data(tdata, NULL);
2150 	    status = pjsip_pres_send_request(acc->mwi_sub, tdata);
2151 	}
2152 
2153 	pjsip_dlg_dec_lock(acc->mwi_dlg);
2154 	return status;
2155     }
2156 
2157     PJ_LOG(4,(THIS_FILE, "Starting MWI subscription.."));
2158     pj_log_push_indent();
2159 
2160     /* Generate suitable Contact header unless one is already set in
2161      * the account
2162      */
2163     if (acc->contact.slen) {
2164 	contact = acc->contact;
2165     } else {
2166 	tmp_pool = pjsua_pool_create("tmpmwi", 512, 256);
2167 	status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
2168 					      acc->index, &acc->cfg.id);
2169 	if (status != PJ_SUCCESS) {
2170 	    pjsua_perror(THIS_FILE, "Unable to generate Contact header",
2171 		         status);
2172 	    goto on_return;
2173 	}
2174     }
2175 
2176     /* Create UAC dialog */
2177     status = pjsip_dlg_create_uac( pjsip_ua_instance(),
2178 				   &acc->cfg.id,
2179 				   &contact,
2180 				   &acc->cfg.id,
2181 				   NULL, &acc->mwi_dlg);
2182     if (status != PJ_SUCCESS) {
2183 	pjsua_perror(THIS_FILE, "Unable to create dialog", status);
2184 	goto on_return;
2185     }
2186 
2187     /* Increment the dialog's lock otherwise when presence session creation
2188      * fails the dialog will be destroyed prematurely.
2189      */
2190     pjsip_dlg_inc_lock(acc->mwi_dlg);
2191 
2192     if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
2193         pjsip_dlg_set_via_sent_by(acc->mwi_dlg, &acc->via_addr, acc->via_tp);
2194     } else if (!pjsua_sip_acc_is_using_stun(acc_id)) {
2195    	/* Choose local interface to use in Via if acc is not using
2196    	 * STUN. See https://trac.pjsip.org/repos/ticket/1412
2197    	 */
2198    	pjsip_host_port via_addr;
2199    	const void *via_tp;
2200 
2201    	if (pjsua_acc_get_uac_addr(acc_id, acc->mwi_dlg->pool, &acc->cfg.id,
2202    				   &via_addr, NULL, NULL,
2203    				   &via_tp) == PJ_SUCCESS)
2204    	{
2205    	    pjsip_dlg_set_via_sent_by(acc->mwi_dlg, &via_addr,
2206    	                              (pjsip_transport*)via_tp);
2207    	}
2208     }
2209 
2210     /* Create UAC subscription */
2211     status = pjsip_mwi_create_uac(acc->mwi_dlg, &mwi_cb,
2212 				  PJSIP_EVSUB_NO_EVENT_ID, &acc->mwi_sub);
2213     if (status != PJ_SUCCESS) {
2214 	pjsua_perror(THIS_FILE, "Error creating MWI subscription", status);
2215 	if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
2216 	goto on_return;
2217     }
2218 
2219     /* If account is locked to specific transport, then lock dialog
2220      * to this transport too.
2221      */
2222     if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
2223 	pjsip_tpselector tp_sel;
2224 
2225 	pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
2226 	pjsip_dlg_set_transport(acc->mwi_dlg, &tp_sel);
2227     }
2228 
2229     /* Set route-set */
2230     if (!pj_list_empty(&acc->route_set)) {
2231 	pjsip_dlg_set_route_set(acc->mwi_dlg, &acc->route_set);
2232     }
2233 
2234     /* Set credentials */
2235     if (acc->cred_cnt) {
2236 	pjsip_auth_clt_set_credentials( &acc->mwi_dlg->auth_sess,
2237 					acc->cred_cnt, acc->cred);
2238     }
2239 
2240     /* Set authentication preference */
2241     pjsip_auth_clt_set_prefs(&acc->mwi_dlg->auth_sess, &acc->cfg.auth_pref);
2242 
2243     pjsip_evsub_set_mod_data(acc->mwi_sub, pjsua_var.mod.id, acc);
2244 
2245     status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata);
2246     if (status != PJ_SUCCESS) {
2247 	if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
2248 	if (acc->mwi_sub) {
2249 	    pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE);
2250 	}
2251 	acc->mwi_sub = NULL;
2252 	acc->mwi_dlg = NULL;
2253 	pjsua_perror(THIS_FILE, "Unable to create initial MWI SUBSCRIBE",
2254 		     status);
2255 	goto on_return;
2256     }
2257 
2258     pjsua_process_msg_data(tdata, NULL);
2259 
2260     status = pjsip_pres_send_request(acc->mwi_sub, tdata);
2261     if (status != PJ_SUCCESS) {
2262 	if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg);
2263 	if (acc->mwi_sub) {
2264 	    pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE);
2265 	}
2266 	acc->mwi_sub = NULL;
2267 	acc->mwi_dlg = NULL;
2268 	pjsua_perror(THIS_FILE, "Unable to send initial MWI SUBSCRIBE",
2269 		     status);
2270 	goto on_return;
2271     }
2272 
2273     pjsip_dlg_dec_lock(acc->mwi_dlg);
2274 
2275 on_return:
2276     if (tmp_pool) pj_pool_release(tmp_pool);
2277 
2278     pj_log_pop_indent();
2279     return status;
2280 }
2281 
2282 
2283 /***************************************************************************
2284  * Unsolicited MWI
2285  */
unsolicited_mwi_on_rx_request(pjsip_rx_data * rdata)2286 static pj_bool_t unsolicited_mwi_on_rx_request(pjsip_rx_data *rdata)
2287 {
2288     pjsip_msg *msg = rdata->msg_info.msg;
2289     pj_str_t EVENT_HDR  = { "Event", 5 };
2290     pj_str_t MWI = { "message-summary", 15 };
2291     pjsip_event_hdr *eh;
2292 
2293     if (pjsip_method_cmp(&msg->line.req.method, pjsip_get_notify_method())!=0)
2294     {
2295 	/* Only interested with NOTIFY request */
2296 	return PJ_FALSE;
2297     }
2298 
2299     eh = (pjsip_event_hdr*) pjsip_msg_find_hdr_by_name(msg, &EVENT_HDR, NULL);
2300     if (!eh) {
2301 	/* Something wrong with the request, it has no Event hdr */
2302 	return PJ_FALSE;
2303     }
2304 
2305     if (pj_stricmp(&eh->event_type, &MWI) != 0) {
2306 	/* Not MWI event */
2307 	return PJ_FALSE;
2308     }
2309 
2310     PJ_LOG(4,(THIS_FILE, "Got unsolicited NOTIFY from %s:%d..",
2311 	      rdata->pkt_info.src_name, rdata->pkt_info.src_port));
2312     pj_log_push_indent();
2313 
2314     /* Got unsolicited MWI request, respond with 200/OK first */
2315     pjsip_endpt_respond(pjsua_get_pjsip_endpt(), NULL, rdata, 200, NULL,
2316 			NULL, NULL, NULL);
2317 
2318 
2319     /* Call callback */
2320     if (pjsua_var.ua_cfg.cb.on_mwi_info) {
2321 	pjsua_acc_id acc_id;
2322 	pjsua_mwi_info mwi_info;
2323 
2324 	acc_id = pjsua_acc_find_for_incoming(rdata);
2325 
2326 	pj_bzero(&mwi_info, sizeof(mwi_info));
2327 	mwi_info.rdata = rdata;
2328 
2329 	(*pjsua_var.ua_cfg.cb.on_mwi_info)(acc_id, &mwi_info);
2330     }
2331 
2332     pj_log_pop_indent();
2333     return PJ_TRUE;
2334 }
2335 
2336 /* The module instance. */
2337 static pjsip_module pjsua_unsolicited_mwi_mod =
2338 {
2339     NULL, NULL,				/* prev, next.		*/
2340     { "mod-unsolicited-mwi", 19 },	/* Name.		*/
2341     -1,					/* Id			*/
2342     PJSIP_MOD_PRIORITY_APPLICATION,	/* Priority	        */
2343     NULL,				/* load()		*/
2344     NULL,				/* start()		*/
2345     NULL,				/* stop()		*/
2346     NULL,				/* unload()		*/
2347     &unsolicited_mwi_on_rx_request,	/* on_rx_request()	*/
2348     NULL,				/* on_rx_response()	*/
2349     NULL,				/* on_tx_request.	*/
2350     NULL,				/* on_tx_response()	*/
2351     NULL,				/* on_tsx_state()	*/
2352 };
2353 
enable_unsolicited_mwi(void)2354 static pj_status_t enable_unsolicited_mwi(void)
2355 {
2356     pj_status_t status;
2357 
2358     status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(),
2359 					 &pjsua_unsolicited_mwi_mod);
2360     if (status != PJ_SUCCESS)
2361 	pjsua_perror(THIS_FILE, "Error registering unsolicited MWI module",
2362 		     status);
2363 
2364     return status;
2365 }
2366 
2367 
2368 
2369 /***************************************************************************/
2370 
2371 /* Timer callback to re-create client subscription */
pres_timer_cb(pj_timer_heap_t * th,pj_timer_entry * entry)2372 static void pres_timer_cb(pj_timer_heap_t *th,
2373 			  pj_timer_entry *entry)
2374 {
2375     unsigned i;
2376     pj_time_val delay = { PJSUA_PRES_TIMER, 0 };
2377 
2378     entry->id = PJ_FALSE;
2379 
2380     /* Retry failed PUBLISH and MWI SUBSCRIBE requests */
2381     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
2382 	pjsua_acc *acc = &pjsua_var.acc[i];
2383 
2384 	/* Acc may not be ready yet, otherwise assertion will happen */
2385 	if (!pjsua_acc_is_valid(i))
2386 	    continue;
2387 
2388 	/* Retry PUBLISH */
2389 	if (acc->cfg.publish_enabled && acc->publish_sess==NULL)
2390 	    pjsua_pres_init_publish_acc(acc->index);
2391 
2392 	/* Re-subscribe MWI subscription if it's terminated prematurely */
2393 	if (acc->cfg.mwi_enabled && !acc->mwi_sub)
2394 	    pjsua_start_mwi(acc->index, PJ_FALSE);
2395     }
2396 
2397     /* #937: No need to do bulk client refresh, as buddies have their
2398      *       own individual timer now.
2399      */
2400     //refresh_client_subscriptions();
2401 
2402     pjsip_endpt_schedule_timer(pjsua_var.endpt, entry, &delay);
2403     entry->id = PJ_TRUE;
2404 
2405     PJ_UNUSED_ARG(th);
2406 }
2407 
2408 
2409 /*
2410  * Init presence
2411  */
pjsua_pres_init()2412 pj_status_t pjsua_pres_init()
2413 {
2414     unsigned i;
2415     pj_status_t status;
2416 
2417     status = pjsip_endpt_register_module( pjsua_var.endpt, &mod_pjsua_pres);
2418     if (status != PJ_SUCCESS) {
2419 	pjsua_perror(THIS_FILE, "Unable to register pjsua presence module",
2420 		     status);
2421     }
2422 
2423     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
2424 	reset_buddy(i);
2425     }
2426 
2427     return status;
2428 }
2429 
2430 
2431 /*
2432  * Start presence subsystem.
2433  */
pjsua_pres_start(void)2434 pj_status_t pjsua_pres_start(void)
2435 {
2436     /* Start presence timer to re-subscribe to buddy's presence when
2437      * subscription has failed.
2438      */
2439     if (pjsua_var.pres_timer.id == PJ_FALSE) {
2440 	pj_time_val pres_interval = {PJSUA_PRES_TIMER, 0};
2441 
2442 	pjsua_var.pres_timer.cb = &pres_timer_cb;
2443 	pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.pres_timer,
2444 				   &pres_interval);
2445 	pjsua_var.pres_timer.id = PJ_TRUE;
2446     }
2447 
2448     if (pjsua_var.ua_cfg.enable_unsolicited_mwi) {
2449 	pj_status_t status = enable_unsolicited_mwi();
2450 	if (status != PJ_SUCCESS)
2451 	    return status;
2452     }
2453 
2454     return PJ_SUCCESS;
2455 }
2456 
2457 
2458 /*
2459  * Shutdown presence.
2460  */
pjsua_pres_shutdown(unsigned flags)2461 void pjsua_pres_shutdown(unsigned flags)
2462 {
2463     unsigned i;
2464 
2465     PJ_LOG(4,(THIS_FILE, "Shutting down presence.."));
2466     pj_log_push_indent();
2467 
2468     if (pjsua_var.pres_timer.id != 0) {
2469 	pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.pres_timer);
2470 	pjsua_var.pres_timer.id = PJ_FALSE;
2471     }
2472 
2473     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
2474 	if (!pjsua_var.acc[i].valid)
2475 	    continue;
2476 	pjsua_pres_delete_acc(i, flags);
2477     }
2478 
2479     for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
2480 	pjsua_var.buddy[i].monitor = 0;
2481     }
2482 
2483     if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) {
2484 	refresh_client_subscriptions();
2485 
2486 	for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
2487 	    if (pjsua_var.acc[i].valid)
2488 		pjsua_pres_update_acc(i, PJ_FALSE);
2489 	}
2490     }
2491 
2492     pj_log_pop_indent();
2493 }
2494