blob: 042a16a091e07ffe6a715e222eddd6ad77ce2e4e [file] [log] [blame]
/* $Id$ */
/*
* Copyright (C) 2003-2007 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/publish.h>
#include <pjsip/sip_auth.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_errno.h>
#include <pjsip/sip_event.h>
#include <pjsip/sip_msg.h>
#include <pjsip/sip_transaction.h>
#include <pjsip/sip_uri.h>
#include <pjsip/sip_util.h>
#include <pj/assert.h>
#include <pj/guid.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>
#include <pj/timer.h>
#define REFRESH_TIMER 1
#define DELAY_BEFORE_REFRESH 5
#define THIS_FILE "publishc.c"
/* Let's define this enum, so that it'll trigger compilation error
* when somebody define the same enum in sip_msg.h
*/
enum
{
PJSIP_PUBLISH_METHOD = PJSIP_OTHER_METHOD,
};
const pjsip_method pjsip_publish_method =
{
(pjsip_method_e)PJSIP_PUBLISH_METHOD,
{ "PUBLISH", 7 }
};
/**
* SIP client publication structure.
*/
struct pjsip_publishc
{
pj_pool_t *pool;
pjsip_endpoint *endpt;
pj_bool_t _delete_flag;
int pending_tsx;
void *token;
pjsip_publishc_cb *cb;
pj_str_t event;
pj_str_t str_target_uri;
pjsip_uri *target_uri;
pjsip_cid_hdr *cid_hdr;
pjsip_cseq_hdr *cseq_hdr;
pj_str_t from_uri;
pjsip_from_hdr *from_hdr;
pjsip_to_hdr *to_hdr;
pj_str_t etag;
pjsip_expires_hdr *expires_hdr;
pj_uint32_t expires;
pjsip_route_hdr route_set;
/* Authorization sessions. */
pjsip_auth_clt_sess auth_sess;
/* Auto refresh publication. */
pj_bool_t auto_refresh;
pj_time_val last_refresh;
pj_time_val next_refresh;
pj_timer_entry timer;
};
/*
* Initialize client publication module.
*/
PJ_DEF(pj_status_t) pjsip_publishc_init_module(pjsip_endpoint *endpt)
{
return pjsip_endpt_add_capability( endpt, NULL, PJSIP_H_ALLOW, NULL,
1, &pjsip_publish_method.name);
}
PJ_DEF(pj_status_t) pjsip_publishc_create( pjsip_endpoint *endpt,
unsigned options,
void *token,
pjsip_publishc_cb *cb,
pjsip_publishc **p_pubc)
{
pj_pool_t *pool;
pjsip_publishc *pubc;
pj_status_t status;
/* Verify arguments. */
PJ_ASSERT_RETURN(endpt && cb && p_pubc, PJ_EINVAL);
PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);
PJ_UNUSED_ARG(options);
pool = pjsip_endpt_create_pool(endpt, "pubc%p", 1024, 1024);
PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
pubc = PJ_POOL_ZALLOC_T(pool, pjsip_publishc);
pubc->pool = pool;
pubc->endpt = endpt;
pubc->token = token;
pubc->cb = cb;
pubc->expires = PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED;
status = pjsip_auth_clt_init(&pubc->auth_sess, endpt, pubc->pool, 0);
if (status != PJ_SUCCESS)
return status;
pj_list_init(&pubc->route_set);
/* Done */
*p_pubc = pubc;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_destroy(pjsip_publishc *pubc)
{
PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
if (pubc->pending_tsx) {
pubc->_delete_flag = 1;
pubc->cb = NULL;
} else {
pjsip_endpt_release_pool(pubc->endpt, pubc->pool);
}
return PJ_SUCCESS;
}
PJ_DEF(pj_pool_t*) pjsip_publishc_get_pool(pjsip_publishc *pubc)
{
return pubc->pool;
}
static void set_expires( pjsip_publishc *pubc, pj_uint32_t expires)
{
if (expires != pubc->expires) {
pubc->expires_hdr = pjsip_expires_hdr_create(pubc->pool, expires);
} else {
pubc->expires_hdr = NULL;
}
}
PJ_DEF(pj_status_t) pjsip_publishc_init(pjsip_publishc *pubc,
const pj_str_t *event,
const pj_str_t *target_uri,
const pj_str_t *from_uri,
const pj_str_t *to_uri,
pj_uint32_t expires)
{
pj_str_t tmp;
PJ_ASSERT_RETURN(pubc && event && target_uri && from_uri && to_uri &&
expires, PJ_EINVAL);
/* Copy event type */
pj_strdup_with_null(pubc->pool, &pubc->event, event);
/* Copy server URL. */
pj_strdup_with_null(pubc->pool, &pubc->str_target_uri, target_uri);
/* Set server URL. */
tmp = pubc->str_target_uri;
pubc->target_uri = pjsip_parse_uri( pubc->pool, tmp.ptr, tmp.slen, 0);
if (pubc->target_uri == NULL) {
return PJSIP_EINVALIDURI;
}
/* Set "From" header. */
pj_strdup_with_null(pubc->pool, &pubc->from_uri, from_uri);
tmp = pubc->from_uri;
pubc->from_hdr = pjsip_from_hdr_create(pubc->pool);
pubc->from_hdr->uri = pjsip_parse_uri(pubc->pool, tmp.ptr, tmp.slen,
PJSIP_PARSE_URI_AS_NAMEADDR);
if (!pubc->from_hdr->uri) {
return PJSIP_EINVALIDURI;
}
/* Set "To" header. */
pj_strdup_with_null(pubc->pool, &tmp, to_uri);
pubc->to_hdr = pjsip_to_hdr_create(pubc->pool);
pubc->to_hdr->uri = pjsip_parse_uri(pubc->pool, tmp.ptr, tmp.slen,
PJSIP_PARSE_URI_AS_NAMEADDR);
if (!pubc->to_hdr->uri) {
return PJSIP_EINVALIDURI;
}
/* Set "Expires" header, if required. */
set_expires( pubc, expires);
/* Set "Call-ID" header. */
pubc->cid_hdr = pjsip_cid_hdr_create(pubc->pool);
pj_create_unique_string(pubc->pool, &pubc->cid_hdr->id);
/* Set "CSeq" header. */
pubc->cseq_hdr = pjsip_cseq_hdr_create(pubc->pool);
pubc->cseq_hdr->cseq = pj_rand() % 0xFFFF;
pjsip_method_set( &pubc->cseq_hdr->method, PJSIP_REGISTER_METHOD);
/* Done. */
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_set_credentials( pjsip_publishc *pubc,
int count,
const pjsip_cred_info cred[] )
{
PJ_ASSERT_RETURN(pubc && count && cred, PJ_EINVAL);
return pjsip_auth_clt_set_credentials(&pubc->auth_sess, count, cred);
}
PJ_DEF(pj_status_t) pjsip_publishc_set_route_set( pjsip_publishc *pubc,
const pjsip_route_hdr *route_set)
{
const pjsip_route_hdr *chdr;
PJ_ASSERT_RETURN(pubc && route_set, PJ_EINVAL);
pj_list_init(&pubc->route_set);
chdr = route_set->next;
while (chdr != route_set) {
pj_list_push_back(&pubc->route_set, pjsip_hdr_clone(pubc->pool, chdr));
chdr = chdr->next;
}
return PJ_SUCCESS;
}
static pj_status_t create_request(pjsip_publishc *pubc,
pjsip_tx_data **p_tdata)
{
const pj_str_t STR_EVENT = { "Event", 5 };
pj_status_t status;
pjsip_generic_string_hdr *hdr;
pjsip_tx_data *tdata;
PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
/* Create the request. */
status = pjsip_endpt_create_request_from_hdr( pubc->endpt,
&pjsip_publish_method,
pubc->target_uri,
pubc->from_hdr,
pubc->to_hdr,
NULL,
pubc->cid_hdr,
pubc->cseq_hdr->cseq,
NULL,
&tdata);
if (status != PJ_SUCCESS)
return status;
/* Add cached authorization headers. */
pjsip_auth_clt_init_req( &pubc->auth_sess, tdata );
/* Add Route headers from route set, ideally after Via header */
if (!pj_list_empty(&pubc->route_set)) {
pjsip_hdr *route_pos;
const pjsip_route_hdr *route;
route_pos = (pjsip_hdr*)
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
if (!route_pos)
route_pos = &tdata->msg->hdr;
route = pubc->route_set.next;
while (route != &pubc->route_set) {
pjsip_hdr *new_hdr = (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool, route);
pj_list_insert_after(route_pos, new_hdr);
route_pos = new_hdr;
route = route->next;
}
}
/* Add Event header */
hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_EVENT,
&pubc->event);
if (hdr)
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
/* Add SIP-If-Match if we have etag */
if (pubc->etag.slen) {
const pj_str_t STR_HNAME = { "SIP-If-Match", 12 };
hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_HNAME,
&pubc->etag);
if (hdr)
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
}
/* Done. */
*p_tdata = tdata;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_publish(pjsip_publishc *pubc,
pj_bool_t auto_refresh,
pjsip_tx_data **p_tdata)
{
pj_status_t status;
pjsip_tx_data *tdata;
PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
status = create_request(pubc, &tdata);
if (status != PJ_SUCCESS)
return status;
/* Add Expires header */
if (pubc->expires_hdr) {
pjsip_hdr *dup;
dup = (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool, pubc->expires_hdr);
if (dup)
pjsip_msg_add_hdr(tdata->msg, dup);
}
/* Cancel existing timer */
if (pubc->timer.id != 0) {
pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
pubc->timer.id = 0;
}
pubc->auto_refresh = auto_refresh;
/* Done */
*p_tdata = tdata;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_unpublish(pjsip_publishc *pubc,
pjsip_tx_data **p_tdata)
{
pjsip_tx_data *tdata;
pjsip_msg *msg;
pjsip_expires_hdr *expires;
pj_status_t status;
PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
if (pubc->timer.id != 0) {
pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
pubc->timer.id = 0;
}
status = create_request(pubc, &tdata);
if (status != PJ_SUCCESS)
return status;
msg = tdata->msg;
/* Add Expires:0 header */
expires = pjsip_expires_hdr_create(tdata->pool, 0);
pjsip_msg_add_hdr( msg, (pjsip_hdr*)expires);
*p_tdata = tdata;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_update_expires( pjsip_publishc *pubc,
pj_uint32_t expires )
{
PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
set_expires( pubc, expires );
return PJ_SUCCESS;
}
static void call_callback(pjsip_publishc *pubc, pj_status_t status,
int st_code, const pj_str_t *reason,
pjsip_rx_data *rdata, pj_int32_t expiration)
{
struct pjsip_publishc_cbparam cbparam;
cbparam.pubc = pubc;
cbparam.token = pubc->token;
cbparam.status = status;
cbparam.code = st_code;
cbparam.reason = *reason;
cbparam.rdata = rdata;
cbparam.expiration = expiration;
(*pubc->cb)(&cbparam);
}
static void pubc_refresh_timer_cb( pj_timer_heap_t *timer_heap,
struct pj_timer_entry *entry)
{
pjsip_publishc *pubc = (pjsip_publishc*) entry->user_data;
pjsip_tx_data *tdata;
pj_status_t status;
PJ_UNUSED_ARG(timer_heap);
entry->id = 0;
status = pjsip_publishc_publish(pubc, 1, &tdata);
if (status == PJ_SUCCESS) {
status = pjsip_publishc_send(pubc, tdata);
}
// Callback should have been called.
// Calling it here will crash the system since pubc might have been
// destroyed
//
//if (status != PJ_SUCCESS) {
// char errmsg[PJ_ERR_MSG_SIZE];
// pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg));
// call_callback(pubc, status, 400, &reason, NULL, -1);
//}
}
static void tsx_callback(void *token, pjsip_event *event)
{
pj_status_t status;
pjsip_publishc *pubc = (pjsip_publishc*) token;
pjsip_transaction *tsx = event->body.tsx_state.tsx;
/* Decrement pending transaction counter. */
pj_assert(pubc->pending_tsx > 0);
--pubc->pending_tsx;
/* If publication data has been deleted by user then remove publication
* data from transaction's callback, and don't call callback.
*/
if (pubc->_delete_flag) {
/* Nothing to do */
;
} else if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED ||
tsx->status_code == PJSIP_SC_UNAUTHORIZED)
{
pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
pjsip_tx_data *tdata;
status = pjsip_auth_clt_reinit_req( &pubc->auth_sess,
rdata,
tsx->last_tx,
&tdata);
if (status == PJ_SUCCESS) {
status = pjsip_publishc_send(pubc, tdata);
}
// Callback should have been called.
// Calling it here will crash the system since pubc might have been
// destroyed
//
//if (status != PJ_SUCCESS) {
// call_callback(pubc, status, tsx->status_code,
// &rdata->msg_info.msg->line.status.reason,
// rdata, -1);
//}
return;
} else {
pjsip_rx_data *rdata;
pj_int32_t expiration = 0xFFFF;
if (tsx->status_code/100 == 2) {
pjsip_msg *msg;
pjsip_expires_hdr *expires;
pjsip_generic_string_hdr *etag_hdr;
const pj_str_t STR_ETAG = { "SIP-ETag", 8 };
rdata = event->body.tsx_state.src.rdata;
msg = rdata->msg_info.msg;
/* Save ETag value */
etag_hdr = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name(msg, &STR_ETAG, NULL);
if (etag_hdr) {
pj_strdup(pubc->pool, &pubc->etag, &etag_hdr->hvalue);
} else {
pubc->etag.slen = 0;
}
/* Update expires value */
expires = (pjsip_expires_hdr*)
pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
if (expires)
expiration = expires->ivalue;
if (pubc->auto_refresh && expiration!=0 && expiration!=0xFFFF) {
pj_time_val delay = { 0, 0};
delay.sec = expiration - DELAY_BEFORE_REFRESH;
if (pubc->expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED &&
delay.sec > (pj_int32_t)pubc->expires)
{
delay.sec = pubc->expires;
}
if (delay.sec < DELAY_BEFORE_REFRESH)
delay.sec = DELAY_BEFORE_REFRESH;
pubc->timer.cb = &pubc_refresh_timer_cb;
pubc->timer.id = REFRESH_TIMER;
pubc->timer.user_data = pubc;
pjsip_endpt_schedule_timer( pubc->endpt, &pubc->timer, &delay);
pj_gettimeofday(&pubc->last_refresh);
pubc->next_refresh = pubc->last_refresh;
pubc->next_refresh.sec += delay.sec;
}
} else {
rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ?
event->body.tsx_state.src.rdata : NULL;
}
/* Call callback. */
if (expiration == 0xFFFF) expiration = -1;
/* Temporarily increment pending_tsx to prevent callback from
* destroying pubc.
*/
++pubc->pending_tsx;
call_callback(pubc, PJ_SUCCESS, tsx->status_code,
(rdata ? &rdata->msg_info.msg->line.status.reason
: pjsip_get_status_text(tsx->status_code)),
rdata, expiration);
--pubc->pending_tsx;
}
/* Delete the record if user destroy pubc during the callback. */
if (pubc->_delete_flag && pubc->pending_tsx==0) {
pjsip_publishc_destroy(pubc);
}
}
PJ_DEF(pj_status_t) pjsip_publishc_send(pjsip_publishc *pubc,
pjsip_tx_data *tdata)
{
pj_status_t status;
pjsip_cseq_hdr *cseq_hdr;
pj_uint32_t cseq;
/* Make sure we don't have pending transaction. */
if (pubc->pending_tsx) {
PJ_LOG(4,(THIS_FILE, "Unable to send request, pubc has another "
"transaction pending"));
pjsip_tx_data_dec_ref( tdata );
return PJSIP_EBUSY;
}
/* Invalidate message buffer. */
pjsip_tx_data_invalidate_msg(tdata);
/* Increment CSeq */
cseq = ++pubc->cseq_hdr->cseq;
cseq_hdr = (pjsip_cseq_hdr*)
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
cseq_hdr->cseq = cseq;
/* Increment pending transaction first, since transaction callback
* may be called even before send_request() returns!
*/
++pubc->pending_tsx;
status = pjsip_endpt_send_request(pubc->endpt, tdata, -1, pubc,
&tsx_callback);
if (status!=PJ_SUCCESS) {
// no need to decrement, callback has been called and it should
// already decremented pending_tsx. Decrementing this here may
// cause accessing freed memory location.
//--pubc->pending_tsx;
PJ_LOG(4,(THIS_FILE, "Error sending request, status=%d", status));
}
return status;
}