blob: a9cc610868599fdebe33a26f6ec944525c956f11 [file] [log] [blame]
/* $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 <pjsip_simple/presence.h>
#include <pjsip/sip_transport.h>
#include <pj/pool.h>
#include <pj/string.h>
#include <pj/guid.h>
#include <pj/os.h>
#include <stdio.h>
/* Forward declarations. */
static void on_query_subscribe(pjsip_rx_data *rdata, int *status);
static void on_subscribe(pjsip_event_sub *sub, pjsip_rx_data *rdata,
pjsip_event_sub_cb **cb, int *expires);
static void on_sub_terminated(pjsip_event_sub *sub, const pj_str_t *reason);
static void on_sub_received_refresh(pjsip_event_sub *sub, pjsip_rx_data *rdata);
static void on_received_notify(pjsip_event_sub *sub, pjsip_rx_data *rdata);
/* Some string constants. */
static pj_str_t PRESENCE_EVENT = { "presence", 8 };
/* Accept types. */
static pj_str_t accept_names[] = {
{ "application/pidf+xml", 20 },
{ "application/xpidf+xml", 21 }
};
static pjsip_media_type accept_types[] = {
{
{ "application", 11 },
{ "pidf+xml", 8 }
},
{
{ "application", 11 },
{ "xpidf+xml", 9 }
}
};
/* Callback that is registered by application. */
static pjsip_presence_cb cb;
/* Package callback to be register to event_notify */
static pjsip_event_sub_pkg_cb pkg_cb = { &on_query_subscribe,
&on_subscribe };
/* Global/static callback to be registered to event_notify */
static pjsip_event_sub_cb sub_cb = { &on_sub_terminated,
&on_sub_received_refresh,
NULL,
&on_received_notify,
NULL };
/*
* Initialize presence module.
* This will register event package "presence" to event framework.
*/
PJ_DEF(void) pjsip_presence_init(const pjsip_presence_cb *pcb)
{
pj_memcpy(&cb, pcb, sizeof(*pcb));
pjsip_event_sub_register_pkg( &PRESENCE_EVENT,
sizeof(accept_names)/sizeof(accept_names[0]),
accept_names,
&pkg_cb);
}
/*
* Create presence subscription.
*/
PJ_DEF(pjsip_presentity*) pjsip_presence_create( pjsip_endpoint *endpt,
const pj_str_t *local_url,
const pj_str_t *remote_url,
int expires,
void *user_data )
{
pjsip_event_sub *sub;
pjsip_presentity *pres;
if (expires < 0)
expires = 300;
/* Create event subscription */
sub = pjsip_event_sub_create(endpt, local_url, remote_url, &PRESENCE_EVENT,
expires,
sizeof(accept_names)/sizeof(accept_names[0]),
accept_names,
NULL, &sub_cb);
if (!sub)
return NULL;
/* Allocate presence descriptor. */
pres = pj_pool_calloc(sub->pool, 1, sizeof(*pres));
pres->sub = sub;
pres->user_data = user_data;
sub->user_data = pres;
return pres;
}
/*
* Send SUBSCRIBE.
*/
PJ_DEF(pj_status_t) pjsip_presence_subscribe( pjsip_presentity *pres )
{
return pjsip_event_sub_subscribe( pres->sub );
}
/*
* Set credentials to be used for outgoing requests.
*/
PJ_DEF(pj_status_t) pjsip_presence_set_credentials( pjsip_presentity *pres,
int count,
const pjsip_cred_info cred[])
{
return pjsip_event_sub_set_credentials(pres->sub, count, cred);
}
/*
* Set route-set.
*/
PJ_DEF(pj_status_t) pjsip_presence_set_route_set( pjsip_presentity *pres,
const pjsip_route_hdr *hdr )
{
return pjsip_event_sub_set_route_set( pres->sub, hdr );
}
/*
* Unsubscribe.
*/
PJ_DEF(pj_status_t) pjsip_presence_unsubscribe( pjsip_presentity *pres )
{
return pjsip_event_sub_unsubscribe(pres->sub);
}
/*
* This is the pjsip_msg_body callback to print XML body.
*/
static int print_xml(pjsip_msg_body *body, char *buf, pj_size_t size)
{
return pj_xml_print( body->data, buf, size, PJ_TRUE );
}
/*
* Create and initialize PIDF document and msg body (notifier only).
*/
static pj_status_t init_presence_info( pjsip_presentity *pres )
{
pj_str_t uri;
pj_pool_t *pool = pres->sub->pool;
char tmp[PJSIP_MAX_URL_SIZE];
pjpidf_tuple *tuple;
const pjsip_media_type *content_type = NULL;
pj_assert(pres->uas_body == NULL);
/* Make entity_id */
uri.ptr = tmp;
uri.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, pres->sub->from->uri,
tmp, sizeof(tmp));
if (uri.slen < 0)
return -1;
if (pres->pres_type == PJSIP_PRES_TYPE_PIDF) {
pj_str_t s;
/* Create <presence>. */
pres->uas_data.pidf = pjpidf_create(pool, &s);
/* Create <tuple> */
pj_create_unique_string(pool, &s);
tuple = pjpidf_pres_add_tuple(pool, pres->uas_data.pidf, &s);
/* Set <contact> */
s.ptr = tmp;
s.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, pres->sub->contact->uri, tmp, sizeof(tmp));
if (s.slen < 0)
return -1;
pjpidf_tuple_set_contact(pool, tuple, &s);
/* Content-Type */
content_type = &accept_types[PJSIP_PRES_TYPE_PIDF];
} else if (pres->pres_type == PJSIP_PRES_TYPE_XPIDF) {
/* Create XPIDF */
pres->uas_data.xpidf = pjxpidf_create(pool, &uri);
/* Content-Type. */
content_type = &accept_types[PJSIP_PRES_TYPE_XPIDF];
}
/* Create message body */
pres->uas_body = pj_pool_alloc(pool, sizeof(pjsip_msg_body));
pres->uas_body->content_type = *content_type;
pres->uas_body->data = pres->uas_data.pidf;
pres->uas_body->len = 0;
pres->uas_body->print_body = &print_xml;
return 0;
}
/*
* Send NOTIFY and set subscription state.
*/
PJ_DEF(pj_status_t) pjsip_presence_notify( pjsip_presentity *pres,
pjsip_event_sub_state state,
pj_bool_t is_online )
{
pj_str_t reason = { "", 0 };
if (pres->uas_data.pidf == NULL) {
if (init_presence_info(pres) != 0)
return -1;
}
/* Update basic status in PIDF/XPIDF document. */
if (pres->pres_type == PJSIP_PRES_TYPE_PIDF) {
pjpidf_tuple *first;
pjpidf_status *status;
pj_time_val now;
pj_parsed_time pnow;
first = pjpidf_op.pres.get_first_tuple(pres->uas_data.pidf);
pj_assert(first);
status = pjpidf_op.tuple.get_status(first);
pj_assert(status);
pjpidf_op.status.set_basic_open(status, is_online);
/* Update timestamp. */
if (pres->timestamp.ptr == 0) {
pres->timestamp.ptr = pj_pool_alloc(pres->sub->pool, 24);
}
pj_gettimeofday(&now);
pj_time_decode(&now, &pnow);
pres->timestamp.slen = sprintf(pres->timestamp.ptr,
"%04d-%02d-%02dT%02d:%02d:%02dZ",
pnow.year, pnow.mon, pnow.day,
pnow.hour, pnow.min, pnow.sec);
pjpidf_op.tuple.set_timestamp_np(pres->sub->pool, first, &pres->timestamp);
} else if (pres->pres_type == PJSIP_PRES_TYPE_XPIDF) {
pjxpidf_set_status( pres->uas_data.xpidf, is_online );
} else {
pj_assert(0);
}
/* Send notify. */
return pjsip_event_sub_notify( pres->sub, state, &reason, pres->uas_body);
}
/*
* Destroy subscription (can be called for both subscriber and notifier).
*/
PJ_DEF(pj_status_t) pjsip_presence_destroy( pjsip_presentity *pres )
{
return pjsip_event_sub_destroy(pres->sub);
}
/*
* This callback is called by event framework to query whether we want to
* accept an incoming subscription.
*/
static void on_query_subscribe(pjsip_rx_data *rdata, int *status)
{
if (cb.accept_presence) {
(*cb.accept_presence)(rdata, status);
}
}
/*
* This callback is called by event framework after we accept the incoming
* subscription, to notify about the new subscription instance.
*/
static void on_subscribe(pjsip_event_sub *sub, pjsip_rx_data *rdata,
pjsip_event_sub_cb **set_sub_cb, int *expires)
{
pjsip_presentity *pres;
pjsip_accept_hdr *accept;
pres = pj_pool_calloc(sub->pool, 1, sizeof(*pres));
pres->sub = sub;
pres->pres_type = PJSIP_PRES_TYPE_PIDF;
sub->user_data = pres;
*set_sub_cb = &sub_cb;
accept = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_ACCEPT, NULL);
if (accept) {
unsigned i;
int found = 0;
for (i=0; i<accept->count && !found; ++i) {
int j;
for (j=0; j<sizeof(accept_names)/sizeof(accept_names[0]); ++j) {
if (!pj_stricmp(&accept->values[i], &accept_names[j])) {
pres->pres_type = j;
found = 1;
break;
}
}
}
pj_assert(found );
}
(*cb.on_received_request)(pres, rdata, expires);
}
/*
* This callback is called by event framework when the subscription is
* terminated.
*/
static void on_sub_terminated(pjsip_event_sub *sub, const pj_str_t *reason)
{
pjsip_presentity *pres = sub->user_data;
if (cb.on_terminated)
(*cb.on_terminated)(pres, reason);
}
/*
* This callback is called by event framework when it receives incoming
* SUBSCRIBE request to refresh the subscription.
*/
static void on_sub_received_refresh(pjsip_event_sub *sub, pjsip_rx_data *rdata)
{
pjsip_presentity *pres = sub->user_data;
if (cb.on_received_refresh)
(*cb.on_received_refresh)(pres, rdata);
}
/*
* This callback is called by event framework when it receives incoming
* NOTIFY request.
*/
static void on_received_notify(pjsip_event_sub *sub, pjsip_rx_data *rdata)
{
pjsip_presentity *pres = sub->user_data;
if (cb.on_received_update) {
pj_status_t is_open;
pjsip_msg_body *body;
int i;
body = rdata->msg->body;
if (!body)
return;
for (i=0; i<sizeof(accept_types)/sizeof(accept_types[0]); ++i) {
if (!pj_stricmp(&body->content_type.type, &accept_types[i].type) &&
!pj_stricmp(&body->content_type.subtype, &accept_types[i].subtype))
{
break;
}
}
if (i==PJSIP_PRES_TYPE_PIDF) {
pjpidf_pres *pres;
pjpidf_tuple *tuple;
pjpidf_status *status;
pres = pjpidf_parse(rdata->pool, body->data, body->len);
if (!pres)
return;
tuple = pjpidf_pres_get_first_tuple(pres);
if (!tuple)
return;
status = pjpidf_tuple_get_status(tuple);
if (!status)
return;
is_open = pjpidf_status_is_basic_open(status);
} else if (i==PJSIP_PRES_TYPE_XPIDF) {
pjxpidf_pres *pres;
pres = pjxpidf_parse(rdata->pool, body->data, body->len);
if (!pres)
return;
is_open = pjxpidf_get_status(pres);
} else {
return;
}
(*cb.on_received_update)(pres, is_open);
}
}