summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsua/pjsua_pres.c
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip/src/pjsua/pjsua_pres.c')
-rw-r--r--pjsip/src/pjsua/pjsua_pres.c471
1 files changed, 471 insertions, 0 deletions
diff --git a/pjsip/src/pjsua/pjsua_pres.c b/pjsip/src/pjsua/pjsua_pres.c
new file mode 100644
index 00000000..db009eed
--- /dev/null
+++ b/pjsip/src/pjsua/pjsua_pres.c
@@ -0,0 +1,471 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * 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 "pjsua.h"
+
+/*
+ * pjsua_pres.c
+ *
+ * Presence related stuffs.
+ */
+
+#define THIS_FILE "pjsua_pres.c"
+
+
+
+/* **************************************************************************
+ * THE FOLLOWING PART HANDLES SERVER SUBSCRIPTION
+ * **************************************************************************
+ */
+
+/* Proto */
+static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata);
+
+/* The module instance. */
+static pjsip_module mod_pjsua_pres =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-pjsua-pres", 14 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
+ NULL, /* User data. */
+ NULL, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ NULL, /* unload() */
+ &pres_on_rx_request, /* on_rx_request() */
+ NULL, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+
+};
+
+
+/* Callback called when *server* subscription state has changed. */
+static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event)
+{
+ pjsua_srv_pres *uapres = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+
+ PJ_UNUSED_ARG(event);
+
+ if (uapres) {
+ PJ_LOG(3,(THIS_FILE, "Server subscription to %s is %s",
+ uapres->remote, pjsip_evsub_get_state_name(sub)));
+
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
+ pj_list_erase(uapres);
+ }
+ }
+}
+
+/* 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)
+{
+ pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
+ pjsua_srv_pres *uapres;
+ pjsip_evsub *sub;
+ pjsip_evsub_user pres_cb;
+ pjsip_tx_data *tdata;
+ pjsip_pres_status pres_status;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ if (pjsip_method_cmp(req_method, &pjsip_subscribe_method) != 0)
+ return PJ_FALSE;
+
+ /* Incoming SUBSCRIBE: */
+
+ /* Create UAS dialog: */
+ status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
+ &pjsua.contact_uri, &dlg);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror("Unable to create UAS dialog for subscription", status);
+ return PJ_FALSE;
+ }
+
+ /* Init callback: */
+ pj_memset(&pres_cb, 0, 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) {
+ PJ_TODO(DESTROY_DIALOG);
+ pjsua_perror("Unable to create server subscription", status);
+ return PJ_FALSE;
+ }
+
+ /* Attach our data to the subscription: */
+ uapres = pj_pool_alloc(dlg->pool, sizeof(pjsua_srv_pres));
+ uapres->sub = sub;
+ uapres->remote = pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE);
+ 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_set_mod_data(sub, pjsua.mod.id, uapres);
+
+ /* Add server subscription to the list: */
+ pj_list_push_back(&pjsua.pres_srv_list, uapres);
+
+
+ /* Create and send 200 (OK) to the SUBSCRIBE request: */
+ status = pjsip_pres_accept(sub, rdata, 200, NULL);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror("Unable to accept presence subscription", status);
+ pj_list_erase(uapres);
+ return PJ_FALSE;
+ }
+
+
+ /* Set our online status: */
+ pj_memset(&pres_status, 0, sizeof(pres_status));
+ pres_status.info_cnt = 1;
+ pres_status.info[0].basic_open = pjsua.online_status;
+ //Both pjsua.local_uri and pjsua.contact_uri are enclosed in "<" and ">"
+ //causing XML parsing to fail.
+ //pres_status.info[0].contact = pjsua.local_uri;
+
+ pjsip_pres_set_status(sub, &pres_status);
+
+ /* Create and send the first NOTIFY to active subscription: */
+ status = pjsip_pres_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, NULL,
+ NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_pres_send_request( sub, tdata);
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror("Unable to create/send NOTIFY", status);
+ pj_list_erase(uapres);
+ return PJ_FALSE;
+ }
+
+
+ /* Done: */
+
+ return PJ_TRUE;
+}
+
+
+/* Refresh subscription (e.g. when our online status has changed) */
+static void refresh_server_subscription()
+{
+ pjsua_srv_pres *uapres;
+
+ uapres = pjsua.pres_srv_list.next;
+
+ while (uapres != &pjsua.pres_srv_list) {
+
+ pjsip_pres_status pres_status;
+ pjsip_tx_data *tdata;
+
+ pjsip_pres_get_status(uapres->sub, &pres_status);
+ if (pres_status.info[0].basic_open != pjsua.online_status) {
+ pres_status.info[0].basic_open = pjsua.online_status;
+ pjsip_pres_set_status(uapres->sub, &pres_status);
+
+ if (pjsua.quit_flag) {
+ pj_str_t reason = { "noresource", 10 };
+ if (pjsip_pres_notify(uapres->sub,
+ PJSIP_EVSUB_STATE_TERMINATED, NULL,
+ &reason, &tdata)==PJ_SUCCESS)
+ {
+ pjsip_pres_send_request(uapres->sub, tdata);
+ }
+ } else {
+ if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS)
+ pjsip_pres_send_request(uapres->sub, tdata);
+ }
+ }
+
+ uapres = uapres->next;
+ }
+}
+
+
+
+/* **************************************************************************
+ * THE FOLLOWING PART HANDLES CLIENT SUBSCRIPTION
+ * **************************************************************************
+ */
+
+/* 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);
+
+ buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+ if (buddy) {
+ PJ_LOG(3,(THIS_FILE,
+ "Presence subscription to %s is %s",
+ buddy->uri.ptr,
+ pjsip_evsub_get_state_name(sub)));
+
+ if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ buddy->sub = NULL;
+ buddy->status.info_cnt = 0;
+ pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
+ }
+ }
+}
+
+/* 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;
+
+ buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+ if (buddy) {
+ /* Update our info. */
+ pjsip_pres_get_status(sub, &buddy->status);
+
+ if (buddy->status.info_cnt) {
+ PJ_LOG(3,(THIS_FILE, "%s is %s",
+ buddy->uri.ptr,
+ (buddy->status.info[0].basic_open?"online":"offline")));
+ } else {
+ PJ_LOG(3,(THIS_FILE, "No presence info for %s",
+ buddy->uri.ptr));
+ }
+ }
+
+ /* 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);
+}
+
+
+/* Event subscription callback. */
+static pjsip_evsub_user pres_callback =
+{
+ &pjsua_evsub_on_state,
+
+ NULL, /* on_tsx_state: don't care about transaction state. */
+
+ NULL, /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless
+ * we want to authenticate
+ */
+
+ &pjsua_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.
+ */
+};
+
+
+/* It does what it says.. */
+static void subscribe_buddy_presence(unsigned index)
+{
+ pjsip_dialog *dlg;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_dlg_create_uac( pjsip_ua_instance(),
+ &pjsua.local_uri,
+ &pjsua.contact_uri,
+ &pjsua.buddies[index].uri,
+ NULL, &dlg);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror("Unable to create dialog", status);
+ return;
+ }
+
+ status = pjsip_pres_create_uac( dlg, &pres_callback,
+ &pjsua.buddies[index].sub);
+ if (status != PJ_SUCCESS) {
+ pjsua.buddies[index].sub = NULL;
+ pjsua_perror("Unable to create presence client", status);
+ return;
+ }
+
+ pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id,
+ &pjsua.buddies[index]);
+
+ status = pjsip_pres_initiate(pjsua.buddies[index].sub, 60, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua.buddies[index].sub = NULL;
+ pjsua_perror("Unable to create initial SUBSCRIBE", status);
+ return;
+ }
+
+ status = pjsip_pres_send_request(pjsua.buddies[index].sub, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua.buddies[index].sub = NULL;
+ pjsua_perror("Unable to send initial SUBSCRIBE", status);
+ return;
+ }
+
+ PJ_TODO(DESTROY_DIALOG_ON_ERROR);
+}
+
+
+/* It does what it says... */
+static void unsubscribe_buddy_presence(unsigned index)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ if (pjsua.buddies[index].sub == NULL)
+ return;
+
+ if (pjsip_evsub_get_state(pjsua.buddies[index].sub) ==
+ PJSIP_EVSUB_STATE_TERMINATED)
+ {
+ pjsua.buddies[index].sub = NULL;
+ return;
+ }
+
+ status = pjsip_pres_initiate( pjsua.buddies[index].sub, 0, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_pres_send_request( pjsua.buddies[index].sub, tdata );
+
+ if (status == PJ_SUCCESS) {
+
+ //pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id,
+ // NULL);
+ //pjsua.buddies[index].sub = NULL;
+
+ } else {
+ pjsua_perror("Unable to unsubscribe presence", status);
+ }
+}
+
+
+/* It does what it says.. */
+static void refresh_client_subscription(void)
+{
+ unsigned i;
+
+ for (i=0; i<pjsua.buddy_cnt; ++i) {
+
+ if (pjsua.buddies[i].monitor && !pjsua.buddies[i].sub) {
+ subscribe_buddy_presence(i);
+
+ } else if (!pjsua.buddies[i].monitor && pjsua.buddies[i].sub) {
+ unsubscribe_buddy_presence(i);
+
+ }
+ }
+}
+
+
+/*
+ * Init presence
+ */
+pj_status_t pjsua_pres_init()
+{
+ pj_status_t status;
+
+ status = pjsip_endpt_register_module( pjsua.endpt, &mod_pjsua_pres);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror("Unable to register pjsua presence module", status);
+ }
+
+ return status;
+}
+
+/*
+ * Refresh presence
+ */
+void pjsua_pres_refresh(void)
+{
+ refresh_client_subscription();
+ refresh_server_subscription();
+}
+
+
+/*
+ * Shutdown presence.
+ */
+void pjsua_pres_shutdown(void)
+{
+ unsigned i;
+
+ pjsua.online_status = 0;
+ for (i=0; i<pjsua.buddy_cnt; ++i) {
+ pjsua.buddies[i].monitor = 0;
+ }
+ pjsua_pres_refresh();
+}
+
+/*
+ * Dump presence status.
+ */
+void pjsua_pres_dump(void)
+{
+ unsigned i;
+
+ PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
+ if (pj_list_empty(&pjsua.pres_srv_list)) {
+ PJ_LOG(3,(THIS_FILE, " - none - "));
+ } else {
+ struct pjsua_srv_pres *uapres;
+
+ uapres = pjsua.pres_srv_list.next;
+ while (uapres != &pjsua.pres_srv_list) {
+
+ PJ_LOG(3,(THIS_FILE, " %10s %s",
+ pjsip_evsub_get_state_name(uapres->sub),
+ uapres->remote));
+
+ uapres = uapres->next;
+ }
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:"));
+ if (pjsua.buddy_cnt == 0) {
+ PJ_LOG(3,(THIS_FILE, " - no buddy list - "));
+ } else {
+ for (i=0; i<pjsua.buddy_cnt; ++i) {
+
+ if (pjsua.buddies[i].sub) {
+ PJ_LOG(3,(THIS_FILE, " %10s %s",
+ pjsip_evsub_get_state_name(pjsua.buddies[i].sub),
+ pjsua.buddies[i].uri.ptr));
+ } else {
+ PJ_LOG(3,(THIS_FILE, " %10s %s",
+ "(null)",
+ pjsua.buddies[i].uri.ptr));
+ }
+ }
+ }
+}
+