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