/* $Id$ */ /* * Copyright (C) 2008-2009 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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) { /* Note: Commented out the capability registration below, since it's wrong to include PUBLISH in Allow header of INVITE requests/ responses. 13.2.1 Creating the Initial INVITE An Allow header field (Section 20.5) SHOULD be present in the INVITE. It indicates what methods can be invoked within a dialog 20.5 Allow The Allow header field lists the set of methods supported by the UA generating the message. While the semantic of Allow header in non-dialog requests is unclear, it's probably best not to include PUBLISH in Allow header for now until we can find out how to customize the inclusion of methods in Allow header for in-dialog vs out-dialog requests. return pjsip_endpt_add_capability( endpt, NULL, PJSIP_H_ALLOW, NULL, 1, &pjsip_publish_method.name); */ PJ_UNUSED_ARG(endpt); return PJ_SUCCESS; } 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) { char errmsg[PJ_ERR_MSG_SIZE]; pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg)); call_callback(pubc, status, 400, &reason, NULL, -1); return; } status = pjsip_publishc_send(pubc, tdata); /* No need to call callback as it should have been called */ } 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) { call_callback(pubc, status, tsx->status_code, &rdata->msg_info.msg->line.status.reason, rdata, -1); } else { status = pjsip_publishc_send(pubc, tdata); } } 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; }