From f3ab456a17af1c89a6e3be4d20c5944853df1cb0 Mon Sep 17 00:00:00 2001 From: "David M. Lee" Date: Mon, 7 Jan 2013 14:24:28 -0600 Subject: Import pjproject-2.0.1 --- pjsip/src/pjsip-simple/errno.c | 116 ++ pjsip/src/pjsip-simple/evsub.c | 2169 ++++++++++++++++++++++++++++++++ pjsip/src/pjsip-simple/evsub_msg.c | 304 +++++ pjsip/src/pjsip-simple/iscomposing.c | 218 ++++ pjsip/src/pjsip-simple/mwi.c | 598 +++++++++ pjsip/src/pjsip-simple/pidf.c | 365 ++++++ pjsip/src/pjsip-simple/presence.c | 941 ++++++++++++++ pjsip/src/pjsip-simple/presence_body.c | 288 +++++ pjsip/src/pjsip-simple/publishc.c | 790 ++++++++++++ pjsip/src/pjsip-simple/rpid.c | 279 ++++ pjsip/src/pjsip-simple/xpidf.c | 301 +++++ 11 files changed, 6369 insertions(+) create mode 100644 pjsip/src/pjsip-simple/errno.c create mode 100644 pjsip/src/pjsip-simple/evsub.c create mode 100644 pjsip/src/pjsip-simple/evsub_msg.c create mode 100644 pjsip/src/pjsip-simple/iscomposing.c create mode 100644 pjsip/src/pjsip-simple/mwi.c create mode 100644 pjsip/src/pjsip-simple/pidf.c create mode 100644 pjsip/src/pjsip-simple/presence.c create mode 100644 pjsip/src/pjsip-simple/presence_body.c create mode 100644 pjsip/src/pjsip-simple/publishc.c create mode 100644 pjsip/src/pjsip-simple/rpid.c create mode 100644 pjsip/src/pjsip-simple/xpidf.c (limited to 'pjsip/src/pjsip-simple') diff --git a/pjsip/src/pjsip-simple/errno.c b/pjsip/src/pjsip-simple/errno.c new file mode 100644 index 0000000..e1bdb95 --- /dev/null +++ b/pjsip/src/pjsip-simple/errno.c @@ -0,0 +1,116 @@ +/* $Id: errno.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 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 + +/* PJSIP-SIMPLE's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + +static const struct +{ + int code; + const char *msg; +} err_str[] = +{ + /* Event errors */ + { PJSIP_SIMPLE_ENOPKG, "No SIP event package with the specified name" }, + { PJSIP_SIMPLE_EPKGEXISTS, "SIP event package already exist" }, + + /* Presence errors */ + { PJSIP_SIMPLE_ENOTSUBSCRIBE, "Expecting SUBSCRIBE request" }, + { PJSIP_SIMPLE_ENOPRESENCE, "No presence associated with the subscription" }, + { PJSIP_SIMPLE_ENOPRESENCEINFO, "No presence info in the server subscription" }, + { PJSIP_SIMPLE_EBADCONTENT, "Bad Content-Type for presence" }, + { PJSIP_SIMPLE_EBADPIDF, "Bad PIDF content for presence" }, + { PJSIP_SIMPLE_EBADXPIDF, "Bad XPIDF content for presence" }, + { PJSIP_SIMPLE_EBADRPID, "Invalid or bad RPID document"}, + + /* isComposing errors. */ + { PJSIP_SIMPLE_EBADISCOMPOSE, "Bad isComposing indication/XML message" }, +}; + + +#endif /* PJ_HAS_ERROR_STRING */ + + +/* + * pjsipsimple_strerror() + */ +PJ_DEF(pj_str_t) pjsipsimple_strerror( pj_status_t statcode, + char *buf, pj_size_t bufsize ) +{ + pj_str_t errstr; + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + + if (statcode >= PJSIP_SIMPLE_ERRNO_START && + statcode < PJSIP_SIMPLE_ERRNO_START + PJ_ERRNO_SPACE_SIZE) + { + /* Find the error in the table. + * Use binary search! + */ + int first = 0; + int n = PJ_ARRAY_SIZE(err_str); + + while (n > 0) { + int half = n/2; + int mid = first + half; + + if (err_str[mid].code < statcode) { + first = mid+1; + n -= (half+1); + } else if (err_str[mid].code > statcode) { + n = half; + } else { + first = mid; + break; + } + } + + + if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { + pj_str_t msg; + + msg.ptr = (char*)err_str[first].msg; + msg.slen = pj_ansi_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + + } + } + +#endif /* PJ_HAS_ERROR_STRING */ + + + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, + "Unknown pjsip-simple error %d", + statcode); + + return errstr; +} + diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c new file mode 100644 index 0000000..b1dfca5 --- /dev/null +++ b/pjsip/src/pjsip-simple/evsub.c @@ -0,0 +1,2169 @@ +/* $Id: evsub.c 4082 2012-04-24 13:09:14Z bennylp $ */ +/* + * Copyright (C) 2008-2011 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 + + +#define THIS_FILE "evsub.c" + +/* + * Global constant + */ + +/* Let's define this enum, so that it'll trigger compilation error + * when somebody define the same enum in sip_msg.h + */ +enum +{ + PJSIP_SUBSCRIBE_METHOD = PJSIP_OTHER_METHOD, + PJSIP_NOTIFY_METHOD = PJSIP_OTHER_METHOD +}; + +PJ_DEF_DATA(const pjsip_method) pjsip_subscribe_method = +{ + (pjsip_method_e) PJSIP_SUBSCRIBE_METHOD, + { "SUBSCRIBE", 9 } +}; + +PJ_DEF_DATA(const pjsip_method) pjsip_notify_method = +{ + (pjsip_method_e) PJSIP_NOTIFY_METHOD, + { "NOTIFY", 6 } +}; + +/** + * SUBSCRIBE method constant. + */ +PJ_DEF(const pjsip_method*) pjsip_get_subscribe_method() +{ + return &pjsip_subscribe_method; +} + +/** + * NOTIFY method constant. + */ +PJ_DEF(const pjsip_method*) pjsip_get_notify_method() +{ + return &pjsip_notify_method; +} + + +/* + * Static prototypes. + */ +static void mod_evsub_on_tsx_state(pjsip_transaction*, pjsip_event*); +static pj_status_t mod_evsub_unload(void); + + +/* + * State names. + */ +static pj_str_t evsub_state_names[] = +{ + { "NULL", 4}, + { "SENT", 4}, + { "ACCEPTED", 8}, + { "PENDING", 7}, + { "ACTIVE", 6}, + { "TERMINATED", 10}, + { "UNKNOWN", 7} +}; + +/* + * Timer constants. + */ + +/* Number of seconds to send SUBSCRIBE before the actual expiration */ +#define TIME_UAC_REFRESH PJSIP_EVSUB_TIME_UAC_REFRESH + +/* Time to wait for the final NOTIFY after sending unsubscription */ +#define TIME_UAC_TERMINATE PJSIP_EVSUB_TIME_UAC_TERMINATE + +/* If client responds NOTIFY with non-2xx final response (such as 401), + * wait for this seconds for further NOTIFY, otherwise client will + * unsubscribe + */ +#define TIME_UAC_WAIT_NOTIFY PJSIP_EVSUB_TIME_UAC_WAIT_NOTIFY + + +/* + * Timer id + */ +enum timer_id +{ + /* No timer. */ + TIMER_TYPE_NONE, + + /* Time to refresh client subscription. + * The action is to call on_client_refresh() callback. + */ + TIMER_TYPE_UAC_REFRESH, + + /* UAS timeout after to subscription refresh. + * The action is to call on_server_timeout() callback. + */ + TIMER_TYPE_UAS_TIMEOUT, + + /* UAC waiting for final NOTIFY after unsubscribing + * The action is to terminate. + */ + TIMER_TYPE_UAC_TERMINATE, + + /* UAC waiting for further NOTIFY after sending non-2xx response to + * NOTIFY. The action is to unsubscribe. + */ + TIMER_TYPE_UAC_WAIT_NOTIFY, + + /* Max nb of timer types. */ + TIMER_TYPE_MAX +}; + +static const char *timer_names[] = +{ + "None", + "UAC_REFRESH", + "UAS_TIMEOUT" + "UAC_TERMINATE", + "UAC_WAIT_NOTIFY", + "INVALID_TIMER" +}; + +/* + * Definition of event package. + */ +struct evpkg +{ + PJ_DECL_LIST_MEMBER(struct evpkg); + + pj_str_t pkg_name; + pjsip_module *pkg_mod; + unsigned pkg_expires; + pjsip_accept_hdr *pkg_accept; +}; + + +/* + * Event subscription module (mod-evsub). + */ +static struct mod_evsub +{ + pjsip_module mod; + pj_pool_t *pool; + pjsip_endpoint *endpt; + struct evpkg pkg_list; + pjsip_allow_events_hdr *allow_events_hdr; + +} mod_evsub = +{ + { + NULL, NULL, /* prev, next. */ + { "mod-evsub", 9 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + &mod_evsub_unload, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + &mod_evsub_on_tsx_state, /* on_tsx_state() */ + } +}; + + +/* + * Event subscription session. + */ +struct pjsip_evsub +{ + char obj_name[PJ_MAX_OBJ_NAME]; /**< Name. */ + pj_pool_t *pool; /**< Pool. */ + pjsip_endpoint *endpt; /**< Endpoint instance. */ + pjsip_dialog *dlg; /**< Underlying dialog. */ + struct evpkg *pkg; /**< The event package. */ + unsigned option; /**< Options. */ + pjsip_evsub_user user; /**< Callback. */ + pj_bool_t call_cb; /**< Notify callback? */ + pjsip_role_e role; /**< UAC=subscriber, UAS=notifier */ + pjsip_evsub_state state; /**< Subscription state. */ + pj_str_t state_str; /**< String describing the state. */ + pjsip_evsub_state dst_state; /**< Pending state to be set. */ + pj_str_t dst_state_str;/**< Pending state to be set. */ + pj_str_t term_reason; /**< Termination reason. */ + pjsip_method method; /**< Method that established subscr.*/ + pjsip_event_hdr *event; /**< Event description. */ + pjsip_expires_hdr *expires; /**< Expires header */ + pjsip_accept_hdr *accept; /**< Local Accept header. */ + pjsip_hdr sub_hdr_list; /**< User-defined header. */ + + pj_time_val refresh_time; /**< Time to refresh. */ + pj_timer_entry timer; /**< Internal timer. */ + int pending_tsx; /**< Number of pending transactions.*/ + pjsip_transaction *pending_sub; /**< Pending UAC SUBSCRIBE tsx. */ + + void *mod_data[PJSIP_MAX_MODULE]; /**< Module data. */ +}; + + +/* + * This is the structure that will be "attached" to dialog. + * The purpose is to allow multiple subscriptions inside a dialog. + */ +struct dlgsub +{ + PJ_DECL_LIST_MEMBER(struct dlgsub); + pjsip_evsub *sub; +}; + + +/* Static vars. */ +static const pj_str_t STR_EVENT = { "Event", 5 }; +static const pj_str_t STR_EVENT_S = { "Event", 5 }; +static const pj_str_t STR_SUB_STATE = { "Subscription-State", 18 }; +static const pj_str_t STR_TERMINATED = { "terminated", 10 }; +static const pj_str_t STR_ACTIVE = { "active", 6 }; +static const pj_str_t STR_PENDING = { "pending", 7 }; +static const pj_str_t STR_TIMEOUT = { "timeout", 7}; + + +/* + * On unload module. + */ +static pj_status_t mod_evsub_unload(void) +{ + pjsip_endpt_release_pool(mod_evsub.endpt, mod_evsub.pool); + mod_evsub.pool = NULL; + + return PJ_SUCCESS; +} + +/* Proto for pjsipsimple_strerror(). + * Defined in errno.c + */ +PJ_DECL(pj_str_t) pjsipsimple_strerror( pj_status_t statcode, + char *buf, pj_size_t bufsize ); + +/* + * Init and register module. + */ +PJ_DEF(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt) +{ + pj_status_t status; + pj_str_t method_tags[] = { + { "SUBSCRIBE", 9}, + { "NOTIFY", 6} + }; + + status = pj_register_strerror(PJSIP_SIMPLE_ERRNO_START, + PJ_ERRNO_SPACE_SIZE, + &pjsipsimple_strerror); + pj_assert(status == PJ_SUCCESS); + + PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(mod_evsub.mod.id == -1, PJ_EINVALIDOP); + + /* Keep endpoint for future reference: */ + mod_evsub.endpt = endpt; + + /* Init event package list: */ + pj_list_init(&mod_evsub.pkg_list); + + /* Create pool: */ + mod_evsub.pool = pjsip_endpt_create_pool(endpt, "evsub", 512, 512); + if (!mod_evsub.pool) + return PJ_ENOMEM; + + /* Register module: */ + status = pjsip_endpt_register_module(endpt, &mod_evsub.mod); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create Allow-Events header: */ + mod_evsub.allow_events_hdr = pjsip_allow_events_hdr_create(mod_evsub.pool); + + /* Register SIP-event specific headers parser: */ + pjsip_evsub_init_parser(); + + /* Register new methods SUBSCRIBE and NOTIFY in Allow-ed header */ + pjsip_endpt_add_capability(endpt, &mod_evsub.mod, PJSIP_H_ALLOW, NULL, + 2, method_tags); + + /* Done. */ + return PJ_SUCCESS; + +on_error: + if (mod_evsub.pool) { + pjsip_endpt_release_pool(endpt, mod_evsub.pool); + mod_evsub.pool = NULL; + } + mod_evsub.endpt = NULL; + return status; +} + + +/* + * Get the instance of the module. + */ +PJ_DEF(pjsip_module*) pjsip_evsub_instance(void) +{ + PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, NULL); + + return &mod_evsub.mod; +} + + +/* + * Get the event subscription instance in the transaction. + */ +PJ_DEF(pjsip_evsub*) pjsip_tsx_get_evsub(pjsip_transaction *tsx) +{ + return (pjsip_evsub*) tsx->mod_data[mod_evsub.mod.id]; +} + + +/* + * Set event subscription's module data. + */ +PJ_DEF(void) pjsip_evsub_set_mod_data( pjsip_evsub *sub, unsigned mod_id, + void *data ) +{ + PJ_ASSERT_ON_FAIL(mod_id < PJSIP_MAX_MODULE, return); + sub->mod_data[mod_id] = data; +} + + +/* + * Get event subscription's module data. + */ +PJ_DEF(void*) pjsip_evsub_get_mod_data( pjsip_evsub *sub, unsigned mod_id ) +{ + PJ_ASSERT_RETURN(mod_id < PJSIP_MAX_MODULE, NULL); + return sub->mod_data[mod_id]; +} + + +/* + * Find registered event package with matching name. + */ +static struct evpkg* find_pkg(const pj_str_t *event_name) +{ + struct evpkg *pkg; + + pkg = mod_evsub.pkg_list.next; + while (pkg != &mod_evsub.pkg_list) { + + if (pj_stricmp(&pkg->pkg_name, event_name) == 0) { + return pkg; + } + + pkg = pkg->next; + } + + return NULL; +} + +/* + * Register an event package + */ +PJ_DEF(pj_status_t) pjsip_evsub_register_pkg( pjsip_module *pkg_mod, + const pj_str_t *event_name, + unsigned expires, + unsigned accept_cnt, + const pj_str_t accept[]) +{ + struct evpkg *pkg; + unsigned i; + + PJ_ASSERT_RETURN(pkg_mod && event_name, PJ_EINVAL); + PJ_ASSERT_RETURN(accept_cnt < PJ_ARRAY_SIZE(pkg->pkg_accept->values), + PJ_ETOOMANY); + + /* Make sure evsub module has been initialized */ + PJ_ASSERT_RETURN(mod_evsub.mod.id != -1, PJ_EINVALIDOP); + + /* Make sure no module with the specified name already registered: */ + + PJ_ASSERT_RETURN(find_pkg(event_name) == NULL, PJSIP_SIMPLE_EPKGEXISTS); + + + /* Create new event package: */ + + pkg = PJ_POOL_ALLOC_T(mod_evsub.pool, struct evpkg); + pkg->pkg_mod = pkg_mod; + pkg->pkg_expires = expires; + pj_strdup(mod_evsub.pool, &pkg->pkg_name, event_name); + + pkg->pkg_accept = pjsip_accept_hdr_create(mod_evsub.pool); + pkg->pkg_accept->count = accept_cnt; + for (i=0; ipkg_accept->values[i], &accept[i]); + } + + /* Add to package list: */ + + pj_list_push_back(&mod_evsub.pkg_list, pkg); + + /* Add to Allow-Events header: */ + + if (mod_evsub.allow_events_hdr->count != + PJ_ARRAY_SIZE(mod_evsub.allow_events_hdr->values)) + { + mod_evsub.allow_events_hdr->values[mod_evsub.allow_events_hdr->count] = + pkg->pkg_name; + ++mod_evsub.allow_events_hdr->count; + } + + /* Add to endpoint's Accept header */ + pjsip_endpt_add_capability(mod_evsub.endpt, &mod_evsub.mod, + PJSIP_H_ACCEPT, NULL, + pkg->pkg_accept->count, + pkg->pkg_accept->values); + + + /* Done */ + + PJ_LOG(5,(THIS_FILE, "Event pkg \"%.*s\" registered by %.*s", + (int)event_name->slen, event_name->ptr, + (int)pkg_mod->name.slen, pkg_mod->name.ptr)); + + return PJ_SUCCESS; +} + + +/* + * Retrieve Allow-Events header + */ +PJ_DEF(const pjsip_hdr*) pjsip_evsub_get_allow_events_hdr(pjsip_module *m) +{ + struct mod_evsub *mod; + + if (m == NULL) + m = pjsip_evsub_instance(); + + mod = (struct mod_evsub*)m; + + return (pjsip_hdr*) mod->allow_events_hdr; +} + + +/* + * Update expiration time. + */ +static void update_expires( pjsip_evsub *sub, pj_uint32_t interval ) +{ + pj_gettimeofday(&sub->refresh_time); + sub->refresh_time.sec += interval; +} + + +/* + * Schedule timer. + */ +static void set_timer( pjsip_evsub *sub, int timer_id, + pj_int32_t seconds) +{ + if (sub->timer.id != TIMER_TYPE_NONE) { + PJ_LOG(5,(sub->obj_name, "%s %s timer", + (timer_id==sub->timer.id ? "Updating" : "Cancelling"), + timer_names[sub->timer.id])); + pjsip_endpt_cancel_timer(sub->endpt, &sub->timer); + sub->timer.id = TIMER_TYPE_NONE; + } + + if (timer_id != TIMER_TYPE_NONE) { + pj_time_val timeout; + + PJ_ASSERT_ON_FAIL(seconds > 0, return); + PJ_ASSERT_ON_FAIL(timer_id>TIMER_TYPE_NONE && timer_idtimer.id = timer_id; + + pjsip_endpt_schedule_timer(sub->endpt, &sub->timer, &timeout); + + PJ_LOG(5,(sub->obj_name, "Timer %s scheduled in %d seconds", + timer_names[sub->timer.id], timeout.sec)); + } +} + + +/* + * Destroy session. + */ +static void evsub_destroy( pjsip_evsub *sub ) +{ + struct dlgsub *dlgsub_head, *dlgsub; + + PJ_LOG(4,(sub->obj_name, "Subscription destroyed")); + + /* Kill timer */ + set_timer(sub, TIMER_TYPE_NONE, 0); + + /* Remove this session from dialog's list of subscription */ + dlgsub_head = (struct dlgsub *) sub->dlg->mod_data[mod_evsub.mod.id]; + dlgsub = dlgsub_head->next; + while (dlgsub != dlgsub_head) { + + if (dlgsub->sub == sub) { + pj_list_erase(dlgsub); + break; + } + + dlgsub = dlgsub->next; + } + + /* Decrement dialog's session */ + pjsip_dlg_dec_session(sub->dlg, &mod_evsub.mod); +} + +/* + * Set subscription session state. + */ +static void set_state( pjsip_evsub *sub, pjsip_evsub_state state, + const pj_str_t *state_str, pjsip_event *event, + const pj_str_t *reason) +{ + pjsip_evsub_state prev_state = sub->state; + pj_str_t old_state_str = sub->state_str; + pjsip_event dummy_event; + + sub->state = state; + + if (state_str && state_str->slen) + pj_strdup_with_null(sub->pool, &sub->state_str, state_str); + else + sub->state_str = evsub_state_names[state]; + + if (reason && sub->term_reason.slen==0) + pj_strdup(sub->pool, &sub->term_reason, reason); + + PJ_LOG(4,(sub->obj_name, + "Subscription state changed %.*s --> %.*s", + (int)old_state_str.slen, + old_state_str.ptr, + (int)sub->state_str.slen, + sub->state_str.ptr)); + pj_log_push_indent(); + + /* don't call the callback with NULL event, it may crash the app! */ + if (!event) { + PJSIP_EVENT_INIT_USER(dummy_event, 0, 0, 0, 0); + event = &dummy_event; + } + + if (sub->user.on_evsub_state && sub->call_cb) + (*sub->user.on_evsub_state)(sub, event); + + if (state == PJSIP_EVSUB_STATE_TERMINATED && + prev_state != PJSIP_EVSUB_STATE_TERMINATED) + { + if (sub->pending_tsx == 0) { + evsub_destroy(sub); + } + } + + pj_log_pop_indent(); +} + + +/* + * Timer callback. + */ +static void on_timer( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pjsip_evsub *sub; + int timer_id; + + PJ_UNUSED_ARG(timer_heap); + + sub = (pjsip_evsub*) entry->user_data; + + pjsip_dlg_inc_lock(sub->dlg); + + timer_id = entry->id; + entry->id = TIMER_TYPE_NONE; + + switch (timer_id) { + + case TIMER_TYPE_UAC_REFRESH: + /* Time for UAC to refresh subscription */ + if (sub->user.on_client_refresh && sub->call_cb) { + (*sub->user.on_client_refresh)(sub); + } else { + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(5,(sub->obj_name, "Refreshing subscription.")); + pj_log_push_indent(); + status = pjsip_evsub_initiate(sub, NULL, + sub->expires->ivalue, + &tdata); + if (status == PJ_SUCCESS) + pjsip_evsub_send_request(sub, tdata); + + pj_log_pop_indent(); + } + break; + + case TIMER_TYPE_UAS_TIMEOUT: + /* Refresh from UAC has not been received */ + if (sub->user.on_server_timeout && sub->call_cb) { + (*sub->user.on_server_timeout)(sub); + } else { + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(5,(sub->obj_name, "Timeout waiting for refresh. " + "Sending NOTIFY to terminate.")); + pj_log_push_indent(); + status = pjsip_evsub_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, &STR_TIMEOUT, &tdata); + if (status == PJ_SUCCESS) + pjsip_evsub_send_request(sub, tdata); + + pj_log_pop_indent(); + } + break; + + case TIMER_TYPE_UAC_TERMINATE: + { + pj_str_t timeout = {"timeout", 7}; + + PJ_LOG(5,(sub->obj_name, "Timeout waiting for final NOTIFY. " + "Terminating..")); + pj_log_push_indent(); + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, + &timeout); + pj_log_pop_indent(); + } + break; + + case TIMER_TYPE_UAC_WAIT_NOTIFY: + { + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(5,(sub->obj_name, + "Timeout waiting for subsequent NOTIFY (we did " + "send non-2xx response for previous NOTIFY). " + "Unsubscribing..")); + pj_log_push_indent(); + status = pjsip_evsub_initiate( sub, NULL, 0, &tdata); + if (status == PJ_SUCCESS) + pjsip_evsub_send_request(sub, tdata); + + pj_log_pop_indent(); + } + break; + + default: + pj_assert(!"Invalid timer id"); + } + + pjsip_dlg_dec_lock(sub->dlg); +} + + +/* + * Create subscription session, used for both client and notifier. + */ +static pj_status_t evsub_create( pjsip_dialog *dlg, + pjsip_role_e role, + const pjsip_evsub_user *user_cb, + const pj_str_t *event, + unsigned option, + pjsip_evsub **p_evsub ) +{ + pjsip_evsub *sub; + struct evpkg *pkg; + struct dlgsub *dlgsub_head, *dlgsub; + pj_status_t status; + + /* Make sure there's package register for the event name: */ + + pkg = find_pkg(event); + if (pkg == NULL) + return PJSIP_SIMPLE_ENOPKG; + + + /* Must lock dialog before using pool etc. */ + pjsip_dlg_inc_lock(dlg); + + /* Init attributes: */ + + sub = PJ_POOL_ZALLOC_T(dlg->pool, struct pjsip_evsub); + sub->pool = dlg->pool; + sub->endpt = dlg->endpt; + sub->dlg = dlg; + sub->pkg = pkg; + sub->role = role; + sub->call_cb = PJ_TRUE; + sub->option = option; + sub->state = PJSIP_EVSUB_STATE_NULL; + sub->state_str = evsub_state_names[sub->state]; + sub->expires = pjsip_expires_hdr_create(sub->pool, pkg->pkg_expires); + sub->accept = (pjsip_accept_hdr*) + pjsip_hdr_clone(sub->pool, pkg->pkg_accept); + pj_list_init(&sub->sub_hdr_list); + + sub->timer.user_data = sub; + sub->timer.cb = &on_timer; + + /* Set name. */ + pj_ansi_snprintf(sub->obj_name, PJ_ARRAY_SIZE(sub->obj_name), + "evsub%p", sub); + + + /* Copy callback, if any: */ + if (user_cb) + pj_memcpy(&sub->user, user_cb, sizeof(pjsip_evsub_user)); + + + /* Create Event header: */ + sub->event = pjsip_event_hdr_create(sub->pool); + pj_strdup(sub->pool, &sub->event->event_type, event); + + + /* Check if another subscription has been registered to the dialog. In + * that case, just add ourselves to the subscription list, otherwise + * create and register a new subscription list. + */ + if (pjsip_dlg_has_usage(dlg, &mod_evsub.mod)) { + dlgsub_head = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id]; + dlgsub = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub); + dlgsub->sub = sub; + pj_list_push_back(dlgsub_head, dlgsub); + } else { + dlgsub_head = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub); + dlgsub = PJ_POOL_ALLOC_T(sub->pool, struct dlgsub); + dlgsub->sub = sub; + + pj_list_init(dlgsub_head); + pj_list_push_back(dlgsub_head, dlgsub); + + + /* Register as dialog usage: */ + + status = pjsip_dlg_add_usage(dlg, &mod_evsub.mod, dlgsub_head); + if (status != PJ_SUCCESS) { + pjsip_dlg_dec_lock(dlg); + return status; + } + } + + PJ_LOG(5,(sub->obj_name, "%s subscription created, using dialog %s", + (role==PJSIP_ROLE_UAC ? "UAC" : "UAS"), + dlg->obj_name)); + + *p_evsub = sub; + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} + + + +/* + * Create client subscription session. + */ +PJ_DEF(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + const pj_str_t *event, + unsigned option, + pjsip_evsub **p_evsub) +{ + pjsip_evsub *sub; + pj_status_t status; + + PJ_ASSERT_RETURN(dlg && event && p_evsub, PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); + status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, option, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Add unique Id to Event header, only when PJSIP_EVSUB_NO_EVENT_ID + * is not specified. + */ + if ((option & PJSIP_EVSUB_NO_EVENT_ID) == 0) { + pj_create_unique_string(sub->pool, &sub->event->id_param); + } + + /* Increment dlg session. */ + pjsip_dlg_inc_session(sub->dlg, &mod_evsub.mod); + + /* Done */ + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Create server subscription session from incoming request. + */ +PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_rx_data *rdata, + unsigned option, + pjsip_evsub **p_evsub) +{ + pjsip_evsub *sub; + pjsip_transaction *tsx; + pjsip_accept_hdr *accept_hdr; + pjsip_event_hdr *event_hdr; + pjsip_expires_hdr *expires_hdr; + pj_status_t status; + + /* Check arguments: */ + PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL); + + /* MUST be request message: */ + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Transaction MUST have been created (in the dialog) */ + tsx = pjsip_rdata_get_tsx(rdata); + PJ_ASSERT_RETURN(tsx != NULL, PJSIP_ENOTSX); + + /* No subscription must have been attached to transaction */ + PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] == NULL, + PJSIP_ETYPEEXISTS); + + /* Package MUST implement on_rx_refresh */ + PJ_ASSERT_RETURN(user_cb->on_rx_refresh, PJ_EINVALIDOP); + + /* Request MUST have "Event" header. We need the Event header to get + * the package name (don't want to add more arguments in the function). + */ + event_hdr = (pjsip_event_hdr*) + pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &STR_EVENT, + &STR_EVENT_S, NULL); + if (event_hdr == NULL) { + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST); + } + + /* Start locking the mutex: */ + + pjsip_dlg_inc_lock(dlg); + + /* Create the session: */ + + status = evsub_create(dlg, PJSIP_UAS_ROLE, user_cb, + &event_hdr->event_type, option, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Just duplicate Event header from the request */ + sub->event = (pjsip_event_hdr*) pjsip_hdr_clone(sub->pool, event_hdr); + + /* Set the method: */ + pjsip_method_copy(sub->pool, &sub->method, + &rdata->msg_info.msg->line.req.method); + + /* Update expiration time according to client request: */ + + expires_hdr = (pjsip_expires_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL); + if (expires_hdr) { + sub->expires->ivalue = expires_hdr->ivalue; + } + + /* Update time. */ + update_expires(sub, sub->expires->ivalue); + + /* Update Accept header: */ + + accept_hdr = (pjsip_accept_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL); + if (accept_hdr) + sub->accept = (pjsip_accept_hdr*)pjsip_hdr_clone(sub->pool,accept_hdr); + + /* We can start the session: */ + + pjsip_dlg_inc_session(dlg, &mod_evsub.mod); + sub->pending_tsx++; + tsx->mod_data[mod_evsub.mod.id] = sub; + + + /* Done. */ + *p_evsub = sub; + + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Forcefully destroy subscription. + */ +PJ_DEF(pj_status_t) pjsip_evsub_terminate( pjsip_evsub *sub, + pj_bool_t notify ) +{ + PJ_ASSERT_RETURN(sub, PJ_EINVAL); + + pjsip_dlg_inc_lock(sub->dlg); + + /* I think it's pretty safe to disable this check. + + if (sub->pending_tsx) { + pj_assert(!"Unable to terminate when there's pending tsx"); + pjsip_dlg_dec_lock(sub->dlg); + return PJ_EINVALIDOP; + } + */ + + sub->call_cb = notify; + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, NULL); + + pjsip_dlg_dec_lock(sub->dlg); + return PJ_SUCCESS; +} + +/* + * Get subscription state. + */ +PJ_DEF(pjsip_evsub_state) pjsip_evsub_get_state(pjsip_evsub *sub) +{ + return sub->state; +} + +/* + * Get state name. + */ +PJ_DEF(const char*) pjsip_evsub_get_state_name(pjsip_evsub *sub) +{ + return sub->state_str.ptr; +} + +/* + * Get termination reason. + */ +PJ_DEF(const pj_str_t*) pjsip_evsub_get_termination_reason(pjsip_evsub *sub) +{ + return &sub->term_reason; +} + +/* + * Initiate client subscription + */ +PJ_DEF(pj_status_t) pjsip_evsub_initiate( pjsip_evsub *sub, + const pjsip_method *method, + pj_int32_t expires, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL); + + /* Use SUBSCRIBE if method is not specified */ + if (method == NULL) + method = &pjsip_subscribe_method; + + pjsip_dlg_inc_lock(sub->dlg); + + /* Update method: */ + if (sub->state == PJSIP_EVSUB_STATE_NULL) + pjsip_method_copy(sub->pool, &sub->method, method); + + status = pjsip_dlg_create_request( sub->dlg, method, -1, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Add Event header: */ + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, sub->event)); + + /* Update and add expires header: */ + if (expires >= 0) + sub->expires->ivalue = expires; + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, sub->expires)); + + /* Add Supported header (it's optional in RFC 3265, but some event package + * RFC may bring this requirement to SHOULD strength - e.g. RFC 5373) + */ + { + const pjsip_hdr *hdr = pjsip_endpt_get_capability(sub->endpt, + PJSIP_H_SUPPORTED, + NULL); + if (hdr) { + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, hdr)); + } + } + + /* Add Accept header: */ + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, sub->accept)); + + + /* Add Allow-Events header: */ + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, + mod_evsub.allow_events_hdr)); + + + /* Add custom headers */ + { + const pjsip_hdr *hdr = sub->sub_hdr_list.next; + while (hdr != &sub->sub_hdr_list) { + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, hdr)); + hdr = hdr->next; + } + } + + + *p_tdata = tdata; + + +on_return: + + pjsip_dlg_dec_lock(sub->dlg); + return status; +} + + +/* + * Add custom headers. + */ +PJ_DEF(pj_status_t) pjsip_evsub_add_header( pjsip_evsub *sub, + const pjsip_hdr *hdr_list ) +{ + const pjsip_hdr *hdr; + + PJ_ASSERT_RETURN(sub && hdr_list, PJ_EINVAL); + + hdr = hdr_list->next; + while (hdr != hdr_list) { + pj_list_push_back(&sub->sub_hdr_list, (pjsip_hdr*) + pjsip_hdr_clone(sub->pool, hdr)); + hdr = hdr->next; + } + + return PJ_SUCCESS; +} + + +/* + * Accept incoming subscription request. + */ +PJ_DEF(pj_status_t) pjsip_evsub_accept( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list ) +{ + pjsip_tx_data *tdata; + pjsip_transaction *tsx; + pj_status_t status; + + /* Check arguments */ + PJ_ASSERT_RETURN(sub && rdata, PJ_EINVAL); + + /* Can only be for server subscription: */ + PJ_ASSERT_RETURN(sub->role == PJSIP_ROLE_UAS, PJ_EINVALIDOP); + + /* Only expect 2xx status code (for now) */ + PJ_ASSERT_RETURN(st_code/100 == 2, PJ_EINVALIDOP); + + /* Subscription MUST have been attached to the transaction. + * Initial subscription request will be attached on evsub_create_uas(), + * while subsequent requests will be attached in tsx_state() + */ + tsx = pjsip_rdata_get_tsx(rdata); + PJ_ASSERT_RETURN(tsx->mod_data[mod_evsub.mod.id] != NULL, + PJ_EINVALIDOP); + + /* Lock dialog */ + pjsip_dlg_inc_lock(sub->dlg); + + /* Create response: */ + status = pjsip_dlg_create_response( sub->dlg, rdata, st_code, NULL, + &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Add expires header: */ + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, sub->expires)); + + /* Add additional header, if any. */ + if (hdr_list) { + const pjsip_hdr *hdr = hdr_list->next; + while (hdr != hdr_list) { + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_hdr_clone(tdata->pool, hdr)); + hdr = hdr->next; + } + } + + /* Send the response: */ + status = pjsip_dlg_send_response( sub->dlg, tsx, tdata ); + if (status != PJ_SUCCESS) + goto on_return; + + +on_return: + + pjsip_dlg_dec_lock(sub->dlg); + return status; +} + + +/* + * Create Subscription-State header based on current server subscription + * state. + */ +static pjsip_sub_state_hdr* sub_state_create( pj_pool_t *pool, + pjsip_evsub *sub, + pjsip_evsub_state state, + const pj_str_t *state_str, + const pj_str_t *reason ) +{ + pjsip_sub_state_hdr *sub_state; + pj_time_val now, delay; + + /* Get the remaining time before refresh is required */ + pj_gettimeofday(&now); + delay = sub->refresh_time; + PJ_TIME_VAL_SUB(delay, now); + + /* Create the Subscription-State header */ + sub_state = pjsip_sub_state_hdr_create(pool); + + /* Fill up the header */ + switch (state) { + case PJSIP_EVSUB_STATE_NULL: + case PJSIP_EVSUB_STATE_SENT: + pj_assert(!"Invalid state!"); + /* Treat as pending */ + + case PJSIP_EVSUB_STATE_ACCEPTED: + case PJSIP_EVSUB_STATE_PENDING: + sub_state->sub_state = STR_PENDING; + sub_state->expires_param = delay.sec; + break; + + case PJSIP_EVSUB_STATE_ACTIVE: + sub_state->sub_state = STR_ACTIVE; + sub_state->expires_param = delay.sec; + break; + + case PJSIP_EVSUB_STATE_TERMINATED: + sub_state->sub_state = STR_TERMINATED; + if (reason != NULL) + pj_strdup(pool, &sub_state->reason_param, reason); + break; + + case PJSIP_EVSUB_STATE_UNKNOWN: + pj_assert(state_str != NULL); + pj_strdup(pool, &sub_state->sub_state, state_str); + break; + } + + return sub_state; +} + +/* + * Create and send NOTIFY request. + */ +PJ_DEF(pj_status_t) pjsip_evsub_notify( pjsip_evsub *sub, + pjsip_evsub_state state, + const pj_str_t *state_str, + const pj_str_t *reason, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsip_sub_state_hdr *sub_state; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub!=NULL && p_tdata!=NULL, PJ_EINVAL); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(sub->dlg); + + /* Create NOTIFY request */ + status = pjsip_dlg_create_request( sub->dlg, pjsip_get_notify_method(), + -1, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Add Event header */ + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, sub->event)); + + /* Add Subscription-State header */ + sub_state = sub_state_create(tdata->pool, sub, state, state_str, + reason); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sub_state); + + /* Add Allow-Events header */ + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, mod_evsub.allow_events_hdr)); + + /* Add Authentication headers. */ + pjsip_auth_clt_init_req( &sub->dlg->auth_sess, tdata ); + + /* Update reason */ + if (reason) + pj_strdup(sub->dlg->pool, &sub->term_reason, reason); + + /* Save destination state. */ + sub->dst_state = state; + if (state_str) + pj_strdup(sub->pool, &sub->dst_state_str, state_str); + else + sub->dst_state_str.slen = 0; + + + *p_tdata = tdata; + +on_return: + /* Unlock dialog */ + pjsip_dlg_dec_lock(sub->dlg); + return status; +} + + +/* + * Create NOTIFY to reflect current status. + */ +PJ_DEF(pj_status_t) pjsip_evsub_current_notify( pjsip_evsub *sub, + pjsip_tx_data **p_tdata ) +{ + return pjsip_evsub_notify( sub, sub->state, &sub->state_str, + NULL, p_tdata ); +} + + +/* + * Send request. + */ +PJ_DEF(pj_status_t) pjsip_evsub_send_request( pjsip_evsub *sub, + pjsip_tx_data *tdata) +{ + pj_status_t status; + + /* Must be request message. */ + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Lock */ + pjsip_dlg_inc_lock(sub->dlg); + + /* Send the request. */ + status = pjsip_dlg_send_request(sub->dlg, tdata, -1, NULL); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Special case for NOTIFY: + * The new state was set in pjsip_evsub_notify(), but we apply the + * new state now, when the request was actually sent. + */ + if (pjsip_method_cmp(&tdata->msg->line.req.method, + &pjsip_notify_method)==0) + { + PJ_ASSERT_ON_FAIL( sub->dst_state!=PJSIP_EVSUB_STATE_NULL, + {goto on_return;}); + + set_state(sub, sub->dst_state, + (sub->dst_state_str.slen ? &sub->dst_state_str : NULL), + NULL, NULL); + + sub->dst_state = PJSIP_EVSUB_STATE_NULL; + sub->dst_state_str.slen = 0; + + } + + +on_return: + pjsip_dlg_dec_lock(sub->dlg); + return status; +} + + +/* Callback to be called to terminate transaction. */ +static void terminate_timer_cb(pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pj_str_t *key; + pjsip_transaction *tsx; + + PJ_UNUSED_ARG(timer_heap); + + key = (pj_str_t*)entry->user_data; + tsx = pjsip_tsx_layer_find_tsx(key, PJ_FALSE); + /* Chance of race condition here */ + if (tsx) { + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_UPDATED); + } +} + + +/* + * Attach subscription session to newly created transaction, if appropriate. + */ +static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx, + pjsip_event *event) +{ + /* + * Newly created transaction will not have subscription session + * attached to it. Find the subscription session from the dialog, + * by matching the Event header. + */ + pjsip_dialog *dlg; + pjsip_event_hdr *event_hdr; + pjsip_msg *msg; + struct dlgsub *dlgsub_head, *dlgsub; + pjsip_evsub *sub; + + dlg = pjsip_tsx_get_dlg(tsx); + if (!dlg) { + pj_assert(!"Transaction should have a dialog instance!"); + return NULL; + } + + + switch (event->body.tsx_state.type) { + case PJSIP_EVENT_RX_MSG: + msg = event->body.tsx_state.src.rdata->msg_info.msg; + break; + case PJSIP_EVENT_TX_MSG: + msg = event->body.tsx_state.src.tdata->msg; + break; + default: + if (tsx->role == PJSIP_ROLE_UAC) + msg = tsx->last_tx->msg; + else + msg = NULL; + break; + } + + if (!msg) { + //Note: + // this transaction can be other transaction in the dialog. + // The assertion below probably only valid for dialog that + // only has one event subscription usage. + //pj_assert(!"First transaction event is not TX or RX!"); + return NULL; + } + + event_hdr = (pjsip_event_hdr*) + pjsip_msg_find_hdr_by_names(msg, &STR_EVENT, + &STR_EVENT_S, NULL); + if (!event_hdr) { + /* Not subscription related message */ + return NULL; + } + + /* Find the subscription in the dialog, based on the content + * of Event header: + */ + + dlgsub_head = (struct dlgsub*) dlg->mod_data[mod_evsub.mod.id]; + if (dlgsub_head == NULL) { + dlgsub_head = PJ_POOL_ALLOC_T(dlg->pool, struct dlgsub); + pj_list_init(dlgsub_head); + dlg->mod_data[mod_evsub.mod.id] = dlgsub_head; + } + dlgsub = dlgsub_head->next; + + while (dlgsub != dlgsub_head) { + + if (pj_stricmp(&dlgsub->sub->event->event_type, + &event_hdr->event_type)==0) + { + /* Event type matched. + * Check if event ID matched too. + */ + if (pj_strcmp(&dlgsub->sub->event->id_param, + &event_hdr->id_param)==0) + { + + break; + + } + /* + * Otherwise if it is an UAC subscription, AND + * PJSIP_EVSUB_NO_EVENT_ID flag is set, AND + * the session's event id is NULL, AND + * the incoming request is NOTIFY with event ID, then + * we consider it as a match, and update the + * session's event id. + */ + else if (dlgsub->sub->role == PJSIP_ROLE_UAC && + (dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID)!=0 && + dlgsub->sub->event->id_param.slen==0 && + !pjsip_method_cmp(&tsx->method, &pjsip_notify_method)) + { + /* Update session's event id. */ + pj_strdup(dlgsub->sub->pool, + &dlgsub->sub->event->id_param, + &event_hdr->id_param); + + break; + } + } + + + + dlgsub = dlgsub->next; + } + + /* Note: + * the second condition is for http://trac.pjsip.org/repos/ticket/911 + */ + if (dlgsub == dlgsub_head || + (dlgsub->sub && + pjsip_evsub_get_state(dlgsub->sub)==PJSIP_EVSUB_STATE_TERMINATED)) + { + const char *reason_msg = + (dlgsub == dlgsub_head ? "Subscription Does Not Exist" : + "Subscription already terminated"); + + /* This could be incoming request to create new subscription */ + PJ_LOG(4,(THIS_FILE, + "%s for %.*s, event=%.*s;id=%.*s", + reason_msg, + (int)tsx->method.name.slen, + tsx->method.name.ptr, + (int)event_hdr->event_type.slen, + event_hdr->event_type.ptr, + (int)event_hdr->id_param.slen, + event_hdr->id_param.ptr)); + + /* If this is an incoming NOTIFY, reject with 481 */ + if (tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0) + { + pj_str_t reason; + pjsip_tx_data *tdata; + pj_status_t status; + + pj_cstr(&reason, reason_msg); + status = pjsip_dlg_create_response(dlg, + event->body.tsx_state.src.rdata, + 481, &reason, + &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(dlg, tsx, tdata); + } + } + return NULL; + } + + /* Found! */ + sub = dlgsub->sub; + + /* Attach session to the transaction */ + tsx->mod_data[mod_evsub.mod.id] = sub; + sub->pending_tsx++; + + /* Special case for outgoing/UAC SUBSCRIBE/REFER transaction. + * We can only have one pending UAC SUBSCRIBE/REFER, so if another + * transaction is started while previous one still alive, terminate + * the older one. + * + * Sample scenario: + * - subscribe sent to destination that doesn't exist, transaction + * is still retransmitting request, then unsubscribe is sent. + */ + if (tsx->role == PJSIP_ROLE_UAC && + tsx->state == PJSIP_TSX_STATE_CALLING && + (pjsip_method_cmp(&tsx->method, &sub->method) == 0 || + pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0)) + { + + if (sub->pending_sub && + sub->pending_sub->state < PJSIP_TSX_STATE_COMPLETED) + { + pj_timer_entry *timer; + pj_str_t *key; + pj_time_val timeout = {0, 0}; + + PJ_LOG(4,(sub->obj_name, + "Cancelling pending subscription request")); + + /* By convention, we use 490 (Request Updated) status code. + * When transaction handler (below) see this status code, it + * will ignore the transaction. + */ + /* This unfortunately may cause deadlock, because at the moment + * we are holding dialog's mutex. If a response to this + * transaction is in progress in another thread, that thread + * will deadlock when trying to acquire dialog mutex, because + * it is holding the transaction mutex. + * + * So the solution is to register timer to kill this transaction. + */ + //pjsip_tsx_terminate(sub->pending_sub, PJSIP_SC_REQUEST_UPDATED); + timer = PJ_POOL_ZALLOC_T(dlg->pool, pj_timer_entry); + key = PJ_POOL_ALLOC_T(dlg->pool, pj_str_t); + pj_strdup(dlg->pool, key, &sub->pending_sub->transaction_key); + timer->cb = &terminate_timer_cb; + timer->user_data = key; + + pjsip_endpt_schedule_timer(dlg->endpt, timer, &timeout); + } + + sub->pending_sub = tsx; + + } + + return sub; +} + + +/* + * Create response, adding custome headers and msg body. + */ +static pj_status_t create_response( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pj_str_t *st_text, + const pjsip_hdr *res_hdr, + const pjsip_msg_body *body, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsip_hdr *hdr; + pj_status_t status; + + status = pjsip_dlg_create_response(sub->dlg, rdata, + st_code, st_text, &tdata); + if (status != PJ_SUCCESS) + return status; + + *p_tdata = tdata; + + /* Add response headers. */ + hdr = res_hdr->next; + while (hdr != res_hdr) { + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_hdr_clone(tdata->pool, hdr)); + hdr = hdr->next; + } + + /* Add msg body, if any */ + if (body) { + tdata->msg->body = pjsip_msg_body_clone(tdata->pool, body); + if (tdata->msg->body == NULL) { + + PJ_LOG(4,(THIS_FILE, "Error: unable to clone msg body")); + + /* Ignore */ + return PJ_SUCCESS; + } + } + + return PJ_SUCCESS; +} + +/* + * Get subscription state from the value of Subscription-State header. + */ +static void get_hdr_state( pjsip_sub_state_hdr *sub_state, + pjsip_evsub_state *state, + pj_str_t **state_str ) +{ + if (pj_stricmp(&sub_state->sub_state, &STR_TERMINATED)==0) { + + *state = PJSIP_EVSUB_STATE_TERMINATED; + *state_str = NULL; + + } else if (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0) { + + *state = PJSIP_EVSUB_STATE_ACTIVE; + *state_str = NULL; + + } else if (pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0) { + + *state = PJSIP_EVSUB_STATE_PENDING; + *state_str = NULL; + + } else { + + *state = PJSIP_EVSUB_STATE_UNKNOWN; + *state_str = &sub_state->sub_state; + + } +} + +/* + * Transaction event processing by UAC, after subscription is sent. + */ +static void on_tsx_state_uac( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event ) +{ + + if (pjsip_method_cmp(&tsx->method, &sub->method)==0 || + pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method)==0) + { + + /* Received response to outgoing request that establishes/refresh + * subscription. + */ + + /* First time initial request is sent. */ + if (sub->state == PJSIP_EVSUB_STATE_NULL && + tsx->state == PJSIP_TSX_STATE_CALLING) + { + set_state(sub, PJSIP_EVSUB_STATE_SENT, NULL, event, NULL); + return; + } + + /* Only interested in final response */ + if (tsx->state != PJSIP_TSX_STATE_COMPLETED && + tsx->state != PJSIP_TSX_STATE_TERMINATED) + { + return; + } + + /* Clear pending subscription */ + if (tsx == sub->pending_sub) { + sub->pending_sub = NULL; + } else if (sub->pending_sub != NULL) { + /* This SUBSCRIBE transaction has been "renewed" with another + * SUBSCRIBE, so we can just ignore this. For example, user + * sent SUBSCRIBE followed immediately with UN-SUBSCRIBE. + */ + return; + } + + /* Handle authentication. */ + if (tsx->status_code==401 || tsx->status_code==407) { + pjsip_tx_data *tdata; + pj_status_t status; + + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + /* Previously failed transaction has terminated */ + return; + } + + status = pjsip_auth_clt_reinit_req(&sub->dlg->auth_sess, + event->body.tsx_state.src.rdata, + tsx->last_tx, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_dlg_send_request(sub->dlg, tdata, -1, NULL); + + if (status != PJ_SUCCESS) { + /* Authentication failed! */ + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, event, &tsx->status_text); + return; + } + + return; + } + + if (tsx->status_code/100 == 2) { + + /* Successfull SUBSCRIBE request! + * This could be: + * - response to initial SUBSCRIBE request + * - response to subsequent refresh + * - response to unsubscription + */ + + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + /* Ignore; this transaction has been processed before */ + return; + } + + /* Update UAC refresh time, if response contains Expires header, + * only when we're not unsubscribing. + */ + if (sub->expires->ivalue != 0) { + pjsip_msg *msg; + pjsip_expires_hdr *expires; + + msg = event->body.tsx_state.src.rdata->msg_info.msg; + expires = (pjsip_expires_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); + if (expires) { + sub->expires->ivalue = expires->ivalue; + } + } + + /* Update time */ + update_expires(sub, sub->expires->ivalue); + + /* Start UAC refresh timer, only when we're not unsubscribing */ + if (sub->expires->ivalue != 0) { + unsigned timeout = (sub->expires->ivalue > TIME_UAC_REFRESH) ? + sub->expires->ivalue - TIME_UAC_REFRESH : sub->expires->ivalue; + + PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", + timeout)); + set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout); + + } else { + /* Otherwise set timer to terminate client subscription when + * NOTIFY to end subscription is not received. + */ + set_timer(sub, TIMER_TYPE_UAC_TERMINATE, TIME_UAC_TERMINATE); + } + + /* Set state, if necessary */ + pj_assert(sub->state != PJSIP_EVSUB_STATE_NULL); + if (sub->state == PJSIP_EVSUB_STATE_SENT) { + set_state(sub, PJSIP_EVSUB_STATE_ACCEPTED, NULL, event, NULL); + } + + } else { + + /* Failed SUBSCRIBE request! + * + * The RFC 3265 says that if outgoing SUBSCRIBE fails with status + * other than 481, the subscription is still considered valid for + * the duration of the last Expires. + * + * Since we send refresh about 5 seconds (TIME_UAC_REFRESH) before + * expiration, theoritically the expiration is still valid for the + * next 5 seconds even when we receive non-481 failed response. + * + * Ah, what the heck! + * + * Just terminate now! + * + */ + + if (sub->state == PJSIP_EVSUB_STATE_TERMINATED) { + /* Ignore, has been handled before */ + return; + } + + /* Ignore 490 (Request Updated) status. + * This happens when application sends SUBSCRIBE/REFER while + * another one is still in progress. + */ + if (tsx->status_code == PJSIP_SC_REQUEST_UPDATED) { + return; + } + + /* Kill any timer. */ + set_timer(sub, TIMER_TYPE_NONE, 0); + + /* Set state to TERMINATED */ + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, event, &tsx->status_text); + + } + + } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) { + + /* Incoming NOTIFY. + * This can be the result of: + * - Initial subscription response + * - UAS updating the resource info. + * - Unsubscription response. + */ + int st_code = 200; + pj_str_t *st_text = NULL; + pjsip_hdr res_hdr; + pjsip_msg_body *body = NULL; + + pjsip_rx_data *rdata; + pjsip_msg *msg; + pjsip_sub_state_hdr *sub_state; + + pjsip_evsub_state new_state; + pj_str_t *new_state_str; + + pjsip_tx_data *tdata; + pj_status_t status; + + /* Only want to handle initial NOTIFY receive event. */ + if (tsx->state != PJSIP_TSX_STATE_TRYING) + return; + + + rdata = event->body.tsx_state.src.rdata; + msg = rdata->msg_info.msg; + + pj_list_init(&res_hdr); + + /* Get subscription state header. */ + sub_state = (pjsip_sub_state_hdr*) + pjsip_msg_find_hdr_by_name(msg, &STR_SUB_STATE, NULL); + if (sub_state == NULL) { + + pjsip_warning_hdr *warn_hdr; + pj_str_t warn_text = { "Missing Subscription-State header", 33}; + + /* Bad request! Add warning header. */ + st_code = PJSIP_SC_BAD_REQUEST; + warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399, + pjsip_endpt_name(sub->endpt), + &warn_text); + pj_list_push_back(&res_hdr, warn_hdr); + } + + /* Call application registered callback to handle incoming NOTIFY, + * if any. + */ + if (st_code==200 && sub->user.on_rx_notify && sub->call_cb) { + (*sub->user.on_rx_notify)(sub, rdata, &st_code, &st_text, + &res_hdr, &body); + + /* Application MUST specify final response! */ + PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; }); + + /* Must be a valid status code */ + PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; }); + } + + + /* If non-2xx should be returned, then send the response. + * No need to update server subscription state. + */ + if (st_code >= 300) { + status = create_response(sub, rdata, st_code, st_text, &res_hdr, + body, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(sub->dlg, tsx, tdata); + } + + /* Start timer to terminate subscription, just in case server + * is not able to generate NOTIFY to our response. + */ + if (status == PJ_SUCCESS) { + unsigned timeout = TIME_UAC_WAIT_NOTIFY; + set_timer(sub, TIMER_TYPE_UAC_WAIT_NOTIFY, timeout); + } else { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_str_t reason; + + reason = pj_strerror(status, errmsg, sizeof(errmsg)); + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, + &reason); + } + + return; + } + + /* Update expiration from the value of expires param in + * Subscription-State header, but ONLY when subscription state + * is "active" or "pending", AND the header contains expires param. + */ + if (sub->expires->ivalue != 0 && + sub_state->expires_param >= 0 && + (pj_stricmp(&sub_state->sub_state, &STR_ACTIVE)==0 || + pj_stricmp(&sub_state->sub_state, &STR_PENDING)==0)) + { + int next_refresh = sub_state->expires_param; + unsigned timeout; + + update_expires(sub, next_refresh); + + /* Start UAC refresh timer, only when we're not unsubscribing */ + timeout = (next_refresh > TIME_UAC_REFRESH) ? + next_refresh - TIME_UAC_REFRESH : next_refresh; + + PJ_LOG(5,(sub->obj_name, "Will refresh in %d seconds", timeout)); + set_timer(sub, TIMER_TYPE_UAC_REFRESH, timeout); + } + + /* Find out the state */ + get_hdr_state(sub_state, &new_state, &new_state_str); + + /* Send response. */ + status = create_response(sub, rdata, st_code, st_text, &res_hdr, + body, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_dlg_send_response(sub->dlg, tsx, tdata); + + /* Set the state */ + if (status == PJ_SUCCESS) { + set_state(sub, new_state, new_state_str, event, + &sub_state->reason_param); + } else { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_str_t reason; + + reason = pj_strerror(status, errmsg, sizeof(errmsg)); + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event, + &reason); + } + + + } else { + + /* + * Unexpected method! + */ + PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s", + (int)tsx->method.name.slen, tsx->method.name.ptr)); + } +} + + +/* + * Transaction event processing by UAS, after subscription is accepted. + */ +static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event) +{ + + if (pjsip_method_cmp(&tsx->method, &sub->method) == 0 || + pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0) + { + + /* + * Incoming request (e.g. SUBSCRIBE or REFER) to refresh subsciption. + * + */ + pjsip_rx_data *rdata; + pjsip_event_hdr *event_hdr; + pjsip_expires_hdr *expires; + pjsip_msg *msg; + pjsip_tx_data *tdata; + int st_code = 200; + pj_str_t *st_text = NULL; + pjsip_hdr res_hdr; + pjsip_msg_body *body = NULL; + pjsip_evsub_state old_state; + pj_str_t old_state_str; + pj_str_t reason = { NULL, 0 }; + pj_status_t status; + + + /* Only wants to handle the first event when the request is + * received. + */ + if (tsx->state != PJSIP_TSX_STATE_TRYING) + return; + + rdata = event->body.tsx_state.src.rdata; + msg = rdata->msg_info.msg; + + /* Set expiration time based on client request (in Expires header), + * or package default expiration time. + */ + event_hdr = (pjsip_event_hdr*) + pjsip_msg_find_hdr_by_names(msg, &STR_EVENT, + &STR_EVENT, NULL); + expires = (pjsip_expires_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); + if (event_hdr && expires) { + struct evpkg *evpkg; + + evpkg = find_pkg(&event_hdr->event_type); + if (evpkg) { + if (expires->ivalue < (pj_int32_t)evpkg->pkg_expires) + sub->expires->ivalue = expires->ivalue; + else + sub->expires->ivalue = evpkg->pkg_expires; + } + } + + /* Update time (before calling on_rx_refresh, since application + * will send NOTIFY. + */ + update_expires(sub, sub->expires->ivalue); + + + /* Save old state. + * If application respond with non-2xx, revert to old state. + */ + old_state = sub->state; + old_state_str = sub->state_str; + + if (sub->expires->ivalue == 0) { + sub->state = PJSIP_EVSUB_STATE_TERMINATED; + sub->state_str = evsub_state_names[sub->state]; + } else if (sub->state == PJSIP_EVSUB_STATE_NULL) { + sub->state = PJSIP_EVSUB_STATE_ACCEPTED; + sub->state_str = evsub_state_names[sub->state]; + } + + /* Call application's on_rx_refresh, just in case it wants to send + * response other than 200 (OK) + */ + pj_list_init(&res_hdr); + + if (sub->user.on_rx_refresh && sub->call_cb) { + (*sub->user.on_rx_refresh)(sub, rdata, &st_code, &st_text, + &res_hdr, &body); + } + + /* Application MUST specify final response! */ + PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; }); + + /* Must be a valid status code */ + PJ_ASSERT_ON_FAIL(st_code <= 699, {st_code=500; }); + + + /* Create and send response */ + status = create_response(sub, rdata, st_code, st_text, &res_hdr, + body, &tdata); + if (status == PJ_SUCCESS) { + /* Add expires header: */ + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, + sub->expires)); + + /* Send */ + status = pjsip_dlg_send_response(sub->dlg, tsx, tdata); + } + + /* Update state or revert state */ + if (st_code/100==2) { + + if (sub->expires->ivalue == 0) { + set_state(sub, sub->state, NULL, event, &reason); + } else if (sub->state == PJSIP_EVSUB_STATE_NULL) { + set_state(sub, sub->state, NULL, event, &reason); + } + + /* Set UAS timeout timer, when state is not terminated. */ + if (sub->state != PJSIP_EVSUB_STATE_TERMINATED) { + PJ_LOG(5,(sub->obj_name, "UAS timeout in %d seconds", + sub->expires->ivalue)); + set_timer(sub, TIMER_TYPE_UAS_TIMEOUT, + sub->expires->ivalue); + } + + } else { + sub->state = old_state; + sub->state_str = old_state_str; + } + + + } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method)==0) { + + /* Handle authentication */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + (tsx->status_code==401 || tsx->status_code==407)) + { + pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_auth_clt_reinit_req( &sub->dlg->auth_sess, rdata, + tsx->last_tx, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_dlg_send_request( sub->dlg, tdata, -1, NULL ); + + if (status != PJ_SUCCESS) { + /* Can't authenticate. Terminate session (?) */ + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL, + &tsx->status_text); + return; + } + + } + /* + * Terminate event usage if we receive 481, 408, and 7 class + * responses. + */ + if (sub->state != PJSIP_EVSUB_STATE_TERMINATED && + (tsx->status_code==481 || tsx->status_code==408 || + tsx->status_code/100 == 7)) + { + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event, + &tsx->status_text); + return; + } + + } else { + + /* + * Unexpected method! + */ + PJ_LOG(4,(sub->obj_name, "Unexpected transaction method %.*s", + (int)tsx->method.name.slen, tsx->method.name.ptr)); + + } +} + + +/* + * Notification when transaction state has changed! + */ +static void mod_evsub_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event) +{ + pjsip_evsub *sub = pjsip_tsx_get_evsub(tsx); + + if (sub == NULL) { + sub = on_new_transaction(tsx, event); + if (sub == NULL) + return; + } + + + /* Call on_tsx_state callback, if any. */ + if (sub->user.on_tsx_state && sub->call_cb) + (*sub->user.on_tsx_state)(sub, tsx, event); + + + /* Process the event: */ + + if (sub->role == PJSIP_ROLE_UAC) { + on_tsx_state_uac(sub, tsx, event); + } else { + on_tsx_state_uas(sub, tsx, event); + } + + + /* Check transaction TERMINATE event */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + --sub->pending_tsx; + + if (sub->state == PJSIP_EVSUB_STATE_TERMINATED && + sub->pending_tsx == 0) + { + evsub_destroy(sub); + } + + } +} + + diff --git a/pjsip/src/pjsip-simple/evsub_msg.c b/pjsip/src/pjsip-simple/evsub_msg.c new file mode 100644 index 0000000..d91afa9 --- /dev/null +++ b/pjsip/src/pjsip-simple/evsub_msg.c @@ -0,0 +1,304 @@ +/* $Id: evsub_msg.c 3841 2011-10-24 09:28:13Z ming $ */ +/* + * Copyright (C) 2008-2011 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 + +/* + * Event header. + */ +static int pjsip_event_hdr_print( pjsip_event_hdr *hdr, + char *buf, pj_size_t size); +static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool, + const pjsip_event_hdr *hdr); +static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_event_hdr*); + +static pjsip_hdr_vptr event_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_event_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_event_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_event_hdr_print, +}; + + +PJ_DEF(pjsip_event_hdr*) pjsip_event_hdr_create(pj_pool_t *pool) +{ + pjsip_event_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_event_hdr); + hdr->type = PJSIP_H_OTHER; + hdr->name.ptr = "Event"; + hdr->name.slen = 5; + hdr->sname.ptr = "o"; + hdr->sname.slen = 1; + hdr->vptr = &event_hdr_vptr; + pj_list_init(hdr); + pj_list_init(&hdr->other_param); + return hdr; +} + +static int pjsip_event_hdr_print( pjsip_event_hdr *hdr, + char *buf, pj_size_t size) +{ + char *p = buf; + char *endbuf = buf+size; + int printed; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + copy_advance(p, hdr->name); + *p++ = ':'; + *p++ = ' '; + + copy_advance(p, hdr->event_type); + copy_advance_pair(p, ";id=", 4, hdr->id_param); + + printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, ';'); + if (printed < 0) + return printed; + + p += printed; + return p - buf; +} + +static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool, + const pjsip_event_hdr *rhs) +{ + pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool); + pj_strdup(pool, &hdr->event_type, &rhs->event_type); + pj_strdup(pool, &hdr->id_param, &rhs->id_param); + pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + +static pjsip_event_hdr* +pjsip_event_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_event_hdr *rhs ) +{ + pjsip_event_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_event_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + + +/* + * Allow-Events header. + */ +PJ_DEF(pjsip_allow_events_hdr*) pjsip_allow_events_hdr_create(pj_pool_t *pool) +{ + const pj_str_t STR_ALLOW_EVENTS = { "Allow-Events", 12}; + pjsip_allow_events_hdr *hdr; + + hdr = pjsip_generic_array_hdr_create(pool, &STR_ALLOW_EVENTS); + + if (hdr) { + hdr->sname.ptr = "u"; + hdr->sname.slen = 1; + } + + return hdr; +} + + +/* + * Subscription-State header. + */ +static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr, + char *buf, pj_size_t size); +static pjsip_sub_state_hdr* +pjsip_sub_state_hdr_clone(pj_pool_t *pool, + const pjsip_sub_state_hdr *hdr); +static pjsip_sub_state_hdr* +pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool, + const pjsip_sub_state_hdr*); + +static pjsip_hdr_vptr sub_state_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_sub_state_hdr_print, +}; + + +PJ_DEF(pjsip_sub_state_hdr*) pjsip_sub_state_hdr_create(pj_pool_t *pool) +{ + pj_str_t sub_state = { "Subscription-State", 18 }; + pjsip_sub_state_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_sub_state_hdr); + hdr->type = PJSIP_H_OTHER; + hdr->name = hdr->sname = sub_state; + hdr->vptr = &sub_state_hdr_vptr; + hdr->expires_param = -1; + hdr->retry_after = -1; + pj_list_init(hdr); + pj_list_init(&hdr->other_param); + return hdr; +} + +static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr, + char *buf, pj_size_t size) +{ + char *p = buf; + char *endbuf = buf+size; + int printed; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + copy_advance(p, hdr->name); + *p++ = ':'; + *p++ = ' '; + + copy_advance_escape(p, hdr->sub_state, pc->pjsip_TOKEN_SPEC); + copy_advance_pair_escape(p, ";reason=", 8, hdr->reason_param, + pc->pjsip_TOKEN_SPEC); + if (hdr->expires_param >= 0) { + pj_memcpy(p, ";expires=", 9); + p += 9; + printed = pj_utoa(hdr->expires_param, p); + p += printed; + } + if (hdr->retry_after >= 0) { + pj_memcpy(p, ";retry-after=", 13); + p += 9; + printed = pj_utoa(hdr->retry_after, p); + p += printed; + } + + printed = pjsip_param_print_on( &hdr->other_param, p, endbuf-p, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, + ';'); + if (printed < 0) + return printed; + + p += printed; + + return p - buf; +} + +static pjsip_sub_state_hdr* +pjsip_sub_state_hdr_clone(pj_pool_t *pool, + const pjsip_sub_state_hdr *rhs) +{ + pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool); + pj_strdup(pool, &hdr->sub_state, &rhs->sub_state); + pj_strdup(pool, &hdr->reason_param, &rhs->reason_param); + hdr->retry_after = rhs->retry_after; + hdr->expires_param = rhs->expires_param; + pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + +static pjsip_sub_state_hdr* +pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool, + const pjsip_sub_state_hdr *rhs) +{ + pjsip_sub_state_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_sub_state_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + + +/* + * Parse Event header. + */ +static pjsip_hdr *parse_hdr_event(pjsip_parse_ctx *ctx) +{ + pjsip_event_hdr *hdr = pjsip_event_hdr_create(ctx->pool); + const pj_str_t id_param = { "id", 2 }; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + pj_scan_get(ctx->scanner, &pc->pjsip_TOKEN_SPEC, &hdr->event_type); + + while (*ctx->scanner->curptr == ';') { + pj_str_t pname, pvalue; + + pj_scan_get_char(ctx->scanner); + pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0); + + if (pj_stricmp(&pname, &id_param)==0) { + hdr->id_param = pvalue; + } else { + pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param); + param->name = pname; + param->value = pvalue; + pj_list_push_back(&hdr->other_param, param); + } + } + pjsip_parse_end_hdr_imp( ctx->scanner ); + return (pjsip_hdr*)hdr; +} + +/* + * Parse Subscription-State header. + */ +static pjsip_hdr* parse_hdr_sub_state( pjsip_parse_ctx *ctx ) +{ + pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(ctx->pool); + const pj_str_t reason = { "reason", 6 }, + expires = { "expires", 7 }, + retry_after = { "retry-after", 11 }; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + pj_scan_get(ctx->scanner, &pc->pjsip_TOKEN_SPEC, &hdr->sub_state); + + while (*ctx->scanner->curptr == ';') { + pj_str_t pname, pvalue; + + pj_scan_get_char(ctx->scanner); + pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0); + + if (pj_stricmp(&pname, &reason) == 0) { + hdr->reason_param = pvalue; + + } else if (pj_stricmp(&pname, &expires) == 0) { + hdr->expires_param = pj_strtoul(&pvalue); + + } else if (pj_stricmp(&pname, &retry_after) == 0) { + hdr->retry_after = pj_strtoul(&pvalue); + + } else { + pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param); + param->name = pname; + param->value = pvalue; + pj_list_push_back(&hdr->other_param, param); + } + } + + pjsip_parse_end_hdr_imp( ctx->scanner ); + return (pjsip_hdr*)hdr; +} + +/* + * Register header parsers. + */ +PJ_DEF(void) pjsip_evsub_init_parser(void) +{ + pjsip_register_hdr_parser( "Event", "o", + &parse_hdr_event); + + pjsip_register_hdr_parser( "Subscription-State", NULL, + &parse_hdr_sub_state); +} + diff --git a/pjsip/src/pjsip-simple/iscomposing.c b/pjsip/src/pjsip-simple/iscomposing.c new file mode 100644 index 0000000..7053539 --- /dev/null +++ b/pjsip/src/pjsip-simple/iscomposing.c @@ -0,0 +1,218 @@ +/* $Id: iscomposing.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 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 + + +/* MIME */ +static const pj_str_t STR_MIME_TYPE = { "application", 11 }; +static const pj_str_t STR_MIME_SUBTYPE = { "im-iscomposing+xml", 18 }; + + +/* XML node constants. */ +static const pj_str_t STR_ISCOMPOSING = { "isComposing", 11 }; +static const pj_str_t STR_STATE = { "state", 5 }; +static const pj_str_t STR_ACTIVE = { "active", 6 }; +static const pj_str_t STR_IDLE = { "idle", 4 }; +static const pj_str_t STR_LASTACTIVE = { "lastactive", 10 }; +static const pj_str_t STR_CONTENTTYPE = { "contenttype", 11 }; +static const pj_str_t STR_REFRESH = { "refresh", 7 }; + + +/* XML attributes constants */ +static const pj_str_t STR_XMLNS_NAME = { "xmlns", 5 }; +static const pj_str_t STR_XMLNS_VAL = { "urn:ietf:params:xml:ns:im-iscomposing", 37 }; +static const pj_str_t STR_XMLNS_XSI_NAME = { "xmlns:xsi", 9 }; +static const pj_str_t STR_XMLNS_XSI_VAL = { "http://www.w3.org/2001/XMLSchema-instance", 41 }; +static const pj_str_t STR_XSI_SLOC_NAME = { "xsi:schemaLocation", 18 }; +static const pj_str_t STR_XSI_SLOC_VAL = { "urn:ietf:params:xml:ns:im-composing iscomposing.xsd", 51 }; + + +PJ_DEF(pj_xml_node*) pjsip_iscomposing_create_xml( pj_pool_t *pool, + pj_bool_t is_composing, + const pj_time_val *lst_actv, + const pj_str_t *content_tp, + int refresh) +{ + pj_xml_node *doc, *node; + pj_xml_attr *attr; + + /* Root document. */ + doc = pj_xml_node_new(pool, &STR_ISCOMPOSING); + + /* Add attributes */ + attr = pj_xml_attr_new(pool, &STR_XMLNS_NAME, &STR_XMLNS_VAL); + pj_xml_add_attr(doc, attr); + + attr = pj_xml_attr_new(pool, &STR_XMLNS_XSI_NAME, &STR_XMLNS_XSI_VAL); + pj_xml_add_attr(doc, attr); + + attr = pj_xml_attr_new(pool, &STR_XSI_SLOC_NAME, &STR_XSI_SLOC_VAL); + pj_xml_add_attr(doc, attr); + + + /* Add state. */ + node = pj_xml_node_new(pool, &STR_STATE); + if (is_composing) + node->content = STR_ACTIVE; + else + node->content = STR_IDLE; + pj_xml_add_node(doc, node); + + /* Add lastactive, if any. */ + PJ_UNUSED_ARG(lst_actv); + //if (!is_composing && lst_actv) { + // PJ_TODO(IMPLEMENT_LAST_ACTIVE_ATTRIBUTE); + //} + + /* Add contenttype, if any. */ + if (content_tp) { + node = pj_xml_node_new(pool, &STR_CONTENTTYPE); + pj_strdup(pool, &node->content, content_tp); + pj_xml_add_node(doc, node); + } + + /* Add refresh, if any. */ + if (is_composing && refresh > 1 && refresh < 3601) { + node = pj_xml_node_new(pool, &STR_REFRESH); + node->content.ptr = (char*) pj_pool_alloc(pool, 10); + node->content.slen = pj_utoa(refresh, node->content.ptr); + pj_xml_add_node(doc, node); + } + + /* Done! */ + + return doc; +} + + + +/* + * Function to print XML message body. + */ +static int xml_print_body( struct pjsip_msg_body *msg_body, + char *buf, pj_size_t size) +{ + return pj_xml_print((const pj_xml_node*)msg_body->data, buf, size, + PJ_TRUE); +} + + +/* + * Function to clone XML document. + */ +static void* xml_clone_data(pj_pool_t *pool, const void *data, unsigned len) +{ + PJ_UNUSED_ARG(len); + return pj_xml_clone( pool, (const pj_xml_node*)data); +} + + + +PJ_DEF(pjsip_msg_body*) pjsip_iscomposing_create_body( pj_pool_t *pool, + pj_bool_t is_composing, + const pj_time_val *lst_actv, + const pj_str_t *content_tp, + int refresh) +{ + pj_xml_node *doc; + pjsip_msg_body *body; + + doc = pjsip_iscomposing_create_xml( pool, is_composing, lst_actv, + content_tp, refresh); + if (doc == NULL) + return NULL; + + + body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + body->content_type.type = STR_MIME_TYPE; + body->content_type.subtype = STR_MIME_SUBTYPE; + + body->data = doc; + body->len = 0; + + body->print_body = &xml_print_body; + body->clone_data = &xml_clone_data; + + return body; +} + + +PJ_DEF(pj_status_t) pjsip_iscomposing_parse( pj_pool_t *pool, + char *msg, + pj_size_t len, + pj_bool_t *p_is_composing, + pj_str_t **p_last_active, + pj_str_t **p_content_type, + int *p_refresh ) +{ + pj_xml_node *doc, *node; + + /* Set defaults: */ + if (p_is_composing) *p_is_composing = PJ_FALSE; + if (p_last_active) *p_last_active = NULL; + if (p_content_type) *p_content_type = NULL; + + /* Parse XML */ + doc = pj_xml_parse( pool, msg, len); + if (!doc) + return PJLIB_UTIL_EINXML; + + /* Root document must be "isComposing" */ + if (pj_stricmp(&doc->name, &STR_ISCOMPOSING) != 0) + return PJSIP_SIMPLE_EBADISCOMPOSE; + + /* Get the status. */ + if (p_is_composing) { + node = pj_xml_find_node(doc, &STR_STATE); + if (node == NULL) + return PJSIP_SIMPLE_EBADISCOMPOSE; + *p_is_composing = (pj_stricmp(&node->content, &STR_ACTIVE)==0); + } + + /* Get last active. */ + if (p_last_active) { + node = pj_xml_find_node(doc, &STR_LASTACTIVE); + if (node) + *p_last_active = &node->content; + } + + /* Get content type */ + if (p_content_type) { + node = pj_xml_find_node(doc, &STR_CONTENTTYPE); + if (node) + *p_content_type = &node->content; + } + + /* Get refresh */ + if (p_refresh) { + node = pj_xml_find_node(doc, &STR_REFRESH); + if (node) + *p_refresh = pj_strtoul(&node->content); + } + + return PJ_SUCCESS; +} + + diff --git a/pjsip/src/pjsip-simple/mwi.c b/pjsip/src/pjsip-simple/mwi.c new file mode 100644 index 0000000..da86131 --- /dev/null +++ b/pjsip/src/pjsip-simple/mwi.c @@ -0,0 +1,598 @@ +/* $Id: mwi.c 4172 2012-06-19 14:35:18Z nanang $ */ +/* + * Copyright (C) 2008-2011 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 + + +#define THIS_FILE "mwi.c" + + /* + * MWI module (mod-mdi) + */ +static struct pjsip_module mod_mwi = +{ + NULL, NULL, /* prev, next. */ + { "mod-mwi", 7 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_DIALOG_USAGE,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + + +/* + * This structure describe an mwi agent (both client and server) + */ +typedef struct pjsip_mwi +{ + pjsip_evsub *sub; /**< Event subscribtion record. */ + pjsip_dialog *dlg; /**< The dialog. */ + pjsip_evsub_user user_cb; /**< The user callback. */ + + /* These are for server subscriptions */ + pj_pool_t *body_pool; /**< Pool to save message body */ + pjsip_media_type mime_type; /**< MIME type of last msg body */ + pj_str_t body; /**< Last sent message body */ +} pjsip_mwi; + + +/* + * Forward decl for evsub callbacks. + */ +static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); +static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event); +static void mwi_on_evsub_rx_refresh( 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); +static void mwi_on_evsub_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); +static void mwi_on_evsub_client_refresh(pjsip_evsub *sub); +static void mwi_on_evsub_server_timeout(pjsip_evsub *sub); + + +/* + * Event subscription callback for mwi. + */ +static pjsip_evsub_user mwi_user = +{ + &mwi_on_evsub_state, + &mwi_on_evsub_tsx_state, + &mwi_on_evsub_rx_refresh, + &mwi_on_evsub_rx_notify, + &mwi_on_evsub_client_refresh, + &mwi_on_evsub_server_timeout, +}; + + +/* + * Some static constants. + */ +static const pj_str_t STR_EVENT = { "Event", 5 }; +static const pj_str_t STR_MWI = { "message-summary", 15 }; +static const pj_str_t STR_APP_SIMPLE_SMS = { "application/simple-message-summary", 34}; + +/* + * Init mwi module. + */ +PJ_DEF(pj_status_t) pjsip_mwi_init_module( pjsip_endpoint *endpt, + pjsip_module *mod_evsub) +{ + pj_status_t status; + pj_str_t accept[1]; + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL); + + /* Must have not been registered */ + PJ_ASSERT_RETURN(mod_mwi.id == -1, PJ_EINVALIDOP); + + /* Register to endpoint */ + status = pjsip_endpt_register_module(endpt, &mod_mwi); + if (status != PJ_SUCCESS) + return status; + + accept[0] = STR_APP_SIMPLE_SMS; + + /* Register event package to event module. */ + status = pjsip_evsub_register_pkg( &mod_mwi, &STR_MWI, + PJSIP_MWI_DEFAULT_EXPIRES, + PJ_ARRAY_SIZE(accept), accept); + if (status != PJ_SUCCESS) { + pjsip_endpt_unregister_module(endpt, &mod_mwi); + return status; + } + + return PJ_SUCCESS; +} + + +/* + * Get mwi module instance. + */ +PJ_DEF(pjsip_module*) pjsip_mwi_instance(void) +{ + return &mod_mwi; +} + + +/* + * Create client subscription. + */ +PJ_DEF(pj_status_t) pjsip_mwi_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + unsigned options, + pjsip_evsub **p_evsub ) +{ + pj_status_t status; + pjsip_mwi *mwi; + pjsip_evsub *sub; + + PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL); + + PJ_UNUSED_ARG(options); + + pjsip_dlg_inc_lock(dlg); + + /* Create event subscription */ + status = pjsip_evsub_create_uac( dlg, &mwi_user, &STR_MWI, + options, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create mwi */ + mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi); + mwi->dlg = dlg; + mwi->sub = sub; + if (user_cb) + pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi); + + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Create server subscription. + */ +PJ_DEF(pj_status_t) pjsip_mwi_create_uas( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_rx_data *rdata, + pjsip_evsub **p_evsub ) +{ + pjsip_accept_hdr *accept; + pjsip_event_hdr *event; + pjsip_evsub *sub; + pjsip_mwi *mwi; + char obj_name[PJ_MAX_OBJ_NAME]; + pj_status_t status; + + /* Check arguments */ + PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL); + + /* Must be request message */ + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Check that request is SUBSCRIBE */ + PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, + &pjsip_subscribe_method)==0, + PJSIP_SIMPLE_ENOTSUBSCRIBE); + + /* Check that Event header contains "mwi" */ + event = (pjsip_event_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL); + if (!event) { + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST); + } + if (pj_stricmp(&event->event_type, &STR_MWI) != 0) { + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT); + } + + /* Check that request contains compatible Accept header. */ + accept = (pjsip_accept_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL); + if (accept) { + unsigned i; + for (i=0; icount; ++i) { + if (pj_stricmp(&accept->values[i], &STR_APP_SIMPLE_SMS)==0) { + break; + } + } + + if (i==accept->count) { + /* Nothing is acceptable */ + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE); + } + + } else { + /* No Accept header. + * Assume client supports "application/simple-message-summary" + */ + } + + /* Lock dialog */ + pjsip_dlg_inc_lock(dlg); + + + /* Create server subscription */ + status = pjsip_evsub_create_uas( dlg, &mwi_user, rdata, 0, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create server mwi subscription */ + mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi); + mwi->dlg = dlg; + mwi->sub = sub; + if (user_cb) + pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "mwibd%p", dlg->pool); + mwi->body_pool = pj_pool_create(dlg->pool->factory, obj_name, + 512, 512, NULL); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi); + + /* Done: */ + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Forcefully terminate mwi. + */ +PJ_DEF(pj_status_t) pjsip_mwi_terminate( pjsip_evsub *sub, + pj_bool_t notify ) +{ + return pjsip_evsub_terminate(sub, notify); +} + +/* + * Create SUBSCRIBE + */ +PJ_DEF(pj_status_t) pjsip_mwi_initiate( pjsip_evsub *sub, + pj_int32_t expires, + pjsip_tx_data **p_tdata) +{ + return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires, + p_tdata); +} + + +/* + * Accept incoming subscription. + */ +PJ_DEF(pj_status_t) pjsip_mwi_accept( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list ) +{ + return pjsip_evsub_accept( sub, rdata, st_code, hdr_list ); +} + +/* + * Create message body and attach it to the (NOTIFY) request. + */ +static pj_status_t mwi_create_msg_body( pjsip_mwi *mwi, + pjsip_tx_data *tdata) +{ + pjsip_msg_body *body; + pj_str_t dup_text; + + PJ_ASSERT_RETURN(mwi->mime_type.type.slen && mwi->body.slen, PJ_EINVALIDOP); + + /* Clone the message body and mime type */ + pj_strdup(tdata->pool, &dup_text, &mwi->body); + + /* Create the message body */ + body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body); + pjsip_media_type_cp(tdata->pool, &body->content_type, &mwi->mime_type); + body->data = dup_text.ptr; + body->len = (unsigned)dup_text.slen; + body->print_body = &pjsip_print_text_body; + body->clone_data = &pjsip_clone_text_data; + + /* Attach to tdata */ + tdata->msg->body = body; + + return PJ_SUCCESS; +} + + +/* + * Create NOTIFY + */ +PJ_DEF(pj_status_t) pjsip_mwi_notify( pjsip_evsub *sub, + pjsip_evsub_state state, + const pj_str_t *state_str, + const pj_str_t *reason, + const pjsip_media_type *mime_type, + const pj_str_t *body, + pjsip_tx_data **p_tdata) +{ + pjsip_mwi *mwi; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub && mime_type && body && p_tdata, PJ_EINVAL); + + /* Get the mwi object. */ + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP); + + /* Lock object. */ + pjsip_dlg_inc_lock(mwi->dlg); + + /* Create the NOTIFY request. */ + status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Update the cached message body */ + if (mime_type || body) + pj_pool_reset(mwi->body_pool); + if (mime_type) + pjsip_media_type_cp(mwi->body_pool, &mwi->mime_type, mime_type); + if (body) + pj_strdup(mwi->body_pool, &mwi->body, body); + + /* Create message body */ + status = mwi_create_msg_body( mwi, tdata ); + if (status != PJ_SUCCESS) + goto on_return; + + /* Done. */ + *p_tdata = tdata; + +on_return: + pjsip_dlg_dec_lock(mwi->dlg); + return status; +} + + +/* + * Create NOTIFY that reflect current state. + */ +PJ_DEF(pj_status_t) pjsip_mwi_current_notify( pjsip_evsub *sub, + pjsip_tx_data **p_tdata ) +{ + pjsip_mwi *mwi; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL); + + /* Get the mwi object. */ + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP); + + /* Lock object. */ + pjsip_dlg_inc_lock(mwi->dlg); + + /* Create the NOTIFY request. */ + status = pjsip_evsub_current_notify( sub, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Create message body to reflect the mwi status. */ + status = mwi_create_msg_body( mwi, tdata ); + if (status != PJ_SUCCESS) + goto on_return; + + /* Done. */ + *p_tdata = tdata; + +on_return: + pjsip_dlg_dec_lock(mwi->dlg); + return status; +} + + +/* + * Send request. + */ +PJ_DEF(pj_status_t) pjsip_mwi_send_request( pjsip_evsub *sub, + pjsip_tx_data *tdata ) +{ + return pjsip_evsub_send_request(sub, tdata); +} + +/* + * This callback is called by event subscription when subscription + * state has changed. + */ +static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + if (mwi->user_cb.on_evsub_state) + (*mwi->user_cb.on_evsub_state)(sub, event); + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + if (mwi->body_pool) { + pj_pool_release(mwi->body_pool); + mwi->body_pool = NULL; + } + } +} + +/* + * Called when transaction state has changed. + */ +static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + if (mwi->user_cb.on_tsx_state) + (*mwi->user_cb.on_tsx_state)(sub, tsx, event); +} + + +/* + * Called when SUBSCRIBE is received. + */ +static void mwi_on_evsub_rx_refresh( 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) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + if (mwi->user_cb.on_rx_refresh) { + (*mwi->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text, + res_hdr, p_body); + + } else { + /* Implementors MUST send NOTIFY if it implements on_rx_refresh */ + pjsip_tx_data *tdata; + pj_str_t timeout = { "timeout", 7}; + pj_status_t status; + + if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) { + status = pjsip_mwi_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, &timeout, NULL, NULL, &tdata); + } else { + status = pjsip_mwi_current_notify(sub, &tdata); + } + + if (status == PJ_SUCCESS) + pjsip_mwi_send_request(sub, tdata); + } +} + + +/* + * Called when NOTIFY is received. + */ +static void mwi_on_evsub_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) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + /* Just notify application. */ + if (mwi->user_cb.on_rx_notify) { + (*mwi->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text, + res_hdr, p_body); + } +} + +/* + * Called when it's time to send SUBSCRIBE. + */ +static void mwi_on_evsub_client_refresh(pjsip_evsub *sub) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + if (mwi->user_cb.on_client_refresh) { + (*mwi->user_cb.on_client_refresh)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_mwi_initiate(sub, -1, &tdata); + if (status == PJ_SUCCESS) + pjsip_mwi_send_request(sub, tdata); + } +} + +/* + * Called when no refresh is received after the interval. + */ +static void mwi_on_evsub_server_timeout(pjsip_evsub *sub) +{ + pjsip_mwi *mwi; + + mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id); + PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;}); + + if (mwi->user_cb.on_server_timeout) { + (*mwi->user_cb.on_server_timeout)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + pj_str_t reason = { "timeout", 7 }; + + status = pjsip_mwi_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, &reason, NULL, NULL, &tdata); + if (status == PJ_SUCCESS) + pjsip_mwi_send_request(sub, tdata); + } +} + diff --git a/pjsip/src/pjsip-simple/pidf.c b/pjsip/src/pjsip-simple/pidf.c new file mode 100644 index 0000000..0b68d1a --- /dev/null +++ b/pjsip/src/pjsip-simple/pidf.c @@ -0,0 +1,365 @@ +/* $Id: pidf.c 3841 2011-10-24 09:28:13Z ming $ */ +/* + * Copyright (C) 2008-2011 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 + + +struct pjpidf_op_desc pjpidf_op = +{ + { + &pjpidf_pres_construct, + &pjpidf_pres_add_tuple, + &pjpidf_pres_get_first_tuple, + &pjpidf_pres_get_next_tuple, + &pjpidf_pres_find_tuple, + &pjpidf_pres_remove_tuple, + &pjpidf_pres_add_note, + &pjpidf_pres_get_first_note, + &pjpidf_pres_get_next_note + }, + { + &pjpidf_tuple_construct, + &pjpidf_tuple_get_id, + &pjpidf_tuple_set_id, + &pjpidf_tuple_get_status, + &pjpidf_tuple_get_contact, + &pjpidf_tuple_set_contact, + &pjpidf_tuple_set_contact_prio, + &pjpidf_tuple_get_contact_prio, + &pjpidf_tuple_add_note, + &pjpidf_tuple_get_first_note, + &pjpidf_tuple_get_next_note, + &pjpidf_tuple_get_timestamp, + &pjpidf_tuple_set_timestamp, + &pjpidf_tuple_set_timestamp_np + }, + { + &pjpidf_status_construct, + &pjpidf_status_is_basic_open, + &pjpidf_status_set_basic_open + } +}; + +static pj_str_t PRESENCE = { "presence", 8 }; +static pj_str_t ENTITY = { "entity", 6}; +static pj_str_t TUPLE = { "tuple", 5 }; +static pj_str_t ID = { "id", 2 }; +static pj_str_t NOTE = { "note", 4 }; +static pj_str_t STATUS = { "status", 6 }; +static pj_str_t CONTACT = { "contact", 7 }; +static pj_str_t PRIORITY = { "priority", 8 }; +static pj_str_t TIMESTAMP = { "timestamp", 9 }; +static pj_str_t BASIC = { "basic", 5 }; +static pj_str_t OPEN = { "open", 4 }; +static pj_str_t CLOSED = { "closed", 6 }; +static pj_str_t EMPTY_STRING = { NULL, 0 }; + +static pj_str_t XMLNS = { "xmlns", 5 }; +static pj_str_t PIDF_XMLNS = { "urn:ietf:params:xml:ns:pidf", 27 }; + +static void xml_init_node(pj_pool_t *pool, pj_xml_node *node, + pj_str_t *name, const pj_str_t *value) +{ + pj_list_init(&node->attr_head); + pj_list_init(&node->node_head); + node->name = *name; + if (value) pj_strdup(pool, &node->content, value); + else node->content.ptr=NULL, node->content.slen=0; +} + +static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name, + const pj_str_t *value) +{ + pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr); + attr->name = *name; + pj_strdup(pool, &attr->value, value); + return attr; +} + +/* Presence */ +PJ_DEF(void) pjpidf_pres_construct(pj_pool_t *pool, pjpidf_pres *pres, + const pj_str_t *entity) +{ + pj_xml_attr *attr; + + xml_init_node(pool, pres, &PRESENCE, NULL); + attr = xml_create_attr(pool, &ENTITY, entity); + pj_xml_add_attr(pres, attr); + attr = xml_create_attr(pool, &XMLNS, &PIDF_XMLNS); + pj_xml_add_attr(pres, attr); +} + +PJ_DEF(pjpidf_tuple*) pjpidf_pres_add_tuple(pj_pool_t *pool, pjpidf_pres *pres, + const pj_str_t *id) +{ + pjpidf_tuple *t = PJ_POOL_ALLOC_T(pool, pjpidf_tuple); + pjpidf_tuple_construct(pool, t, id); + pj_xml_add_node(pres, t); + return t; +} + +PJ_DEF(pjpidf_tuple*) pjpidf_pres_get_first_tuple(pjpidf_pres *pres) +{ + return pj_xml_find_node(pres, &TUPLE); +} + +PJ_DEF(pjpidf_tuple*) pjpidf_pres_get_next_tuple(pjpidf_pres *pres, + pjpidf_tuple *tuple) +{ + return pj_xml_find_next_node(pres, tuple, &TUPLE); +} + +static pj_bool_t find_tuple_by_id(const pj_xml_node *node, const void *id) +{ + return pj_xml_find_attr(node, &ID, (const pj_str_t*)id) != NULL; +} + +PJ_DEF(pjpidf_tuple*) pjpidf_pres_find_tuple(pjpidf_pres *pres, const pj_str_t *id) +{ + return pj_xml_find(pres, &TUPLE, id, &find_tuple_by_id); +} + +PJ_DEF(void) pjpidf_pres_remove_tuple(pjpidf_pres *pres, pjpidf_tuple *t) +{ + PJ_UNUSED_ARG(pres); + pj_list_erase(t); +} + +PJ_DEF(pjpidf_note*) pjpidf_pres_add_note(pj_pool_t *pool, pjpidf_pres *pres, + const pj_str_t *text) +{ + pjpidf_note *note = PJ_POOL_ALLOC_T(pool, pjpidf_note); + xml_init_node(pool, note, &NOTE, text); + pj_xml_add_node(pres, note); + return note; +} + +PJ_DEF(pjpidf_note*) pjpidf_pres_get_first_note(pjpidf_pres *pres) +{ + return pj_xml_find_node( pres, &NOTE); +} + +PJ_DEF(pjpidf_note*) pjpidf_pres_get_next_note(pjpidf_pres *t, pjpidf_note *note) +{ + return pj_xml_find_next_node(t, note, &NOTE); +} + + +/* Tuple */ +PJ_DEF(void) pjpidf_tuple_construct(pj_pool_t *pool, pjpidf_tuple *t, + const pj_str_t *id) +{ + pj_xml_attr *attr; + pjpidf_status *st; + + xml_init_node(pool, t, &TUPLE, NULL); + attr = xml_create_attr(pool, &ID, id); + pj_xml_add_attr(t, attr); + st = PJ_POOL_ALLOC_T(pool, pjpidf_status); + pjpidf_status_construct(pool, st); + pj_xml_add_node(t, st); +} + +PJ_DEF(const pj_str_t*) pjpidf_tuple_get_id(const pjpidf_tuple *t) +{ + const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)t, &ID, NULL); + pj_assert(attr); + return &attr->value; +} + +PJ_DEF(void) pjpidf_tuple_set_id(pj_pool_t *pool, pjpidf_tuple *t, const pj_str_t *id) +{ + pj_xml_attr *attr = pj_xml_find_attr(t, &ID, NULL); + pj_assert(attr); + pj_strdup(pool, &attr->value, id); +} + + +PJ_DEF(pjpidf_status*) pjpidf_tuple_get_status(pjpidf_tuple *t) +{ + pjpidf_status *st = (pjpidf_status*)pj_xml_find_node(t, &STATUS); + pj_assert(st); + return st; +} + + +PJ_DEF(const pj_str_t*) pjpidf_tuple_get_contact(const pjpidf_tuple *t) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &CONTACT); + if (!node) + return &EMPTY_STRING; + return &node->content; +} + +PJ_DEF(void) pjpidf_tuple_set_contact(pj_pool_t *pool, pjpidf_tuple *t, + const pj_str_t *contact) +{ + pj_xml_node *node = pj_xml_find_node(t, &CONTACT); + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &CONTACT, contact); + pj_xml_add_node(t, node); + } else { + pj_strdup(pool, &node->content, contact); + } +} + +PJ_DEF(void) pjpidf_tuple_set_contact_prio(pj_pool_t *pool, pjpidf_tuple *t, + const pj_str_t *prio) +{ + pj_xml_node *node = pj_xml_find_node(t, &CONTACT); + pj_xml_attr *attr; + + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &CONTACT, NULL); + pj_xml_add_node(t, node); + } + attr = pj_xml_find_attr(node, &PRIORITY, NULL); + if (!attr) { + attr = xml_create_attr(pool, &PRIORITY, prio); + pj_xml_add_attr(node, attr); + } else { + pj_strdup(pool, &attr->value, prio); + } +} + +PJ_DEF(const pj_str_t*) pjpidf_tuple_get_contact_prio(const pjpidf_tuple *t) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &CONTACT); + pj_xml_attr *attr; + + if (!node) + return &EMPTY_STRING; + attr = pj_xml_find_attr(node, &PRIORITY, NULL); + if (!attr) + return &EMPTY_STRING; + return &attr->value; +} + + +PJ_DEF(pjpidf_note*) pjpidf_tuple_add_note(pj_pool_t *pool, pjpidf_tuple *t, + const pj_str_t *text) +{ + pjpidf_note *note = PJ_POOL_ALLOC_T(pool, pjpidf_note); + xml_init_node(pool, note, &NOTE, text); + pj_xml_add_node(t, note); + return note; +} + +PJ_DEF(pjpidf_note*) pjpidf_tuple_get_first_note(pjpidf_tuple *t) +{ + return pj_xml_find_node(t, &NOTE); +} + +PJ_DEF(pjpidf_note*) pjpidf_tuple_get_next_note(pjpidf_tuple *t, pjpidf_note *n) +{ + return pj_xml_find_next_node(t, n, &NOTE); +} + + +PJ_DEF(const pj_str_t*) pjpidf_tuple_get_timestamp(const pjpidf_tuple *t) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &TIMESTAMP); + return node ? &node->content : &EMPTY_STRING; +} + +PJ_DEF(void) pjpidf_tuple_set_timestamp(pj_pool_t *pool, pjpidf_tuple *t, + const pj_str_t *ts) +{ + pj_xml_node *node = pj_xml_find_node(t, &TIMESTAMP); + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &TIMESTAMP, ts); + pj_xml_add_node(t, node); + } else { + pj_strdup(pool, &node->content, ts); + } +} + + +PJ_DEF(void) pjpidf_tuple_set_timestamp_np(pj_pool_t *pool, pjpidf_tuple *t, + pj_str_t *ts) +{ + pj_xml_node *node = pj_xml_find_node(t, &TIMESTAMP); + if (!node) { + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &TIMESTAMP, ts); + } else { + node->content = *ts; + } +} + + +/* Status */ +PJ_DEF(void) pjpidf_status_construct(pj_pool_t *pool, pjpidf_status *st) +{ + pj_xml_node *node; + + xml_init_node(pool, st, &STATUS, NULL); + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + xml_init_node(pool, node, &BASIC, &CLOSED); + pj_xml_add_node(st, node); +} + +PJ_DEF(pj_bool_t) pjpidf_status_is_basic_open(const pjpidf_status *st) +{ + pj_xml_node *node = pj_xml_find_node((pj_xml_node*)st, &BASIC); + if (!node) + return PJ_FALSE; + return pj_stricmp(&node->content, &OPEN)==0; +} + +PJ_DEF(void) pjpidf_status_set_basic_open(pjpidf_status *st, pj_bool_t open) +{ + pj_xml_node *node = pj_xml_find_node(st, &BASIC); + if (node) + node->content = open ? OPEN : CLOSED; +} + +PJ_DEF(pjpidf_pres*) pjpidf_create(pj_pool_t *pool, const pj_str_t *entity) +{ + pjpidf_pres *pres = PJ_POOL_ALLOC_T(pool, pjpidf_pres); + pjpidf_pres_construct(pool, pres, entity); + return pres; +} + +PJ_DEF(pjpidf_pres*) pjpidf_parse(pj_pool_t *pool, char *text, int len) +{ + pjpidf_pres *pres = pj_xml_parse(pool, text, len); + if (pres && pres->name.slen >= 8) { + pj_str_t name; + + name.ptr = pres->name.ptr + (pres->name.slen - 8); + name.slen = 8; + + if (pj_stricmp(&name, &PRESENCE) == 0) + return pres; + } + return NULL; +} + +PJ_DEF(int) pjpidf_print(const pjpidf_pres* pres, char *buf, int len) +{ + return pj_xml_print(pres, buf, len, PJ_TRUE); +} + diff --git a/pjsip/src/pjsip-simple/presence.c b/pjsip/src/pjsip-simple/presence.c new file mode 100644 index 0000000..5b96ec7 --- /dev/null +++ b/pjsip/src/pjsip-simple/presence.c @@ -0,0 +1,941 @@ +/* $Id: presence.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 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 + + +#define THIS_FILE "presence.c" +#define PRES_DEFAULT_EXPIRES PJSIP_PRES_DEFAULT_EXPIRES + +#if PJSIP_PRES_BAD_CONTENT_RESPONSE < 200 || \ + PJSIP_PRES_BAD_CONTENT_RESPONSE > 699 || \ + PJSIP_PRES_BAD_CONTENT_RESPONSE/100 == 3 +# error Invalid PJSIP_PRES_BAD_CONTENT_RESPONSE value +#endif + +/* + * Presence module (mod-presence) + */ +static struct pjsip_module mod_presence = +{ + NULL, NULL, /* prev, next. */ + { "mod-presence", 12 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_DIALOG_USAGE,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + + +/* + * Presence message body type. + */ +typedef enum content_type_e +{ + CONTENT_TYPE_NONE, + CONTENT_TYPE_PIDF, + CONTENT_TYPE_XPIDF, +} content_type_e; + +/* + * This structure describe a presentity, for both subscriber and notifier. + */ +struct pjsip_pres +{ + pjsip_evsub *sub; /**< Event subscribtion record. */ + pjsip_dialog *dlg; /**< The dialog. */ + content_type_e content_type; /**< Content-Type. */ + pj_pool_t *status_pool; /**< Pool for pres_status */ + pjsip_pres_status status; /**< Presence status. */ + pj_pool_t *tmp_pool; /**< Pool for tmp_status */ + pjsip_pres_status tmp_status; /**< Temp, before NOTIFY is answred.*/ + pjsip_evsub_user user_cb; /**< The user callback. */ +}; + + +typedef struct pjsip_pres pjsip_pres; + + +/* + * Forward decl for evsub callback. + */ +static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); +static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event); +static void pres_on_evsub_rx_refresh( 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); +static void pres_on_evsub_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); +static void pres_on_evsub_client_refresh(pjsip_evsub *sub); +static void pres_on_evsub_server_timeout(pjsip_evsub *sub); + + +/* + * Event subscription callback for presence. + */ +static pjsip_evsub_user pres_user = +{ + &pres_on_evsub_state, + &pres_on_evsub_tsx_state, + &pres_on_evsub_rx_refresh, + &pres_on_evsub_rx_notify, + &pres_on_evsub_client_refresh, + &pres_on_evsub_server_timeout, +}; + + +/* + * Some static constants. + */ +const pj_str_t STR_EVENT = { "Event", 5 }; +const pj_str_t STR_PRESENCE = { "presence", 8 }; +const pj_str_t STR_APPLICATION = { "application", 11 }; +const pj_str_t STR_PIDF_XML = { "pidf+xml", 8}; +const pj_str_t STR_XPIDF_XML = { "xpidf+xml", 9}; +const pj_str_t STR_APP_PIDF_XML = { "application/pidf+xml", 20 }; +const pj_str_t STR_APP_XPIDF_XML = { "application/xpidf+xml", 21 }; + + +/* + * Init presence module. + */ +PJ_DEF(pj_status_t) pjsip_pres_init_module( pjsip_endpoint *endpt, + pjsip_module *mod_evsub) +{ + pj_status_t status; + pj_str_t accept[2]; + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL); + + /* Must have not been registered */ + PJ_ASSERT_RETURN(mod_presence.id == -1, PJ_EINVALIDOP); + + /* Register to endpoint */ + status = pjsip_endpt_register_module(endpt, &mod_presence); + if (status != PJ_SUCCESS) + return status; + + accept[0] = STR_APP_PIDF_XML; + accept[1] = STR_APP_XPIDF_XML; + + /* Register event package to event module. */ + status = pjsip_evsub_register_pkg( &mod_presence, &STR_PRESENCE, + PRES_DEFAULT_EXPIRES, + PJ_ARRAY_SIZE(accept), accept); + if (status != PJ_SUCCESS) { + pjsip_endpt_unregister_module(endpt, &mod_presence); + return status; + } + + return PJ_SUCCESS; +} + + +/* + * Get presence module instance. + */ +PJ_DEF(pjsip_module*) pjsip_pres_instance(void) +{ + return &mod_presence; +} + + +/* + * Create client subscription. + */ +PJ_DEF(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + unsigned options, + pjsip_evsub **p_evsub ) +{ + pj_status_t status; + pjsip_pres *pres; + char obj_name[PJ_MAX_OBJ_NAME]; + pjsip_evsub *sub; + + PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); + + /* Create event subscription */ + status = pjsip_evsub_create_uac( dlg, &pres_user, &STR_PRESENCE, + options, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create presence */ + pres = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_pres); + pres->dlg = dlg; + pres->sub = sub; + if (user_cb) + pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "pres%p", dlg->pool); + pres->status_pool = pj_pool_create(dlg->pool->factory, obj_name, + 512, 512, NULL); + pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "tmpres%p", dlg->pool); + pres->tmp_pool = pj_pool_create(dlg->pool->factory, obj_name, + 512, 512, NULL); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_presence.id, pres); + + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Create server subscription. + */ +PJ_DEF(pj_status_t) pjsip_pres_create_uas( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_rx_data *rdata, + pjsip_evsub **p_evsub ) +{ + pjsip_accept_hdr *accept; + pjsip_event_hdr *event; + content_type_e content_type = CONTENT_TYPE_NONE; + pjsip_evsub *sub; + pjsip_pres *pres; + char obj_name[PJ_MAX_OBJ_NAME]; + pj_status_t status; + + /* Check arguments */ + PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL); + + /* Must be request message */ + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Check that request is SUBSCRIBE */ + PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, + &pjsip_subscribe_method)==0, + PJSIP_SIMPLE_ENOTSUBSCRIBE); + + /* Check that Event header contains "presence" */ + event = (pjsip_event_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL); + if (!event) { + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST); + } + if (pj_stricmp(&event->event_type, &STR_PRESENCE) != 0) { + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT); + } + + /* Check that request contains compatible Accept header. */ + accept = (pjsip_accept_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL); + if (accept) { + unsigned i; + for (i=0; icount; ++i) { + if (pj_stricmp(&accept->values[i], &STR_APP_PIDF_XML)==0) { + content_type = CONTENT_TYPE_PIDF; + break; + } else + if (pj_stricmp(&accept->values[i], &STR_APP_XPIDF_XML)==0) { + content_type = CONTENT_TYPE_XPIDF; + break; + } + } + + if (i==accept->count) { + /* Nothing is acceptable */ + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE); + } + + } else { + /* No Accept header. + * Treat as "application/pidf+xml" + */ + content_type = CONTENT_TYPE_PIDF; + } + + /* Lock dialog */ + pjsip_dlg_inc_lock(dlg); + + + /* Create server subscription */ + status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, 0, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create server presence subscription */ + pres = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_pres); + pres->dlg = dlg; + pres->sub = sub; + pres->content_type = content_type; + if (user_cb) + pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "pres%p", dlg->pool); + pres->status_pool = pj_pool_create(dlg->pool->factory, obj_name, + 512, 512, NULL); + pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "tmpres%p", dlg->pool); + pres->tmp_pool = pj_pool_create(dlg->pool->factory, obj_name, + 512, 512, NULL); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_presence.id, pres); + + /* Done: */ + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Forcefully terminate presence. + */ +PJ_DEF(pj_status_t) pjsip_pres_terminate( pjsip_evsub *sub, + pj_bool_t notify ) +{ + return pjsip_evsub_terminate(sub, notify); +} + +/* + * Create SUBSCRIBE + */ +PJ_DEF(pj_status_t) pjsip_pres_initiate( pjsip_evsub *sub, + pj_int32_t expires, + pjsip_tx_data **p_tdata) +{ + return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires, + p_tdata); +} + + +/* + * Add custom headers. + */ +PJ_DEF(pj_status_t) pjsip_pres_add_header( pjsip_evsub *sub, + const pjsip_hdr *hdr_list ) +{ + return pjsip_evsub_add_header( sub, hdr_list ); +} + + +/* + * Accept incoming subscription. + */ +PJ_DEF(pj_status_t) pjsip_pres_accept( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list ) +{ + return pjsip_evsub_accept( sub, rdata, st_code, hdr_list ); +} + + +/* + * Get presence status. + */ +PJ_DEF(pj_status_t) pjsip_pres_get_status( pjsip_evsub *sub, + pjsip_pres_status *status ) +{ + pjsip_pres *pres; + + PJ_ASSERT_RETURN(sub && status, PJ_EINVAL); + + pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE); + + if (pres->tmp_status._is_valid) { + PJ_ASSERT_RETURN(pres->tmp_pool!=NULL, PJSIP_SIMPLE_ENOPRESENCE); + pj_memcpy(status, &pres->tmp_status, sizeof(pjsip_pres_status)); + } else { + PJ_ASSERT_RETURN(pres->status_pool!=NULL, PJSIP_SIMPLE_ENOPRESENCE); + pj_memcpy(status, &pres->status, sizeof(pjsip_pres_status)); + } + + return PJ_SUCCESS; +} + + +/* + * Set presence status. + */ +PJ_DEF(pj_status_t) pjsip_pres_set_status( pjsip_evsub *sub, + const pjsip_pres_status *status ) +{ + unsigned i; + pj_pool_t *tmp; + pjsip_pres *pres; + + PJ_ASSERT_RETURN(sub && status, PJ_EINVAL); + + pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE); + + for (i=0; iinfo_cnt; ++i) { + pres->status.info[i].basic_open = status->info[i].basic_open; + if (pres->status.info[i].id.slen) { + /* Id already set */ + } else if (status->info[i].id.slen == 0) { + pj_create_unique_string(pres->dlg->pool, + &pres->status.info[i].id); + } else { + pj_strdup(pres->dlg->pool, + &pres->status.info[i].id, + &status->info[i].id); + } + pj_strdup(pres->tmp_pool, + &pres->status.info[i].contact, + &status->info[i].contact); + + /* Duplicate */ + pres->status.info[i].rpid.activity = + status->info[i].rpid.activity; + pj_strdup(pres->tmp_pool, + &pres->status.info[i].rpid.id, + &status->info[i].rpid.id); + pj_strdup(pres->tmp_pool, + &pres->status.info[i].rpid.note, + &status->info[i].rpid.note); + + } + + pres->status.info_cnt = status->info_cnt; + + /* Swap pools */ + tmp = pres->tmp_pool; + pres->tmp_pool = pres->status_pool; + pres->status_pool = tmp; + pj_pool_reset(pres->tmp_pool); + + return PJ_SUCCESS; +} + + +/* + * Create message body. + */ +static pj_status_t pres_create_msg_body( pjsip_pres *pres, + pjsip_tx_data *tdata) +{ + pj_str_t entity; + + /* Get publisher URI */ + entity.ptr = (char*) pj_pool_alloc(tdata->pool, PJSIP_MAX_URL_SIZE); + entity.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + pres->dlg->local.info->uri, + entity.ptr, PJSIP_MAX_URL_SIZE); + if (entity.slen < 1) + return PJ_ENOMEM; + + if (pres->content_type == CONTENT_TYPE_PIDF) { + + return pjsip_pres_create_pidf(tdata->pool, &pres->status, + &entity, &tdata->msg->body); + + } else if (pres->content_type == CONTENT_TYPE_XPIDF) { + + return pjsip_pres_create_xpidf(tdata->pool, &pres->status, + &entity, &tdata->msg->body); + + } else { + return PJSIP_SIMPLE_EBADCONTENT; + } +} + + +/* + * Create NOTIFY + */ +PJ_DEF(pj_status_t) pjsip_pres_notify( pjsip_evsub *sub, + pjsip_evsub_state state, + const pj_str_t *state_str, + const pj_str_t *reason, + pjsip_tx_data **p_tdata) +{ + pjsip_pres *pres; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub, PJ_EINVAL); + + /* Get the presence object. */ + pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE); + + /* Must have at least one presence info, unless state is + * PJSIP_EVSUB_STATE_TERMINATED. This could happen if subscription + * has not been active (e.g. we're waiting for user authorization) + * and remote cancels the subscription. + */ + PJ_ASSERT_RETURN(state==PJSIP_EVSUB_STATE_TERMINATED || + pres->status.info_cnt > 0, PJSIP_SIMPLE_ENOPRESENCEINFO); + + + /* Lock object. */ + pjsip_dlg_inc_lock(pres->dlg); + + /* Create the NOTIFY request. */ + status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Create message body to reflect the presence status. + * Only do this if we have presence status info to send (see above). + */ + if (pres->status.info_cnt > 0) { + status = pres_create_msg_body( pres, tdata ); + if (status != PJ_SUCCESS) + goto on_return; + } + + /* Done. */ + *p_tdata = tdata; + + +on_return: + pjsip_dlg_dec_lock(pres->dlg); + return status; +} + + +/* + * Create NOTIFY that reflect current state. + */ +PJ_DEF(pj_status_t) pjsip_pres_current_notify( pjsip_evsub *sub, + pjsip_tx_data **p_tdata ) +{ + pjsip_pres *pres; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub, PJ_EINVAL); + + /* Get the presence object. */ + pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE); + + /* We may not have a presence info yet, e.g. when we receive SUBSCRIBE + * to refresh subscription while we're waiting for user authorization. + */ + //PJ_ASSERT_RETURN(pres->status.info_cnt > 0, + // PJSIP_SIMPLE_ENOPRESENCEINFO); + + + /* Lock object. */ + pjsip_dlg_inc_lock(pres->dlg); + + /* Create the NOTIFY request. */ + status = pjsip_evsub_current_notify( sub, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Create message body to reflect the presence status. */ + if (pres->status.info_cnt > 0) { + status = pres_create_msg_body( pres, tdata ); + if (status != PJ_SUCCESS) + goto on_return; + } + + /* Done. */ + *p_tdata = tdata; + + +on_return: + pjsip_dlg_dec_lock(pres->dlg); + return status; +} + + +/* + * Send request. + */ +PJ_DEF(pj_status_t) pjsip_pres_send_request( pjsip_evsub *sub, + pjsip_tx_data *tdata ) +{ + return pjsip_evsub_send_request(sub, tdata); +} + + +/* + * This callback is called by event subscription when subscription + * state has changed. + */ +static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsip_pres *pres; + + pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); + + if (pres->user_cb.on_evsub_state) + (*pres->user_cb.on_evsub_state)(sub, event); + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + if (pres->status_pool) { + pj_pool_release(pres->status_pool); + pres->status_pool = NULL; + } + if (pres->tmp_pool) { + pj_pool_release(pres->tmp_pool); + pres->tmp_pool = NULL; + } + } +} + +/* + * Called when transaction state has changed. + */ +static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event) +{ + pjsip_pres *pres; + + pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); + + if (pres->user_cb.on_tsx_state) + (*pres->user_cb.on_tsx_state)(sub, tsx, event); +} + + +/* + * Called when SUBSCRIBE is received. + */ +static void pres_on_evsub_rx_refresh( 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) +{ + pjsip_pres *pres; + + pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); + + if (pres->user_cb.on_rx_refresh) { + (*pres->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text, + res_hdr, p_body); + + } else { + /* Implementors MUST send NOTIFY if it implements on_rx_refresh */ + pjsip_tx_data *tdata; + pj_str_t timeout = { "timeout", 7}; + pj_status_t status; + + if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) { + status = pjsip_pres_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, &timeout, &tdata); + } else { + status = pjsip_pres_current_notify(sub, &tdata); + } + + if (status == PJ_SUCCESS) + pjsip_pres_send_request(sub, tdata); + } +} + + +/* + * Process the content of incoming NOTIFY request and update temporary + * status. + * + * return PJ_SUCCESS if incoming request is acceptable. If return value + * is not PJ_SUCCESS, res_hdr may be added with Warning header. + */ +static pj_status_t pres_process_rx_notify( pjsip_pres *pres, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr) +{ + const pj_str_t STR_MULTIPART = { "multipart", 9 }; + pjsip_ctype_hdr *ctype_hdr; + pj_status_t status = PJ_SUCCESS; + + *p_st_text = NULL; + + /* Check Content-Type and msg body are present. */ + ctype_hdr = rdata->msg_info.ctype; + + if (ctype_hdr==NULL || rdata->msg_info.msg->body==NULL) { + + pjsip_warning_hdr *warn_hdr; + pj_str_t warn_text; + + *p_st_code = PJSIP_SC_BAD_REQUEST; + + warn_text = pj_str("Message body is not present"); + warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399, + pjsip_endpt_name(pres->dlg->endpt), + &warn_text); + pj_list_push_back(res_hdr, warn_hdr); + + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST); + } + + /* Parse content. */ + if (pj_stricmp(&ctype_hdr->media.type, &STR_MULTIPART)==0) { + pjsip_multipart_part *mpart; + pjsip_media_type ctype; + + pjsip_media_type_init(&ctype, (pj_str_t*)&STR_APPLICATION, + (pj_str_t*)&STR_PIDF_XML); + mpart = pjsip_multipart_find_part(rdata->msg_info.msg->body, + &ctype, NULL); + if (mpart) { + status = pjsip_pres_parse_pidf2((char*)mpart->body->data, + mpart->body->len, pres->tmp_pool, + &pres->tmp_status); + } + + if (mpart==NULL) { + pjsip_media_type_init(&ctype, (pj_str_t*)&STR_APPLICATION, + (pj_str_t*)&STR_XPIDF_XML); + mpart = pjsip_multipart_find_part(rdata->msg_info.msg->body, + &ctype, NULL); + if (mpart) { + status = pjsip_pres_parse_xpidf2((char*)mpart->body->data, + mpart->body->len, + pres->tmp_pool, + &pres->tmp_status); + } else { + status = PJSIP_SIMPLE_EBADCONTENT; + } + } + } + else + if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 && + pj_stricmp(&ctype_hdr->media.subtype, &STR_PIDF_XML)==0) + { + status = pjsip_pres_parse_pidf( rdata, pres->tmp_pool, + &pres->tmp_status); + } + else + if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 && + pj_stricmp(&ctype_hdr->media.subtype, &STR_XPIDF_XML)==0) + { + status = pjsip_pres_parse_xpidf( rdata, pres->tmp_pool, + &pres->tmp_status); + } + else + { + status = PJSIP_SIMPLE_EBADCONTENT; + } + + if (status != PJ_SUCCESS) { + /* Unsupported or bad Content-Type */ + if (PJSIP_PRES_BAD_CONTENT_RESPONSE >= 300) { + pjsip_accept_hdr *accept_hdr; + pjsip_warning_hdr *warn_hdr; + + *p_st_code = PJSIP_PRES_BAD_CONTENT_RESPONSE; + + /* Add Accept header */ + accept_hdr = pjsip_accept_hdr_create(rdata->tp_info.pool); + accept_hdr->values[accept_hdr->count++] = STR_APP_PIDF_XML; + accept_hdr->values[accept_hdr->count++] = STR_APP_XPIDF_XML; + pj_list_push_back(res_hdr, accept_hdr); + + /* Add Warning header */ + warn_hdr = pjsip_warning_hdr_create_from_status( + rdata->tp_info.pool, + pjsip_endpt_name(pres->dlg->endpt), + status); + pj_list_push_back(res_hdr, warn_hdr); + + return status; + } else { + pj_assert(PJSIP_PRES_BAD_CONTENT_RESPONSE/100 == 2); + PJ_PERROR(4,(THIS_FILE, status, + "Ignoring presence error due to " + "PJSIP_PRES_BAD_CONTENT_RESPONSE setting [%d]", + PJSIP_PRES_BAD_CONTENT_RESPONSE)); + *p_st_code = PJSIP_PRES_BAD_CONTENT_RESPONSE; + status = PJ_SUCCESS; + } + } + + /* If application calls pres_get_status(), redirect the call to + * retrieve the temporary status. + */ + pres->tmp_status._is_valid = PJ_TRUE; + + return PJ_SUCCESS; +} + + +/* + * Called when NOTIFY is received. + */ +static void pres_on_evsub_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) +{ + pjsip_pres *pres; + pj_status_t status; + + pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); + + if (rdata->msg_info.msg->body) { + status = pres_process_rx_notify( pres, rdata, p_st_code, p_st_text, + res_hdr ); + if (status != PJ_SUCCESS) + return; + + } else { +#if 1 + /* This is the newest change, http://trac.pjsip.org/repos/ticket/873 + * Some app want to be notified about the empty NOTIFY, e.g. to + * decide whether it should consider the buddy as offline. + * In this case, leave the buddy state unchanged, but set the + * "tuple_node" in pjsip_pres_status to NULL. + */ + unsigned i; + for (i=0; istatus.info_cnt; ++i) { + pres->status.info[i].tuple_node = NULL; + } + +#elif 0 + /* This has just been changed. Previously, we treat incoming NOTIFY + * with no message body as having the presence subscription closed. + * Now we treat it as no change in presence status (ref: EyeBeam). + */ + *p_st_code = 200; + return; +#else + unsigned i; + /* Subscription is terminated. Consider contact is offline */ + pres->tmp_status._is_valid = PJ_TRUE; + for (i=0; itmp_status.info_cnt; ++i) + pres->tmp_status.info[i].basic_open = PJ_FALSE; +#endif + } + + /* Notify application. */ + if (pres->user_cb.on_rx_notify) { + (*pres->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text, + res_hdr, p_body); + } + + + /* If application responded NOTIFY with 2xx, copy temporary status + * to main status, and mark the temporary status as invalid. + */ + if ((*p_st_code)/100 == 2) { + pj_pool_t *tmp; + + pj_memcpy(&pres->status, &pres->tmp_status, sizeof(pjsip_pres_status)); + + /* Swap the pool */ + tmp = pres->tmp_pool; + pres->tmp_pool = pres->status_pool; + pres->status_pool = tmp; + } + + pres->tmp_status._is_valid = PJ_FALSE; + pj_pool_reset(pres->tmp_pool); + + /* Done */ +} + +/* + * Called when it's time to send SUBSCRIBE. + */ +static void pres_on_evsub_client_refresh(pjsip_evsub *sub) +{ + pjsip_pres *pres; + + pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); + + if (pres->user_cb.on_client_refresh) { + (*pres->user_cb.on_client_refresh)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_pres_initiate(sub, -1, &tdata); + if (status == PJ_SUCCESS) + pjsip_pres_send_request(sub, tdata); + } +} + +/* + * Called when no refresh is received after the interval. + */ +static void pres_on_evsub_server_timeout(pjsip_evsub *sub) +{ + pjsip_pres *pres; + + pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id); + PJ_ASSERT_ON_FAIL(pres!=NULL, {return;}); + + if (pres->user_cb.on_server_timeout) { + (*pres->user_cb.on_server_timeout)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + pj_str_t reason = { "timeout", 7 }; + + status = pjsip_pres_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + NULL, &reason, &tdata); + if (status == PJ_SUCCESS) + pjsip_pres_send_request(sub, tdata); + } +} + diff --git a/pjsip/src/pjsip-simple/presence_body.c b/pjsip/src/pjsip-simple/presence_body.c new file mode 100644 index 0000000..e692f47 --- /dev/null +++ b/pjsip/src/pjsip-simple/presence_body.c @@ -0,0 +1,288 @@ +/* $Id: presence_body.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 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 + + +#define THIS_FILE "presence_body.c" + + +static const pj_str_t STR_APPLICATION = { "application", 11 }; +static const pj_str_t STR_PIDF_XML = { "pidf+xml", 8 }; +static const pj_str_t STR_XPIDF_XML = { "xpidf+xml", 9 }; + + + + +/* + * Function to print XML message body. + */ +static int pres_print_body(struct pjsip_msg_body *msg_body, + char *buf, pj_size_t size) +{ + return pj_xml_print((const pj_xml_node*)msg_body->data, buf, size, + PJ_TRUE); +} + + +/* + * Function to clone XML document. + */ +static void* xml_clone_data(pj_pool_t *pool, const void *data, unsigned len) +{ + PJ_UNUSED_ARG(len); + return pj_xml_clone( pool, (const pj_xml_node*) data); +} + + +/* + * This is a utility function to create PIDF message body from PJSIP + * presence status (pjsip_pres_status). + */ +PJ_DEF(pj_status_t) pjsip_pres_create_pidf( pj_pool_t *pool, + const pjsip_pres_status *status, + const pj_str_t *entity, + pjsip_msg_body **p_body ) +{ + pjpidf_pres *pidf; + pjsip_msg_body *body; + unsigned i; + + /* Create . */ + pidf = pjpidf_create(pool, entity); + + /* Create */ + for (i=0; iinfo_cnt; ++i) { + + pjpidf_tuple *pidf_tuple; + pjpidf_status *pidf_status; + pj_str_t id; + + /* Add tuple id. */ + if (status->info[i].id.slen == 0) { + /* xs:ID must start with letter */ + //pj_create_unique_string(pool, &id); + id.ptr = (char*)pj_pool_alloc(pool, PJ_GUID_STRING_LENGTH+2); + id.ptr += 2; + pj_generate_unique_string(&id); + id.ptr -= 2; + id.ptr[0] = 'p'; + id.ptr[1] = 'j'; + id.slen += 2; + } else { + id = status->info[i].id; + } + + pidf_tuple = pjpidf_pres_add_tuple(pool, pidf, &id); + + /* Set */ + if (status->info[i].contact.slen) + pjpidf_tuple_set_contact(pool, pidf_tuple, + &status->info[i].contact); + + + /* Set basic status */ + pidf_status = pjpidf_tuple_get_status(pidf_tuple); + pjpidf_status_set_basic_open(pidf_status, + status->info[i].basic_open); + + /* Add if configured */ +#if defined(PJSIP_PRES_PIDF_ADD_TIMESTAMP) && PJSIP_PRES_PIDF_ADD_TIMESTAMP + if (PJSIP_PRES_PIDF_ADD_TIMESTAMP) { + char buf[50]; + int tslen = 0; + pj_time_val tv; + pj_parsed_time pt; + + pj_gettimeofday(&tv); + /* TODO: convert time to GMT! (unsupported by pjlib) */ + pj_time_decode( &tv, &pt); + + tslen = pj_ansi_snprintf(buf, sizeof(buf), + "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", + pt.year, pt.mon+1, pt.day, + pt.hour, pt.min, pt.sec, pt.msec); + if (tslen > 0 && tslen < (int)sizeof(buf)) { + pj_str_t time = pj_str(buf); + pjpidf_tuple_set_timestamp(pool, pidf_tuple, &time); + } + } +#endif + } + + /* Create (RPID) */ + if (status->info_cnt) { + pjrpid_add_element(pidf, pool, 0, &status->info[0].rpid); + } + + body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + body->data = pidf; + body->content_type.type = STR_APPLICATION; + body->content_type.subtype = STR_PIDF_XML; + body->print_body = &pres_print_body; + body->clone_data = &xml_clone_data; + + *p_body = body; + + return PJ_SUCCESS; +} + + +/* + * This is a utility function to create X-PIDF message body from PJSIP + * presence status (pjsip_pres_status). + */ +PJ_DEF(pj_status_t) pjsip_pres_create_xpidf( pj_pool_t *pool, + const pjsip_pres_status *status, + const pj_str_t *entity, + pjsip_msg_body **p_body ) +{ + /* Note: PJSIP implementation of XPIDF is not complete! + */ + pjxpidf_pres *xpidf; + pjsip_msg_body *body; + + PJ_LOG(4,(THIS_FILE, "Warning: XPIDF format is not fully supported " + "by PJSIP")); + + /* Create XPIDF document. */ + xpidf = pjxpidf_create(pool, entity); + + /* Set basic status. */ + if (status->info_cnt > 0) + pjxpidf_set_status( xpidf, status->info[0].basic_open); + else + pjxpidf_set_status( xpidf, PJ_FALSE); + + body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + body->data = xpidf; + body->content_type.type = STR_APPLICATION; + body->content_type.subtype = STR_XPIDF_XML; + body->print_body = &pres_print_body; + body->clone_data = &xml_clone_data; + + *p_body = body; + + return PJ_SUCCESS; +} + + + +/* + * This is a utility function to parse PIDF body into PJSIP presence status. + */ +PJ_DEF(pj_status_t) pjsip_pres_parse_pidf( pjsip_rx_data *rdata, + pj_pool_t *pool, + pjsip_pres_status *pres_status) +{ + return pjsip_pres_parse_pidf2((char*)rdata->msg_info.msg->body->data, + rdata->msg_info.msg->body->len, + pool, pres_status); +} + +PJ_DEF(pj_status_t) pjsip_pres_parse_pidf2(char *body, unsigned body_len, + pj_pool_t *pool, + pjsip_pres_status *pres_status) +{ + pjpidf_pres *pidf; + pjpidf_tuple *pidf_tuple; + + pidf = pjpidf_parse(pool, body, body_len); + if (pidf == NULL) + return PJSIP_SIMPLE_EBADPIDF; + + pres_status->info_cnt = 0; + + pidf_tuple = pjpidf_pres_get_first_tuple(pidf); + while (pidf_tuple && pres_status->info_cnt < PJSIP_PRES_STATUS_MAX_INFO) { + pjpidf_status *pidf_status; + + pres_status->info[pres_status->info_cnt].tuple_node = + pj_xml_clone(pool, pidf_tuple); + + pj_strdup(pool, + &pres_status->info[pres_status->info_cnt].id, + pjpidf_tuple_get_id(pidf_tuple)); + + pj_strdup(pool, + &pres_status->info[pres_status->info_cnt].contact, + pjpidf_tuple_get_contact(pidf_tuple)); + + pidf_status = pjpidf_tuple_get_status(pidf_tuple); + if (pidf_status) { + pres_status->info[pres_status->info_cnt].basic_open = + pjpidf_status_is_basic_open(pidf_status); + } else { + pres_status->info[pres_status->info_cnt].basic_open = PJ_FALSE; + } + + pidf_tuple = pjpidf_pres_get_next_tuple( pidf, pidf_tuple ); + pres_status->info_cnt++; + } + + /* Parse (RPID) */ + pjrpid_get_element(pidf, pool, &pres_status->info[0].rpid); + + return PJ_SUCCESS; +} + + +/* + * This is a utility function to parse X-PIDF body into PJSIP presence status. + */ +PJ_DEF(pj_status_t) pjsip_pres_parse_xpidf(pjsip_rx_data *rdata, + pj_pool_t *pool, + pjsip_pres_status *pres_status) +{ + return pjsip_pres_parse_xpidf2((char*)rdata->msg_info.msg->body->data, + rdata->msg_info.msg->body->len, + pool, pres_status); +} + +PJ_DEF(pj_status_t) pjsip_pres_parse_xpidf2(char *body, unsigned body_len, + pj_pool_t *pool, + pjsip_pres_status *pres_status) +{ + pjxpidf_pres *xpidf; + + xpidf = pjxpidf_parse(pool, body, body_len); + if (xpidf == NULL) + return PJSIP_SIMPLE_EBADXPIDF; + + pres_status->info_cnt = 1; + + pj_strdup(pool, + &pres_status->info[0].contact, + pjxpidf_get_uri(xpidf)); + pres_status->info[0].basic_open = pjxpidf_get_status(xpidf); + pres_status->info[0].id.slen = 0; + pres_status->info[0].tuple_node = NULL; + + return PJ_SUCCESS; +} + + diff --git a/pjsip/src/pjsip-simple/publishc.c b/pjsip/src/pjsip-simple/publishc.c new file mode 100644 index 0000000..efee6fb --- /dev/null +++ b/pjsip/src/pjsip-simple/publishc.c @@ -0,0 +1,790 @@ +/* $Id: publishc.c 4173 2012-06-20 10:39:05Z ming $ */ +/* + * Copyright (C) 2008-2011 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 PJSIP_PUBLISHC_DELAY_BEFORE_REFRESH +#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 } +}; + + +/** + * Pending request list. + */ +typedef struct pending_publish +{ + PJ_DECL_LIST_MEMBER(pjsip_tx_data); +} pending_publish; + + +/** + * SIP client publication structure. + */ +struct pjsip_publishc +{ + pj_pool_t *pool; + pjsip_endpoint *endpt; + pj_bool_t _delete_flag; + int pending_tsx; + pj_bool_t in_callback; + pj_mutex_t *mutex; + + pjsip_publishc_opt opt; + 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; + pjsip_hdr usr_hdr; + pjsip_host_port via_addr; + const void *via_tp; + + /* 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; + + /* Pending PUBLISH request */ + pending_publish pending_reqs; +}; + + +PJ_DEF(void) pjsip_publishc_opt_default(pjsip_publishc_opt *opt) +{ + pj_bzero(opt, sizeof(*opt)); + opt->queue_request = PJSIP_PUBLISHC_QUEUE_REQUEST; +} + + +/* + * 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, + const pjsip_publishc_opt *opt, + void *token, + pjsip_publishc_cb *cb, + pjsip_publishc **p_pubc) +{ + pj_pool_t *pool; + pjsip_publishc *pubc; + pjsip_publishc_opt default_opt; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(endpt && cb && p_pubc, PJ_EINVAL); + + 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; + + if (!opt) { + pjsip_publishc_opt_default(&default_opt); + opt = &default_opt; + } + pj_memcpy(&pubc->opt, opt, sizeof(*opt)); + pj_list_init(&pubc->pending_reqs); + + status = pj_mutex_create_recursive(pubc->pool, "pubc%p", &pubc->mutex); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + + status = pjsip_auth_clt_init(&pubc->auth_sess, endpt, pubc->pool, 0); + if (status != PJ_SUCCESS) { + pj_mutex_destroy(pubc->mutex); + pj_pool_release(pool); + return status; + } + + pj_list_init(&pubc->route_set); + pj_list_init(&pubc->usr_hdr); + + /* 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->in_callback) { + pubc->_delete_flag = 1; + pubc->cb = NULL; + } else { + /* Cancel existing timer, if any */ + if (pubc->timer.id != 0) { + pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer); + pubc->timer.id = 0; + } + + if (pubc->mutex) + pj_mutex_destroy(pubc->mutex); + 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 && + expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED) + { + 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; +} + +PJ_DEF(pj_status_t) pjsip_publishc_set_headers( pjsip_publishc *pubc, + const pjsip_hdr *hdr_list) +{ + const pjsip_hdr *h; + + PJ_ASSERT_RETURN(pubc && hdr_list, PJ_EINVAL); + + pj_list_init(&pubc->usr_hdr); + h = hdr_list->next; + while (h != hdr_list) { + pj_list_push_back(&pubc->usr_hdr, pjsip_hdr_clone(pubc->pool, h)); + h = h->next; + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsip_publishc_set_via_sent_by(pjsip_publishc *pubc, + pjsip_host_port *via_addr, + pjsip_transport *via_tp) +{ + PJ_ASSERT_RETURN(pubc, PJ_EINVAL); + + if (!via_addr) + pj_bzero(&pubc->via_addr, sizeof(pubc->via_addr)); + else + pubc->via_addr = *via_addr; + pubc->via_tp = via_tp; + + 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); + } + + /* Add user headers */ + if (!pj_list_empty(&pubc->usr_hdr)) { + const pjsip_hdr *hdr; + + hdr = pubc->usr_hdr.next; + while (hdr != &pubc->usr_hdr) { + pjsip_hdr *new_hdr = (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, hdr); + pjsip_msg_add_hdr(tdata->msg, new_hdr); + hdr = hdr->next; + } + } + + + /* 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; + + /* Mark that we're in callback to prevent deletion (#1164) */ + ++pubc->in_callback; + + /* 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 (pubc->auto_refresh && expires) + expiration = expires->ivalue; + + if (pubc->auto_refresh && expiration!=0 && expiration!=0xFFFF) { + pj_time_val delay = { 0, 0}; + + /* Cancel existing timer, if any */ + if (pubc->timer.id != 0) { + pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer); + pubc->timer.id = 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; + + /* If we have pending request(s), send them now */ + pj_mutex_lock(pubc->mutex); + while (!pj_list_empty(&pubc->pending_reqs)) { + pjsip_tx_data *tdata = pubc->pending_reqs.next; + pj_list_erase(tdata); + + /* Add SIP-If-Match if we have etag and the request doesn't have + * one (http://trac.pjsip.org/repos/ticket/996) + */ + if (pubc->etag.slen) { + const pj_str_t STR_HNAME = { "SIP-If-Match", 12 }; + pjsip_generic_string_hdr *sim_hdr; + + sim_hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(tdata->msg, &STR_HNAME, NULL); + if (!sim_hdr) { + /* Create the header */ + sim_hdr = pjsip_generic_string_hdr_create(tdata->pool, + &STR_HNAME, + &pubc->etag); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sim_hdr); + + } else { + /* Update */ + if (pj_strcmp(&pubc->etag, &sim_hdr->hvalue)) + pj_strdup(tdata->pool, &sim_hdr->hvalue, &pubc->etag); + } + } + + status = pjsip_publishc_send(pubc, tdata); + if (status == PJ_EPENDING) { + pj_assert(!"Not expected"); + pj_list_erase(tdata); + pjsip_tx_data_dec_ref(tdata); + } else if (status == PJ_SUCCESS) { + break; + } + } + pj_mutex_unlock(pubc->mutex); + } + + /* No longer in callback. */ + --pubc->in_callback; + + /* 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; + + PJ_ASSERT_RETURN(pubc && tdata, PJ_EINVAL); + + /* Make sure we don't have pending transaction. */ + pj_mutex_lock(pubc->mutex); + if (pubc->pending_tsx) { + if (pubc->opt.queue_request) { + pj_list_push_back(&pubc->pending_reqs, tdata); + pj_mutex_unlock(pubc->mutex); + PJ_LOG(4,(THIS_FILE, "Request is queued, pubc has another " + "transaction pending")); + return PJ_EPENDING; + } else { + pjsip_tx_data_dec_ref(tdata); + pj_mutex_unlock(pubc->mutex); + PJ_LOG(4,(THIS_FILE, "Unable to send request, pubc has another " + "transaction pending")); + return PJ_EBUSY; + } + } + pj_mutex_unlock(pubc->mutex); + + /* If via_addr is set, use this address for the Via header. */ + if (pubc->via_addr.host.slen > 0) { + tdata->via_addr = pubc->via_addr; + tdata->via_tp = pubc->via_tp; + } + + /* 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; +} + diff --git a/pjsip/src/pjsip-simple/rpid.c b/pjsip/src/pjsip-simple/rpid.c new file mode 100644 index 0000000..9ec3bb0 --- /dev/null +++ b/pjsip/src/pjsip-simple/rpid.c @@ -0,0 +1,279 @@ +/* $Id: rpid.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 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 + + +static const pj_str_t DM_NAME = {"xmlns:dm", 8}; +static const pj_str_t DM_VAL = {"urn:ietf:params:xml:ns:pidf:data-model", 38}; +static const pj_str_t RPID_NAME = {"xmlns:rpid", 10}; +static const pj_str_t RPID_VAL = {"urn:ietf:params:xml:ns:pidf:rpid", 32}; + +static const pj_str_t DM_NOTE = {"dm:note", 7}; +static const pj_str_t DM_PERSON = {"dm:person", 9}; +static const pj_str_t ID = {"id", 2}; +static const pj_str_t NOTE = {"note", 4}; +static const pj_str_t RPID_ACTIVITIES = {"rpid:activities", 15}; +static const pj_str_t RPID_AWAY = {"rpid:away", 9}; +static const pj_str_t RPID_BUSY = {"rpid:busy", 9}; +static const pj_str_t RPID_UNKNOWN = {"rpid:unknown", 12}; + + +/* Duplicate RPID element */ +PJ_DEF(void) pjrpid_element_dup(pj_pool_t *pool, pjrpid_element *dst, + const pjrpid_element *src) +{ + pj_memcpy(dst, src, sizeof(pjrpid_element)); + pj_strdup(pool, &dst->id, &src->id); + pj_strdup(pool, &dst->note, &src->note); +} + + +/* Update RPID namespaces. */ +static void update_namespaces(pjpidf_pres *pres, + pj_pool_t *pool) +{ + /* Check if namespace is already present. */ + if (pj_xml_find_attr(pres, &DM_NAME, NULL) != NULL) + return; + + pj_xml_add_attr(pres, pj_xml_attr_new(pool, &DM_NAME, &DM_VAL)); + pj_xml_add_attr(pres, pj_xml_attr_new(pool, &RPID_NAME, &RPID_VAL)); +} + + +/* Comparison function to find node name substring */ +static pj_bool_t substring_match(const pj_xml_node *node, + const char *part_name, + int part_len) +{ + pj_str_t end_name; + + if (part_len < 1) + part_len = pj_ansi_strlen(part_name); + + if (node->name.slen < part_len) + return PJ_FALSE; + + end_name.ptr = node->name.ptr + (node->name.slen - part_len); + end_name.slen = part_len; + + return pj_strnicmp2(&end_name, part_name, part_len)==0; +} + +/* Util to find child node with the specified substring */ +static pj_xml_node *find_node(const pj_xml_node *parent, + const char *part_name) +{ + const pj_xml_node *node = parent->node_head.next, + *head = (pj_xml_node*) &parent->node_head; + int part_len = pj_ansi_strlen(part_name); + + while (node != head) { + if (substring_match(node, part_name, part_len)) + return (pj_xml_node*) node; + + node = node->next; + } + + return NULL; +} + +/* + * Add RPID element into existing PIDF document. + */ +PJ_DEF(pj_status_t) pjrpid_add_element(pjpidf_pres *pres, + pj_pool_t *pool, + unsigned options, + const pjrpid_element *elem) +{ + pj_xml_node *nd_person, *nd_activities, *nd_activity, *nd_note; + pj_xml_attr *attr; + + PJ_ASSERT_RETURN(pres && pool && options==0 && elem, PJ_EINVAL); + + PJ_UNUSED_ARG(options); + + /* Check if we need to add RPID information into the PIDF document. */ + if (elem->id.slen==0 && + elem->activity==PJRPID_ACTIVITY_UNKNOWN && + elem->note.slen==0) + { + /* No RPID information to be added. */ + return PJ_SUCCESS; + } + + /* Add to */ + if (elem->note.slen != 0) { + pj_xml_node *nd_tuple; + + nd_tuple = find_node(pres, "tuple"); + + if (nd_tuple) { + nd_note = pj_xml_node_new(pool, &NOTE); + pj_strdup(pool, &nd_note->content, &elem->note); + pj_xml_add_node(nd_tuple, nd_note); + nd_note = NULL; + } + } + + /* Update namespace */ + update_namespaces(pres, pool); + + /* Add */ + nd_person = pj_xml_node_new(pool, &DM_PERSON); + if (elem->id.slen != 0) { + attr = pj_xml_attr_new(pool, &ID, &elem->id); + } else { + pj_str_t person_id; + /* xs:ID must start with letter */ + //pj_create_unique_string(pool, &person_id); + person_id.ptr = (char*)pj_pool_alloc(pool, PJ_GUID_STRING_LENGTH+2); + person_id.ptr += 2; + pj_generate_unique_string(&person_id); + person_id.ptr -= 2; + person_id.ptr[0] = 'p'; + person_id.ptr[1] = 'j'; + person_id.slen += 2; + + attr = pj_xml_attr_new(pool, &ID, &person_id); + } + pj_xml_add_attr(nd_person, attr); + pj_xml_add_node(pres, nd_person); + + /* Add */ + nd_activities = pj_xml_node_new(pool, &RPID_ACTIVITIES); + pj_xml_add_node(nd_person, nd_activities); + + /* Add the activity */ + switch (elem->activity) { + case PJRPID_ACTIVITY_AWAY: + nd_activity = pj_xml_node_new(pool, &RPID_AWAY); + break; + case PJRPID_ACTIVITY_BUSY: + nd_activity = pj_xml_node_new(pool, &RPID_BUSY); + break; + case PJRPID_ACTIVITY_UNKNOWN: + default: + nd_activity = pj_xml_node_new(pool, &RPID_UNKNOWN); + break; + } + pj_xml_add_node(nd_activities, nd_activity); + + /* Add custom text if required. */ + if (elem->note.slen != 0) { + nd_note = pj_xml_node_new(pool, &DM_NOTE); + pj_strdup(pool, &nd_note->content, &elem->note); + pj_xml_add_node(nd_person, nd_note); + } + + /* Done */ + return PJ_SUCCESS; +} + + +/* Get element from PIDF element */ +static pj_status_t get_tuple_note(const pjpidf_pres *pres, + pj_pool_t *pool, + pjrpid_element *elem) +{ + const pj_xml_node *nd_tuple, *nd_note; + + nd_tuple = find_node(pres, "tuple"); + if (!nd_tuple) + return PJSIP_SIMPLE_EBADRPID; + + nd_note = find_node(pres, "note"); + if (nd_note) { + pj_strdup(pool, &elem->note, &nd_note->content); + return PJ_SUCCESS; + } + + return PJSIP_SIMPLE_EBADRPID; +} + +/* + * Get RPID element from PIDF document, if any. + */ +PJ_DEF(pj_status_t) pjrpid_get_element(const pjpidf_pres *pres, + pj_pool_t *pool, + pjrpid_element *elem) +{ + const pj_xml_node *nd_person, *nd_activities, *nd_note = NULL; + const pj_xml_attr *attr; + + /* Reset */ + pj_bzero(elem, sizeof(*elem)); + elem->activity = PJRPID_ACTIVITY_UNKNOWN; + + /* Find */ + nd_person = find_node(pres, "person"); + if (!nd_person) { + /* not found, try to get from */ + return get_tuple_note(pres, pool, elem); + } + + /* Get element id attribute */ + attr = pj_xml_find_attr((pj_xml_node*)nd_person, &ID, NULL); + if (attr) + pj_strdup(pool, &elem->id, &attr->value); + + /* Get */ + nd_activities = find_node(nd_person, "activities"); + if (nd_activities) { + const pj_xml_node *nd_activity; + + /* Try to get from */ + nd_note = find_node(nd_activities, "note"); + + /* Get the activity */ + nd_activity = nd_activities->node_head.next; + if (nd_activity == nd_note) + nd_activity = nd_activity->next; + + if (nd_activity != (pj_xml_node*) &nd_activities->node_head) { + if (substring_match(nd_activity, "busy", -1)) + elem->activity = PJRPID_ACTIVITY_BUSY; + else if (substring_match(nd_activity, "away", -1)) + elem->activity = PJRPID_ACTIVITY_AWAY; + else + elem->activity = PJRPID_ACTIVITY_UNKNOWN; + + } + } + + /* If is not found, get from */ + if (nd_note == NULL) + nd_note = find_node(nd_person, "note"); + + if (nd_note) { + pj_strdup(pool, &elem->note, &nd_note->content); + } else { + get_tuple_note(pres, pool, elem); + } + + return PJ_SUCCESS; +} + + diff --git a/pjsip/src/pjsip-simple/xpidf.c b/pjsip/src/pjsip-simple/xpidf.c new file mode 100644 index 0000000..22801d7 --- /dev/null +++ b/pjsip/src/pjsip-simple/xpidf.c @@ -0,0 +1,301 @@ +/* $Id: xpidf.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 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 + +static pj_str_t STR_PRESENCE = { "presence", 8 }; +static pj_str_t STR_STATUS = { "status", 6 }; +static pj_str_t STR_OPEN = { "open", 4 }; +static pj_str_t STR_CLOSED = { "closed", 6 }; +static pj_str_t STR_URI = { "uri", 3 }; +static pj_str_t STR_ATOM = { "atom", 4 }; +static pj_str_t STR_ATOMID = { "atomid", 6 }; +static pj_str_t STR_ID = { "id", 2 }; +static pj_str_t STR_ADDRESS = { "address", 7 }; +static pj_str_t STR_SUBSCRIBE_PARAM = { ";method=SUBSCRIBE", 17 }; +static pj_str_t STR_PRESENTITY = { "presentity", 10 }; +static pj_str_t STR_EMPTY_STRING = { NULL, 0 }; + +static pj_xml_node* xml_create_node(pj_pool_t *pool, + pj_str_t *name, const pj_str_t *value) +{ + pj_xml_node *node; + + node = PJ_POOL_ALLOC_T(pool, pj_xml_node); + pj_list_init(&node->attr_head); + pj_list_init(&node->node_head); + node->name = *name; + if (value) pj_strdup(pool, &node->content, value); + else node->content.ptr=NULL, node->content.slen=0; + + return node; +} + +static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name, + const pj_str_t *value) +{ + pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr); + attr->name = *name; + pj_strdup(pool, &attr->value, value); + return attr; +} + + +PJ_DEF(pjxpidf_pres*) pjxpidf_create(pj_pool_t *pool, const pj_str_t *uri_cstr) +{ + pjxpidf_pres *pres; + pj_xml_node *presentity; + pj_xml_node *atom; + pj_xml_node *addr; + pj_xml_node *status; + pj_xml_attr *attr; + pj_str_t uri; + pj_str_t tmp; + + /* */ + pres = xml_create_node(pool, &STR_PRESENCE, NULL); + + /* */ + presentity = xml_create_node(pool, &STR_PRESENTITY, NULL); + pj_xml_add_node(pres, presentity); + + /* uri attribute */ + uri.ptr = (char*) pj_pool_alloc(pool, uri_cstr->slen + + STR_SUBSCRIBE_PARAM.slen); + pj_strcpy( &uri, uri_cstr); + pj_strcat( &uri, &STR_SUBSCRIBE_PARAM); + attr = xml_create_attr(pool, &STR_URI, &uri); + pj_xml_add_attr(presentity, attr); + + /* */ + atom = xml_create_node(pool, &STR_ATOM, NULL); + pj_xml_add_node(pres, atom); + + /* atom id */ + pj_create_unique_string(pool, &tmp); + attr = xml_create_attr(pool, &STR_ATOMID, &tmp); + pj_xml_add_attr(atom, attr); + + /* address */ + addr = xml_create_node(pool, &STR_ADDRESS, NULL); + pj_xml_add_node(atom, addr); + + /* address'es uri */ + attr = xml_create_attr(pool, &STR_URI, uri_cstr); + pj_xml_add_attr(addr, attr); + + /* status */ + status = xml_create_node(pool, &STR_STATUS, NULL); + pj_xml_add_node(addr, status); + + /* status attr */ + attr = xml_create_attr(pool, &STR_STATUS, &STR_OPEN); + pj_xml_add_attr(status, attr); + + return pres; +} + + + +PJ_DEF(pjxpidf_pres*) pjxpidf_parse(pj_pool_t *pool, char *text, pj_size_t len) +{ + pjxpidf_pres *pres; + pj_xml_node *node; + + pres = pj_xml_parse(pool, text, len); + if (!pres) + return NULL; + + /* Validate */ + if (pj_stricmp(&pres->name, &STR_PRESENCE) != 0) + return NULL; + + /* Validate */ + node = pj_xml_find_node(pres, &STR_PRESENTITY); + if (node == NULL) + return NULL; + if (pj_xml_find_attr(node, &STR_URI, NULL) == NULL) + return NULL; + + /* Validate */ + node = pj_xml_find_node(pres, &STR_ATOM); + if (node == NULL) + return NULL; + if (pj_xml_find_attr(node, &STR_ATOMID, NULL) == NULL && + pj_xml_find_attr(node, &STR_ID, NULL) == NULL) + { + return NULL; + } + + /* Address */ + node = pj_xml_find_node(node, &STR_ADDRESS); + if (node == NULL) + return NULL; + if (pj_xml_find_attr(node, &STR_URI, NULL) == NULL) + return NULL; + + + /* Status */ + node = pj_xml_find_node(node, &STR_STATUS); + if (node == NULL) + return NULL; + if (pj_xml_find_attr(node, &STR_STATUS, NULL) == NULL) + return NULL; + + return pres; +} + + +PJ_DEF(int) pjxpidf_print( pjxpidf_pres *pres, char *text, pj_size_t len) +{ + return pj_xml_print(pres, text, len, PJ_TRUE); +} + + +PJ_DEF(pj_str_t*) pjxpidf_get_uri(pjxpidf_pres *pres) +{ + pj_xml_node *presentity; + pj_xml_attr *attr; + + presentity = pj_xml_find_node(pres, &STR_PRESENTITY); + if (!presentity) + return &STR_EMPTY_STRING; + + attr = pj_xml_find_attr(presentity, &STR_URI, NULL); + if (!attr) + return &STR_EMPTY_STRING; + + return &attr->value; +} + + +PJ_DEF(pj_status_t) pjxpidf_set_uri(pj_pool_t *pool, pjxpidf_pres *pres, + const pj_str_t *uri) +{ + pj_xml_node *presentity; + pj_xml_node *atom; + pj_xml_node *addr; + pj_xml_attr *attr; + pj_str_t dup_uri; + + presentity = pj_xml_find_node(pres, &STR_PRESENTITY); + if (!presentity) { + pj_assert(0); + return -1; + } + atom = pj_xml_find_node(pres, &STR_ATOM); + if (!atom) { + pj_assert(0); + return -1; + } + addr = pj_xml_find_node(atom, &STR_ADDRESS); + if (!addr) { + pj_assert(0); + return -1; + } + + /* Set uri in presentity */ + attr = pj_xml_find_attr(presentity, &STR_URI, NULL); + if (!attr) { + pj_assert(0); + return -1; + } + pj_strdup(pool, &dup_uri, uri); + attr->value = dup_uri; + + /* Set uri in address. */ + attr = pj_xml_find_attr(addr, &STR_URI, NULL); + if (!attr) { + pj_assert(0); + return -1; + } + attr->value = dup_uri; + + return 0; +} + + +PJ_DEF(pj_bool_t) pjxpidf_get_status(pjxpidf_pres *pres) +{ + pj_xml_node *atom; + pj_xml_node *addr; + pj_xml_node *status; + pj_xml_attr *attr; + + atom = pj_xml_find_node(pres, &STR_ATOM); + if (!atom) { + pj_assert(0); + return PJ_FALSE; + } + addr = pj_xml_find_node(atom, &STR_ADDRESS); + if (!addr) { + pj_assert(0); + return PJ_FALSE; + } + status = pj_xml_find_node(addr, &STR_STATUS); + if (!status) { + pj_assert(0); + return PJ_FALSE; + } + attr = pj_xml_find_attr(status, &STR_STATUS, NULL); + if (!attr) { + pj_assert(0); + return PJ_FALSE; + } + + return pj_stricmp(&attr->value, &STR_OPEN)==0 ? PJ_TRUE : PJ_FALSE; +} + + +PJ_DEF(pj_status_t) pjxpidf_set_status(pjxpidf_pres *pres, pj_bool_t online_status) +{ + pj_xml_node *atom; + pj_xml_node *addr; + pj_xml_node *status; + pj_xml_attr *attr; + + atom = pj_xml_find_node(pres, &STR_ATOM); + if (!atom) { + pj_assert(0); + return -1; + } + addr = pj_xml_find_node(atom, &STR_ADDRESS); + if (!addr) { + pj_assert(0); + return -1; + } + status = pj_xml_find_node(addr, &STR_STATUS); + if (!status) { + pj_assert(0); + return -1; + } + attr = pj_xml_find_attr(status, &STR_STATUS, NULL); + if (!attr) { + pj_assert(0); + return -1; + } + + attr->value = ( online_status ? STR_OPEN : STR_CLOSED ); + return 0; +} + -- cgit v1.2.3