/* $Id$ */ /* * Copyright (C) 2003-2006 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 "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, /* 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) { int acc_index; 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: */ /* Find which account for the incoming request. */ acc_index = pjsua_find_account_for_incoming(rdata); /* Create UAS dialog: */ status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, &pjsua.acc[acc_index].contact_uri, &dlg); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "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(THIS_FILE, "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.acc[acc_index].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(THIS_FILE, "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.acc[acc_index].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(THIS_FILE, "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(int acc_index) { pjsua_srv_pres *uapres; uapres = pjsua.acc[acc_index].pres_srv_list.next; while (uapres != &pjsua.acc[acc_index].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.acc[acc_index].online_status) { pres_status.info[0].basic_open = pjsua.acc[acc_index].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) { int acc_index; pjsip_dialog *dlg; pjsip_tx_data *tdata; pj_status_t status; acc_index = pjsua.buddies[index].acc_index; status = pjsip_dlg_create_uac( pjsip_ua_instance(), &pjsua.acc[acc_index].local_uri, &pjsua.acc[acc_index].contact_uri, &pjsua.buddies[index].uri, NULL, &dlg); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "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(THIS_FILE, "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, -1, &tdata); if (status != PJ_SUCCESS) { pjsua.buddies[index].sub = NULL; pjsua_perror(THIS_FILE, "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(THIS_FILE, "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(THIS_FILE, "Unable to unsubscribe presence", status); } } /* It does what it says.. */ static void refresh_client_subscription(void) { int i; for (i=0; isub), 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