From f3ab456a17af1c89a6e3be4d20c5944853df1cb0 Mon Sep 17 00:00:00 2001 From: "David M. Lee" Date: Mon, 7 Jan 2013 14:24:28 -0600 Subject: Import pjproject-2.0.1 --- pjsip/src/pjsua-lib/pjsua_pres.c | 2402 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 2402 insertions(+) create mode 100644 pjsip/src/pjsua-lib/pjsua_pres.c (limited to 'pjsip/src/pjsua-lib/pjsua_pres.c') diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c new file mode 100644 index 0000000..4551c91 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_pres.c @@ -0,0 +1,2402 @@ +/* $Id: pjsua_pres.c 4186 2012-06-29 01:37:50Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + + +#define THIS_FILE "pjsua_pres.c" + + +static void subscribe_buddy_presence(pjsua_buddy_id buddy_id); +static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id); + + +/* + * Find buddy. + */ +static pjsua_buddy_id find_buddy(const pjsip_uri *uri) +{ + const pjsip_sip_uri *sip_uri; + unsigned i; + + uri = (const pjsip_uri*) pjsip_uri_get_uri((pjsip_uri*)uri); + + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) + return PJSUA_INVALID_ID; + + sip_uri = (const pjsip_sip_uri*) uri; + + for (i=0; iuser, &b->name)==0 && + pj_stricmp(&sip_uri->host, &b->host)==0 && + (sip_uri->port==(int)b->port || (sip_uri->port==0 && b->port==5060))) + { + /* Match */ + return i; + } + } + + return PJSUA_INVALID_ID; +} + +#define LOCK_DIALOG 1 +#define LOCK_PJSUA 2 +#define LOCK_ALL (LOCK_DIALOG | LOCK_PJSUA) + +/* Buddy lock object */ +struct buddy_lock +{ + pjsua_buddy *buddy; + pjsip_dialog *dlg; + pj_uint8_t flag; +}; + +/* Acquire lock to the specified buddy_id */ +pj_status_t lock_buddy(const char *title, + pjsua_buddy_id buddy_id, + struct buddy_lock *lck, + unsigned _unused_) +{ + enum { MAX_RETRY=50 }; + pj_bool_t has_pjsua_lock = PJ_FALSE; + unsigned retry; + + PJ_UNUSED_ARG(_unused_); + + pj_bzero(lck, sizeof(*lck)); + + for (retry=0; retryflag = LOCK_PJSUA; + lck->buddy = &pjsua_var.buddy[buddy_id]; + + if (lck->buddy->dlg == NULL) + return PJ_SUCCESS; + + if (pjsip_dlg_try_inc_lock(lck->buddy->dlg) != PJ_SUCCESS) { + lck->flag = 0; + lck->buddy = NULL; + has_pjsua_lock = PJ_FALSE; + PJSUA_UNLOCK(); + pj_thread_sleep(retry/10); + continue; + } + + lck->dlg = lck->buddy->dlg; + lck->flag = LOCK_DIALOG; + PJSUA_UNLOCK(); + + break; + } + + if (lck->flag == 0) { + if (has_pjsua_lock == PJ_FALSE) + PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire PJSUA mutex " + "(possibly system has deadlocked) in %s", + title)); + else + PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire dialog mutex " + "(possibly system has deadlocked) in %s", + title)); + return PJ_ETIMEDOUT; + } + + return PJ_SUCCESS; +} + +/* Release buddy lock */ +static void unlock_buddy(struct buddy_lock *lck) +{ + if (lck->flag & LOCK_DIALOG) + pjsip_dlg_dec_lock(lck->dlg); + + if (lck->flag & LOCK_PJSUA) + PJSUA_UNLOCK(); +} + + +/* + * Get total number of buddies. + */ +PJ_DEF(unsigned) pjsua_get_buddy_count(void) +{ + return pjsua_var.buddy_cnt; +} + + +/* + * Find buddy. + */ +PJ_DEF(pjsua_buddy_id) pjsua_buddy_find(const pj_str_t *uri_str) +{ + pj_str_t input; + pj_pool_t *pool; + pjsip_uri *uri; + pjsua_buddy_id buddy_id; + + pool = pjsua_pool_create("buddyfind", 512, 512); + pj_strdup_with_null(pool, &input, uri_str); + + uri = pjsip_parse_uri(pool, input.ptr, input.slen, 0); + if (!uri) + buddy_id = PJSUA_INVALID_ID; + else { + PJSUA_LOCK(); + buddy_id = find_buddy(uri); + PJSUA_UNLOCK(); + } + + pj_pool_release(pool); + + return buddy_id; +} + + +/* + * Check if buddy ID is valid. + */ +PJ_DEF(pj_bool_t) pjsua_buddy_is_valid(pjsua_buddy_id buddy_id) +{ + return buddy_id>=0 && buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy) && + pjsua_var.buddy[buddy_id].uri.slen != 0; +} + + +/* + * Enum buddy IDs. + */ +PJ_DEF(pj_status_t) pjsua_enum_buddies( pjsua_buddy_id ids[], + unsigned *count) +{ + unsigned i, c; + + PJ_ASSERT_RETURN(ids && count, PJ_EINVAL); + + PJSUA_LOCK(); + + for (i=0, c=0; c<*count && iid = buddy->index; + if (pjsua_var.buddy[buddy_id].uri.slen == 0) { + unlock_buddy(&lck); + return PJ_SUCCESS; + } + + /* uri */ + info->uri.ptr = info->buf_ + total; + pj_strncpy(&info->uri, &buddy->uri, sizeof(info->buf_)-total); + total += info->uri.slen; + + /* contact */ + info->contact.ptr = info->buf_ + total; + pj_strncpy(&info->contact, &buddy->contact, sizeof(info->buf_)-total); + total += info->contact.slen; + + /* Presence status */ + pj_memcpy(&info->pres_status, &buddy->status, sizeof(pjsip_pres_status)); + + /* status and status text */ + if (buddy->sub == NULL || buddy->status.info_cnt==0) { + info->status = PJSUA_BUDDY_STATUS_UNKNOWN; + info->status_text = pj_str("?"); + } else if (pjsua_var.buddy[buddy_id].status.info[0].basic_open) { + info->status = PJSUA_BUDDY_STATUS_ONLINE; + + /* copy RPID information */ + info->rpid = buddy->status.info[0].rpid; + + if (info->rpid.note.slen) + info->status_text = info->rpid.note; + else + info->status_text = pj_str("Online"); + + } else { + info->status = PJSUA_BUDDY_STATUS_OFFLINE; + info->rpid = buddy->status.info[0].rpid; + + if (info->rpid.note.slen) + info->status_text = info->rpid.note; + else + info->status_text = pj_str("Offline"); + } + + /* monitor pres */ + info->monitor_pres = buddy->monitor; + + /* subscription state and termination reason */ + info->sub_term_code = buddy->term_code; + if (buddy->sub) { + info->sub_state = pjsip_evsub_get_state(buddy->sub); + info->sub_state_name = pjsip_evsub_get_state_name(buddy->sub); + if (info->sub_state == PJSIP_EVSUB_STATE_TERMINATED && + total < sizeof(info->buf_)) + { + info->sub_term_reason.ptr = info->buf_ + total; + pj_strncpy(&info->sub_term_reason, + pjsip_evsub_get_termination_reason(buddy->sub), + sizeof(info->buf_) - total); + total += info->sub_term_reason.slen; + } else { + info->sub_term_reason = pj_str(""); + } + } else if (total < sizeof(info->buf_)) { + info->sub_state_name = "NULL"; + info->sub_term_reason.ptr = info->buf_ + total; + pj_strncpy(&info->sub_term_reason, &buddy->term_reason, + sizeof(info->buf_) - total); + total += info->sub_term_reason.slen; + } else { + info->sub_state_name = "NULL"; + info->sub_term_reason = pj_str(""); + } + + unlock_buddy(&lck); + return PJ_SUCCESS; +} + +/* + * Set the user data associated with the buddy object. + */ +PJ_DEF(pj_status_t) pjsua_buddy_set_user_data( pjsua_buddy_id buddy_id, + void *user_data) +{ + struct buddy_lock lck; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL); + + status = lock_buddy("pjsua_buddy_set_user_data()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + pjsua_var.buddy[buddy_id].user_data = user_data; + + unlock_buddy(&lck); + + return PJ_SUCCESS; +} + + +/* + * Get the user data associated with the budy object. + */ +PJ_DEF(void*) pjsua_buddy_get_user_data(pjsua_buddy_id buddy_id) +{ + struct buddy_lock lck; + pj_status_t status; + void *user_data; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), NULL); + + status = lock_buddy("pjsua_buddy_get_user_data()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return NULL; + + user_data = pjsua_var.buddy[buddy_id].user_data; + + unlock_buddy(&lck); + + return user_data; +} + + +/* + * Reset buddy descriptor. + */ +static void reset_buddy(pjsua_buddy_id id) +{ + pj_pool_t *pool = pjsua_var.buddy[id].pool; + pj_bzero(&pjsua_var.buddy[id], sizeof(pjsua_var.buddy[id])); + pjsua_var.buddy[id].pool = pool; + pjsua_var.buddy[id].index = id; +} + + +/* + * Add new buddy. + */ +PJ_DEF(pj_status_t) pjsua_buddy_add( const pjsua_buddy_config *cfg, + pjsua_buddy_id *p_buddy_id) +{ + pjsip_name_addr *url; + pjsua_buddy *buddy; + pjsip_sip_uri *sip_uri; + int index; + pj_str_t tmp; + + PJ_ASSERT_RETURN(pjsua_var.buddy_cnt <= + PJ_ARRAY_SIZE(pjsua_var.buddy), + PJ_ETOOMANY); + + PJ_LOG(4,(THIS_FILE, "Adding buddy: %.*s", + (int)cfg->uri.slen, cfg->uri.ptr)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + /* Find empty slot */ + for (index=0; index<(int)PJ_ARRAY_SIZE(pjsua_var.buddy); ++index) { + if (pjsua_var.buddy[index].uri.slen == 0) + break; + } + + /* Expect to find an empty slot */ + if (index == PJ_ARRAY_SIZE(pjsua_var.buddy)) { + PJSUA_UNLOCK(); + /* This shouldn't happen */ + pj_assert(!"index < PJ_ARRAY_SIZE(pjsua_var.buddy)"); + pj_log_pop_indent(); + return PJ_ETOOMANY; + } + + buddy = &pjsua_var.buddy[index]; + + /* Create pool for this buddy */ + if (buddy->pool) { + pj_pool_reset(buddy->pool); + } else { + char name[PJ_MAX_OBJ_NAME]; + pj_ansi_snprintf(name, sizeof(name), "buddy%03d", index); + buddy->pool = pjsua_pool_create(name, 512, 256); + } + + /* Init buffers for presence subscription status */ + buddy->term_reason.ptr = (char*) + pj_pool_alloc(buddy->pool, + PJSUA_BUDDY_SUB_TERM_REASON_LEN); + + /* Get name and display name for buddy */ + pj_strdup_with_null(buddy->pool, &tmp, &cfg->uri); + url = (pjsip_name_addr*)pjsip_parse_uri(buddy->pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + + if (url == NULL) { + pjsua_perror(THIS_FILE, "Unable to add buddy", PJSIP_EINVALIDURI); + pj_pool_release(buddy->pool); + buddy->pool = NULL; + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJSIP_EINVALIDURI; + } + + /* Only support SIP schemes */ + if (!PJSIP_URI_SCHEME_IS_SIP(url) && !PJSIP_URI_SCHEME_IS_SIPS(url)) { + pj_pool_release(buddy->pool); + buddy->pool = NULL; + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJSIP_EINVALIDSCHEME; + } + + /* Reset buddy, to make sure everything is cleared with default + * values + */ + reset_buddy(index); + + /* Save URI */ + pjsua_var.buddy[index].uri = tmp; + + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(url->uri); + pjsua_var.buddy[index].name = sip_uri->user; + pjsua_var.buddy[index].display = url->display; + pjsua_var.buddy[index].host = sip_uri->host; + pjsua_var.buddy[index].port = sip_uri->port; + pjsua_var.buddy[index].monitor = cfg->subscribe; + if (pjsua_var.buddy[index].port == 0) + pjsua_var.buddy[index].port = 5060; + + /* Save user data */ + pjsua_var.buddy[index].user_data = (void*)cfg->user_data; + + if (p_buddy_id) + *p_buddy_id = index; + + pjsua_var.buddy_cnt++; + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "Buddy %d added.", index)); + + pjsua_buddy_subscribe_pres(index, cfg->subscribe); + + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Delete buddy. + */ +PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id) +{ + struct buddy_lock lck; + pj_status_t status; + + PJ_ASSERT_RETURN(buddy_id>=0 && + buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy), + PJ_EINVAL); + + if (pjsua_var.buddy[buddy_id].uri.slen == 0) { + return PJ_SUCCESS; + } + + status = lock_buddy("pjsua_buddy_del()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + PJ_LOG(4,(THIS_FILE, "Buddy %d: deleting..", buddy_id)); + pj_log_push_indent(); + + /* Unsubscribe presence */ + pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE); + + /* Not interested with further events for this buddy */ + if (pjsua_var.buddy[buddy_id].sub) { + pjsip_evsub_set_mod_data(pjsua_var.buddy[buddy_id].sub, + pjsua_var.mod.id, NULL); + } + + /* Remove buddy */ + pjsua_var.buddy[buddy_id].uri.slen = 0; + pjsua_var.buddy_cnt--; + + /* Clear timer */ + if (pjsua_var.buddy[buddy_id].timer.id) { + pjsua_cancel_timer(&pjsua_var.buddy[buddy_id].timer); + pjsua_var.buddy[buddy_id].timer.id = PJ_FALSE; + } + + /* Reset buddy struct */ + reset_buddy(buddy_id); + + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Enable/disable buddy's presence monitoring. + */ +PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id buddy_id, + pj_bool_t subscribe) +{ + struct buddy_lock lck; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL); + + status = lock_buddy("pjsua_buddy_subscribe_pres()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + PJ_LOG(4,(THIS_FILE, "Buddy %d: unsubscribing presence..", buddy_id)); + pj_log_push_indent(); + + lck.buddy->monitor = subscribe; + + pjsua_buddy_update_pres(buddy_id); + + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Update buddy's presence. + */ +PJ_DEF(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id) +{ + struct buddy_lock lck; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL); + + status = lock_buddy("pjsua_buddy_update_pres()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + PJ_LOG(4,(THIS_FILE, "Buddy %d: updating presence..", buddy_id)); + pj_log_push_indent(); + + /* Is this an unsubscribe request? */ + if (!lck.buddy->monitor) { + unsubscribe_buddy_presence(buddy_id); + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; + } + + /* Ignore if presence is already active for the buddy */ + if (lck.buddy->sub) { + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; + } + + /* Initiate presence subscription */ + subscribe_buddy_presence(buddy_id); + + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Dump presence subscriptions to log file. + */ +PJ_DEF(void) pjsua_pres_dump(pj_bool_t verbose) +{ + unsigned acc_id; + unsigned i; + + + PJSUA_LOCK(); + + /* + * When no detail is required, just dump number of server and client + * subscriptions. + */ + if (verbose == PJ_FALSE) { + + int count = 0; + + for (acc_id=0; acc_idnext; + } + } + } + + PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d", + count)); + + count = 0; + + for (i=0; isub), + uapres->remote)); + + uapres = uapres->next; + } + } + } + + /* + * Dumping all client (UAC) subscriptions + */ + PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:")); + + if (pjsua_var.buddy_cnt == 0) { + + PJ_LOG(3,(THIS_FILE, " - no buddy list - ")); + + } else { + for (i=0; iremote, pjsip_evsub_get_state_name(sub))); + pj_log_push_indent(); + + state = pjsip_evsub_get_state(sub); + + if (pjsua_var.ua_cfg.cb.on_srv_subscribe_state) { + pj_str_t from; + + from = uapres->dlg->remote.info_str; + (*pjsua_var.ua_cfg.cb.on_srv_subscribe_state)(uapres->acc_id, + uapres, &from, + state, event); + } + + if (state == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + pj_list_erase(uapres); + } + pj_log_pop_indent(); + } + + PJSUA_UNLOCK(); +} + +/* This is called when request is received. + * We need to check for incoming SUBSCRIBE request. + */ +static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) +{ + int acc_id; + pjsua_acc *acc; + pj_str_t contact; + pjsip_method *req_method = &rdata->msg_info.msg->line.req.method; + pjsua_srv_pres *uapres; + pjsip_evsub *sub; + pjsip_evsub_user pres_cb; + pjsip_dialog *dlg; + pjsip_status_code st_code; + pj_str_t reason; + pjsip_expires_hdr *expires_hdr; + pjsua_msg_data msg_data; + pj_status_t status; + + if (pjsip_method_cmp(req_method, pjsip_get_subscribe_method()) != 0) + return PJ_FALSE; + + /* Incoming SUBSCRIBE: */ + + /* Don't want to accept the request if shutdown is in progress */ + if (pjsua_var.thread_quit_flag) { + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, + PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL, + NULL, NULL); + return PJ_TRUE; + } + + PJSUA_LOCK(); + + /* Find which account for the incoming request. */ + acc_id = pjsua_acc_find_for_incoming(rdata); + acc = &pjsua_var.acc[acc_id]; + + PJ_LOG(4,(THIS_FILE, "Creating server subscription, using account %d", + acc_id)); + pj_log_push_indent(); + + /* Create suitable Contact header */ + if (acc->contact.slen) { + contact = acc->contact; + } else { + status = pjsua_acc_create_uas_contact(rdata->tp_info.pool, &contact, + acc_id, rdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + PJSUA_UNLOCK(); + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL, + NULL, NULL); + pj_log_pop_indent(); + return PJ_TRUE; + } + } + + /* Create UAS dialog: */ + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, + &contact, &dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to create UAS dialog for subscription", + status); + PJSUA_UNLOCK(); + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL, + NULL, NULL); + pj_log_pop_indent(); + return PJ_TRUE; + } + + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) + pjsip_dlg_set_via_sent_by(dlg, &acc->via_addr, acc->via_tp); + + /* Set credentials and preference. */ + pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->cred_cnt, acc->cred); + pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref); + + /* Init callback: */ + pj_bzero(&pres_cb, sizeof(pres_cb)); + pres_cb.on_evsub_state = &pres_evsub_on_srv_state; + + /* Create server presence subscription: */ + status = pjsip_pres_create_uas( dlg, &pres_cb, rdata, &sub); + if (status != PJ_SUCCESS) { + int code = PJSIP_ERRNO_TO_SIP_STATUS(status); + pjsip_tx_data *tdata; + + pjsua_perror(THIS_FILE, "Unable to create server subscription", + status); + + if (code==599 || code > 699 || code < 300) { + code = 400; + } + + status = pjsip_dlg_create_response(dlg, rdata, code, NULL, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), + tdata); + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_TRUE; + } + + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(dlg, &tp_sel); + } + + /* Attach our data to the subscription: */ + uapres = PJ_POOL_ALLOC_T(dlg->pool, pjsua_srv_pres); + uapres->sub = sub; + uapres->remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE); + uapres->acc_id = acc_id; + uapres->dlg = dlg; + status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri, + uapres->remote, PJSIP_MAX_URL_SIZE); + if (status < 1) + pj_ansi_strcpy(uapres->remote, "<-- url is too long-->"); + else + uapres->remote[status] = '\0'; + + pjsip_evsub_add_header(sub, &acc->cfg.sub_hdr_list); + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, uapres); + + /* Add server subscription to the list: */ + pj_list_push_back(&pjsua_var.acc[acc_id].pres_srv_list, uapres); + + + /* Capture the value of Expires header. */ + expires_hdr = (pjsip_expires_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, + NULL); + if (expires_hdr) + uapres->expires = expires_hdr->ivalue; + else + uapres->expires = -1; + + st_code = (pjsip_status_code)200; + reason = pj_str("OK"); + pjsua_msg_data_init(&msg_data); + + /* Notify application callback, if any */ + if (pjsua_var.ua_cfg.cb.on_incoming_subscribe) { + pjsua_buddy_id buddy_id; + + buddy_id = find_buddy(rdata->msg_info.from->uri); + + (*pjsua_var.ua_cfg.cb.on_incoming_subscribe)(acc_id, uapres, buddy_id, + &dlg->remote.info_str, + rdata, &st_code, &reason, + &msg_data); + } + + /* Handle rejection case */ + if (st_code >= 300) { + pjsip_tx_data *tdata; + + /* Create response */ + status = pjsip_dlg_create_response(dlg, rdata, st_code, + &reason, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating response", status); + pj_list_erase(uapres); + pjsip_pres_terminate(sub, PJ_FALSE); + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_FALSE; + } + + /* Add header list, if any */ + pjsua_process_msg_data(tdata, &msg_data); + + /* Send the response */ + status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), + tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error sending response", status); + /* This is not fatal */ + } + + /* Terminate presence subscription */ + pj_list_erase(uapres); + pjsip_pres_terminate(sub, PJ_FALSE); + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_TRUE; + } + + /* Create and send 2xx response to the SUBSCRIBE request: */ + status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to accept presence subscription", + status); + pj_list_erase(uapres); + pjsip_pres_terminate(sub, PJ_FALSE); + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_FALSE; + } + + /* If code is 200, send NOTIFY now */ + if (st_code == 200) { + pjsua_pres_notify(acc_id, uapres, PJSIP_EVSUB_STATE_ACTIVE, + NULL, NULL, PJ_TRUE, &msg_data); + } + + /* Done: */ + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_TRUE; +} + + +/* + * Send NOTIFY. + */ +PJ_DEF(pj_status_t) 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) +{ + pjsua_acc *acc; + pjsip_pres_status pres_status; + pjsua_buddy_id buddy_id; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check parameters */ + PJ_ASSERT_RETURN(acc_id!=-1 && srv_pres, PJ_EINVAL); + + /* Check that account ID is valid */ + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + /* Check that account is valid */ + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJ_LOG(4,(THIS_FILE, "Acc %d: sending NOTIFY for srv_pres=0x%p..", + acc_id, (int)(long)srv_pres)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + acc = &pjsua_var.acc[acc_id]; + + /* Check that the server presence subscription is still valid */ + if (pj_list_find_node(&acc->pres_srv_list, srv_pres) == NULL) { + /* Subscription has been terminated */ + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_EINVALIDOP; + } + + /* Set our online status: */ + pj_bzero(&pres_status, sizeof(pres_status)); + pres_status.info_cnt = 1; + pres_status.info[0].basic_open = acc->online_status; + pres_status.info[0].id = acc->cfg.pidf_tuple_id; + //Both pjsua_var.local_uri and pjsua_var.contact_uri are enclosed in "<" and ">" + //causing XML parsing to fail. + //pres_status.info[0].contact = pjsua_var.local_uri; + /* add RPID information */ + pj_memcpy(&pres_status.info[0].rpid, &acc->rpid, + sizeof(pjrpid_element)); + + pjsip_pres_set_status(srv_pres->sub, &pres_status); + + /* Check expires value. If it's zero, send our presense state but + * set subscription state to TERMINATED. + */ + if (srv_pres->expires == 0) + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + + /* Create and send the NOTIFY to active subscription: */ + status = pjsip_pres_notify(srv_pres->sub, ev_state, state_str, + reason, &tdata); + if (status == PJ_SUCCESS) { + /* Force removal of message body if msg_body==FALSE */ + if (!with_body) { + tdata->msg->body = NULL; + } + pjsua_process_msg_data(tdata, msg_data); + status = pjsip_pres_send_request( srv_pres->sub, tdata); + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create/send NOTIFY", + status); + pj_list_erase(srv_pres); + pjsip_pres_terminate(srv_pres->sub, PJ_FALSE); + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; + } + + + /* Subscribe to buddy's presence if we're not subscribed */ + buddy_id = find_buddy(srv_pres->dlg->remote.info->uri); + if (buddy_id != PJSUA_INVALID_ID) { + pjsua_buddy *b = &pjsua_var.buddy[buddy_id]; + if (b->monitor && b->sub == NULL) { + PJ_LOG(4,(THIS_FILE, "Received SUBSCRIBE from buddy %d, " + "activating outgoing subscription", buddy_id)); + subscribe_buddy_presence(buddy_id); + } + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Client presence publication callback. + */ +static void publish_cb(struct pjsip_publishc_cbparam *param) +{ + pjsua_acc *acc = (pjsua_acc*) param->token; + + if (param->code/100 != 2 || param->status != PJ_SUCCESS) { + + pjsip_publishc_destroy(param->pubc); + acc->publish_sess = NULL; + + if (param->status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(param->status, errmsg, sizeof(errmsg)); + PJ_LOG(1,(THIS_FILE, + "Client publication (PUBLISH) failed, status=%d, msg=%s", + param->status, errmsg)); + } else if (param->code == 412) { + /* 412 (Conditional Request Failed) + * The PUBLISH refresh has failed, retry with new one. + */ + pjsua_pres_init_publish_acc(acc->index); + + } else { + PJ_LOG(1,(THIS_FILE, + "Client publication (PUBLISH) failed (%d/%.*s)", + param->code, (int)param->reason.slen, + param->reason.ptr)); + } + + } else { + if (param->expiration < 1) { + /* Could happen if server "forgot" to include Expires header + * in the response. We will not renew, so destroy the pubc. + */ + pjsip_publishc_destroy(param->pubc); + acc->publish_sess = NULL; + } + } +} + + +/* + * Send PUBLISH request. + */ +static pj_status_t send_publish(int acc_id, pj_bool_t active) +{ + pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg; + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsip_pres_status pres_status; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(5,(THIS_FILE, "Acc %d: sending %sPUBLISH..", + acc_id, (active ? "" : "un-"))); + pj_log_push_indent(); + + /* Create PUBLISH request */ + if (active) { + char *bpos; + pj_str_t entity; + + status = pjsip_publishc_publish(acc->publish_sess, PJ_TRUE, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status); + goto on_error; + } + + /* Set our online status: */ + pj_bzero(&pres_status, sizeof(pres_status)); + pres_status.info_cnt = 1; + pres_status.info[0].basic_open = acc->online_status; + pres_status.info[0].id = acc->cfg.pidf_tuple_id; + /* .. including RPID information */ + pj_memcpy(&pres_status.info[0].rpid, &acc->rpid, + sizeof(pjrpid_element)); + + /* Be careful not to send PIDF with presence entity ID containing + * "<" character. + */ + if ((bpos=pj_strchr(&acc_cfg->id, '<')) != NULL) { + char *epos = pj_strchr(&acc_cfg->id, '>'); + if (epos - bpos < 2) { + pj_assert(!"Unexpected invalid URI"); + status = PJSIP_EINVALIDURI; + goto on_error; + } + entity.ptr = bpos+1; + entity.slen = epos - bpos - 1; + } else { + entity = acc_cfg->id; + } + + /* Create and add PIDF message body */ + status = pjsip_pres_create_pidf(tdata->pool, &pres_status, + &entity, &tdata->msg->body); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating PIDF for PUBLISH request", + status); + pjsip_tx_data_dec_ref(tdata); + goto on_error; + } + } else { + status = pjsip_publishc_unpublish(acc->publish_sess, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status); + goto on_error; + } + } + + /* Add headers etc */ + pjsua_process_msg_data(tdata, NULL); + + /* Set Via sent-by */ + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) { + pjsip_publishc_set_via_sent_by(acc->publish_sess, &acc->via_addr, + acc->via_tp); + } + + /* Send the PUBLISH request */ + status = pjsip_publishc_send(acc->publish_sess, tdata); + if (status == PJ_EPENDING) { + PJ_LOG(3,(THIS_FILE, "Previous request is in progress, " + "PUBLISH request is queued")); + } else if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error sending PUBLISH request", status); + goto on_error; + } + + acc->publish_state = acc->online_status; + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + if (acc->publish_sess) { + pjsip_publishc_destroy(acc->publish_sess); + acc->publish_sess = NULL; + } + pj_log_pop_indent(); + return status; +} + + +/* Create client publish session */ +pj_status_t pjsua_pres_init_publish_acc(int acc_id) +{ + const pj_str_t STR_PRESENCE = { "presence", 8 }; + pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg; + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pj_status_t status; + + /* Create and init client publication session */ + if (acc_cfg->publish_enabled) { + + /* Create client publication */ + status = pjsip_publishc_create(pjsua_var.endpt, &acc_cfg->publish_opt, + acc, &publish_cb, + &acc->publish_sess); + if (status != PJ_SUCCESS) { + acc->publish_sess = NULL; + return status; + } + + /* Initialize client publication */ + status = pjsip_publishc_init(acc->publish_sess, &STR_PRESENCE, + &acc_cfg->id, &acc_cfg->id, + &acc_cfg->id, + PJSUA_PUBLISH_EXPIRATION); + if (status != PJ_SUCCESS) { + acc->publish_sess = NULL; + return status; + } + + /* Add credential for authentication */ + if (acc->cred_cnt) { + pjsip_publishc_set_credentials(acc->publish_sess, acc->cred_cnt, + acc->cred); + } + + /* Set route-set */ + pjsip_publishc_set_route_set(acc->publish_sess, &acc->route_set); + + /* Send initial PUBLISH request */ + if (acc->online_status != 0) { + status = send_publish(acc_id, PJ_TRUE); + if (status != PJ_SUCCESS) + return status; + } + + } else { + acc->publish_sess = NULL; + } + + return PJ_SUCCESS; +} + + +/* Init presence for account */ +pj_status_t pjsua_pres_init_acc(int acc_id) +{ + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + + /* Init presence subscription */ + pj_list_init(&acc->pres_srv_list); + + return PJ_SUCCESS; +} + + +/* Unpublish presence publication */ +void pjsua_pres_unpublish(pjsua_acc *acc, unsigned flags) +{ + if (acc->publish_sess) { + pjsua_acc_config *acc_cfg = &acc->cfg; + + acc->online_status = PJ_FALSE; + + if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) { + send_publish(acc->index, PJ_FALSE); + } + + /* By ticket #364, don't destroy the session yet (let the callback + destroy it) + if (acc->publish_sess) { + pjsip_publishc_destroy(acc->publish_sess); + acc->publish_sess = NULL; + } + */ + acc_cfg->publish_enabled = PJ_FALSE; + } +} + +/* Terminate server subscription for the account */ +void pjsua_pres_delete_acc(int acc_id, unsigned flags) +{ + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsua_srv_pres *uapres; + + uapres = pjsua_var.acc[acc_id].pres_srv_list.next; + + /* Notify all subscribers that we're no longer available */ + while (uapres != &acc->pres_srv_list) { + + pjsip_pres_status pres_status; + pj_str_t reason = { "noresource", 10 }; + pjsua_srv_pres *next; + pjsip_tx_data *tdata; + + next = uapres->next; + + pjsip_pres_get_status(uapres->sub, &pres_status); + + pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status; + pjsip_pres_set_status(uapres->sub, &pres_status); + + if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) { + if (pjsip_pres_notify(uapres->sub, + PJSIP_EVSUB_STATE_TERMINATED, NULL, + &reason, &tdata)==PJ_SUCCESS) + { + pjsip_pres_send_request(uapres->sub, tdata); + } + } else { + pjsip_pres_terminate(uapres->sub, PJ_FALSE); + } + + uapres = next; + } + + /* Clear server presence subscription list because account might be reused + * later. */ + pj_list_init(&acc->pres_srv_list); + + /* Terminate presence publication, if any */ + pjsua_pres_unpublish(acc, flags); +} + + +/* Update server subscription (e.g. when our online status has changed) */ +void pjsua_pres_update_acc(int acc_id, pj_bool_t force) +{ + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg; + pjsua_srv_pres *uapres; + + uapres = pjsua_var.acc[acc_id].pres_srv_list.next; + + while (uapres != &acc->pres_srv_list) { + + pjsip_pres_status pres_status; + pjsip_tx_data *tdata; + + pjsip_pres_get_status(uapres->sub, &pres_status); + + /* Only send NOTIFY once subscription is active. Some subscriptions + * may still be in NULL (when app is adding a new buddy while in the + * on_incoming_subscribe() callback) or PENDING (when user approval is + * being requested) state and we don't send NOTIFY to these subs until + * the user accepted the request. + */ + if (pjsip_evsub_get_state(uapres->sub)==PJSIP_EVSUB_STATE_ACTIVE && + (force || pres_status.info[0].basic_open != acc->online_status)) + { + + pres_status.info[0].basic_open = acc->online_status; + pj_memcpy(&pres_status.info[0].rpid, &acc->rpid, + sizeof(pjrpid_element)); + + pjsip_pres_set_status(uapres->sub, &pres_status); + + if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS) { + pjsua_process_msg_data(tdata, NULL); + pjsip_pres_send_request(uapres->sub, tdata); + } + } + + uapres = uapres->next; + } + + /* Send PUBLISH if required. We only do this when we have a PUBLISH + * session. If we don't have a PUBLISH session, then it could be + * that we're waiting until registration has completed before we + * send the first PUBLISH. + */ + if (acc_cfg->publish_enabled && acc->publish_sess) { + if (force || acc->publish_state != acc->online_status) { + send_publish(acc_id, PJ_TRUE); + } + } +} + + + +/*************************************************************************** + * Client subscription. + */ + +static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry) +{ + pjsua_buddy *buddy = (pjsua_buddy*)entry->user_data; + + PJ_UNUSED_ARG(th); + + entry->id = PJ_FALSE; + pjsua_buddy_update_pres(buddy->index); +} + +/* Reschedule subscription refresh timer or terminate the subscription + * refresh timer for the specified buddy. + */ +static void buddy_resubscribe(pjsua_buddy *buddy, pj_bool_t resched, + unsigned msec_interval) +{ + if (buddy->timer.id) { + pjsua_cancel_timer(&buddy->timer); + buddy->timer.id = PJ_FALSE; + } + + if (resched) { + pj_time_val delay; + + PJ_LOG(4,(THIS_FILE, + "Resubscribing buddy id %u in %u ms (reason: %.*s)", + buddy->index, msec_interval, + (int)buddy->term_reason.slen, + buddy->term_reason.ptr)); + + pj_timer_entry_init(&buddy->timer, 0, buddy, &buddy_timer_cb); + delay.sec = 0; + delay.msec = msec_interval; + pj_time_val_normalize(&delay); + + if (pjsua_schedule_timer(&buddy->timer, &delay)==PJ_SUCCESS) + buddy->timer.id = PJ_TRUE; + } +} + +/* Callback called when *client* subscription state has changed. */ +static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsua_buddy *buddy; + + PJ_UNUSED_ARG(event); + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (buddy) { + PJ_LOG(4,(THIS_FILE, + "Presence subscription to %.*s is %s", + (int)pjsua_var.buddy[buddy->index].uri.slen, + pjsua_var.buddy[buddy->index].uri.ptr, + pjsip_evsub_get_state_name(sub))); + pj_log_push_indent(); + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + int resub_delay = -1; + + if (buddy->term_reason.ptr == NULL) { + buddy->term_reason.ptr = (char*) + pj_pool_alloc(buddy->pool, + PJSUA_BUDDY_SUB_TERM_REASON_LEN); + } + pj_strncpy(&buddy->term_reason, + pjsip_evsub_get_termination_reason(sub), + PJSUA_BUDDY_SUB_TERM_REASON_LEN); + + buddy->term_code = 200; + + /* Determine whether to resubscribe automatically */ + if (event && event->type==PJSIP_EVENT_TSX_STATE) { + const pjsip_transaction *tsx = event->body.tsx_state.tsx; + if (pjsip_method_cmp(&tsx->method, + &pjsip_subscribe_method)==0) + { + buddy->term_code = tsx->status_code; + switch (tsx->status_code) { + case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST: + /* 481: we refreshed too late? resubscribe + * immediately. + */ + /* But this must only happen when the 481 is received + * on subscription refresh request. We MUST NOT try to + * resubscribe automatically if the 481 is received + * on the initial SUBSCRIBE (if server returns this + * response for some reason). + */ + if (buddy->dlg->remote.contact) + resub_delay = 500; + break; + } + } else if (pjsip_method_cmp(&tsx->method, + &pjsip_notify_method)==0) + { + if (pj_stricmp2(&buddy->term_reason, "deactivated")==0 || + pj_stricmp2(&buddy->term_reason, "timeout")==0) { + /* deactivated: The subscription has been terminated, + * but the subscriber SHOULD retry immediately with + * a new subscription. + */ + /* timeout: The subscription has been terminated + * because it was not refreshed before it expired. + * Clients MAY re-subscribe immediately. The + * "retry-after" parameter has no semantics for + * "timeout". + */ + resub_delay = 500; + } + else if (pj_stricmp2(&buddy->term_reason, "probation")==0|| + pj_stricmp2(&buddy->term_reason, "giveup")==0) { + /* probation: The subscription has been terminated, + * but the client SHOULD retry at some later time. + * If a "retry-after" parameter is also present, the + * client SHOULD wait at least the number of seconds + * specified by that parameter before attempting to re- + * subscribe. + */ + /* giveup: The subscription has been terminated because + * the notifier could not obtain authorization in a + * timely fashion. If a "retry-after" parameter is + * also present, the client SHOULD wait at least the + * number of seconds specified by that parameter before + * attempting to re-subscribe; otherwise, the client + * MAY retry immediately, but will likely get put back + * into pending state. + */ + const pjsip_sub_state_hdr *sub_hdr; + pj_str_t sub_state = { "Subscription-State", 18 }; + const pjsip_msg *msg; + + msg = event->body.tsx_state.src.rdata->msg_info.msg; + sub_hdr = (const pjsip_sub_state_hdr*) + pjsip_msg_find_hdr_by_name(msg, &sub_state, + NULL); + if (sub_hdr && sub_hdr->retry_after > 0) + resub_delay = sub_hdr->retry_after * 1000; + } + + } + } + + /* For other cases of subscription termination, if resubscribe + * timer is not set, schedule with default expiration (plus minus + * some random value, to avoid sending SUBSCRIBEs all at once) + */ + if (resub_delay == -1) { + pj_assert(PJSUA_PRES_TIMER >= 3); + resub_delay = PJSUA_PRES_TIMER*1000 - 2500 + (pj_rand()%5000); + } + + buddy_resubscribe(buddy, PJ_TRUE, resub_delay); + + } else { + /* This will clear the last termination code/reason */ + buddy->term_code = 0; + buddy->term_reason.slen = 0; + } + + /* Call callbacks */ + if (pjsua_var.ua_cfg.cb.on_buddy_evsub_state) + (*pjsua_var.ua_cfg.cb.on_buddy_evsub_state)(buddy->index, sub, + event); + + if (pjsua_var.ua_cfg.cb.on_buddy_state) + (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index); + + /* Clear subscription */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + buddy->sub = NULL; + buddy->status.info_cnt = 0; + buddy->dlg = NULL; + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + } + + pj_log_pop_indent(); + } +} + + +/* Callback when transaction state has changed. */ +static void pjsua_evsub_on_tsx_state(pjsip_evsub *sub, + pjsip_transaction *tsx, + pjsip_event *event) +{ + pjsua_buddy *buddy; + pjsip_contact_hdr *contact_hdr; + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!buddy) { + return; + } + + /* We only use this to update buddy's Contact, when it's not + * set. + */ + if (buddy->contact.slen != 0) { + /* Contact already set */ + return; + } + + /* Only care about 2xx response to outgoing SUBSCRIBE */ + if (tsx->status_code/100 != 2 || + tsx->role != PJSIP_UAC_ROLE || + event->type != PJSIP_EVENT_RX_MSG || + pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method())!=0) + { + return; + } + + /* Find contact header. */ + contact_hdr = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg, + PJSIP_H_CONTACT, NULL); + if (!contact_hdr || !contact_hdr->uri) { + return; + } + + buddy->contact.ptr = (char*) + pj_pool_alloc(buddy->pool, PJSIP_MAX_URL_SIZE); + buddy->contact.slen = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, + contact_hdr->uri, + buddy->contact.ptr, + PJSIP_MAX_URL_SIZE); + if (buddy->contact.slen < 0) + buddy->contact.slen = 0; +} + + +/* Callback called when we receive NOTIFY */ +static void 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) +{ + pjsua_buddy *buddy; + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (buddy) { + /* Update our info. */ + pjsip_pres_get_status(sub, &buddy->status); + } + + /* The default is to send 200 response to NOTIFY. + * Just leave it there.. + */ + PJ_UNUSED_ARG(rdata); + PJ_UNUSED_ARG(p_st_code); + PJ_UNUSED_ARG(p_st_text); + PJ_UNUSED_ARG(res_hdr); + PJ_UNUSED_ARG(p_body); +} + + +/* It does what it says.. */ +static void subscribe_buddy_presence(pjsua_buddy_id buddy_id) +{ + pjsip_evsub_user pres_callback; + pj_pool_t *tmp_pool = NULL; + pjsua_buddy *buddy; + int acc_id; + pjsua_acc *acc; + pj_str_t contact; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Event subscription callback. */ + pj_bzero(&pres_callback, sizeof(pres_callback)); + pres_callback.on_evsub_state = &pjsua_evsub_on_state; + pres_callback.on_tsx_state = &pjsua_evsub_on_tsx_state; + pres_callback.on_rx_notify = &pjsua_evsub_on_rx_notify; + + buddy = &pjsua_var.buddy[buddy_id]; + acc_id = pjsua_acc_find_for_outgoing(&buddy->uri); + + acc = &pjsua_var.acc[acc_id]; + + PJ_LOG(4,(THIS_FILE, "Buddy %d: subscribing presence,using account %d..", + buddy_id, acc_id)); + pj_log_push_indent(); + + /* Generate suitable Contact header unless one is already set in + * the account + */ + if (acc->contact.slen) { + contact = acc->contact; + } else { + tmp_pool = pjsua_pool_create("tmpbuddy", 512, 256); + + status = pjsua_acc_create_uac_contact(tmp_pool, &contact, + acc_id, &buddy->uri); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + } + + /* Create UAC dialog */ + status = pjsip_dlg_create_uac( pjsip_ua_instance(), + &acc->cfg.id, + &contact, + &buddy->uri, + NULL, &buddy->dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create dialog", + status); + if (tmp_pool) pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + + /* Increment the dialog's lock otherwise when presence session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(buddy->dlg); + + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) + pjsip_dlg_set_via_sent_by(buddy->dlg, &acc->via_addr, acc->via_tp); + + status = pjsip_pres_create_uac( buddy->dlg, &pres_callback, + PJSIP_EVSUB_NO_EVENT_ID, &buddy->sub); + if (status != PJ_SUCCESS) { + buddy->sub = NULL; + pjsua_perror(THIS_FILE, "Unable to create presence client", + status); + /* This should destroy the dialog since there's no session + * referencing it + */ + if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg); + if (tmp_pool) pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(buddy->dlg, &tp_sel); + } + + /* Set route-set */ + if (!pj_list_empty(&acc->route_set)) { + pjsip_dlg_set_route_set(buddy->dlg, &acc->route_set); + } + + /* Set credentials */ + if (acc->cred_cnt) { + pjsip_auth_clt_set_credentials( &buddy->dlg->auth_sess, + acc->cred_cnt, acc->cred); + } + + /* Set authentication preference */ + pjsip_auth_clt_set_prefs(&buddy->dlg->auth_sess, &acc->cfg.auth_pref); + + pjsip_evsub_set_mod_data(buddy->sub, pjsua_var.mod.id, buddy); + + status = pjsip_pres_initiate(buddy->sub, -1, &tdata); + if (status != PJ_SUCCESS) { + if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg); + if (buddy->sub) { + pjsip_pres_terminate(buddy->sub, PJ_FALSE); + } + buddy->sub = NULL; + pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE", + status); + if (tmp_pool) pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + + pjsua_process_msg_data(tdata, NULL); + + status = pjsip_pres_send_request(buddy->sub, tdata); + if (status != PJ_SUCCESS) { + if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg); + if (buddy->sub) { + pjsip_pres_terminate(buddy->sub, PJ_FALSE); + } + buddy->sub = NULL; + pjsua_perror(THIS_FILE, "Unable to send initial SUBSCRIBE", + status); + if (tmp_pool) pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + + pjsip_dlg_dec_lock(buddy->dlg); + if (tmp_pool) pj_pool_release(tmp_pool); + pj_log_pop_indent(); +} + + +/* It does what it says... */ +static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id) +{ + pjsua_buddy *buddy; + pjsip_tx_data *tdata; + pj_status_t status; + + buddy = &pjsua_var.buddy[buddy_id]; + + if (buddy->sub == NULL) + return; + + if (pjsip_evsub_get_state(buddy->sub) == PJSIP_EVSUB_STATE_TERMINATED) { + buddy->sub = NULL; + return; + } + + PJ_LOG(5,(THIS_FILE, "Buddy %d: unsubscribing..", buddy_id)); + pj_log_push_indent(); + + status = pjsip_pres_initiate( buddy->sub, 0, &tdata); + if (status == PJ_SUCCESS) { + pjsua_process_msg_data(tdata, NULL); + status = pjsip_pres_send_request( buddy->sub, tdata ); + } + + if (status != PJ_SUCCESS && buddy->sub) { + pjsip_pres_terminate(buddy->sub, PJ_FALSE); + buddy->sub = NULL; + pjsua_perror(THIS_FILE, "Unable to unsubscribe presence", + status); + } + + pj_log_pop_indent(); +} + +/* It does what it says.. */ +static pj_status_t refresh_client_subscriptions(void) +{ + unsigned i; + pj_status_t status; + + for (i=0; icfg.id.slen, acc->cfg.id.ptr, + pjsip_evsub_get_state_name(sub))); + + /* Call callback */ + if (pjsua_var.ua_cfg.cb.on_mwi_state) { + (*pjsua_var.ua_cfg.cb.on_mwi_state)(acc->index, sub); + } + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + /* Clear subscription */ + acc->mwi_dlg = NULL; + acc->mwi_sub = NULL; + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + + } +} + +/* Callback called when we receive NOTIFY */ +static void 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) +{ + pjsua_mwi_info mwi_info; + pjsua_acc *acc; + + PJ_UNUSED_ARG(p_st_code); + PJ_UNUSED_ARG(p_st_text); + PJ_UNUSED_ARG(res_hdr); + PJ_UNUSED_ARG(p_body); + + acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!acc) + return; + + /* Construct mwi_info */ + pj_bzero(&mwi_info, sizeof(mwi_info)); + mwi_info.evsub = sub; + mwi_info.rdata = rdata; + + PJ_LOG(4,(THIS_FILE, "MWI got NOTIFY..")); + pj_log_push_indent(); + + /* Call callback */ + if (pjsua_var.ua_cfg.cb.on_mwi_info) { + (*pjsua_var.ua_cfg.cb.on_mwi_info)(acc->index, &mwi_info); + } + + pj_log_pop_indent(); +} + + +/* Event subscription callback. */ +static pjsip_evsub_user mwi_cb = +{ + &mwi_evsub_on_state, + NULL, /* on_tsx_state: not interested */ + NULL, /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless + * we want to authenticate + */ + + &mwi_evsub_on_rx_notify, + + NULL, /* on_client_refresh: Use default behaviour, which is to + * refresh client subscription. */ + + NULL, /* on_server_timeout: Use default behaviour, which is to send + * NOTIFY to terminate. + */ +}; + +pj_status_t pjsua_start_mwi(pjsua_acc_id acc_id, pj_bool_t force_renew) +{ + pjsua_acc *acc; + pj_pool_t *tmp_pool = NULL; + pj_str_t contact; + pjsip_tx_data *tdata; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc) + && pjsua_var.acc[acc_id].valid, PJ_EINVAL); + + acc = &pjsua_var.acc[acc_id]; + + if (!acc->cfg.mwi_enabled) { + if (acc->mwi_sub) { + /* Terminate MWI subscription */ + pjsip_evsub *sub = acc->mwi_sub; + + /* Detach sub from this account */ + acc->mwi_sub = NULL; + acc->mwi_dlg = NULL; + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + + /* Unsubscribe */ + status = pjsip_mwi_initiate(sub, 0, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_mwi_send_request(sub, tdata); + } + } + return status; + } + + /* Subscription is already active */ + if (acc->mwi_sub) { + if (!force_renew) + return PJ_SUCCESS; + + /* Update MWI subscription */ + pj_assert(acc->mwi_dlg); + pjsip_dlg_inc_lock(acc->mwi_dlg); + + status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata); + if (status == PJ_SUCCESS) { + pjsua_process_msg_data(tdata, NULL); + status = pjsip_pres_send_request(acc->mwi_sub, tdata); + } + + pjsip_dlg_dec_lock(acc->mwi_dlg); + return status; + } + + PJ_LOG(4,(THIS_FILE, "Starting MWI subscription..")); + pj_log_push_indent(); + + /* Generate suitable Contact header unless one is already set in + * the account + */ + if (acc->contact.slen) { + contact = acc->contact; + } else { + tmp_pool = pjsua_pool_create("tmpmwi", 512, 256); + status = pjsua_acc_create_uac_contact(tmp_pool, &contact, + acc->index, &acc->cfg.id); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + goto on_return; + } + } + + /* Create UAC dialog */ + status = pjsip_dlg_create_uac( pjsip_ua_instance(), + &acc->cfg.id, + &contact, + &acc->cfg.id, + NULL, &acc->mwi_dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create dialog", status); + goto on_return; + } + + /* Increment the dialog's lock otherwise when presence session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(acc->mwi_dlg); + + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) + pjsip_dlg_set_via_sent_by(acc->mwi_dlg, &acc->via_addr, acc->via_tp); + + /* Create UAC subscription */ + status = pjsip_mwi_create_uac(acc->mwi_dlg, &mwi_cb, + PJSIP_EVSUB_NO_EVENT_ID, &acc->mwi_sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating MWI subscription", status); + if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg); + goto on_return; + } + + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(acc->mwi_dlg, &tp_sel); + } + + /* Set route-set */ + if (!pj_list_empty(&acc->route_set)) { + pjsip_dlg_set_route_set(acc->mwi_dlg, &acc->route_set); + } + + /* Set credentials */ + if (acc->cred_cnt) { + pjsip_auth_clt_set_credentials( &acc->mwi_dlg->auth_sess, + acc->cred_cnt, acc->cred); + } + + /* Set authentication preference */ + pjsip_auth_clt_set_prefs(&acc->mwi_dlg->auth_sess, &acc->cfg.auth_pref); + + pjsip_evsub_set_mod_data(acc->mwi_sub, pjsua_var.mod.id, acc); + + status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata); + if (status != PJ_SUCCESS) { + if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg); + if (acc->mwi_sub) { + pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE); + } + acc->mwi_sub = NULL; + acc->mwi_dlg = NULL; + pjsua_perror(THIS_FILE, "Unable to create initial MWI SUBSCRIBE", + status); + goto on_return; + } + + pjsua_process_msg_data(tdata, NULL); + + status = pjsip_pres_send_request(acc->mwi_sub, tdata); + if (status != PJ_SUCCESS) { + if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg); + if (acc->mwi_sub) { + pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE); + } + acc->mwi_sub = NULL; + acc->mwi_dlg = NULL; + pjsua_perror(THIS_FILE, "Unable to send initial MWI SUBSCRIBE", + status); + goto on_return; + } + + pjsip_dlg_dec_lock(acc->mwi_dlg); + +on_return: + if (tmp_pool) pj_pool_release(tmp_pool); + + pj_log_pop_indent(); + return status; +} + + +/*************************************************************************** + * Unsolicited MWI + */ +static pj_bool_t unsolicited_mwi_on_rx_request(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + pj_str_t EVENT_HDR = { "Event", 5 }; + pj_str_t MWI = { "message-summary", 15 }; + pjsip_event_hdr *eh; + + if (pjsip_method_cmp(&msg->line.req.method, &pjsip_notify_method)!=0) { + /* Only interested with NOTIFY request */ + return PJ_FALSE; + } + + eh = (pjsip_event_hdr*) pjsip_msg_find_hdr_by_name(msg, &EVENT_HDR, NULL); + if (!eh) { + /* Something wrong with the request, it has no Event hdr */ + return PJ_FALSE; + } + + if (pj_stricmp(&eh->event_type, &MWI) != 0) { + /* Not MWI event */ + return PJ_FALSE; + } + + PJ_LOG(4,(THIS_FILE, "Got unsolicited NOTIFY from %s:%d..", + rdata->pkt_info.src_name, rdata->pkt_info.src_port)); + pj_log_push_indent(); + + /* Got unsolicited MWI request, respond with 200/OK first */ + pjsip_endpt_respond(pjsua_get_pjsip_endpt(), NULL, rdata, 200, NULL, + NULL, NULL, NULL); + + + /* Call callback */ + if (pjsua_var.ua_cfg.cb.on_mwi_info) { + pjsua_acc_id acc_id; + pjsua_mwi_info mwi_info; + + acc_id = pjsua_acc_find_for_incoming(rdata); + + pj_bzero(&mwi_info, sizeof(mwi_info)); + mwi_info.rdata = rdata; + + (*pjsua_var.ua_cfg.cb.on_mwi_info)(acc_id, &mwi_info); + } + + pj_log_pop_indent(); + return PJ_TRUE; +} + +/* The module instance. */ +static pjsip_module pjsua_unsolicited_mwi_mod = +{ + NULL, NULL, /* prev, next. */ + { "mod-unsolicited-mwi", 19 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &unsolicited_mwi_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +static pj_status_t enable_unsolicited_mwi(void) +{ + pj_status_t status; + + status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(), + &pjsua_unsolicited_mwi_mod); + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error registering unsolicited MWI module", + status); + + return status; +} + + + +/***************************************************************************/ + +/* Timer callback to re-create client subscription */ +static void pres_timer_cb(pj_timer_heap_t *th, + pj_timer_entry *entry) +{ + unsigned i; + pj_time_val delay = { PJSUA_PRES_TIMER, 0 }; + + entry->id = PJ_FALSE; + + /* Retry failed PUBLISH and MWI SUBSCRIBE requests */ + for (i=0; icfg.publish_enabled && acc->publish_sess==NULL) + pjsua_pres_init_publish_acc(acc->index); + + /* Re-subscribe MWI subscription if it's terminated prematurely */ + if (acc->cfg.mwi_enabled && !acc->mwi_sub) + pjsua_start_mwi(acc->index, PJ_FALSE); + } + + /* #937: No need to do bulk client refresh, as buddies have their + * own individual timer now. + */ + //refresh_client_subscriptions(); + + pjsip_endpt_schedule_timer(pjsua_var.endpt, entry, &delay); + entry->id = PJ_TRUE; + + PJ_UNUSED_ARG(th); +} + + +/* + * Init presence + */ +pj_status_t pjsua_pres_init() +{ + unsigned i; + pj_status_t status; + + status = pjsip_endpt_register_module( pjsua_var.endpt, &mod_pjsua_pres); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to register pjsua presence module", + status); + } + + for (i=0; i