diff options
Diffstat (limited to 'pjsip/src')
82 files changed, 75487 insertions, 0 deletions
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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/errno.h> +#include <pj/string.h> + +/* 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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/evsub.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip-simple/errno.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_dialog.h> +#include <pjsip/sip_auth.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_event.h> +#include <pj/assert.h> +#include <pj/guid.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/string.h> + + +#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; i<accept_cnt; ++i) { + pj_strdup(mod_evsub.pool, &pkg->pkg_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_id<TIMER_TYPE_MAX, + return); + + timeout.sec = seconds; + timeout.msec = 0; + sub->timer.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/evsub_msg.h> +#include <pjsip/print_util.h> +#include <pjsip/sip_parser.h> +#include <pjlib-util/string.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/except.h> + +/* + * 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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/iscomposing.h> +#include <pjsip-simple/errno.h> +#include <pjsip/sip_msg.h> +#include <pjlib-util/errno.h> +#include <pj/pool.h> +#include <pj/string.h> + + +/* 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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/mwi.h> +#include <pjsip-simple/errno.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_dialog.h> +#include <pj/assert.h> +#include <pj/guid.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/string.h> + + +#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; i<accept->count; ++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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/pidf.h> +#include <pj/string.h> +#include <pj/pool.h> +#include <pj/assert.h> + + +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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/presence.h> +#include <pjsip-simple/errno.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_multipart.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_dialog.h> +#include <pj/assert.h> +#include <pj/guid.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/string.h> + + +#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; i<accept->count; ++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; i<status->info_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 <person> */ + 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; i<pres->status.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; i<pres->tmp_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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/presence.h> +#include <pjsip-simple/errno.h> +#include <pjsip/sip_msg.h> +#include <pjsip/sip_transport.h> +#include <pj/guid.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/string.h> + + +#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 <presence>. */ + pidf = pjpidf_create(pool, entity); + + /* Create <tuple> */ + for (i=0; i<status->info_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 <contact> */ + 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 <timestamp> 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 <person> (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 <person> (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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/publish.h> +#include <pjsip/sip_auth.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_msg.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_uri.h> +#include <pjsip/sip_util.h> +#include <pj/assert.h> +#include <pj/guid.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/rand.h> +#include <pj/string.h> +#include <pj/timer.h> + + +#define REFRESH_TIMER 1 +#define DELAY_BEFORE_REFRESH 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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/rpid.h> +#include <pjsip-simple/errno.h> +#include <pj/assert.h> +#include <pj/guid.h> +#include <pj/pool.h> +#include <pj/string.h> + + +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 <note> to <tuple> */ + 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 <person> */ + 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 <activities> */ + 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 <note> element from PIDF <tuple> 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 <person> */ + nd_person = find_node(pres, "person"); + if (!nd_person) { + /* <person> not found, try to get <note> from <tuple> */ + 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 <activities> */ + nd_activities = find_node(nd_person, "activities"); + if (nd_activities) { + const pj_xml_node *nd_activity; + + /* Try to get <note> from <activities> */ + 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 <note> is not found, get <note> from <person> */ + 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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-simple/xpidf.h> +#include <pj/assert.h> +#include <pj/guid.h> +#include <pj/pool.h> +#include <pj/string.h> + +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; + + /* <presence> */ + pres = xml_create_node(pool, &STR_PRESENCE, NULL); + + /* <presentity> */ + 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> */ + 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 <presence> */ + if (pj_stricmp(&pres->name, &STR_PRESENCE) != 0) + return NULL; + + /* Validate <presentity> */ + 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 <atom> */ + 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; +} + diff --git a/pjsip/src/pjsip-ua/sip_100rel.c b/pjsip/src/pjsip-ua/sip_100rel.c new file mode 100644 index 0000000..07122c4 --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_100rel.c @@ -0,0 +1,905 @@ +/* $Id: sip_100rel.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-ua/sip_100rel.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_transaction.h> +#include <pj/assert.h> +#include <pj/ctype.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/rand.h> +#include <pj/string.h> + +#define THIS_FILE "sip_100rel.c" + +/* PRACK method */ +PJ_DEF_DATA(const pjsip_method) pjsip_prack_method = +{ + PJSIP_OTHER_METHOD, + { "PRACK", 5 } +}; + +typedef struct dlg_data dlg_data; + +/* + * Static prototypes. + */ +static pj_status_t mod_100rel_load(pjsip_endpoint *endpt); + +static void on_retransmit(pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry); + + +const pj_str_t tag_100rel = { "100rel", 6 }; +const pj_str_t RSEQ = { "RSeq", 4 }; +const pj_str_t RACK = { "RAck", 4 }; + + +/* 100rel module */ +static struct mod_100rel +{ + pjsip_module mod; + pjsip_endpoint *endpt; +} mod_100rel = +{ + { + NULL, NULL, /* prev, next. */ + { "mod-100rel", 10 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */ + &mod_100rel_load, /* 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() */ + } + +}; + +/* List of pending transmission (may include the final response as well) */ +typedef struct tx_data_list_t +{ + PJ_DECL_LIST_MEMBER(struct tx_data_list_t); + pj_uint32_t rseq; + pjsip_tx_data *tdata; +} tx_data_list_t; + + +/* Below, UAS and UAC roles are of the INVITE transaction */ + +/* UAS state. */ +typedef struct uas_state_t +{ + pj_int32_t cseq; + pj_uint32_t rseq; /* Initialized to -1 */ + tx_data_list_t tx_data_list; + unsigned retransmit_count; + pj_timer_entry retransmit_timer; +} uas_state_t; + + +/* UAC state */ +typedef struct uac_state_t +{ + pj_str_t tag; /* To tag */ + pj_int32_t cseq; + pj_uint32_t rseq; /* Initialized to -1 */ + struct uac_state_t *next; /* next call leg */ +} uac_state_t; + + +/* State attached to each dialog. */ +struct dlg_data +{ + pjsip_inv_session *inv; + uas_state_t *uas_state; + uac_state_t *uac_state_list; +}; + + +/***************************************************************************** + ** + ** Module + ** + ***************************************************************************** + */ +static pj_status_t mod_100rel_load(pjsip_endpoint *endpt) +{ + mod_100rel.endpt = endpt; + pjsip_endpt_add_capability(endpt, &mod_100rel.mod, + PJSIP_H_ALLOW, NULL, + 1, &pjsip_prack_method.name); + pjsip_endpt_add_capability(endpt, &mod_100rel.mod, + PJSIP_H_SUPPORTED, NULL, + 1, &tag_100rel); + + return PJ_SUCCESS; +} + +static pjsip_require_hdr *find_req_hdr(pjsip_msg *msg) +{ + pjsip_require_hdr *hreq; + + hreq = (pjsip_require_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL); + + while (hreq) { + unsigned i; + for (i=0; i<hreq->count; ++i) { + if (!pj_stricmp(&hreq->values[i], &tag_100rel)) { + return hreq; + } + } + + if ((void*)hreq->next == (void*)&msg->hdr) + return NULL; + + hreq = (pjsip_require_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, hreq->next); + + } + + return NULL; +} + + +/* + * Get PRACK method constant. + */ +PJ_DEF(const pjsip_method*) pjsip_get_prack_method(void) +{ + return &pjsip_prack_method; +} + + +/* + * init module + */ +PJ_DEF(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt) +{ + if (mod_100rel.mod.id != -1) + return PJ_SUCCESS; + + return pjsip_endpt_register_module(endpt, &mod_100rel.mod); +} + + +/* + * API: attach 100rel support in invite session. Called by + * sip_inv.c + */ +PJ_DEF(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv) +{ + dlg_data *dd; + + /* Check that 100rel module has been initialized */ + PJ_ASSERT_RETURN(mod_100rel.mod.id >= 0, PJ_EINVALIDOP); + + /* Create and attach as dialog usage */ + dd = PJ_POOL_ZALLOC_T(inv->dlg->pool, dlg_data); + dd->inv = inv; + pjsip_dlg_add_usage(inv->dlg, &mod_100rel.mod, (void*)dd); + + PJ_LOG(5,(dd->inv->dlg->obj_name, "100rel module attached")); + + return PJ_SUCCESS; +} + + +/* + * Check if incoming response has reliable provisional response feature. + */ +PJ_DEF(pj_bool_t) pjsip_100rel_is_reliable(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + + PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG, PJ_FALSE); + + return msg->line.status.code > 100 && msg->line.status.code < 200 && + rdata->msg_info.require != NULL && + find_req_hdr(msg) != NULL; +} + + +/* + * Create PRACK request for the incoming reliable provisional response. + */ +PJ_DEF(pj_status_t) pjsip_100rel_create_prack( pjsip_inv_session *inv, + pjsip_rx_data *rdata, + pjsip_tx_data **p_tdata) +{ + dlg_data *dd; + uac_state_t *uac_state = NULL; + const pj_str_t *to_tag = &rdata->msg_info.to->tag; + pjsip_transaction *tsx; + pjsip_msg *msg; + pjsip_generic_string_hdr *rseq_hdr; + pjsip_generic_string_hdr *rack_hdr; + unsigned rseq; + pj_str_t rack; + char rack_buf[80]; + pjsip_tx_data *tdata; + pj_status_t status; + + *p_tdata = NULL; + + dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; + PJ_ASSERT_RETURN(dd != NULL, PJSIP_ENOTINITIALIZED); + + tsx = pjsip_rdata_get_tsx(rdata); + msg = rdata->msg_info.msg; + + /* Check our assumptions */ + pj_assert( tsx->role == PJSIP_ROLE_UAC && + tsx->method.id == PJSIP_INVITE_METHOD && + msg->line.status.code > 100 && + msg->line.status.code < 200); + + + /* Get the RSeq header */ + rseq_hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(msg, &RSEQ, NULL); + if (rseq_hdr == NULL) { + PJ_LOG(4,(dd->inv->dlg->obj_name, + "Ignoring 100rel response with no RSeq header")); + return PJSIP_EMISSINGHDR; + } + rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue); + + /* Find UAC state for the specified call leg */ + uac_state = dd->uac_state_list; + while (uac_state) { + if (pj_strcmp(&uac_state->tag, to_tag)==0) + break; + uac_state = uac_state->next; + } + + /* Create new UAC state if we don't have one */ + if (uac_state == NULL) { + uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool, uac_state_t); + uac_state->cseq = rdata->msg_info.cseq->cseq; + uac_state->rseq = rseq - 1; + pj_strdup(dd->inv->dlg->pool, &uac_state->tag, to_tag); + uac_state->next = dd->uac_state_list; + dd->uac_state_list = uac_state; + } + + /* If this is from new INVITE transaction, reset UAC state. */ + if (rdata->msg_info.cseq->cseq != uac_state->cseq) { + uac_state->cseq = rdata->msg_info.cseq->cseq; + uac_state->rseq = rseq - 1; + } + + /* Ignore provisional response retransmission */ + if (rseq <= uac_state->rseq) { + /* This should have been handled before */ + return PJ_EIGNORED; + + /* Ignore provisional response with out-of-order RSeq */ + } else if (rseq != uac_state->rseq + 1) { + PJ_LOG(4,(dd->inv->dlg->obj_name, + "Ignoring 100rel response because RSeq jump " + "(expecting %u, got %u)", + uac_state->rseq+1, rseq)); + return PJ_EIGNORED; + } + + /* Update our RSeq */ + uac_state->rseq = rseq; + + /* Create PRACK */ + status = pjsip_dlg_create_request(dd->inv->dlg, &pjsip_prack_method, + -1, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* If this response is a forked response from a different call-leg, + * update the req URI (https://trac.pjsip.org/repos/ticket/1364) + */ + if (pj_strcmp(&uac_state->tag, &dd->inv->dlg->remote.info->tag)) { + const pjsip_contact_hdr *mhdr; + + mhdr = (const pjsip_contact_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, + PJSIP_H_CONTACT, NULL); + if (!mhdr || !mhdr->uri) { + PJ_LOG(4,(dd->inv->dlg->obj_name, + "Ignoring 100rel response with no or " + "invalid Contact header")); + pjsip_tx_data_dec_ref(tdata); + return PJ_EIGNORED; + } + tdata->msg->line.req.uri = (pjsip_uri*) + pjsip_uri_clone(tdata->pool, mhdr->uri); + } + + /* Create RAck header */ + rack.ptr = rack_buf; + rack.slen = pj_ansi_snprintf(rack.ptr, sizeof(rack_buf), + "%u %u %.*s", + rseq, rdata->msg_info.cseq->cseq, + (int)tsx->method.name.slen, + tsx->method.name.ptr); + rack_hdr = pjsip_generic_string_hdr_create(tdata->pool, &RACK, &rack); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) rack_hdr); + + /* Done */ + *p_tdata = tdata; + + return PJ_SUCCESS; +} + + +/* + * Send PRACK request. + */ +PJ_DEF(pj_status_t) pjsip_100rel_send_prack( pjsip_inv_session *inv, + pjsip_tx_data *tdata) +{ + dlg_data *dd; + + dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; + PJ_ASSERT_ON_FAIL(dd != NULL, + {pjsip_tx_data_dec_ref(tdata); return PJSIP_ENOTINITIALIZED; }); + + return pjsip_dlg_send_request(inv->dlg, tdata, + mod_100rel.mod.id, (void*) dd); + +} + + +/* + * Notify 100rel module that the invite session has been disconnected. + */ +PJ_DEF(pj_status_t) pjsip_100rel_end_session(pjsip_inv_session *inv) +{ + dlg_data *dd; + + dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; + if (!dd) + return PJ_SUCCESS; + + /* Make sure we don't have pending transmission */ + if (dd->uas_state) { + pj_assert(!dd->uas_state->retransmit_timer.id); + pj_assert(pj_list_empty(&dd->uas_state->tx_data_list)); + } + + return PJ_SUCCESS; +} + + +static void parse_rack(const pj_str_t *rack, + pj_uint32_t *p_rseq, pj_int32_t *p_seq, + pj_str_t *p_method) +{ + const char *p = rack->ptr, *end = p + rack->slen; + pj_str_t token; + + token.ptr = (char*)p; + while (p < end && pj_isdigit(*p)) + ++p; + token.slen = p - token.ptr; + *p_rseq = pj_strtoul(&token); + + ++p; + token.ptr = (char*)p; + while (p < end && pj_isdigit(*p)) + ++p; + token.slen = p - token.ptr; + *p_seq = pj_strtoul(&token); + + ++p; + if (p < end) { + p_method->ptr = (char*)p; + p_method->slen = end - p; + } else { + p_method->ptr = NULL; + p_method->slen = 0; + } +} + +/* Clear all responses in the transmission list */ +static void clear_all_responses(dlg_data *dd) +{ + tx_data_list_t *tl; + + tl = dd->uas_state->tx_data_list.next; + while (tl != &dd->uas_state->tx_data_list) { + pjsip_tx_data_dec_ref(tl->tdata); + tl = tl->next; + } + pj_list_init(&dd->uas_state->tx_data_list); +} + + +/* + * Handle incoming PRACK request. + */ +PJ_DEF(pj_status_t) pjsip_100rel_on_rx_prack( pjsip_inv_session *inv, + pjsip_rx_data *rdata) +{ + dlg_data *dd; + pjsip_transaction *tsx; + pjsip_msg *msg; + pjsip_generic_string_hdr *rack_hdr; + pjsip_tx_data *tdata; + pj_uint32_t rseq; + pj_int32_t cseq; + pj_str_t method; + pj_status_t status; + + tsx = pjsip_rdata_get_tsx(rdata); + pj_assert(tsx != NULL); + + msg = rdata->msg_info.msg; + + dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; + if (dd == NULL) { + /* UAC sends us PRACK while we didn't send reliable provisional + * response. Respond with 400 (?) + */ + const pj_str_t reason = pj_str("Unexpected PRACK"); + + status = pjsip_dlg_create_response(inv->dlg, rdata, 400, + &reason, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(inv->dlg, tsx, tdata); + } + return PJSIP_ENOTINITIALIZED; + } + + /* Always reply with 200/OK for PRACK */ + status = pjsip_dlg_create_response(inv->dlg, rdata, 200, NULL, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(inv->dlg, tsx, tdata); + } + + /* Ignore if we don't have pending transmission */ + if (dd->uas_state == NULL || pj_list_empty(&dd->uas_state->tx_data_list)) { + PJ_LOG(4,(dd->inv->dlg->obj_name, + "PRACK ignored - no pending response")); + return PJ_EIGNORED; + } + + /* Find RAck header */ + rack_hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(msg, &RACK, NULL); + if (!rack_hdr) { + /* RAck header not found */ + PJ_LOG(4,(dd->inv->dlg->obj_name, "No RAck header")); + return PJSIP_EMISSINGHDR; + } + + /* Parse RAck header */ + parse_rack(&rack_hdr->hvalue, &rseq, &cseq, &method); + + + /* Match RAck against outgoing transmission */ + if (rseq == dd->uas_state->tx_data_list.next->rseq && + cseq == dd->uas_state->cseq) + { + /* + * Yes this PRACK matches outgoing transmission. + */ + tx_data_list_t *tl = dd->uas_state->tx_data_list.next; + + if (dd->uas_state->retransmit_timer.id) { + pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, + &dd->uas_state->retransmit_timer); + dd->uas_state->retransmit_timer.id = PJ_FALSE; + } + + /* Remove from the list */ + if (tl != &dd->uas_state->tx_data_list) { + pj_list_erase(tl); + + /* Destroy the response */ + pjsip_tx_data_dec_ref(tl->tdata); + } + + /* Schedule next packet */ + dd->uas_state->retransmit_count = 0; + if (!pj_list_empty(&dd->uas_state->tx_data_list)) { + on_retransmit(NULL, &dd->uas_state->retransmit_timer); + } + + } else { + /* No it doesn't match */ + PJ_LOG(4,(dd->inv->dlg->obj_name, + "Rx PRACK with no matching reliable response")); + return PJ_EIGNORED; + } + + return PJ_SUCCESS; +} + + +/* + * This is retransmit timer callback, called initially to send the response, + * and subsequently when the retransmission time elapses. + */ +static void on_retransmit(pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + dlg_data *dd; + tx_data_list_t *tl; + pjsip_tx_data *tdata; + pj_bool_t final; + pj_time_val delay; + + PJ_UNUSED_ARG(timer_heap); + + dd = (dlg_data*) entry->user_data; + + entry->id = PJ_FALSE; + + ++dd->uas_state->retransmit_count; + if (dd->uas_state->retransmit_count >= 7) { + /* If a reliable provisional response is retransmitted for + 64*T1 seconds without reception of a corresponding PRACK, + the UAS SHOULD reject the original request with a 5xx + response. + */ + pj_str_t reason = pj_str("Reliable response timed out"); + pj_status_t status; + + /* Clear all pending responses */ + clear_all_responses(dd); + + /* Send 500 response */ + status = pjsip_inv_end_session(dd->inv, 500, &reason, &tdata); + if (status == PJ_SUCCESS) { + pjsip_dlg_send_response(dd->inv->dlg, + dd->inv->invite_tsx, + tdata); + } + return; + } + + pj_assert(!pj_list_empty(&dd->uas_state->tx_data_list)); + tl = dd->uas_state->tx_data_list.next; + tdata = tl->tdata; + + pjsip_tx_data_add_ref(tdata); + final = tdata->msg->line.status.code >= 200; + + if (dd->uas_state->retransmit_count == 1) { + pjsip_tsx_send_msg(dd->inv->invite_tsx, tdata); + } else { + pjsip_tsx_retransmit_no_state(dd->inv->invite_tsx, tdata); + } + + if (final) { + /* This is final response, which will be retransmitted by + * UA layer. There's no more task to do, so clear the + * transmission list and bail out. + */ + clear_all_responses(dd); + return; + } + + /* Schedule next retransmission */ + if (dd->uas_state->retransmit_count < 6) { + delay.sec = 0; + delay.msec = (1 << dd->uas_state->retransmit_count) * + pjsip_cfg()->tsx.t1; + pj_time_val_normalize(&delay); + } else { + delay.sec = 1; + delay.msec = 500; + } + + + pjsip_endpt_schedule_timer(dd->inv->dlg->endpt, + &dd->uas_state->retransmit_timer, + &delay); + + entry->id = PJ_TRUE; +} + + +/* Clone response. */ +static pjsip_tx_data *clone_tdata(dlg_data *dd, + const pjsip_tx_data *src) +{ + pjsip_tx_data *dst; + const pjsip_hdr *hsrc; + pjsip_msg *msg; + pj_status_t status; + + status = pjsip_endpt_create_tdata(dd->inv->dlg->endpt, &dst); + if (status != PJ_SUCCESS) + return NULL; + + msg = pjsip_msg_create(dst->pool, PJSIP_RESPONSE_MSG); + dst->msg = msg; + pjsip_tx_data_add_ref(dst); + + /* Duplicate status line */ + msg->line.status.code = src->msg->line.status.code; + pj_strdup(dst->pool, &msg->line.status.reason, + &src->msg->line.status.reason); + + /* Duplicate all headers */ + hsrc = src->msg->hdr.next; + while (hsrc != &src->msg->hdr) { + pjsip_hdr *h = (pjsip_hdr*) pjsip_hdr_clone(dst->pool, hsrc); + pjsip_msg_add_hdr(msg, h); + hsrc = hsrc->next; + } + + /* Duplicate message body */ + if (src->msg->body) + msg->body = pjsip_msg_body_clone(dst->pool, src->msg->body); + + PJ_LOG(5,(dd->inv->dlg->obj_name, + "Reliable response %s created", + pjsip_tx_data_get_info(dst))); + + return dst; +} + + +/* Check if any pending response in transmission list has SDP */ +static pj_bool_t has_sdp(dlg_data *dd) +{ + tx_data_list_t *tl; + + tl = dd->uas_state->tx_data_list.next; + while (tl != &dd->uas_state->tx_data_list) { + if (tl->tdata->msg->body) + return PJ_TRUE; + tl = tl->next; + } + + return PJ_FALSE; +} + + +/* Send response reliably */ +PJ_DEF(pj_status_t) pjsip_100rel_tx_response(pjsip_inv_session *inv, + pjsip_tx_data *tdata) +{ + pjsip_cseq_hdr *cseq_hdr; + pjsip_generic_string_hdr *rseq_hdr; + pjsip_require_hdr *req_hdr; + int status_code; + dlg_data *dd; + pjsip_tx_data *old_tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG, + PJSIP_ENOTRESPONSEMSG); + + status_code = tdata->msg->line.status.code; + + /* 100 response doesn't need PRACK */ + if (status_code == 100) + return pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata); + + + /* Get the 100rel data attached to this dialog */ + dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; + PJ_ASSERT_RETURN(dd != NULL, PJ_EINVALIDOP); + + + /* Clone tdata. + * We need to clone tdata because we may need to keep it in our + * retransmission list, while the original dialog may modify it + * if it wants to send another response. + */ + old_tdata = tdata; + tdata = clone_tdata(dd, old_tdata); + pjsip_tx_data_dec_ref(old_tdata); + + + /* Get CSeq header, and make sure this is INVITE response */ + cseq_hdr = (pjsip_cseq_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); + PJ_ASSERT_RETURN(cseq_hdr != NULL, PJ_EBUG); + PJ_ASSERT_RETURN(cseq_hdr->method.id == PJSIP_INVITE_METHOD, + PJ_EINVALIDOP); + + /* Remove existing Require header */ + req_hdr = find_req_hdr(tdata->msg); + if (req_hdr) { + pj_list_erase(req_hdr); + } + + /* Remove existing RSeq header */ + rseq_hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(tdata->msg, &RSEQ, NULL); + if (rseq_hdr) + pj_list_erase(rseq_hdr); + + /* Different treatment for provisional and final response */ + if (status_code/100 == 2) { + + /* RFC 3262 Section 3: UAS Behavior: + + The UAS MAY send a final response to the initial request + before having received PRACKs for all unacknowledged + reliable provisional responses, unless the final response + is 2xx and any of the unacknowledged reliable provisional + responses contained a session description. In that case, + it MUST NOT send a final response until those provisional + responses are acknowledged. + */ + + if (dd->uas_state && has_sdp(dd)) { + /* Yes we have transmitted 1xx with SDP reliably. + * In this case, must queue the 2xx response. + */ + tx_data_list_t *tl; + + tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t); + tl->tdata = tdata; + tl->rseq = (pj_uint32_t)-1; + pj_list_push_back(&dd->uas_state->tx_data_list, tl); + + /* Will send later */ + status = PJ_SUCCESS; + + PJ_LOG(4,(dd->inv->dlg->obj_name, + "2xx response will be sent after PRACK")); + + } else if (dd->uas_state) { + /* + RFC 3262 Section 3: UAS Behavior: + + If the UAS does send a final response when reliable + responses are still unacknowledged, it SHOULD NOT + continue to retransmit the unacknowledged reliable + provisional responses, but it MUST be prepared to + process PRACK requests for those outstanding + responses. + */ + + PJ_LOG(4,(dd->inv->dlg->obj_name, + "No SDP sent so far, sending 2xx now")); + + /* Cancel the retransmit timer */ + if (dd->uas_state->retransmit_timer.id) { + pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, + &dd->uas_state->retransmit_timer); + dd->uas_state->retransmit_timer.id = PJ_FALSE; + } + + /* Clear all pending responses (drop 'em) */ + clear_all_responses(dd); + + /* And transmit the 2xx response */ + status=pjsip_dlg_send_response(inv->dlg, + inv->invite_tsx, tdata); + + } else { + /* We didn't send any reliable provisional response */ + + /* Transmit the 2xx response */ + status=pjsip_dlg_send_response(inv->dlg, + inv->invite_tsx, tdata); + } + + } else if (status_code >= 300) { + + /* + RFC 3262 Section 3: UAS Behavior: + + If the UAS does send a final response when reliable + responses are still unacknowledged, it SHOULD NOT + continue to retransmit the unacknowledged reliable + provisional responses, but it MUST be prepared to + process PRACK requests for those outstanding + responses. + */ + + /* Cancel the retransmit timer */ + if (dd->uas_state && dd->uas_state->retransmit_timer.id) { + pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, + &dd->uas_state->retransmit_timer); + dd->uas_state->retransmit_timer.id = PJ_FALSE; + + /* Clear all pending responses (drop 'em) */ + clear_all_responses(dd); + } + + /* And transmit the 2xx response */ + status=pjsip_dlg_send_response(inv->dlg, + inv->invite_tsx, tdata); + + } else { + /* + * This is provisional response. + */ + char rseq_str[32]; + pj_str_t rseq; + tx_data_list_t *tl; + + /* Create UAS state if we don't have one */ + if (dd->uas_state == NULL) { + dd->uas_state = PJ_POOL_ZALLOC_T(inv->dlg->pool, + uas_state_t); + dd->uas_state->cseq = cseq_hdr->cseq; + dd->uas_state->rseq = pj_rand() % 0x7FFF; + pj_list_init(&dd->uas_state->tx_data_list); + dd->uas_state->retransmit_timer.user_data = dd; + dd->uas_state->retransmit_timer.cb = &on_retransmit; + } + + /* Check that CSeq match */ + PJ_ASSERT_RETURN(cseq_hdr->cseq == dd->uas_state->cseq, + PJ_EINVALIDOP); + + /* Add Require header */ + req_hdr = pjsip_require_hdr_create(tdata->pool); + req_hdr->count = 1; + req_hdr->values[0] = tag_100rel; + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)req_hdr); + + /* Add RSeq header */ + pj_ansi_snprintf(rseq_str, sizeof(rseq_str), "%u", + dd->uas_state->rseq); + rseq = pj_str(rseq_str); + rseq_hdr = pjsip_generic_string_hdr_create(tdata->pool, + &RSEQ, &rseq); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rseq_hdr); + + /* Create list entry for this response */ + tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t); + tl->tdata = tdata; + tl->rseq = dd->uas_state->rseq++; + + /* Add to queue if there's pending response, otherwise + * transmit immediately. + */ + if (!pj_list_empty(&dd->uas_state->tx_data_list)) { + + int code = tdata->msg->line.status.code; + + /* Will send later */ + pj_list_push_back(&dd->uas_state->tx_data_list, tl); + status = PJ_SUCCESS; + + PJ_LOG(4,(dd->inv->dlg->obj_name, + "Reliable %d response enqueued (%d pending)", + code, pj_list_size(&dd->uas_state->tx_data_list))); + + } else { + pj_list_push_back(&dd->uas_state->tx_data_list, tl); + + dd->uas_state->retransmit_count = 0; + on_retransmit(NULL, &dd->uas_state->retransmit_timer); + status = PJ_SUCCESS; + } + + } + + return status; +} + + diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c new file mode 100644 index 0000000..8db5308 --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_inv.c @@ -0,0 +1,4491 @@ +/* $Id: sip_inv.c 4156 2012-06-06 07:24:08Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-ua/sip_inv.h> +#include <pjsip-ua/sip_100rel.h> +#include <pjsip-ua/sip_timer.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_multipart.h> +#include <pjsip/sip_transaction.h> +#include <pjmedia/sdp.h> +#include <pjmedia/sdp_neg.h> +#include <pjmedia/errno.h> +#include <pj/string.h> +#include <pj/pool.h> +#include <pj/assert.h> +#include <pj/os.h> +#include <pj/log.h> +#include <pj/rand.h> + +/* + * Note on offer/answer: + * + * The offer/answer framework in this implementation assumes the occurence + * of SDP in a particular request/response according to this table: + + offer answer Note: + ======================================================================== + INVITE X INVITE may contain offer + 18x/INVITE X X Response may contain offer or answer + 2xx/INVITE X X Response may contain offer or answer + ACK X ACK may contain answer + + PRACK X PRACK can only contain answer + 2xx/PRACK Response may not have offer nor answer + + UPDATE X UPDATE may only contain offer + 2xx/UPDATE X Response may only contain answer + ======================================================================== + + * + */ + +#define THIS_FILE "sip_inv.c" + +static const char *inv_state_names[] = +{ + "NULL", + "CALLING", + "INCOMING", + "EARLY", + "CONNECTING", + "CONFIRMED", + "DISCONNCTD", + "TERMINATED", +}; + +/* UPDATE method */ +static const pjsip_method pjsip_update_method = +{ + PJSIP_OTHER_METHOD, + { "UPDATE", 6 } +}; + +#define POOL_INIT_SIZE 256 +#define POOL_INC_SIZE 256 + +/* + * Static prototypes. + */ +static pj_status_t mod_inv_load(pjsip_endpoint *endpt); +static pj_status_t mod_inv_unload(void); +static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata); +static void mod_inv_on_tsx_state(pjsip_transaction*, pjsip_event*); + +static void inv_on_state_null( pjsip_inv_session *inv, pjsip_event *e); +static void inv_on_state_calling( pjsip_inv_session *inv, pjsip_event *e); +static void inv_on_state_incoming( pjsip_inv_session *inv, pjsip_event *e); +static void inv_on_state_early( pjsip_inv_session *inv, pjsip_event *e); +static void inv_on_state_connecting( pjsip_inv_session *inv, pjsip_event *e); +static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e); +static void inv_on_state_disconnected( pjsip_inv_session *inv, pjsip_event *e); + +static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_rx_data *rdata); +static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv ); +static pjsip_msg_body *create_sdp_body(pj_pool_t *pool, + const pjmedia_sdp_session *c_sdp); +static pj_status_t process_answer( pjsip_inv_session *inv, + int st_code, + pjsip_tx_data *tdata, + const pjmedia_sdp_session *local_sdp); + +static pj_status_t handle_timer_response(pjsip_inv_session *inv, + const pjsip_rx_data *rdata, + pj_bool_t end_sess_on_failure); + +static void (*inv_state_handler[])( pjsip_inv_session *inv, pjsip_event *e) = +{ + &inv_on_state_null, + &inv_on_state_calling, + &inv_on_state_incoming, + &inv_on_state_early, + &inv_on_state_connecting, + &inv_on_state_confirmed, + &inv_on_state_disconnected, +}; + +static struct mod_inv +{ + pjsip_module mod; + pjsip_endpoint *endpt; + pjsip_inv_callback cb; +} mod_inv = +{ + { + NULL, NULL, /* prev, next. */ + { "mod-invite", 10 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */ + &mod_inv_load, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + &mod_inv_unload, /* unload() */ + &mod_inv_on_rx_request, /* on_rx_request() */ + &mod_inv_on_rx_response, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + &mod_inv_on_tsx_state, /* on_tsx_state() */ + } +}; + + +/* Invite session data to be attached to transaction. */ +struct tsx_inv_data +{ + pjsip_inv_session *inv; /* The invite session */ + pj_bool_t sdp_done; /* SDP negotiation done for this tsx? */ + pj_bool_t retrying; /* Resend (e.g. due to 401/407) */ + pj_str_t done_tag; /* To tag in RX response with answer */ + pj_bool_t done_early;/* Negotiation was done for early med? */ +}; + +/* + * Module load() + */ +static pj_status_t mod_inv_load(pjsip_endpoint *endpt) +{ + pj_str_t allowed[] = {{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6}, + { "UPDATE", 6}}; + pj_str_t accepted = { "application/sdp", 15 }; + + /* Register supported methods: INVITE, ACK, BYE, CANCEL, UPDATE */ + pjsip_endpt_add_capability(endpt, &mod_inv.mod, PJSIP_H_ALLOW, NULL, + PJ_ARRAY_SIZE(allowed), allowed); + + /* Register "application/sdp" in Accept header */ + pjsip_endpt_add_capability(endpt, &mod_inv.mod, PJSIP_H_ACCEPT, NULL, + 1, &accepted); + + return PJ_SUCCESS; +} + +/* + * Module unload() + */ +static pj_status_t mod_inv_unload(void) +{ + /* Should remove capability here */ + return PJ_SUCCESS; +} + +/* + * Set session state. + */ +void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state, + pjsip_event *e) +{ + pjsip_inv_state prev_state = inv->state; + pj_bool_t dont_notify = PJ_FALSE; + pj_status_t status; + + /* Prevent STATE_CALLING from being reported more than once because + * of authentication + * https://trac.pjsip.org/repos/ticket/1318 + */ + if (state==PJSIP_INV_STATE_CALLING && + (inv->cb_called & (1 << PJSIP_INV_STATE_CALLING)) != 0) + { + dont_notify = PJ_TRUE; + } + + /* If state is confirmed, check that SDP negotiation is done, + * otherwise disconnect the session. + */ + if (state == PJSIP_INV_STATE_CONFIRMED) { + struct tsx_inv_data *tsx_inv_data = NULL; + + if (inv->invite_tsx) { + tsx_inv_data = (struct tsx_inv_data*) + inv->invite_tsx->mod_data[mod_inv.mod.id]; + } + + if (pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE && + (tsx_inv_data && !tsx_inv_data->sdp_done) ) + { + pjsip_tx_data *bye; + + PJ_LOG(4,(inv->obj_name, "SDP offer/answer incomplete, ending the " + "session")); + + status = pjsip_inv_end_session(inv, PJSIP_SC_NOT_ACCEPTABLE, + NULL, &bye); + if (status == PJ_SUCCESS && bye) + status = pjsip_inv_send_msg(inv, bye); + + return; + } + } + + /* Set state. */ + inv->state = state; + + /* If state is DISCONNECTED, cause code MUST have been set. */ + pj_assert(inv->state != PJSIP_INV_STATE_DISCONNECTED || + inv->cause != 0); + + /* Mark the callback as called for this state */ + inv->cb_called |= (1 << state); + + /* Call on_state_changed() callback. */ + if (mod_inv.cb.on_state_changed && inv->notify && !dont_notify) + (*mod_inv.cb.on_state_changed)(inv, e); + + /* Only decrement when previous state is not already DISCONNECTED */ + if (inv->state == PJSIP_INV_STATE_DISCONNECTED && + prev_state != PJSIP_INV_STATE_DISCONNECTED) + { + if (inv->last_ack) { + pjsip_tx_data_dec_ref(inv->last_ack); + inv->last_ack = NULL; + } + if (inv->invite_req) { + pjsip_tx_data_dec_ref(inv->invite_req); + inv->invite_req = NULL; + } + pjsip_100rel_end_session(inv); + pjsip_timer_end_session(inv); + pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod); + + /* Release the flip-flop pools */ + pj_pool_release(inv->pool_prov); + inv->pool_prov = NULL; + pj_pool_release(inv->pool_active); + inv->pool_active = NULL; + } +} + + +/* + * Set cause code. + */ +void inv_set_cause(pjsip_inv_session *inv, int cause_code, + const pj_str_t *cause_text) +{ + if (cause_code > inv->cause) { + inv->cause = (pjsip_status_code) cause_code; + if (cause_text) + pj_strdup(inv->pool, &inv->cause_text, cause_text); + else if (cause_code/100 == 2) + inv->cause_text = pj_str("Normal call clearing"); + else + inv->cause_text = *pjsip_get_status_text(cause_code); + } +} + + +/* + * Check if outgoing request needs to have SDP answer. + * This applies for both ACK and PRACK requests. + */ +static const pjmedia_sdp_session *inv_has_pending_answer(pjsip_inv_session *inv, + pjsip_transaction *tsx) +{ + pjmedia_sdp_neg_state neg_state; + const pjmedia_sdp_session *sdp = NULL; + pj_status_t status; + + /* If SDP negotiator is ready, start negotiation. */ + + /* Start nego when appropriate. */ + neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) : + PJMEDIA_SDP_NEG_STATE_NULL; + + if (neg_state == PJMEDIA_SDP_NEG_STATE_DONE) { + + /* Nothing to do */ + + } else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO && + pjmedia_sdp_neg_has_local_answer(inv->neg) ) + { + struct tsx_inv_data *tsx_inv_data; + struct tsx_inv_data dummy; + + /* Get invite session's transaction data. + * Note that tsx may be NULL, for example when application sends + * delayed ACK request (at this time, the original INVITE + * transaction may have been destroyed. + */ + if (tsx) { + tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id]; + } else { + tsx_inv_data = &dummy; + pj_bzero(&dummy, sizeof(dummy)); + dummy.inv = inv; + } + + status = inv_negotiate_sdp(inv); + if (status != PJ_SUCCESS) + return NULL; + + /* Mark this transaction has having SDP offer/answer done. */ + tsx_inv_data->sdp_done = 1; + + status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp); + + } else { + /* This remark is only valid for ACK. + PJ_LOG(4,(inv->dlg->obj_name, + "FYI, the SDP negotiator state (%s) is in a mess " + "when sending this ACK/PRACK request", + pjmedia_sdp_neg_state_str(neg_state))); + */ + } + + return sdp; +} + + +/* + * Send ACK for 2xx response. + */ +static pj_status_t inv_send_ack(pjsip_inv_session *inv, pjsip_event *e) +{ + pjsip_rx_data *rdata; + pjsip_event ack_e; + pj_status_t status; + + if (e->type == PJSIP_EVENT_TSX_STATE) + rdata = e->body.tsx_state.src.rdata; + else if (e->type == PJSIP_EVENT_RX_MSG) + rdata = e->body.rx_msg.rdata; + else { + pj_assert(!"Unsupported event type"); + return PJ_EBUG; + } + + PJ_LOG(5,(inv->obj_name, "Received %s, sending ACK", + pjsip_rx_data_get_info(rdata))); + + /* Check if we have cached ACK request. Must not use the cached ACK + * if it's still marked as pending by transport (#1011) + */ + if (inv->last_ack && rdata->msg_info.cseq->cseq == inv->last_ack_cseq && + !inv->last_ack->is_pending) + { + pjsip_tx_data_add_ref(inv->last_ack); + + } else if (mod_inv.cb.on_send_ack) { + /* If application handles ACK transmission manually, just notify the + * callback + */ + PJ_LOG(5,(inv->obj_name, "Received %s, notifying application callback", + pjsip_rx_data_get_info(rdata))); + + (*mod_inv.cb.on_send_ack)(inv, rdata); + return PJ_SUCCESS; + + } else { + status = pjsip_inv_create_ack(inv, rdata->msg_info.cseq->cseq, + &inv->last_ack); + if (status != PJ_SUCCESS) + return status; + } + + PJSIP_EVENT_INIT_TX_MSG(ack_e, inv->last_ack); + + /* Send ACK */ + status = pjsip_dlg_send_request(inv->dlg, inv->last_ack, -1, NULL); + if (status != PJ_SUCCESS) { + /* Better luck next time */ + pj_assert(!"Unable to send ACK!"); + return status; + } + + + /* Set state to CONFIRMED (if we're not in CONFIRMED yet). + * But don't set it to CONFIRMED if we're already DISCONNECTED + * (this may have been a late 200/OK response. + */ + if (inv->state < PJSIP_INV_STATE_CONFIRMED) { + inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &ack_e); + } + + return PJ_SUCCESS; +} + +/* + * Module on_rx_request() + * + * This callback is called for these events: + * - endpoint receives request which was unhandled by higher priority + * modules (e.g. transaction layer, dialog layer). + * - dialog distributes incoming request to its usages. + */ +static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata) +{ + pjsip_method *method; + pjsip_dialog *dlg; + pjsip_inv_session *inv; + + /* Only wants to receive request from a dialog. */ + dlg = pjsip_rdata_get_dlg(rdata); + if (dlg == NULL) + return PJ_FALSE; + + inv = (pjsip_inv_session*) dlg->mod_data[mod_inv.mod.id]; + + /* Report to dialog that we handle INVITE, CANCEL, BYE, ACK. + * If we need to send response, it will be sent in the state + * handlers. + */ + method = &rdata->msg_info.msg->line.req.method; + + if (method->id == PJSIP_INVITE_METHOD) { + return PJ_TRUE; + } + + /* BYE and CANCEL must have existing invite session */ + if (method->id == PJSIP_BYE_METHOD || + method->id == PJSIP_CANCEL_METHOD) + { + if (inv == NULL) + return PJ_FALSE; + + return PJ_TRUE; + } + + /* On receipt ACK request, when state is CONNECTING, + * move state to CONFIRMED. + */ + if (method->id == PJSIP_ACK_METHOD && inv) { + + /* Ignore if we don't have INVITE in progress */ + if (!inv->invite_tsx) { + return PJ_TRUE; + } + + /* Ignore ACK if pending INVITE transaction has not finished. */ + if (inv->invite_tsx->state < PJSIP_TSX_STATE_COMPLETED) { + return PJ_TRUE; + } + + /* Ignore ACK with different CSeq + * https://trac.pjsip.org/repos/ticket/1391 + */ + if (rdata->msg_info.cseq->cseq != inv->invite_tsx->cseq) { + return PJ_TRUE; + } + + /* Terminate INVITE transaction, if it's still present. */ + if (inv->invite_tsx->state <= PJSIP_TSX_STATE_COMPLETED) { + /* Before we terminate INVITE transaction, process the SDP + * in the ACK request, if any. + * Only do this when invite state is not already disconnected + * (http://trac.pjsip.org/repos/ticket/640). + */ + if (inv->state < PJSIP_INV_STATE_DISCONNECTED) { + inv_check_sdp_in_incoming_msg(inv, inv->invite_tsx, rdata); + + /* Check if local offer got no SDP answer and INVITE session + * is in CONFIRMED state. + */ + if (pjmedia_sdp_neg_get_state(inv->neg)== + PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER && + inv->state==PJSIP_INV_STATE_CONFIRMED) + { + pjmedia_sdp_neg_cancel_offer(inv->neg); + } + } + + /* Now we can terminate the INVITE transaction */ + pj_assert(inv->invite_tsx->status_code >= 200); + pjsip_tsx_terminate(inv->invite_tsx, + inv->invite_tsx->status_code); + inv->invite_tsx = NULL; + if (inv->last_answer) { + pjsip_tx_data_dec_ref(inv->last_answer); + inv->last_answer = NULL; + } + } + + /* On receipt of ACK, only set state to confirmed when state + * is CONNECTING (e.g. we don't want to set the state to confirmed + * when we receive ACK retransmission after sending non-2xx!) + */ + if (inv->state == PJSIP_INV_STATE_CONNECTING) { + pjsip_event event; + + PJSIP_EVENT_INIT_RX_MSG(event, rdata); + inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &event); + } + } + + return PJ_FALSE; +} + +/* This function will process Session Timer headers in received + * 2xx or 422 response of INVITE/UPDATE request. + */ +static pj_status_t handle_timer_response(pjsip_inv_session *inv, + const pjsip_rx_data *rdata, + pj_bool_t end_sess_on_failure) +{ + pjsip_status_code st_code; + pj_status_t status; + + status = pjsip_timer_process_resp(inv, rdata, &st_code); + if (status != PJ_SUCCESS && end_sess_on_failure) { + pjsip_tx_data *tdata; + pj_status_t status2; + + status2 = pjsip_inv_end_session(inv, st_code, NULL, &tdata); + if (tdata && status2 == PJ_SUCCESS) + pjsip_inv_send_msg(inv, tdata); + } + + return status; +} + +/* + * Module on_rx_response(). + * + * This callback is called for these events: + * - dialog distributes incoming 2xx response to INVITE (outside + * transaction) to its usages. + * - endpoint distributes strayed responses. + */ +static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata) +{ + pjsip_dialog *dlg; + pjsip_inv_session *inv; + pjsip_msg *msg = rdata->msg_info.msg; + + dlg = pjsip_rdata_get_dlg(rdata); + + /* Ignore responses outside dialog */ + if (dlg == NULL) + return PJ_FALSE; + + /* Ignore responses not belonging to invite session */ + inv = pjsip_dlg_get_inv_session(dlg); + if (inv == NULL) + return PJ_FALSE; + + /* This MAY be retransmission of 2xx response to INVITE. + * If it is, we need to send ACK. + */ + if (msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/100==2 && + rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD && + inv->invite_tsx == NULL) + { + pjsip_event e; + + PJSIP_EVENT_INIT_RX_MSG(e, rdata); + inv_send_ack(inv, &e); + return PJ_TRUE; + + } + + /* No other processing needs to be done here. */ + return PJ_FALSE; +} + +/* + * Module on_tsx_state() + * + * This callback is called by dialog framework for all transactions + * inside the dialog for all its dialog usages. + */ +static void mod_inv_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) +{ + pjsip_dialog *dlg; + pjsip_inv_session *inv; + + dlg = pjsip_tsx_get_dlg(tsx); + if (dlg == NULL) + return; + + inv = pjsip_dlg_get_inv_session(dlg); + if (inv == NULL) + return; + + /* Call state handler for the invite session. */ + (*inv_state_handler[inv->state])(inv, e); + + /* Call on_tsx_state */ + if (mod_inv.cb.on_tsx_state_changed && inv->notify) + (*mod_inv.cb.on_tsx_state_changed)(inv, tsx, e); + + /* Clear invite transaction when tsx is confirmed. + * Previously we set invite_tsx to NULL only when transaction has + * terminated, but this didn't work when ACK has the same Via branch + * value as the INVITE (see http://www.pjsip.org/trac/ticket/113) + */ + if (tsx->state>=PJSIP_TSX_STATE_CONFIRMED && tsx == inv->invite_tsx) { + inv->invite_tsx = NULL; + if (inv->last_answer) { + pjsip_tx_data_dec_ref(inv->last_answer); + inv->last_answer = NULL; + } + } +} + + +/* + * Initialize the invite module. + */ +PJ_DEF(pj_status_t) pjsip_inv_usage_init( pjsip_endpoint *endpt, + const pjsip_inv_callback *cb) +{ + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt && cb, PJ_EINVAL); + + /* Some callbacks are mandatory */ + PJ_ASSERT_RETURN(cb->on_state_changed && cb->on_new_session, PJ_EINVAL); + + /* Check if module already registered. */ + PJ_ASSERT_RETURN(mod_inv.mod.id == -1, PJ_EINVALIDOP); + + /* Copy param. */ + pj_memcpy(&mod_inv.cb, cb, sizeof(pjsip_inv_callback)); + + mod_inv.endpt = endpt; + + /* Register the module. */ + status = pjsip_endpt_register_module(endpt, &mod_inv.mod); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + +/* + * Get the instance of invite module. + */ +PJ_DEF(pjsip_module*) pjsip_inv_usage_instance(void) +{ + return &mod_inv.mod; +} + + + +/* + * Return the invite session for the specified dialog. + */ +PJ_DEF(pjsip_inv_session*) pjsip_dlg_get_inv_session(pjsip_dialog *dlg) +{ + return (pjsip_inv_session*) dlg->mod_data[mod_inv.mod.id]; +} + + +/* + * Get INVITE state name. + */ +PJ_DEF(const char *) pjsip_inv_state_name(pjsip_inv_state state) +{ + PJ_ASSERT_RETURN(state >= PJSIP_INV_STATE_NULL && + state <= PJSIP_INV_STATE_DISCONNECTED, + "??"); + + return inv_state_names[state]; +} + +/* + * Create UAC invite session. + */ +PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg, + const pjmedia_sdp_session *local_sdp, + unsigned options, + pjsip_inv_session **p_inv) +{ + pjsip_inv_session *inv; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(dlg && p_inv, PJ_EINVAL); + + /* Must lock dialog first */ + pjsip_dlg_inc_lock(dlg); + + /* Normalize options */ + if (options & PJSIP_INV_REQUIRE_100REL) + options |= PJSIP_INV_SUPPORT_100REL; + if (options & PJSIP_INV_REQUIRE_TIMER) + options |= PJSIP_INV_SUPPORT_TIMER; + + /* Create the session */ + inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session); + pj_assert(inv != NULL); + + inv->pool = dlg->pool; + inv->role = PJSIP_ROLE_UAC; + inv->state = PJSIP_INV_STATE_NULL; + inv->dlg = dlg; + inv->options = options; + inv->notify = PJ_TRUE; + inv->cause = (pjsip_status_code) 0; + + /* Create flip-flop pool (see ticket #877) */ + /* (using inv->obj_name as temporary variable for pool names */ + pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg->pool); + inv->pool_prov = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name, + POOL_INIT_SIZE, POOL_INC_SIZE); + inv->pool_active = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name, + POOL_INIT_SIZE, POOL_INC_SIZE); + + /* Object name will use the same dialog pointer. */ + pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg); + + /* Create negotiator if local_sdp is specified. */ + if (local_sdp) { + status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, + local_sdp, &inv->neg); + if (status != PJ_SUCCESS) { + pjsip_dlg_dec_lock(dlg); + return status; + } + } + + /* Register invite as dialog usage. */ + status = pjsip_dlg_add_usage(dlg, &mod_inv.mod, inv); + if (status != PJ_SUCCESS) { + pjsip_dlg_dec_lock(dlg); + return status; + } + + /* Increment dialog session */ + pjsip_dlg_inc_session(dlg, &mod_inv.mod); + + /* Create 100rel handler */ + pjsip_100rel_attach(inv); + + /* Done */ + *p_inv = inv; + + pjsip_dlg_dec_lock(dlg); + + PJ_LOG(5,(inv->obj_name, "UAC invite session created for dialog %s", + dlg->obj_name)); + + return PJ_SUCCESS; +} + +PJ_DEF(pjsip_rdata_sdp_info*) pjsip_rdata_get_sdp_info(pjsip_rx_data *rdata) +{ + pjsip_rdata_sdp_info *sdp_info; + pjsip_msg_body *body = rdata->msg_info.msg->body; + pjsip_ctype_hdr *ctype_hdr = rdata->msg_info.ctype; + pjsip_media_type app_sdp; + + sdp_info = (pjsip_rdata_sdp_info*) + rdata->endpt_info.mod_data[mod_inv.mod.id]; + if (sdp_info) + return sdp_info; + + sdp_info = PJ_POOL_ZALLOC_T(rdata->tp_info.pool, + pjsip_rdata_sdp_info); + PJ_ASSERT_RETURN(mod_inv.mod.id >= 0, sdp_info); + rdata->endpt_info.mod_data[mod_inv.mod.id] = sdp_info; + + pjsip_media_type_init2(&app_sdp, "application", "sdp"); + + if (body && ctype_hdr && + pj_stricmp(&ctype_hdr->media.type, &app_sdp.type)==0 && + pj_stricmp(&ctype_hdr->media.subtype, &app_sdp.subtype)==0) + { + sdp_info->body.ptr = (char*)body->data; + sdp_info->body.slen = body->len; + } else if (body && ctype_hdr && + pj_stricmp2(&ctype_hdr->media.type, "multipart")==0 && + (pj_stricmp2(&ctype_hdr->media.subtype, "mixed")==0 || + pj_stricmp2(&ctype_hdr->media.subtype, "alternative")==0)) + { + pjsip_multipart_part *part; + + part = pjsip_multipart_find_part(body, &app_sdp, NULL); + if (part) { + sdp_info->body.ptr = (char*)part->body->data; + sdp_info->body.slen = part->body->len; + } + } + + if (sdp_info->body.ptr) { + pj_status_t status; + status = pjmedia_sdp_parse(rdata->tp_info.pool, + sdp_info->body.ptr, + sdp_info->body.slen, + &sdp_info->sdp); + if (status == PJ_SUCCESS) + status = pjmedia_sdp_validate(sdp_info->sdp); + + if (status != PJ_SUCCESS) { + sdp_info->sdp = NULL; + PJ_PERROR(1,(THIS_FILE, status, + "Error parsing/validating SDP body")); + } + + sdp_info->sdp_err = status; + } + + return sdp_info; +} + + +/* + * Verify incoming INVITE request. + */ +PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata, + pj_pool_t *tmp_pool, + unsigned *options, + const pjmedia_sdp_session *r_sdp, + const pjmedia_sdp_session *l_sdp, + pjsip_dialog *dlg, + pjsip_endpoint *endpt, + pjsip_tx_data **p_tdata) +{ + pjsip_msg *msg = NULL; + pjsip_allow_hdr *allow = NULL; + pjsip_supported_hdr *sup_hdr = NULL; + pjsip_require_hdr *req_hdr = NULL; + pjsip_contact_hdr *c_hdr = NULL; + int code = 200; + unsigned rem_option = 0; + pj_status_t status = PJ_SUCCESS; + pjsip_hdr res_hdr_list; + pjsip_rdata_sdp_info *sdp_info; + + /* Init return arguments. */ + if (p_tdata) *p_tdata = NULL; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(tmp_pool != NULL && options != NULL, PJ_EINVAL); + + /* Normalize options */ + if (*options & PJSIP_INV_REQUIRE_100REL) + *options |= PJSIP_INV_SUPPORT_100REL; + if (*options & PJSIP_INV_REQUIRE_TIMER) + *options |= PJSIP_INV_SUPPORT_TIMER; + if (*options & PJSIP_INV_REQUIRE_ICE) + *options |= PJSIP_INV_SUPPORT_ICE; + + if (rdata) { + /* Get the message in rdata */ + msg = rdata->msg_info.msg; + + /* Must be INVITE request. */ + PJ_ASSERT_RETURN(msg && msg->type == PJSIP_REQUEST_MSG && + msg->line.req.method.id == PJSIP_INVITE_METHOD, + PJ_EINVAL); + } + + /* If tdata is specified, then either dlg or endpt must be specified */ + PJ_ASSERT_RETURN((!p_tdata) || (endpt || dlg), PJ_EINVAL); + + /* Get the endpoint */ + endpt = endpt ? endpt : dlg->endpt; + + /* Init response header list */ + pj_list_init(&res_hdr_list); + + /* Check the Contact header */ + if (msg) { + c_hdr = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL); + } + if (msg && (!c_hdr || !c_hdr->uri)) { + /* Missing Contact header or Contact contains "*" */ + pjsip_warning_hdr *w; + pj_str_t warn_text; + + warn_text = pj_str("Bad/missing Contact header"); + w = pjsip_warning_hdr_create(tmp_pool, 399, + pjsip_endpt_name(endpt), + &warn_text); + if (w) { + pj_list_push_back(&res_hdr_list, w); + } + + code = PJSIP_SC_BAD_REQUEST; + status = PJSIP_ERRNO_FROM_SIP_STATUS(code); + goto on_return; + } + + /* Check the request body, see if it's something that we support, + * only when the body hasn't been parsed before. + */ + if (r_sdp == NULL && rdata) { + sdp_info = pjsip_rdata_get_sdp_info(rdata); + } else { + sdp_info = NULL; + } + + if (r_sdp==NULL && msg && msg->body) { + + /* Check if body really contains SDP. */ + if (sdp_info->body.ptr == NULL) { + /* Couldn't find "application/sdp" */ + code = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE; + status = PJSIP_ERRNO_FROM_SIP_STATUS(code); + + if (p_tdata) { + /* Add Accept header to response */ + pjsip_accept_hdr *acc; + + acc = pjsip_accept_hdr_create(tmp_pool); + PJ_ASSERT_RETURN(acc, PJ_ENOMEM); + acc->values[acc->count++] = pj_str("application/sdp"); + pj_list_push_back(&res_hdr_list, acc); + } + + goto on_return; + } + + if (sdp_info->sdp_err != PJ_SUCCESS) { + /* Unparseable or invalid SDP */ + code = PJSIP_SC_BAD_REQUEST; + + if (p_tdata) { + /* Add Warning header. */ + pjsip_warning_hdr *w; + + w = pjsip_warning_hdr_create_from_status(tmp_pool, + pjsip_endpt_name(endpt), + sdp_info->sdp_err); + PJ_ASSERT_RETURN(w, PJ_ENOMEM); + + pj_list_push_back(&res_hdr_list, w); + } + + goto on_return; + } + + r_sdp = sdp_info->sdp; + } + + if (r_sdp) { + /* Negotiate with local SDP */ + if (l_sdp) { + pjmedia_sdp_neg *neg; + + /* Local SDP must be valid! */ + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(l_sdp))==PJ_SUCCESS, + status); + + /* Create SDP negotiator */ + status = pjmedia_sdp_neg_create_w_remote_offer( + tmp_pool, l_sdp, r_sdp, &neg); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Negotiate SDP */ + status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0); + if (status != PJ_SUCCESS) { + + /* Incompatible media */ + code = PJSIP_SC_NOT_ACCEPTABLE_HERE; + + if (p_tdata) { + pjsip_accept_hdr *acc; + pjsip_warning_hdr *w; + + /* Add Warning header. */ + w = pjsip_warning_hdr_create_from_status( + tmp_pool, + pjsip_endpt_name(endpt), status); + PJ_ASSERT_RETURN(w, PJ_ENOMEM); + + pj_list_push_back(&res_hdr_list, w); + + /* Add Accept header to response */ + acc = pjsip_accept_hdr_create(tmp_pool); + PJ_ASSERT_RETURN(acc, PJ_ENOMEM); + acc->values[acc->count++] = pj_str("application/sdp"); + pj_list_push_back(&res_hdr_list, acc); + + } + + goto on_return; + } + } + } + + /* Check supported methods, see if peer supports UPDATE. + * We just assume that peer supports standard INVITE, ACK, CANCEL, and BYE + * implicitly by sending this INVITE. + */ + if (msg) { + allow = (pjsip_allow_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_ALLOW, + NULL); + } + if (allow) { + unsigned i; + const pj_str_t STR_UPDATE = { "UPDATE", 6 }; + + for (i=0; i<allow->count; ++i) { + if (pj_stricmp(&allow->values[i], &STR_UPDATE)==0) + break; + } + + if (i != allow->count) { + /* UPDATE is present in Allow */ + rem_option |= PJSIP_INV_SUPPORT_UPDATE; + } + + } + + /* Check Supported header */ + if (msg) { + sup_hdr = (pjsip_supported_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_SUPPORTED, NULL); + } + if (sup_hdr) { + unsigned i; + const pj_str_t STR_100REL = { "100rel", 6}; + const pj_str_t STR_TIMER = { "timer", 5}; + const pj_str_t STR_ICE = { "ice", 3 }; + + for (i=0; i<sup_hdr->count; ++i) { + if (pj_stricmp(&sup_hdr->values[i], &STR_100REL)==0) + rem_option |= PJSIP_INV_SUPPORT_100REL; + else if (pj_stricmp(&sup_hdr->values[i], &STR_TIMER)==0) + rem_option |= PJSIP_INV_SUPPORT_TIMER; + else if (pj_stricmp(&sup_hdr->values[i], &STR_ICE)==0) + rem_option |= PJSIP_INV_SUPPORT_ICE; + } + } + + /* Check Require header */ + if (msg) { + req_hdr = (pjsip_require_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL); + } + if (req_hdr) { + unsigned i; + const pj_str_t STR_100REL = { "100rel", 6}; + const pj_str_t STR_REPLACES = { "replaces", 8 }; + const pj_str_t STR_TIMER = { "timer", 5 }; + const pj_str_t STR_ICE = { "ice", 3 }; + unsigned unsupp_cnt = 0; + pj_str_t unsupp_tags[PJSIP_GENERIC_ARRAY_MAX_COUNT]; + + for (i=0; i<req_hdr->count; ++i) { + if ((*options & PJSIP_INV_SUPPORT_100REL) && + pj_stricmp(&req_hdr->values[i], &STR_100REL)==0) + { + rem_option |= PJSIP_INV_REQUIRE_100REL; + + } else if ((*options & PJSIP_INV_SUPPORT_TIMER) && + pj_stricmp(&req_hdr->values[i], &STR_TIMER)==0) + { + rem_option |= PJSIP_INV_REQUIRE_TIMER; + + } else if (pj_stricmp(&req_hdr->values[i], &STR_REPLACES)==0) { + pj_bool_t supp; + + supp = pjsip_endpt_has_capability(endpt, PJSIP_H_SUPPORTED, + NULL, &STR_REPLACES); + if (!supp) + unsupp_tags[unsupp_cnt++] = req_hdr->values[i]; + } else if ((*options & PJSIP_INV_SUPPORT_ICE) && + pj_stricmp(&req_hdr->values[i], &STR_ICE)==0) + { + rem_option |= PJSIP_INV_REQUIRE_ICE; + + } else if (!pjsip_endpt_has_capability(endpt, PJSIP_H_SUPPORTED, + NULL, &req_hdr->values[i])) + { + /* Unknown/unsupported extension tag! */ + unsupp_tags[unsupp_cnt++] = req_hdr->values[i]; + } + } + + /* Check if there are required tags that we don't support */ + if (unsupp_cnt) { + + code = PJSIP_SC_BAD_EXTENSION; + status = PJSIP_ERRNO_FROM_SIP_STATUS(code); + + if (p_tdata) { + pjsip_unsupported_hdr *unsupp_hdr; + const pjsip_hdr *h; + + /* Add Unsupported header. */ + unsupp_hdr = pjsip_unsupported_hdr_create(tmp_pool); + PJ_ASSERT_RETURN(unsupp_hdr != NULL, PJ_ENOMEM); + + unsupp_hdr->count = unsupp_cnt; + for (i=0; i<unsupp_cnt; ++i) + unsupp_hdr->values[i] = unsupp_tags[i]; + + pj_list_push_back(&res_hdr_list, unsupp_hdr); + + /* Add Supported header. */ + h = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, + NULL); + pj_assert(h); + if (h) { + sup_hdr = (pjsip_supported_hdr*) + pjsip_hdr_clone(tmp_pool, h); + pj_list_push_back(&res_hdr_list, sup_hdr); + } + } + + goto on_return; + } + } + + /* Check if there are local requirements that are not supported + * by peer. + */ + if ( msg && (((*options & PJSIP_INV_REQUIRE_100REL)!=0 && + (rem_option & PJSIP_INV_SUPPORT_100REL)==0) || + ((*options & PJSIP_INV_REQUIRE_TIMER)!=0 && + (rem_option & PJSIP_INV_SUPPORT_TIMER)==0))) + { + code = PJSIP_SC_EXTENSION_REQUIRED; + status = PJSIP_ERRNO_FROM_SIP_STATUS(code); + + if (p_tdata) { + const pjsip_hdr *h; + + /* Add Require header. */ + req_hdr = pjsip_require_hdr_create(tmp_pool); + PJ_ASSERT_RETURN(req_hdr != NULL, PJ_ENOMEM); + + if (*options & PJSIP_INV_REQUIRE_100REL) + req_hdr->values[req_hdr->count++] = pj_str("100rel"); + if (*options & PJSIP_INV_REQUIRE_TIMER) + req_hdr->values[req_hdr->count++] = pj_str("timer"); + + pj_list_push_back(&res_hdr_list, req_hdr); + + /* Add Supported header. */ + h = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, + NULL); + pj_assert(h); + if (h) { + sup_hdr = (pjsip_supported_hdr*) + pjsip_hdr_clone(tmp_pool, h); + pj_list_push_back(&res_hdr_list, sup_hdr); + } + + } + + goto on_return; + } + + /* If remote Require something that we support, make us Require + * that feature too. + */ + if (rem_option & PJSIP_INV_REQUIRE_100REL) { + pj_assert(*options & PJSIP_INV_SUPPORT_100REL); + *options |= PJSIP_INV_REQUIRE_100REL; + } + if (rem_option & PJSIP_INV_REQUIRE_TIMER) { + pj_assert(*options & PJSIP_INV_SUPPORT_TIMER); + *options |= PJSIP_INV_REQUIRE_TIMER; + } + +on_return: + + /* Create response if necessary */ + if (code != 200 && p_tdata) { + pjsip_tx_data *tdata; + const pjsip_hdr *h; + + if (!rdata) { + return PJSIP_ERRNO_FROM_SIP_STATUS(code); + } + + if (dlg) { + status = pjsip_dlg_create_response(dlg, rdata, code, NULL, + &tdata); + } else { + status = pjsip_endpt_create_response(endpt, rdata, code, NULL, + &tdata); + } + + if (status != PJ_SUCCESS) + return status; + + /* Add response headers. */ + h = res_hdr_list.next; + while (h != &res_hdr_list) { + pjsip_hdr *cloned; + + cloned = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, h); + PJ_ASSERT_RETURN(cloned, PJ_ENOMEM); + + pjsip_msg_add_hdr(tdata->msg, cloned); + + h = h->next; + } + + *p_tdata = tdata; + + /* Can not return PJ_SUCCESS when response message is produced. + * Ref: PROTOS test ~#2490 + */ + if (status == PJ_SUCCESS) + status = PJSIP_ERRNO_FROM_SIP_STATUS(code); + + } + + return status; +} + + +/* + * Verify incoming INVITE request. + */ +PJ_DEF(pj_status_t) pjsip_inv_verify_request2(pjsip_rx_data *rdata, + unsigned *options, + const pjmedia_sdp_session *r_sdp, + const pjmedia_sdp_session *l_sdp, + pjsip_dialog *dlg, + pjsip_endpoint *endpt, + pjsip_tx_data **p_tdata) +{ + return pjsip_inv_verify_request3(rdata, rdata->tp_info.pool, + options, r_sdp, l_sdp, dlg, + endpt, p_tdata); +} + + +/* + * Verify incoming INVITE request. + */ +PJ_DEF(pj_status_t) pjsip_inv_verify_request( pjsip_rx_data *rdata, + unsigned *options, + const pjmedia_sdp_session *l_sdp, + pjsip_dialog *dlg, + pjsip_endpoint *endpt, + pjsip_tx_data **p_tdata) +{ + return pjsip_inv_verify_request3(rdata, rdata->tp_info.pool, + options, NULL, l_sdp, dlg, + endpt, p_tdata); +} + +/* + * Create UAS invite session. + */ +PJ_DEF(pj_status_t) pjsip_inv_create_uas( pjsip_dialog *dlg, + pjsip_rx_data *rdata, + const pjmedia_sdp_session *local_sdp, + unsigned options, + pjsip_inv_session **p_inv) +{ + pjsip_inv_session *inv; + struct tsx_inv_data *tsx_inv_data; + pjsip_msg *msg; + pjsip_rdata_sdp_info *sdp_info; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(dlg && rdata && p_inv, PJ_EINVAL); + + /* Dialog MUST have been initialised. */ + PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata) != NULL, PJ_EINVALIDOP); + + msg = rdata->msg_info.msg; + + /* rdata MUST contain INVITE request */ + PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG && + msg->line.req.method.id == PJSIP_INVITE_METHOD, + PJ_EINVALIDOP); + + /* Lock dialog */ + pjsip_dlg_inc_lock(dlg); + + /* Normalize options */ + if (options & PJSIP_INV_REQUIRE_100REL) + options |= PJSIP_INV_SUPPORT_100REL; + if (options & PJSIP_INV_REQUIRE_TIMER) + options |= PJSIP_INV_SUPPORT_TIMER; + + /* Create the session */ + inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session); + pj_assert(inv != NULL); + + inv->pool = dlg->pool; + inv->role = PJSIP_ROLE_UAS; + inv->state = PJSIP_INV_STATE_NULL; + inv->dlg = dlg; + inv->options = options; + inv->notify = PJ_TRUE; + inv->cause = (pjsip_status_code) 0; + + /* Create flip-flop pool (see ticket #877) */ + /* (using inv->obj_name as temporary variable for pool names */ + pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg->pool); + inv->pool_prov = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name, + POOL_INIT_SIZE, POOL_INC_SIZE); + inv->pool_active = pjsip_endpt_create_pool(dlg->endpt, inv->obj_name, + POOL_INIT_SIZE, POOL_INC_SIZE); + + /* Object name will use the same dialog pointer. */ + pj_ansi_snprintf(inv->obj_name, PJ_MAX_OBJ_NAME, "inv%p", dlg); + + /* Process SDP in message body, if present. */ + sdp_info = pjsip_rdata_get_sdp_info(rdata); + if (sdp_info->sdp_err) { + pjsip_dlg_dec_lock(dlg); + return sdp_info->sdp_err; + } + + /* Create negotiator. */ + if (sdp_info->sdp) { + status = pjmedia_sdp_neg_create_w_remote_offer(inv->pool, local_sdp, + sdp_info->sdp, + &inv->neg); + + } else if (local_sdp) { + status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, + local_sdp, &inv->neg); + } else { + status = PJ_SUCCESS; + } + + if (status != PJ_SUCCESS) { + pjsip_dlg_dec_lock(dlg); + return status; + } + + /* Register invite as dialog usage. */ + status = pjsip_dlg_add_usage(dlg, &mod_inv.mod, inv); + if (status != PJ_SUCCESS) { + pjsip_dlg_dec_lock(dlg); + return status; + } + + /* Increment session in the dialog. */ + pjsip_dlg_inc_session(dlg, &mod_inv.mod); + + /* Save the invite transaction. */ + inv->invite_tsx = pjsip_rdata_get_tsx(rdata); + + /* Attach our data to the transaction. */ + tsx_inv_data = PJ_POOL_ZALLOC_T(inv->invite_tsx->pool, struct tsx_inv_data); + tsx_inv_data->inv = inv; + inv->invite_tsx->mod_data[mod_inv.mod.id] = tsx_inv_data; + + /* Create 100rel handler */ + if (inv->options & PJSIP_INV_REQUIRE_100REL) { + pjsip_100rel_attach(inv); + } + + /* Done */ + pjsip_dlg_dec_lock(dlg); + *p_inv = inv; + + PJ_LOG(5,(inv->obj_name, "UAS invite session created for dialog %s", + dlg->obj_name)); + + return PJ_SUCCESS; +} + +/* + * Forcefully terminate the session. + */ +PJ_DEF(pj_status_t) pjsip_inv_terminate( pjsip_inv_session *inv, + int st_code, + pj_bool_t notify) +{ + PJ_ASSERT_RETURN(inv, PJ_EINVAL); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(inv->dlg); + + /* Set callback notify flag. */ + inv->notify = notify; + + /* If there's pending transaction, terminate the transaction. + * This may subsequently set the INVITE session state to + * disconnected. + */ + if (inv->invite_tsx && + inv->invite_tsx->state <= PJSIP_TSX_STATE_COMPLETED) + { + pjsip_tsx_terminate(inv->invite_tsx, st_code); + + } + + /* Set cause. */ + inv_set_cause(inv, st_code, NULL); + + /* Forcefully terminate the session if state is not DISCONNECTED */ + if (inv->state != PJSIP_INV_STATE_DISCONNECTED) { + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, NULL); + } + + /* Done. + * The dec_lock() below will actually destroys the dialog if it + * has no other session. + */ + pjsip_dlg_dec_lock(inv->dlg); + + return PJ_SUCCESS; +} + + +/* + * Restart UAC session, possibly because app or us wants to re-send the + * INVITE request due to 401/407 challenge or 3xx response. + */ +PJ_DEF(pj_status_t) pjsip_inv_uac_restart(pjsip_inv_session *inv, + pj_bool_t new_offer) +{ + PJ_ASSERT_RETURN(inv, PJ_EINVAL); + + inv->state = PJSIP_INV_STATE_NULL; + inv->invite_tsx = NULL; + if (inv->last_answer) { + pjsip_tx_data_dec_ref(inv->last_answer); + inv->last_answer = NULL; + } + + if (new_offer && inv->neg) { + pjmedia_sdp_neg_state neg_state; + + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) { + pjmedia_sdp_neg_cancel_offer(inv->neg); + } + } + + return PJ_SUCCESS; +} + + +static void *clone_sdp(pj_pool_t *pool, const void *data, unsigned len) +{ + PJ_UNUSED_ARG(len); + return pjmedia_sdp_session_clone(pool, (const pjmedia_sdp_session*)data); +} + +static int print_sdp(pjsip_msg_body *body, char *buf, pj_size_t len) +{ + return pjmedia_sdp_print((const pjmedia_sdp_session*)body->data, buf, len); +} + + +PJ_DEF(pj_status_t) pjsip_create_sdp_body( pj_pool_t *pool, + pjmedia_sdp_session *sdp, + pjsip_msg_body **p_body) +{ + const pj_str_t STR_APPLICATION = { "application", 11}; + const pj_str_t STR_SDP = { "sdp", 3 }; + pjsip_msg_body *body; + + body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + PJ_ASSERT_RETURN(body != NULL, PJ_ENOMEM); + + pjsip_media_type_init(&body->content_type, (pj_str_t*)&STR_APPLICATION, + (pj_str_t*)&STR_SDP); + body->data = sdp; + body->len = 0; + body->clone_data = &clone_sdp; + body->print_body = &print_sdp; + + *p_body = body; + + return PJ_SUCCESS; +} + +static pjsip_msg_body *create_sdp_body(pj_pool_t *pool, + const pjmedia_sdp_session *c_sdp) +{ + pjsip_msg_body *body; + pj_status_t status; + + status = pjsip_create_sdp_body(pool, + pjmedia_sdp_session_clone(pool, c_sdp), + &body); + + if (status != PJ_SUCCESS) + return NULL; + + return body; +} + +/* + * Create initial INVITE request. + */ +PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv, + pjsip_tx_data **p_tdata ) +{ + pjsip_tx_data *tdata; + const pjsip_hdr *hdr; + pj_bool_t has_sdp; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + /* State MUST be NULL or CONFIRMED. */ + PJ_ASSERT_RETURN(inv->state == PJSIP_INV_STATE_NULL || + inv->state == PJSIP_INV_STATE_CONFIRMED, + PJ_EINVALIDOP); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(inv->dlg); + + /* Create the INVITE request. */ + status = pjsip_dlg_create_request(inv->dlg, pjsip_get_invite_method(), -1, + &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* If this is the first INVITE, then copy the headers from inv_hdr. + * These are the headers parsed from the request URI when the + * dialog was created. + */ + if (inv->state == PJSIP_INV_STATE_NULL) { + hdr = inv->dlg->inv_hdr.next; + + while (hdr != &inv->dlg->inv_hdr) { + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, hdr)); + hdr = hdr->next; + } + } + + /* See if we have SDP to send. */ + if (inv->neg) { + pjmedia_sdp_neg_state neg_state; + + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + + has_sdp = (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER || + (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO && + pjmedia_sdp_neg_has_local_answer(inv->neg))); + + + } else { + has_sdp = PJ_FALSE; + } + + /* Add SDP, if any. */ + if (has_sdp) { + const pjmedia_sdp_session *offer; + + status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + goto on_return; + } + + tdata->msg->body = create_sdp_body(tdata->pool, offer); + } + + /* Add Allow header. */ + if (inv->dlg->add_allow) { + hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_ALLOW, NULL); + if (hdr) { + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, hdr)); + } + } + + /* Add Supported header */ + hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_SUPPORTED, NULL); + if (hdr) { + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, hdr)); + } + + /* Add Require header. */ + if ((inv->options & PJSIP_INV_REQUIRE_100REL) || + (inv->options & PJSIP_INV_REQUIRE_TIMER)) + { + pjsip_require_hdr *hreq; + + hreq = pjsip_require_hdr_create(tdata->pool); + + if (inv->options & PJSIP_INV_REQUIRE_100REL) + hreq->values[hreq->count++] = pj_str("100rel"); + if (inv->options & PJSIP_INV_REQUIRE_TIMER) + hreq->values[hreq->count++] = pj_str("timer"); + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hreq); + } + + status = pjsip_timer_update_req(inv, tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Done. */ + *p_tdata = tdata; + + +on_return: + pjsip_dlg_dec_lock(inv->dlg); + return status; +} + + +/* Util: swap pool */ +static void swap_pool(pj_pool_t **p1, pj_pool_t **p2) +{ + pj_pool_t *tmp = *p1; + *p1 = *p2; + *p2 = tmp; +} + + +/* + * Initiate SDP negotiation in the SDP negotiator. + */ +static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv ) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(pjmedia_sdp_neg_get_state(inv->neg) == + PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + PJMEDIA_SDPNEG_EINSTATE); + + status = pjmedia_sdp_neg_negotiate(inv->pool_prov, inv->neg, 0); + + PJ_LOG(5,(inv->obj_name, "SDP negotiation done, status=%d", status)); + + if (mod_inv.cb.on_media_update && inv->notify) + (*mod_inv.cb.on_media_update)(inv, status); + + /* Invite session may have been terminated by the application even + * after a successful SDP negotiation, for example when no audio + * codec is present in the offer (see ticket #1034). + */ + if (inv->state != PJSIP_INV_STATE_DISCONNECTED) { + + /* Swap the flip-flop pool when SDP negotiation success. */ + if (status == PJ_SUCCESS) { + swap_pool(&inv->pool_prov, &inv->pool_active); + } + + /* Reset the provisional pool regardless SDP negotiation result. */ + pj_pool_reset(inv->pool_prov); + + } else { + + status = PJSIP_ERRNO_FROM_SIP_STATUS(inv->cause); + } + + return status; +} + +/* + * Check in incoming message for SDP offer/answer. + */ +static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_rx_data *rdata) +{ + struct tsx_inv_data *tsx_inv_data; + pj_status_t status; + pjsip_msg *msg; + pjsip_rdata_sdp_info *sdp_info; + + /* Check if SDP is present in the message. */ + + msg = rdata->msg_info.msg; + if (msg->body == NULL) { + /* Message doesn't have body. */ + return PJ_SUCCESS; + } + + sdp_info = pjsip_rdata_get_sdp_info(rdata); + if (sdp_info->body.ptr == NULL) { + /* Message body is not "application/sdp" */ + return PJMEDIA_SDP_EINSDP; + } + + /* Get/attach invite session's transaction data */ + tsx_inv_data = (struct tsx_inv_data*) tsx->mod_data[mod_inv.mod.id]; + if (tsx_inv_data == NULL) { + tsx_inv_data = PJ_POOL_ZALLOC_T(tsx->pool, struct tsx_inv_data); + tsx_inv_data->inv = inv; + tsx->mod_data[mod_inv.mod.id] = tsx_inv_data; + } + + /* MUST NOT do multiple SDP offer/answer in a single transaction, + * EXCEPT if: + * - this is an initial UAC INVITE transaction (i.e. not re-INVITE), and + * - the previous negotiation was done on an early media (18x) and + * this response is a final/2xx response, and + * - the 2xx response has different To tag than the 18x response + * (i.e. the request has forked). + * + * The exception above is to add a rudimentary support for early media + * forking (sample case: custom ringback). See this ticket for more + * info: http://trac.pjsip.org/repos/ticket/657 + */ + if (tsx_inv_data->sdp_done) { + pj_str_t res_tag; + + res_tag = rdata->msg_info.to->tag; + + /* Allow final response after SDP has been negotiated in early + * media, IF this response is a final response with different + * tag. + */ + if (tsx->role == PJSIP_ROLE_UAC && + rdata->msg_info.msg->line.status.code/100 == 2 && + tsx_inv_data->done_early && + pj_strcmp(&tsx_inv_data->done_tag, &res_tag)) + { + const pjmedia_sdp_session *reoffer_sdp = NULL; + + PJ_LOG(4,(inv->obj_name, "Received forked final response " + "after SDP negotiation has been done in early " + "media. Renegotiating SDP..")); + + /* Retrieve original SDP offer from INVITE request */ + reoffer_sdp = (const pjmedia_sdp_session*) + tsx->last_tx->msg->body->data; + + /* Feed the original offer to negotiator */ + status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov, + inv->neg, + reoffer_sdp); + if (status != PJ_SUCCESS) { + PJ_LOG(1,(inv->obj_name, "Error updating local offer for " + "forked 2xx response (err=%d)", status)); + return status; + } + + } else { + + if (rdata->msg_info.msg->body) { + PJ_LOG(4,(inv->obj_name, "SDP negotiation done, message " + "body is ignored")); + } + return PJ_SUCCESS; + } + } + + /* Process the SDP body. */ + if (sdp_info->sdp_err) { + PJ_PERROR(4,(THIS_FILE, sdp_info->sdp_err, + "Error parsing SDP in %s", + pjsip_rx_data_get_info(rdata))); + return PJMEDIA_SDP_EINSDP; + } + + pj_assert(sdp_info->sdp != NULL); + + /* The SDP can be an offer or answer, depending on negotiator's state */ + + if (inv->neg == NULL || + pjmedia_sdp_neg_get_state(inv->neg) == PJMEDIA_SDP_NEG_STATE_DONE) + { + + /* This is an offer. */ + + PJ_LOG(5,(inv->obj_name, "Got SDP offer in %s", + pjsip_rx_data_get_info(rdata))); + + if (inv->neg == NULL) { + status=pjmedia_sdp_neg_create_w_remote_offer(inv->pool, NULL, + sdp_info->sdp, + &inv->neg); + } else { + status=pjmedia_sdp_neg_set_remote_offer(inv->pool_prov, inv->neg, + sdp_info->sdp); + } + + if (status != PJ_SUCCESS) { + PJ_PERROR(4,(THIS_FILE, status, "Error processing SDP offer in %", + pjsip_rx_data_get_info(rdata))); + return PJMEDIA_SDP_EINSDP; + } + + /* Inform application about remote offer. */ + if (mod_inv.cb.on_rx_offer && inv->notify) { + + (*mod_inv.cb.on_rx_offer)(inv, sdp_info->sdp); + + } + + /* application must have supplied an answer at this point. */ + if (pjmedia_sdp_neg_get_state(inv->neg) != + PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) + { + return PJ_EINVALIDOP; + } + + } else if (pjmedia_sdp_neg_get_state(inv->neg) == + PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) + { + int status_code; + + /* This is an answer. + * Process and negotiate remote answer. + */ + + PJ_LOG(5,(inv->obj_name, "Got SDP answer in %s", + pjsip_rx_data_get_info(rdata))); + + status = pjmedia_sdp_neg_set_remote_answer(inv->pool_prov, inv->neg, + sdp_info->sdp); + + if (status != PJ_SUCCESS) { + PJ_PERROR(4,(THIS_FILE, status, "Error processing SDP answer in %s", + pjsip_rx_data_get_info(rdata))); + return PJMEDIA_SDP_EINSDP; + } + + /* Negotiate SDP */ + + inv_negotiate_sdp(inv); + + /* Mark this transaction has having SDP offer/answer done, and + * save the reference to the To tag + */ + + tsx_inv_data->sdp_done = 1; + status_code = rdata->msg_info.msg->line.status.code; + tsx_inv_data->done_early = (status_code/100==1); + pj_strdup(tsx->pool, &tsx_inv_data->done_tag, + &rdata->msg_info.to->tag); + + } else { + + PJ_LOG(5,(THIS_FILE, "Ignored SDP in %s: negotiator state is %s", + pjsip_rx_data_get_info(rdata), + pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_get_state(inv->neg)))); + } + + return PJ_SUCCESS; +} + + +/* + * Process INVITE answer, for both initial and subsequent re-INVITE + */ +static pj_status_t process_answer( pjsip_inv_session *inv, + int st_code, + pjsip_tx_data *tdata, + const pjmedia_sdp_session *local_sdp) +{ + pj_status_t status; + const pjmedia_sdp_session *sdp = NULL; + + /* If local_sdp is specified, then we MUST NOT have answered the + * offer before. + */ + if (local_sdp && (st_code/100==1 || st_code/100==2)) { + + if (inv->neg == NULL) { + status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, + local_sdp, + &inv->neg); + } else if (pjmedia_sdp_neg_get_state(inv->neg)== + PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER) + { + status = pjmedia_sdp_neg_set_local_answer(inv->pool_prov, inv->neg, + local_sdp); + } else { + + /* Can not specify local SDP at this state. */ + pj_assert(0); + status = PJMEDIA_SDPNEG_EINSTATE; + } + + if (status != PJ_SUCCESS) + return status; + + } + + + /* If SDP negotiator is ready, start negotiation. */ + if (st_code/100==2 || (st_code/10==18 && st_code!=180)) { + + pjmedia_sdp_neg_state neg_state; + + /* Start nego when appropriate. */ + neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) : + PJMEDIA_SDP_NEG_STATE_NULL; + + if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) { + + status = pjmedia_sdp_neg_get_neg_local(inv->neg, &sdp); + + } else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO && + pjmedia_sdp_neg_has_local_answer(inv->neg) ) + { + struct tsx_inv_data *tsx_inv_data; + + /* Get invite session's transaction data */ + tsx_inv_data = (struct tsx_inv_data*) + inv->invite_tsx->mod_data[mod_inv.mod.id]; + + status = inv_negotiate_sdp(inv); + if (status != PJ_SUCCESS) + return status; + + /* Mark this transaction has having SDP offer/answer done. */ + tsx_inv_data->sdp_done = 1; + + status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp); + } + } + + /* Include SDP when it's available for 2xx and 18x (but not 180) response. + * Subsequent response will include this SDP. + * + * Note note: + * - When offer/answer has been completed in reliable 183, we MUST NOT + * send SDP in 2xx response. So if we don't have SDP to send, clear + * the SDP in the message body ONLY if 100rel is active in this + * session. + */ + if (sdp) { + tdata->msg->body = create_sdp_body(tdata->pool, sdp); + } else { + if (inv->options & PJSIP_INV_REQUIRE_100REL) { + tdata->msg->body = NULL; + } + } + + + return PJ_SUCCESS; +} + + +/* + * Create first response to INVITE + */ +PJ_DEF(pj_status_t) pjsip_inv_initial_answer( pjsip_inv_session *inv, + pjsip_rx_data *rdata, + int st_code, + const pj_str_t *st_text, + const pjmedia_sdp_session *sdp, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pj_status_t status; + pjsip_status_code st_code2; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + /* Must have INVITE transaction. */ + PJ_ASSERT_RETURN(inv->invite_tsx, PJ_EBUG); + + pj_log_push_indent(); + + pjsip_dlg_inc_lock(inv->dlg); + + /* Create response */ + status = pjsip_dlg_create_response(inv->dlg, rdata, st_code, st_text, + &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Invoke Session Timers module */ + status = pjsip_timer_process_req(inv, rdata, &st_code2); + if (status != PJ_SUCCESS) { + pj_status_t status2; + + status2 = pjsip_dlg_modify_response(inv->dlg, tdata, st_code2, NULL); + if (status2 != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + goto on_return; + } + status2 = pjsip_timer_update_resp(inv, tdata); + if (status2 == PJ_SUCCESS) + *p_tdata = tdata; + else + pjsip_tx_data_dec_ref(tdata); + + goto on_return; + } + + /* Process SDP in answer */ + status = process_answer(inv, st_code, tdata, sdp); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + goto on_return; + } + + /* Save this answer */ + inv->last_answer = tdata; + pjsip_tx_data_add_ref(inv->last_answer); + PJ_LOG(5,(inv->dlg->obj_name, "Initial answer %s", + pjsip_tx_data_get_info(inv->last_answer))); + + /* Invoke Session Timers */ + pjsip_timer_update_resp(inv, tdata); + + *p_tdata = tdata; + +on_return: + pjsip_dlg_dec_lock(inv->dlg); + pj_log_pop_indent(); + return status; +} + + +/* + * Answer initial INVITE + * Re-INVITE will be answered automatically, and will not use this function. + */ +PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv, + int st_code, + const pj_str_t *st_text, + const pjmedia_sdp_session *local_sdp, + pjsip_tx_data **p_tdata ) +{ + pjsip_tx_data *last_res; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + /* Must have INVITE transaction. */ + PJ_ASSERT_RETURN(inv->invite_tsx, PJ_EBUG); + + /* Must have created an answer before */ + PJ_ASSERT_RETURN(inv->last_answer, PJ_EINVALIDOP); + + pj_log_push_indent(); + + pjsip_dlg_inc_lock(inv->dlg); + + /* Modify last response. */ + last_res = inv->last_answer; + status = pjsip_dlg_modify_response(inv->dlg, last_res, st_code, st_text); + if (status != PJ_SUCCESS) + goto on_return; + + /* For non-2xx final response, strip message body */ + if (st_code >= 300) { + last_res->msg->body = NULL; + } + + /* Process SDP in answer */ + status = process_answer(inv, st_code, last_res, local_sdp); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(last_res); + goto on_return; + } + + /* Invoke Session Timers */ + pjsip_timer_update_resp(inv, last_res); + + *p_tdata = last_res; + +on_return: + pjsip_dlg_dec_lock(inv->dlg); + pj_log_pop_indent(); + return status; +} + + +/* + * Set local SDP offer/answer. + */ +PJ_DEF(pj_status_t) pjsip_inv_set_local_sdp(pjsip_inv_session *inv, + const pjmedia_sdp_session *sdp ) +{ + const pjmedia_sdp_session *offer; + pj_status_t status; + + PJ_ASSERT_RETURN(inv && sdp, PJ_EINVAL); + + /* If we have remote SDP offer, set local answer to respond to the offer, + * otherwise we set/modify our local offer (and create an SDP negotiator + * if we don't have one yet). + */ + if (inv->neg) { + pjmedia_sdp_neg_state neg_state = pjmedia_sdp_neg_get_state(inv->neg); + + if ((neg_state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER || + neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) && + pjmedia_sdp_neg_get_neg_remote(inv->neg, &offer) == PJ_SUCCESS) + { + status = pjsip_inv_set_sdp_answer(inv, sdp); + } else if (neg_state == PJMEDIA_SDP_NEG_STATE_DONE) { + status = pjmedia_sdp_neg_modify_local_offer(inv->pool, + inv->neg, sdp); + } else + return PJMEDIA_SDPNEG_EINSTATE; + } else { + status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, + sdp, &inv->neg); + } + + return status; +} + + +/* + * Set SDP answer. + */ +PJ_DEF(pj_status_t) pjsip_inv_set_sdp_answer( pjsip_inv_session *inv, + const pjmedia_sdp_session *sdp ) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(inv && sdp, PJ_EINVAL); + + pjsip_dlg_inc_lock(inv->dlg); + status = pjmedia_sdp_neg_set_local_answer( inv->pool_prov, inv->neg, sdp); + pjsip_dlg_dec_lock(inv->dlg); + + return status; +} + + +/* + * End session. + */ +PJ_DEF(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv, + int st_code, + const pj_str_t *st_text, + pjsip_tx_data **p_tdata ) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + pj_log_push_indent(); + + /* Set cause code. */ + inv_set_cause(inv, st_code, st_text); + + /* Create appropriate message. */ + switch (inv->state) { + case PJSIP_INV_STATE_CALLING: + case PJSIP_INV_STATE_EARLY: + case PJSIP_INV_STATE_INCOMING: + + if (inv->role == PJSIP_ROLE_UAC) { + + /* For UAC when session has not been confirmed, create CANCEL. */ + + /* MUST have the original UAC INVITE transaction. */ + PJ_ASSERT_RETURN(inv->invite_tsx != NULL, PJ_EBUG); + + /* But CANCEL should only be called when we have received a + * provisional response. If we haven't received any responses, + * just destroy the transaction. + */ + if (inv->invite_tsx->status_code < 100) { + + /* Do not stop INVITE retransmission, see ticket #506 */ + //pjsip_tsx_stop_retransmit(inv->invite_tsx); + inv->cancelling = PJ_TRUE; + inv->pending_cancel = PJ_TRUE; + *p_tdata = NULL; + PJ_LOG(4, (inv->obj_name, "Delaying CANCEL since no " + "provisional response is received yet")); + pj_log_pop_indent(); + return PJ_SUCCESS; + } + + /* The CSeq here assumes that the dialog is started with an + * INVITE session. This may not be correct; dialog can be + * started as SUBSCRIBE session. + * So fix this! + */ + status = pjsip_endpt_create_cancel(inv->dlg->endpt, + inv->invite_tsx->last_tx, + &tdata); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + return status; + } + + /* Set timeout for the INVITE transaction, in case UAS is not + * able to respond the INVITE with 487 final response. The + * timeout value is 64*T1. + */ + pjsip_tsx_set_timeout(inv->invite_tsx, 64 * pjsip_cfg()->tsx.t1); + + } else { + + /* For UAS, send a final response. */ + tdata = inv->invite_tsx->last_tx; + PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP); + + //status = pjsip_dlg_modify_response(inv->dlg, tdata, st_code, + // st_text); + status = pjsip_inv_answer(inv, st_code, st_text, NULL, &tdata); + } + break; + + case PJSIP_INV_STATE_CONNECTING: + case PJSIP_INV_STATE_CONFIRMED: + /* End Session Timer */ + pjsip_timer_end_session(inv); + + /* For established dialog, send BYE */ + status = pjsip_dlg_create_request(inv->dlg, pjsip_get_bye_method(), + -1, &tdata); + break; + + case PJSIP_INV_STATE_DISCONNECTED: + /* No need to do anything. */ + pj_log_pop_indent(); + return PJSIP_ESESSIONTERMINATED; + + default: + pj_assert(!"Invalid operation!"); + pj_log_pop_indent(); + return PJ_EINVALIDOP; + } + + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + return status; + } + + + /* Done */ + + inv->cancelling = PJ_TRUE; + *p_tdata = tdata; + + pj_log_pop_indent(); + return PJ_SUCCESS; +} + +/* Following redirection recursion, get next target from the target set and + * notify user. + * + * Returns PJ_FALSE if recursion fails (either because there's no more target + * or user rejects the recursion). If we return PJ_FALSE, caller should + * disconnect the session. + * + * Note: + * the event 'e' argument may be NULL. + */ +static pj_bool_t inv_uac_recurse(pjsip_inv_session *inv, int code, + const pj_str_t *reason, pjsip_event *e) +{ + pjsip_redirect_op op; + pjsip_target *target; + + /* Won't redirect if the callback is not implemented. */ + if (mod_inv.cb.on_redirected == NULL) + return PJ_FALSE; + + if (reason == NULL) + reason = pjsip_get_status_text(code); + + /* Set status of current target */ + pjsip_target_assign_status(inv->dlg->target_set.current, inv->dlg->pool, + code, reason); + + /* Fetch next target from the target set. We only want to + * process SIP/SIPS URI for now. + */ + for (;;) { + target = pjsip_target_set_get_next(&inv->dlg->target_set); + if (target == NULL) { + /* No more target. */ + return PJ_FALSE; + } + + if (!PJSIP_URI_SCHEME_IS_SIP(target->uri) && + !PJSIP_URI_SCHEME_IS_SIPS(target->uri)) + { + code = PJSIP_SC_UNSUPPORTED_URI_SCHEME; + reason = pjsip_get_status_text(code); + + /* Mark this target as unusable and fetch next target. */ + pjsip_target_assign_status(target, inv->dlg->pool, code, reason); + } else { + /* Found a target */ + break; + } + } + + /* We have target in 'target'. Set this target as current target + * and notify callback. + */ + pjsip_target_set_set_current(&inv->dlg->target_set, target); + + op = (*mod_inv.cb.on_redirected)(inv, target->uri, e); + + + /* Check what the application wants to do now */ + switch (op) { + case PJSIP_REDIRECT_ACCEPT: + case PJSIP_REDIRECT_STOP: + /* Must increment session counter, that's the convention of the + * pjsip_inv_process_redirect(). + */ + pjsip_dlg_inc_session(inv->dlg, &mod_inv.mod); + + /* Act on the recursion */ + pjsip_inv_process_redirect(inv, op, e); + return PJ_TRUE; + + case PJSIP_REDIRECT_PENDING: + /* Increment session so that the dialog/session is not destroyed + * while we're waiting for user confirmation. + */ + pjsip_dlg_inc_session(inv->dlg, &mod_inv.mod); + + /* Also clear the invite_tsx variable, otherwise when this tsx is + * terminated, it will also terminate the session. + */ + inv->invite_tsx = NULL; + + /* Done. The processing will continue once the application calls + * pjsip_inv_process_redirect(). + */ + return PJ_TRUE; + + case PJSIP_REDIRECT_REJECT: + /* Recursively call this function again to fetch next target, if any. + */ + return inv_uac_recurse(inv, PJSIP_SC_REQUEST_TERMINATED, NULL, e); + + } + + pj_assert(!"Should not reach here"); + return PJ_FALSE; +} + + +/* Process redirection/recursion */ +PJ_DEF(pj_status_t) pjsip_inv_process_redirect( pjsip_inv_session *inv, + pjsip_redirect_op op, + pjsip_event *e) +{ + const pjsip_status_code cancel_code = PJSIP_SC_REQUEST_TERMINATED; + pjsip_event usr_event; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(inv && op != PJSIP_REDIRECT_PENDING, PJ_EINVAL); + + if (e == NULL) { + PJSIP_EVENT_INIT_USER(usr_event, NULL, NULL, NULL, NULL); + e = &usr_event; + } + + pjsip_dlg_inc_lock(inv->dlg); + + /* Decrement session. That's the convention here to prevent the dialog + * or session from being destroyed while we're waiting for user + * confirmation. + */ + pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod); + + /* See what the application wants to do now */ + switch (op) { + case PJSIP_REDIRECT_ACCEPT: + /* User accept the redirection. Reset the session and resend the + * INVITE request. + */ + { + pjsip_tx_data *tdata; + pjsip_via_hdr *via; + + /* Get the original INVITE request. */ + tdata = inv->invite_req; + pjsip_tx_data_add_ref(tdata); + + /* Restore strict route set. + * See http://trac.pjsip.org/repos/ticket/492 + */ + pjsip_restore_strict_route_set(tdata); + + /* Set target */ + tdata->msg->line.req.uri = (pjsip_uri*) + pjsip_uri_clone(tdata->pool, inv->dlg->target_set.current->uri); + + /* Remove branch param in Via header. */ + via = (pjsip_via_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->branch_param.slen = 0; + + /* Reset message destination info (see #1248). */ + pj_bzero(&tdata->dest_info, sizeof(tdata->dest_info)); + + /* Must invalidate the message! */ + pjsip_tx_data_invalidate_msg(tdata); + + /* Reset the session */ + pjsip_inv_uac_restart(inv, PJ_FALSE); + + /* (re)Send the INVITE request */ + status = pjsip_inv_send_msg(inv, tdata); + } + break; + + case PJSIP_REDIRECT_STOP: + /* User doesn't want the redirection. Disconnect the session now. */ + inv_set_cause(inv, cancel_code, pjsip_get_status_text(cancel_code)); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + + /* Caller should expect that the invite session is gone now, so + * we don't need to set status to PJSIP_ESESSIONTERMINATED here. + */ + break; + + case PJSIP_REDIRECT_REJECT: + /* Current target is rejected. Fetch next target if any. */ + if (inv_uac_recurse(inv, cancel_code, NULL, NULL) == PJ_FALSE) { + inv_set_cause(inv, cancel_code, + pjsip_get_status_text(cancel_code)); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + + /* Tell caller that the invite session is gone now */ + status = PJSIP_ESESSIONTERMINATED; + } + break; + + + case PJSIP_REDIRECT_PENDING: + pj_assert(!"Should not happen"); + break; + } + + + pjsip_dlg_dec_lock(inv->dlg); + + return status; +} + + +/* + * Create re-INVITE. + */ +PJ_DEF(pj_status_t) pjsip_inv_reinvite( pjsip_inv_session *inv, + const pj_str_t *new_contact, + const pjmedia_sdp_session *new_offer, + pjsip_tx_data **p_tdata ) +{ + pj_status_t status; + pjsip_contact_hdr *contact_hdr = NULL; + + /* Check arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + /* Must NOT have a pending INVITE transaction */ + if (inv->invite_tsx!=NULL) + return PJ_EINVALIDOP; + + pj_log_push_indent(); + + pjsip_dlg_inc_lock(inv->dlg); + + if (new_contact) { + pj_str_t tmp; + const pj_str_t STR_CONTACT = { "Contact", 7 }; + + pj_strdup_with_null(inv->dlg->pool, &tmp, new_contact); + contact_hdr = (pjsip_contact_hdr*) + pjsip_parse_hdr(inv->dlg->pool, &STR_CONTACT, + tmp.ptr, tmp.slen, NULL); + if (!contact_hdr) { + status = PJSIP_EINVALIDURI; + goto on_return; + } + } + + + if (new_offer) { + if (!inv->neg) { + status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, + new_offer, + &inv->neg); + if (status != PJ_SUCCESS) + goto on_return; + + } else switch (pjmedia_sdp_neg_get_state(inv->neg)) { + + case PJMEDIA_SDP_NEG_STATE_NULL: + pj_assert(!"Unexpected SDP neg state NULL"); + status = PJ_EBUG; + goto on_return; + + case PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER: + PJ_LOG(4,(inv->obj_name, + "pjsip_inv_reinvite: already have an offer, new " + "offer is ignored")); + break; + + case PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER: + status = pjmedia_sdp_neg_set_local_answer(inv->pool_prov, + inv->neg, + new_offer); + if (status != PJ_SUCCESS) + goto on_return; + break; + + case PJMEDIA_SDP_NEG_STATE_WAIT_NEGO: + PJ_LOG(4,(inv->obj_name, + "pjsip_inv_reinvite: SDP in WAIT_NEGO state, new " + "offer is ignored")); + break; + + case PJMEDIA_SDP_NEG_STATE_DONE: + status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov, + inv->neg, + new_offer); + if (status != PJ_SUCCESS) + goto on_return; + break; + } + } + + if (contact_hdr) + inv->dlg->local.contact = contact_hdr; + + status = pjsip_inv_invite(inv, p_tdata); + +on_return: + pjsip_dlg_dec_lock(inv->dlg); + pj_log_pop_indent(); + return status; +} + +/* + * Create UPDATE. + */ +PJ_DEF(pj_status_t) pjsip_inv_update ( pjsip_inv_session *inv, + const pj_str_t *new_contact, + const pjmedia_sdp_session *offer, + pjsip_tx_data **p_tdata ) +{ + pjsip_contact_hdr *contact_hdr = NULL; + pjsip_tx_data *tdata = NULL; + pjmedia_sdp_session *sdp_copy; + pj_status_t status = PJ_SUCCESS; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + /* Dialog must have been established */ + PJ_ASSERT_RETURN(inv->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED, + PJ_EINVALIDOP); + + /* Invite session must not have been disconnected */ + PJ_ASSERT_RETURN(inv->state < PJSIP_INV_STATE_DISCONNECTED, + PJ_EINVALIDOP); + + pj_log_push_indent(); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(inv->dlg); + + /* Process offer, if any */ + if (offer) { + if (pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) { + PJ_LOG(4,(inv->dlg->obj_name, + "Invalid SDP offer/answer state for UPDATE")); + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Notify negotiator about the new offer. This will fix the offer + * with correct SDP origin. + */ + status = pjmedia_sdp_neg_modify_local_offer(inv->pool_prov, inv->neg, + offer); + if (status != PJ_SUCCESS) + goto on_error; + + /* Retrieve the "fixed" offer from negotiator */ + pjmedia_sdp_neg_get_neg_local(inv->neg, &offer); + } + + /* Update Contact if required */ + if (new_contact) { + pj_str_t tmp; + const pj_str_t STR_CONTACT = { "Contact", 7 }; + + pj_strdup_with_null(inv->dlg->pool, &tmp, new_contact); + contact_hdr = (pjsip_contact_hdr*) + pjsip_parse_hdr(inv->dlg->pool, &STR_CONTACT, + tmp.ptr, tmp.slen, NULL); + if (!contact_hdr) { + status = PJSIP_EINVALIDURI; + goto on_error; + } + + inv->dlg->local.contact = contact_hdr; + } + + /* Create request */ + status = pjsip_dlg_create_request(inv->dlg, &pjsip_update_method, + -1, &tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Attach SDP body */ + if (offer) { + sdp_copy = pjmedia_sdp_session_clone(tdata->pool, offer); + pjsip_create_sdp_body(tdata->pool, sdp_copy, &tdata->msg->body); + } + + /* Unlock dialog. */ + pjsip_dlg_dec_lock(inv->dlg); + + status = pjsip_timer_update_req(inv, tdata); + if (status != PJ_SUCCESS) + goto on_error; + + *p_tdata = tdata; + + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + if (tdata) + pjsip_tx_data_dec_ref(tdata); + + /* Unlock dialog. */ + pjsip_dlg_dec_lock(inv->dlg); + + pj_log_pop_indent(); + return status; +} + +/* + * Create an ACK request. + */ +PJ_DEF(pj_status_t) pjsip_inv_create_ack(pjsip_inv_session *inv, + int cseq, + pjsip_tx_data **p_tdata) +{ + const pjmedia_sdp_session *sdp = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(inv->dlg); + + /* Destroy last_ack */ + if (inv->last_ack) { + pjsip_tx_data_dec_ref(inv->last_ack); + inv->last_ack = NULL; + } + + /* Create new ACK request */ + status = pjsip_dlg_create_request(inv->dlg, pjsip_get_ack_method(), + cseq, &inv->last_ack); + if (status != PJ_SUCCESS) { + pjsip_dlg_dec_lock(inv->dlg); + return status; + } + + /* See if we have pending SDP answer to send */ + sdp = inv_has_pending_answer(inv, inv->invite_tsx); + if (sdp) { + inv->last_ack->msg->body = create_sdp_body(inv->last_ack->pool, sdp); + } + + /* Keep this for subsequent response retransmission */ + inv->last_ack_cseq = cseq; + pjsip_tx_data_add_ref(inv->last_ack); + + /* Done */ + *p_tdata = inv->last_ack; + + /* Unlock dialog. */ + pjsip_dlg_dec_lock(inv->dlg); + + return PJ_SUCCESS; +} + +/* + * Send a request or response message. + */ +PJ_DEF(pj_status_t) pjsip_inv_send_msg( pjsip_inv_session *inv, + pjsip_tx_data *tdata) +{ + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL); + + pj_log_push_indent(); + + PJ_LOG(5,(inv->obj_name, "Sending %s", + pjsip_tx_data_get_info(tdata))); + + if (tdata->msg->type == PJSIP_REQUEST_MSG) { + struct tsx_inv_data *tsx_inv_data; + + pjsip_dlg_inc_lock(inv->dlg); + + /* Check again that we didn't receive incoming re-INVITE */ + if (tdata->msg->line.req.method.id==PJSIP_INVITE_METHOD && + inv->invite_tsx) + { + pjsip_tx_data_dec_ref(tdata); + pjsip_dlg_dec_lock(inv->dlg); + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Associate our data in outgoing invite transaction */ + tsx_inv_data = PJ_POOL_ZALLOC_T(inv->pool, struct tsx_inv_data); + tsx_inv_data->inv = inv; + + pjsip_dlg_dec_lock(inv->dlg); + + status = pjsip_dlg_send_request(inv->dlg, tdata, mod_inv.mod.id, + tsx_inv_data); + if (status != PJ_SUCCESS) { + goto on_error; + } + + } else { + pjsip_cseq_hdr *cseq; + + /* Can only do this to send response to original INVITE + * request. + */ + PJ_ASSERT_RETURN((cseq=(pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL))!=NULL + && (cseq->cseq == inv->invite_tsx->cseq), + PJ_EINVALIDOP); + + if (inv->options & PJSIP_INV_REQUIRE_100REL) { + status = pjsip_100rel_tx_response(inv, tdata); + } else + { + status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata); + } + + if (status != PJ_SUCCESS) { + goto on_error; + } + } + + /* Done */ + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + pj_log_pop_indent(); + return status; +} + + +/* + * Respond to incoming CANCEL request. + */ +static void inv_respond_incoming_cancel(pjsip_inv_session *inv, + pjsip_transaction *cancel_tsx, + pjsip_rx_data *rdata) +{ + pjsip_tx_data *tdata; + pjsip_transaction *invite_tsx; + pj_str_t key; + pj_status_t status; + + /* See if we have matching INVITE server transaction: */ + + pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAS, + pjsip_get_invite_method(), rdata); + invite_tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); + + if (invite_tsx == NULL) { + + /* Invite transaction not found! + * Respond CANCEL with 481 (RFC 3261 Section 9.2 page 55) + */ + status = pjsip_dlg_create_response( inv->dlg, rdata, 481, NULL, + &tdata); + + } else { + /* Always answer CANCEL will 200 (OK) regardless of + * the state of the INVITE transaction. + */ + status = pjsip_dlg_create_response( inv->dlg, rdata, 200, NULL, + &tdata); + } + + /* See if we have created the response successfully. */ + if (status != PJ_SUCCESS) return; + + /* Send the CANCEL response */ + status = pjsip_dlg_send_response(inv->dlg, cancel_tsx, tdata); + if (status != PJ_SUCCESS) return; + + + /* See if we need to terminate the UAS INVITE transaction + * with 487 (Request Terminated) response. + */ + if (invite_tsx && invite_tsx->status_code < 200) { + + pj_assert(invite_tsx->last_tx != NULL); + + tdata = invite_tsx->last_tx; + + status = pjsip_dlg_modify_response(inv->dlg, tdata, 487, NULL); + if (status == PJ_SUCCESS) { + /* Remove the message body */ + tdata->msg->body = NULL; + if (inv->options & PJSIP_INV_REQUIRE_100REL) { + status = pjsip_100rel_tx_response(inv, tdata); + } else { + status = pjsip_dlg_send_response(inv->dlg, invite_tsx, + tdata); + } + } + } + + if (invite_tsx) + pj_mutex_unlock(invite_tsx->mutex); +} + + +/* + * Respond to incoming BYE request. + */ +static void inv_respond_incoming_bye( pjsip_inv_session *inv, + pjsip_transaction *bye_tsx, + pjsip_rx_data *rdata, + pjsip_event *e ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + + /* Respond BYE with 200: */ + + status = pjsip_dlg_create_response(inv->dlg, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) return; + + status = pjsip_dlg_send_response(inv->dlg, bye_tsx, tdata); + if (status != PJ_SUCCESS) return; + + /* Terminate session: */ + + if (inv->state != PJSIP_INV_STATE_DISCONNECTED) { + inv_set_cause(inv, PJSIP_SC_OK, NULL); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } +} + +/* + * Respond to BYE request. + */ +static void inv_handle_bye_response( pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_rx_data *rdata, + pjsip_event *e ) +{ + pj_status_t status; + + if (e->body.tsx_state.type != PJSIP_EVENT_RX_MSG) { + inv_set_cause(inv, PJSIP_SC_OK, NULL); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + return; + } + + /* Handle 401/407 challenge. */ + if (tsx->status_code == 401 || tsx->status_code == 407) { + + pjsip_tx_data *tdata; + + status = pjsip_auth_clt_reinit_req( &inv->dlg->auth_sess, + rdata, + tsx->last_tx, + &tdata); + + if (status != PJ_SUCCESS) { + + /* Does not have proper credentials. + * End the session anyway. + */ + inv_set_cause(inv, PJSIP_SC_OK, NULL); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + + } else { + struct tsx_inv_data *tsx_inv_data; + + tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id]; + if (tsx_inv_data) + tsx_inv_data->retrying = PJ_TRUE; + + /* Re-send BYE. */ + status = pjsip_inv_send_msg(inv, tdata); + } + + } else { + + /* End the session. */ + inv_set_cause(inv, PJSIP_SC_OK, NULL); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } + +} + +/* + * Respond to incoming UPDATE request. + */ +static void inv_respond_incoming_update(pjsip_inv_session *inv, + pjsip_rx_data *rdata) +{ + pjmedia_sdp_neg_state neg_state; + pj_status_t status; + pjsip_tx_data *tdata = NULL; + pjsip_status_code st_code; + + /* Invoke Session Timers module */ + status = pjsip_timer_process_req(inv, rdata, &st_code); + if (status != PJ_SUCCESS) { + status = pjsip_dlg_create_response(inv->dlg, rdata, st_code, + NULL, &tdata); + goto on_return; + } + + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + + /* Send 491 if we receive UPDATE while we're waiting for an answer */ + if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) { + status = pjsip_dlg_create_response(inv->dlg, rdata, + PJSIP_SC_REQUEST_PENDING, NULL, + &tdata); + } + /* Send 500 with Retry-After header set randomly between 0 and 10 if we + * receive UPDATE while we haven't sent answer. + */ + else if (neg_state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER || + neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) { + status = pjsip_dlg_create_response(inv->dlg, rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR, + NULL, &tdata); + + /* If UPDATE doesn't contain SDP, just respond with 200/OK. + * This is a valid scenario according to session-timer draft. + */ + } else if (rdata->msg_info.msg->body == NULL) { + + status = pjsip_dlg_create_response(inv->dlg, rdata, + 200, NULL, &tdata); + + } else { + /* We receive new offer from remote */ + inv_check_sdp_in_incoming_msg(inv, pjsip_rdata_get_tsx(rdata), rdata); + + /* Application MUST have supplied the answer by now. + * If so, negotiate the SDP. + */ + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + if (neg_state != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO || + (status=inv_negotiate_sdp(inv)) != PJ_SUCCESS) + { + /* Negotiation has failed. If negotiator is still + * stuck at non-DONE state, cancel any ongoing offer. + */ + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + if (neg_state != PJMEDIA_SDP_NEG_STATE_DONE) { + pjmedia_sdp_neg_cancel_offer(inv->neg); + } + + status = pjsip_dlg_create_response(inv->dlg, rdata, + PJSIP_SC_NOT_ACCEPTABLE_HERE, + NULL, &tdata); + } else { + /* New media has been negotiated successfully, send 200/OK */ + status = pjsip_dlg_create_response(inv->dlg, rdata, + PJSIP_SC_OK, NULL, &tdata); + if (status == PJ_SUCCESS) { + const pjmedia_sdp_session *sdp; + status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp); + if (status == PJ_SUCCESS) + tdata->msg->body = create_sdp_body(tdata->pool, sdp); + } + } + } + +on_return: + /* Invoke Session Timers */ + if (status == PJ_SUCCESS) + status = pjsip_timer_update_resp(inv, tdata); + + if (status != PJ_SUCCESS) { + if (tdata != NULL) { + pjsip_tx_data_dec_ref(tdata); + tdata = NULL; + } + return; + } + + pjsip_dlg_send_response(inv->dlg, pjsip_rdata_get_tsx(rdata), tdata); +} + + +/* + * Handle incoming response to UAC UPDATE request. + */ +static pj_bool_t inv_handle_update_response( pjsip_inv_session *inv, + pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + struct tsx_inv_data *tsx_inv_data; + pj_bool_t handled = PJ_FALSE; + pj_status_t status = -1; + + tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id]; + pj_assert(tsx_inv_data); + + /* Handle 401/407 challenge. */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + (tsx->status_code == 401 || tsx->status_code == 407)) + { + pjsip_tx_data *tdata; + + status = pjsip_auth_clt_reinit_req( &inv->dlg->auth_sess, + e->body.tsx_state.src.rdata, + tsx->last_tx, + &tdata); + + if (status != PJ_SUCCESS) { + + /* Somehow failed. Probably it's not a good idea to terminate + * the session since this is just a request within dialog. And + * even if we terminate we should send BYE. + */ + /* + inv_set_cause(inv, PJSIP_SC_OK, NULL); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + */ + + } else { + if (tsx_inv_data) + tsx_inv_data->retrying = PJ_TRUE; + + /* Re-send request. */ + status = pjsip_inv_send_msg(inv, tdata); + } + + handled = PJ_TRUE; + } + + /* Process 422 response */ + else if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + tsx->status_code == 422) + { + status = handle_timer_response(inv, e->body.tsx_state.src.rdata, + PJ_FALSE); + handled = PJ_TRUE; + } + + /* Process 2xx response */ + else if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + tsx->status_code/100 == 2 && + e->body.tsx_state.src.rdata->msg_info.msg->body) + { + status = handle_timer_response(inv, e->body.tsx_state.src.rdata, + PJ_FALSE); + status = inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + handled = PJ_TRUE; + } + + /* Get/attach invite session's transaction data */ + else + { + /* Session-Timer needs to see any error responses, to determine + * whether peer supports UPDATE with empty body. + */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + tsx->role == PJSIP_ROLE_UAC) + { + status = handle_timer_response(inv, e->body.tsx_state.src.rdata, + PJ_FALSE); + handled = PJ_TRUE; + } + } + + /* Cancel the negotiation if we don't get successful negotiation by now, + * unless it's authentication challenge and the request is being retried. + */ + if (pjmedia_sdp_neg_get_state(inv->neg) == + PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER && + tsx_inv_data && tsx_inv_data->sdp_done == PJ_FALSE && + !tsx_inv_data->retrying) + { + pjmedia_sdp_neg_cancel_offer(inv->neg); + + /* Prevent from us cancelling different offer! */ + tsx_inv_data->sdp_done = PJ_TRUE; + } + + return handled; +} + + +/* + * Handle incoming reliable response. + */ +static void inv_handle_incoming_reliable_response(pjsip_inv_session *inv, + pjsip_rx_data *rdata) +{ + pjsip_tx_data *tdata; + const pjmedia_sdp_session *sdp; + pj_status_t status; + + /* Create PRACK */ + status = pjsip_100rel_create_prack(inv, rdata, &tdata); + if (status != PJ_SUCCESS) + return; + + /* See if we need to attach SDP answer on the PRACK request */ + sdp = inv_has_pending_answer(inv, pjsip_rdata_get_tsx(rdata)); + if (sdp) { + tdata->msg->body = create_sdp_body(tdata->pool, sdp); + } + + /* Send PRACK (must be using 100rel module!) */ + pjsip_100rel_send_prack(inv, tdata); +} + + +/* + * Handle incoming PRACK. + */ +static void inv_respond_incoming_prack(pjsip_inv_session *inv, + pjsip_rx_data *rdata) +{ + pj_status_t status; + + /* Run through 100rel module to see if we can accept this + * PRACK request. The 100rel will send 200/OK to PRACK request. + */ + status = pjsip_100rel_on_rx_prack(inv, rdata); + if (status != PJ_SUCCESS) + return; + + /* Now check for SDP answer in the PRACK request */ + if (rdata->msg_info.msg->body) { + status = inv_check_sdp_in_incoming_msg(inv, + pjsip_rdata_get_tsx(rdata), rdata); + } else { + /* No SDP body */ + status = -1; + } + + /* If SDP negotiation has been successful, also mark the + * SDP negotiation flag in the invite transaction to be + * done too. + */ + if (status == PJ_SUCCESS && inv->invite_tsx) { + struct tsx_inv_data *tsx_inv_data; + + /* Get/attach invite session's transaction data */ + tsx_inv_data = (struct tsx_inv_data*) + inv->invite_tsx->mod_data[mod_inv.mod.id]; + if (tsx_inv_data == NULL) { + tsx_inv_data = PJ_POOL_ZALLOC_T(inv->invite_tsx->pool, + struct tsx_inv_data); + tsx_inv_data->inv = inv; + inv->invite_tsx->mod_data[mod_inv.mod.id] = tsx_inv_data; + } + + tsx_inv_data->sdp_done = PJ_TRUE; + } +} + + +/* + * State NULL is before anything is sent/received. + */ +static void inv_on_state_null( pjsip_inv_session *inv, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + /* Keep the initial INVITE transaction. */ + if (inv->invite_tsx == NULL) + inv->invite_tsx = tsx; + + if (dlg->role == PJSIP_ROLE_UAC) { + + /* Save the original INVITE request, if on_redirected() callback + * is implemented. We may need to resend the INVITE if we receive + * redirection response. + */ + if (mod_inv.cb.on_redirected) { + if (inv->invite_req) { + pjsip_tx_data_dec_ref(inv->invite_req); + inv->invite_req = NULL; + } + inv->invite_req = tsx->last_tx; + pjsip_tx_data_add_ref(inv->invite_req); + } + + switch (tsx->state) { + case PJSIP_TSX_STATE_CALLING: + inv_set_state(inv, PJSIP_INV_STATE_CALLING, e); + break; + default: + inv_on_state_calling(inv, e); + break; + } + + } else { + switch (tsx->state) { + case PJSIP_TSX_STATE_TRYING: + inv_set_state(inv, PJSIP_INV_STATE_INCOMING, e); + break; + case PJSIP_TSX_STATE_PROCEEDING: + inv_set_state(inv, PJSIP_INV_STATE_INCOMING, e); + if (tsx->status_code > 100) + inv_set_state(inv, PJSIP_INV_STATE_EARLY, e); + break; + case PJSIP_TSX_STATE_TERMINATED: + /* there is a failure in sending response. */ + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + break; + default: + inv_on_state_incoming(inv, e); + break; + } + } + + } else { + pj_assert(!"Unexpected transaction type"); + } +} + +/* + * Generic UAC transaction handler: + * - resend request on 401 or 407 response. + * - terminate dialog on 408 and 481 response. + * - resend request on 422 response. + */ +static pj_bool_t handle_uac_tsx_response(pjsip_inv_session *inv, + pjsip_event *e) +{ + /* RFC 3261 Section 12.2.1.2: + * If the response for a request within a dialog is a 481 + * (Call/Transaction Does Not Exist) or a 408 (Request Timeout), the UAC + * SHOULD terminate the dialog. A UAC SHOULD also terminate a dialog if + * no response at all is received for the request (the client + * transaction would inform the TU about the timeout.) + * + * For INVITE initiated dialogs, terminating the dialog consists of + * sending a BYE. + * + * Note: + * according to X, this should terminate dialog usage only, not the + * dialog. + */ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + + pj_assert(tsx->role == PJSIP_UAC_ROLE); + + /* Note that 481 response to CANCEL does not terminate dialog usage, + * but only the transaction. + */ + if (inv->state != PJSIP_INV_STATE_DISCONNECTED && + ((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST && + tsx->method.id != PJSIP_CANCEL_METHOD) || + tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT || + tsx->status_code == PJSIP_SC_TSX_TIMEOUT || + tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)) + { + pjsip_tx_data *bye; + pj_status_t status; + + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + + /* Send BYE */ + status = pjsip_dlg_create_request(inv->dlg, pjsip_get_bye_method(), + -1, &bye); + if (status == PJ_SUCCESS) { + pjsip_inv_send_msg(inv, bye); + } + + return PJ_TRUE; /* Handled */ + + } + /* Handle 401/407 challenge. */ + else if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + (tsx->status_code == PJSIP_SC_UNAUTHORIZED || + tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED)) + { + pjsip_tx_data *tdata; + pj_status_t status; + + if (tsx->method.id == PJSIP_INVITE_METHOD) + inv->invite_tsx = NULL; + + status = pjsip_auth_clt_reinit_req( &inv->dlg->auth_sess, + e->body.tsx_state.src.rdata, + tsx->last_tx, &tdata); + + if (status != PJ_SUCCESS) { + /* Somehow failed. Probably it's not a good idea to terminate + * the session since this is just a request within dialog. And + * even if we terminate we should send BYE. + */ + /* + inv_set_cause(inv, PJSIP_SC_OK, NULL); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + */ + + } else { + struct tsx_inv_data *tsx_inv_data; + + tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id]; + if (tsx_inv_data) + tsx_inv_data->retrying = PJ_TRUE; + + /* Re-send request. */ + status = pjsip_inv_send_msg(inv, tdata); + } + + return PJ_TRUE; /* Handled */ + } + + /* Handle session timer 422 response. */ + else if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + tsx->status_code == PJSIP_SC_SESSION_TIMER_TOO_SMALL) + { + handle_timer_response(inv, e->body.tsx_state.src.rdata, + PJ_FALSE); + + return PJ_TRUE; /* Handled */ + + } else { + return PJ_FALSE; /* Unhandled */ + } +} + + +/* Handle call rejection, especially with regard to processing call + * redirection. We need to handle the following scenarios: + * - 3xx response is received -- see if on_redirected() callback is + * implemented. If so, add the Contact URIs in the response to the + * target set and notify user. + * - 4xx - 6xx resposne is received -- see if we're currently recursing, + * if so fetch the next target if any and notify the on_redirected() + * callback. + * - for other cases -- disconnect the session. + */ +static void handle_uac_call_rejection(pjsip_inv_session *inv, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pj_status_t status; + + if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 300)) { + + if (mod_inv.cb.on_redirected == NULL) { + + /* Redirection callback is not implemented, disconnect the + * call. + */ + goto terminate_session; + + } else { + const pjsip_msg *res_msg; + + res_msg = e->body.tsx_state.src.rdata->msg_info.msg; + + /* Gather all Contact URI's in the response and add them + * to target set. The function will take care of removing + * duplicate URI's. + */ + pjsip_target_set_add_from_msg(&inv->dlg->target_set, + inv->dlg->pool, res_msg); + + /* Recurse to alternate targets if application allows us */ + if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e)) + { + /* Recursion fails, terminate session now */ + goto terminate_session; + } + + /* Done */ + } + + } else if ((tsx->status_code==401 || tsx->status_code==407) && + !inv->cancelling) + { + + /* Handle authentication failure: + * Resend the request with Authorization header. + */ + pjsip_tx_data *tdata; + + status = pjsip_auth_clt_reinit_req(&inv->dlg->auth_sess, + e->body.tsx_state.src.rdata, + tsx->last_tx, + &tdata); + + if (status != PJ_SUCCESS) { + + /* Does not have proper credentials. If we are currently + * recursing, try the next target. Otherwise end the session. + */ + if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e)) + { + /* Recursion fails, terminate session now */ + goto terminate_session; + } + + } else { + + /* Restart session. */ + pjsip_inv_uac_restart(inv, PJ_FALSE); + + /* Send the request. */ + status = pjsip_inv_send_msg(inv, tdata); + } + + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + tsx->status_code == PJSIP_SC_SESSION_TIMER_TOO_SMALL) + { + /* Handle session timer 422 response: + * Resend the request with requested session timer setting. + */ + status = handle_timer_response(inv, e->body.tsx_state.src.rdata, + PJ_TRUE); + + } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 600)) { + /* Global error */ + goto terminate_session; + + } else { + /* See if we have alternate target to try */ + if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e)) { + /* Recursion fails, terminate session now */ + goto terminate_session; + } + } + return; + +terminate_session: + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); +} + + +/* + * State CALLING is after sending initial INVITE request but before + * any response (with tag) is received. + */ +static void inv_on_state_calling( pjsip_inv_session *inv, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + pj_status_t status; + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx == inv->invite_tsx) { + + switch (tsx->state) { + + case PJSIP_TSX_STATE_CALLING: + inv_set_state(inv, PJSIP_INV_STATE_CALLING, e); + break; + + case PJSIP_TSX_STATE_PROCEEDING: + if (inv->pending_cancel) { + pjsip_tx_data *cancel; + + inv->pending_cancel = PJ_FALSE; + + status = pjsip_inv_end_session(inv, 487, NULL, &cancel); + if (status == PJ_SUCCESS && cancel) + status = pjsip_inv_send_msg(inv, cancel); + } + + if (dlg->remote.info->tag.slen) { + + inv_set_state(inv, PJSIP_INV_STATE_EARLY, e); + + inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + + if (pjsip_100rel_is_reliable(e->body.tsx_state.src.rdata)) { + inv_handle_incoming_reliable_response( + inv, e->body.tsx_state.src.rdata); + } + + } else { + /* Ignore 100 (Trying) response, as it doesn't change + * session state. It only ceases retransmissions. + */ + } + break; + + case PJSIP_TSX_STATE_COMPLETED: + if (tsx->status_code/100 == 2) { + + /* This should not happen. + * When transaction receives 2xx, it should be terminated + */ + pj_assert(0); + + /* Process session timer response. */ + status = handle_timer_response(inv, + e->body.tsx_state.src.rdata, + PJ_TRUE); + if (status != PJ_SUCCESS) + break; + + inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e); + + inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + + } else { + handle_uac_call_rejection(inv, e); + } + break; + + case PJSIP_TSX_STATE_TERMINATED: + /* INVITE transaction can be terminated either because UAC + * transaction received 2xx response or because of transport + * error. + */ + if (tsx->status_code/100 == 2) { + /* This must be receipt of 2xx response */ + + /* Process session timer response. */ + status = handle_timer_response(inv, + e->body.tsx_state.src.rdata, + PJ_TRUE); + if (status != PJ_SUCCESS) + break; + + /* Set state to CONNECTING */ + inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e); + + inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + + /* Send ACK */ + pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG); + + inv_send_ack(inv, e); + + } else { + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } + break; + + default: + break; + } + + } else if (tsx->role == PJSIP_ROLE_UAC) { + /* + * Handle case when outgoing request is answered with 481 (Call/ + * Transaction Does Not Exist), 408, or when it's timed out. In these + * cases, disconnect session (i.e. dialog usage only). + * Note that 481 response to CANCEL does not terminate dialog usage, + * but only the transaction. + */ + if ((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST && + tsx->method.id != PJSIP_CANCEL_METHOD) || + tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT || + tsx->status_code == PJSIP_SC_TSX_TIMEOUT || + tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR) + { + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } + } +} + +/* + * State INCOMING is after we received the request, but before + * responses with tag are sent. + */ +static void inv_on_state_incoming( pjsip_inv_session *inv, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx == inv->invite_tsx) { + + /* + * Handle the INVITE state transition. + */ + + switch (tsx->state) { + + case PJSIP_TSX_STATE_TRYING: + inv_set_state(inv, PJSIP_INV_STATE_INCOMING, e); + break; + + case PJSIP_TSX_STATE_PROCEEDING: + /* + * Transaction sent provisional response. + */ + if (tsx->status_code > 100) + inv_set_state(inv, PJSIP_INV_STATE_EARLY, e); + break; + + case PJSIP_TSX_STATE_COMPLETED: + /* + * Transaction sent final response. + */ + if (tsx->status_code/100 == 2) { + inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e); + } else { + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } + break; + + case PJSIP_TSX_STATE_TERMINATED: + /* + * This happens on transport error (e.g. failed to send + * response) + */ + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + break; + + default: + pj_assert(!"Unexpected INVITE state"); + break; + } + + } else if (tsx->method.id == PJSIP_CANCEL_METHOD && + tsx->role == PJSIP_ROLE_UAS && + tsx->state < PJSIP_TSX_STATE_COMPLETED && + e->body.tsx_state.type == PJSIP_EVENT_RX_MSG ) + { + + /* + * Handle incoming CANCEL request. + */ + + inv_respond_incoming_cancel(inv, tsx, e->body.tsx_state.src.rdata); + + } +} + +/* + * State EARLY is for both UAS and UAC, after response with To tag + * is sent/received. + */ +static void inv_on_state_early( pjsip_inv_session *inv, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx == inv->invite_tsx) { + + /* + * Handle the INVITE state progress. + */ + + switch (tsx->state) { + + case PJSIP_TSX_STATE_PROCEEDING: + /* Send/received another provisional response. */ + inv_set_state(inv, PJSIP_INV_STATE_EARLY, e); + + if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { + inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + + if (pjsip_100rel_is_reliable(e->body.tsx_state.src.rdata)) { + inv_handle_incoming_reliable_response( + inv, e->body.tsx_state.src.rdata); + } + } + break; + + case PJSIP_TSX_STATE_COMPLETED: + if (tsx->status_code/100 == 2) { + inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e); + if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { + pj_status_t status; + + /* Process session timer response. */ + status = handle_timer_response(inv, + e->body.tsx_state.src.rdata, + PJ_TRUE); + if (status != PJ_SUCCESS) + break; + + inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + } + + } else if (tsx->role == PJSIP_ROLE_UAC) { + + handle_uac_call_rejection(inv, e); + + } else { + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } + break; + + case PJSIP_TSX_STATE_CONFIRMED: + /* For some reason can go here (maybe when ACK for 2xx has + * the same branch value as the INVITE transaction) */ + + case PJSIP_TSX_STATE_TERMINATED: + /* INVITE transaction can be terminated either because UAC + * transaction received 2xx response or because of transport + * error. + */ + if (tsx->status_code/100 == 2) { + + /* This must be receipt of 2xx response */ + + /* Set state to CONNECTING */ + inv_set_state(inv, PJSIP_INV_STATE_CONNECTING, e); + + if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { + pj_status_t status; + + /* Process session timer response. */ + status = handle_timer_response(inv, + e->body.tsx_state.src.rdata, + PJ_TRUE); + if (status != PJ_SUCCESS) + break; + + inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + } + + /* if UAC, send ACK and move state to confirmed. */ + if (tsx->role == PJSIP_ROLE_UAC) { + pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG); + + inv_send_ack(inv, e); + } + + } else { + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } + break; + + default: + pj_assert(!"Unexpected INVITE tsx state"); + } + + } else if (inv->role == PJSIP_ROLE_UAS && + tsx->role == PJSIP_ROLE_UAS && + tsx->method.id == PJSIP_CANCEL_METHOD && + tsx->state < PJSIP_TSX_STATE_COMPLETED && + e->body.tsx_state.type == PJSIP_EVENT_RX_MSG ) + { + + /* + * Handle incoming CANCEL request. + */ + + inv_respond_incoming_cancel(inv, tsx, e->body.tsx_state.src.rdata); + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle incoming UPDATE + */ + inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata); + + + } else if (tsx->role == PJSIP_ROLE_UAC && + (tsx->state == PJSIP_TSX_STATE_COMPLETED || + tsx->state == PJSIP_TSX_STATE_TERMINATED) && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle response to outgoing UPDATE request. + */ + inv_handle_update_response(inv, e); + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0) + { + /* + * Handle incoming PRACK + */ + inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata); + + } else if (tsx->role == PJSIP_ROLE_UAC) { + + /* Generic handling for UAC tsx completion */ + handle_uac_tsx_response(inv, e); + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->method.id == PJSIP_BYE_METHOD && + tsx->status_code < 200 && + e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + /* Received BYE before the 2xx/OK response to INVITE. + * Assume that the 2xx/OK response is lost and the BYE + * arrives earlier. + */ + inv_respond_incoming_bye(inv, tsx, e->body.tsx_state.src.rdata, e); + + if (inv->invite_tsx->role == PJSIP_ROLE_UAC) { + /* Set timer just in case we will never get the final response + * for INVITE. + */ + pjsip_tsx_set_timeout(inv->invite_tsx, 64*pjsip_cfg()->tsx.t1); + } else if (inv->invite_tsx->status_code < 200) { + pjsip_tx_data *tdata; + pjsip_msg *msg; + + /* For UAS, send a final response. */ + tdata = inv->invite_tsx->last_tx; + PJ_ASSERT_ON_FAIL(tdata != NULL, return); + + msg = tdata->msg; + msg->line.status.code = PJSIP_SC_REQUEST_TERMINATED; + msg->line.status.reason = + *pjsip_get_status_text(PJSIP_SC_REQUEST_TERMINATED); + msg->body = NULL; + + pjsip_tx_data_invalidate_msg(tdata); + pjsip_tx_data_add_ref(tdata); + + pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata); + } + } +} + +/* + * State CONNECTING is after 2xx response to INVITE is sent/received. + */ +static void inv_on_state_connecting( pjsip_inv_session *inv, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx == inv->invite_tsx) { + + /* + * Handle INVITE state progression. + */ + switch (tsx->state) { + + case PJSIP_TSX_STATE_CONFIRMED: + /* It can only go here if incoming ACK request has the same Via + * branch parameter as the INVITE transaction. + */ + if (tsx->status_code/100 == 2) { + if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { + inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + } + + inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, e); + } + break; + + case PJSIP_TSX_STATE_TERMINATED: + /* INVITE transaction can be terminated either because UAC + * transaction received 2xx response or because of transport + * error. + */ + if (tsx->status_code/100 != 2) { + if (tsx->role == PJSIP_ROLE_UAC) { + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } else { + pjsip_tx_data *bye; + pj_status_t status; + + /* Send BYE */ + status = pjsip_dlg_create_request(inv->dlg, + pjsip_get_bye_method(), + -1, &bye); + if (status == PJ_SUCCESS) { + pjsip_inv_send_msg(inv, bye); + } + } + } + break; + + case PJSIP_TSX_STATE_DESTROYED: + /* Do nothing. */ + break; + + default: + pj_assert(!"Unexpected state"); + } + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->method.id == PJSIP_BYE_METHOD && + tsx->status_code < 200 && + e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + + /* + * Handle incoming BYE. + */ + + inv_respond_incoming_bye( inv, tsx, e->body.tsx_state.src.rdata, e ); + + } else if (tsx->method.id == PJSIP_BYE_METHOD && + tsx->role == PJSIP_ROLE_UAC && + (tsx->state == PJSIP_TSX_STATE_COMPLETED || + tsx->state == PJSIP_TSX_STATE_TERMINATED)) + { + + /* + * Outgoing BYE + */ + inv_handle_bye_response( inv, tsx, e->body.tsx_state.src.rdata, e); + + } + else if (tsx->method.id == PJSIP_CANCEL_METHOD && + tsx->role == PJSIP_ROLE_UAS && + tsx->status_code < 200 && + e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + + /* + * Handle strandled incoming CANCEL. + */ + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) return; + + status = pjsip_dlg_send_response(dlg, tsx, tdata); + if (status != PJ_SUCCESS) return; + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_invite_method)==0) + { + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pj_status_t status; + + /* See https://trac.pjsip.org/repos/ticket/1455 + * Handle incoming re-INVITE before current INVITE is confirmed. + * According to RFC 5407: + * - answer with 200 if we don't have pending offer-answer + * - answer with 491 if we *have* pending offer-answer + * + * But unfortunately accepting the re-INVITE would mean we have + * two outstanding INVITEs, and we don't support that because + * we will get confused when we handle the ACK. + */ + status = pjsip_dlg_create_response(inv->dlg, rdata, + PJSIP_SC_REQUEST_PENDING, + NULL, &tdata); + if (status != PJ_SUCCESS) + return; + pjsip_timer_update_resp(inv, tdata); + status = pjsip_dlg_send_response(dlg, tsx, tdata); + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle incoming UPDATE + */ + inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata); + + + } else if (tsx->role == PJSIP_ROLE_UAC && + (tsx->state == PJSIP_TSX_STATE_COMPLETED || + tsx->state == PJSIP_TSX_STATE_TERMINATED) && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle response to outgoing UPDATE request. + */ + if (inv_handle_update_response(inv, e) == PJ_FALSE) + handle_uac_tsx_response(inv, e); + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0) + { + /* + * Handle incoming PRACK + */ + inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata); + + } else if (tsx->role == PJSIP_ROLE_UAC) { + + /* Generic handling for UAC tsx completion */ + handle_uac_tsx_response(inv, e); + + } + +} + +/* + * State CONFIRMED is after ACK is sent/received. + */ +static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + + if (tsx->method.id == PJSIP_BYE_METHOD && + tsx->role == PJSIP_ROLE_UAC && + (tsx->state == PJSIP_TSX_STATE_COMPLETED || + tsx->state == PJSIP_TSX_STATE_TERMINATED)) + { + + /* + * Outgoing BYE + */ + + inv_handle_bye_response( inv, tsx, e->body.tsx_state.src.rdata, e); + + } + else if (tsx->method.id == PJSIP_BYE_METHOD && + tsx->role == PJSIP_ROLE_UAS && + tsx->status_code < 200 && + e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + + /* + * Handle incoming BYE. + */ + + inv_respond_incoming_bye( inv, tsx, e->body.tsx_state.src.rdata, e ); + + } + else if (tsx->method.id == PJSIP_CANCEL_METHOD && + tsx->role == PJSIP_ROLE_UAS && + tsx->status_code < 200 && + e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + + /* + * Handle strandled incoming CANCEL. + */ + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) return; + + status = pjsip_dlg_send_response(dlg, tsx, tdata); + if (status != PJ_SUCCESS) return; + + } + else if (tsx->method.id == PJSIP_INVITE_METHOD && + tsx->role == PJSIP_ROLE_UAS) + { + + /* + * Handle incoming re-INVITE + */ + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pj_status_t status; + pjsip_rdata_sdp_info *sdp_info; + pjsip_status_code st_code; + + /* Check if we have INVITE pending. */ + if (inv->invite_tsx && inv->invite_tsx!=tsx) { + int code; + pj_str_t reason; + + reason = pj_str("Another INVITE transaction in progress"); + + if (inv->invite_tsx->role == PJSIP_ROLE_UAC) + code = 491; + else + code = 500; + + /* Can not receive re-INVITE while another one is pending. */ + status = pjsip_dlg_create_response( inv->dlg, rdata, code, + &reason, &tdata); + if (status != PJ_SUCCESS) + return; + + if (code == 500) { + /* MUST include Retry-After header with random value + * between 0-10. + */ + pjsip_retry_after_hdr *ra_hdr; + int val = (pj_rand() % 10); + + ra_hdr = pjsip_retry_after_hdr_create(tdata->pool, val); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)ra_hdr); + } + + status = pjsip_dlg_send_response( inv->dlg, tsx, tdata); + + + return; + } + + /* Save the invite transaction. */ + inv->invite_tsx = tsx; + + /* Process session timers headers in the re-INVITE */ + status = pjsip_timer_process_req(inv, rdata, &st_code); + if (status != PJ_SUCCESS) { + status = pjsip_dlg_create_response(inv->dlg, rdata, st_code, + NULL, &tdata); + if (status != PJ_SUCCESS) + return; + + pjsip_timer_update_resp(inv, tdata); + status = pjsip_dlg_send_response(dlg, tsx, tdata); + return; + } + + /* Send 491 if we receive re-INVITE while another offer/answer + * negotiation is in progress + */ + if (pjmedia_sdp_neg_get_state(inv->neg) != + PJMEDIA_SDP_NEG_STATE_DONE) + { + status = pjsip_dlg_create_response(inv->dlg, rdata, + PJSIP_SC_REQUEST_PENDING, + NULL, &tdata); + if (status != PJ_SUCCESS) + return; + pjsip_timer_update_resp(inv, tdata); + status = pjsip_dlg_send_response(dlg, tsx, tdata); + return; + } + + /* Process SDP in incoming message. */ + status = inv_check_sdp_in_incoming_msg(inv, tsx, rdata); + + if (status != PJ_SUCCESS) { + + /* Not Acceptable */ + const pjsip_hdr *accept; + + /* The incoming SDP is unacceptable. If the SDP negotiator + * state has just been changed, i.e: DONE -> REMOTE_OFFER, + * revert it back. + */ + if (pjmedia_sdp_neg_get_state(inv->neg) == + PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER) + { + pjmedia_sdp_neg_cancel_offer(inv->neg); + } + + status = pjsip_dlg_create_response(inv->dlg, rdata, + 488, NULL, &tdata); + if (status != PJ_SUCCESS) + return; + + + accept = pjsip_endpt_get_capability(dlg->endpt, PJSIP_H_ACCEPT, + NULL); + if (accept) { + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) + pjsip_hdr_clone(tdata->pool, accept)); + } + + status = pjsip_dlg_send_response(dlg, tsx, tdata); + + return; + } + + /* Create 2xx ANSWER */ + status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) + return; + + /* If the INVITE request has SDP body, send answer. + * Otherwise generate offer from local active SDP. + */ + sdp_info = pjsip_rdata_get_sdp_info(rdata); + if (sdp_info->sdp != NULL) { + status = process_answer(inv, 200, tdata, NULL); + } else { + /* INVITE does not have SDP. + * If on_create_offer() callback is implemented, ask app. + * to generate an offer, otherwise just send active local + * SDP to signal that nothing gets modified. + */ + pjmedia_sdp_session *sdp = NULL; + + if (mod_inv.cb.on_create_offer) { + (*mod_inv.cb.on_create_offer)(inv, &sdp); + if (sdp) { + /* Notify negotiator about the new offer. This will + * fix the offer with correct SDP origin. + */ + status = + pjmedia_sdp_neg_modify_local_offer(inv->pool_prov, + inv->neg, + sdp); + + /* Retrieve the "fixed" offer from negotiator */ + if (status==PJ_SUCCESS) { + const pjmedia_sdp_session *lsdp = NULL; + pjmedia_sdp_neg_get_neg_local(inv->neg, &lsdp); + sdp = (pjmedia_sdp_session*)lsdp; + } + } + } + + if (sdp == NULL) { + const pjmedia_sdp_session *active_sdp = NULL; + status = pjmedia_sdp_neg_send_local_offer(inv->pool_prov, + inv->neg, + &active_sdp); + if (status == PJ_SUCCESS) + sdp = (pjmedia_sdp_session*) active_sdp; + } + + if (sdp) { + tdata->msg->body = create_sdp_body(tdata->pool, sdp); + } + } + + if (status != PJ_SUCCESS) { + /* + * SDP negotiation has failed. + */ + pj_status_t rc; + pj_str_t reason; + + /* Delete the 2xx answer */ + pjsip_tx_data_dec_ref(tdata); + + /* Create 500 response */ + reason = pj_str("SDP negotiation failed"); + rc = pjsip_dlg_create_response(dlg, rdata, 500, &reason, + &tdata); + if (rc == PJ_SUCCESS) { + pjsip_warning_hdr *w; + const pj_str_t *endpt_name; + + endpt_name = pjsip_endpt_name(dlg->endpt); + w = pjsip_warning_hdr_create_from_status(tdata->pool, + endpt_name, + status); + if (w) + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)w); + + pjsip_inv_send_msg(inv, tdata); + } + return; + } + + /* Invoke Session Timers */ + pjsip_timer_update_resp(inv, tdata); + + /* Send 2xx regardless of the status of negotiation */ + status = pjsip_inv_send_msg(inv, tdata); + + } else if (tsx->state == PJSIP_TSX_STATE_CONFIRMED) { + /* This is the case where ACK has the same branch as + * the INVITE request. + */ + if (tsx->status_code/100 == 2 && + e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + + /* Check if local offer got no SDP answer */ + if (pjmedia_sdp_neg_get_state(inv->neg)== + PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) + { + pjmedia_sdp_neg_cancel_offer(inv->neg); + } + } + + } + + } + else if (tsx->method.id == PJSIP_INVITE_METHOD && + tsx->role == PJSIP_ROLE_UAC) + { + + /* + * Handle outgoing re-INVITE + */ + if (tsx->state == PJSIP_TSX_STATE_CALLING) { + + /* Must not have other pending INVITE transaction */ + pj_assert(inv->invite_tsx==NULL || tsx==inv->invite_tsx); + + /* Save pending invite transaction */ + inv->invite_tsx = tsx; + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED && + tsx->status_code/100 == 2) + { + pj_status_t status; + + /* Re-INVITE was accepted. */ + + /* Process session timer response. */ + status = handle_timer_response(inv, + e->body.tsx_state.src.rdata, + PJ_TRUE); + if (status != PJ_SUCCESS) + return; + + /* Process SDP */ + inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + + /* Check if local offer got no SDP answer */ + if (pjmedia_sdp_neg_get_state(inv->neg)== + PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) + { + pjmedia_sdp_neg_cancel_offer(inv->neg); + } + + /* Send ACK */ + inv_send_ack(inv, e); + + } else if (handle_uac_tsx_response(inv, e)) { + + /* Handle response that terminates dialog */ + /* Nothing to do (already handled) */ + + } else if (tsx->status_code >= 300 && tsx->status_code < 700) { + + pjmedia_sdp_neg_state neg_state; + struct tsx_inv_data *tsx_inv_data; + + tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id]; + + /* Outgoing INVITE transaction has failed, cancel SDP nego */ + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER && + tsx_inv_data->retrying == PJ_FALSE) + { + pjmedia_sdp_neg_cancel_offer(inv->neg); + } + + if (tsx == inv->invite_tsx) + inv->invite_tsx = NULL; + } + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle incoming UPDATE + */ + inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata); + + } else if (tsx->role == PJSIP_ROLE_UAC && + (tsx->state == PJSIP_TSX_STATE_COMPLETED || + tsx->state == PJSIP_TSX_STATE_TERMINATED) && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle response to outgoing UPDATE request. + */ + if (inv_handle_update_response(inv, e) == PJ_FALSE) + handle_uac_tsx_response(inv, e); + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0) + { + /* + * Handle strandled incoming PRACK + */ + inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata); + + } else if (tsx->role == PJSIP_ROLE_UAC) { + /* + * Handle 401/407/408/481/422 response + */ + handle_uac_tsx_response(inv, e); + } + +} + +/* + * After session has been terminated, but before dialog is destroyed + * (because dialog has other usages, or because dialog is waiting for + * the last transaction to terminate). + */ +static void inv_on_state_disconnected( pjsip_inv_session *inv, pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx); + + PJ_ASSERT_ON_FAIL(tsx && dlg, return); + + if (tsx->role == PJSIP_ROLE_UAS && + tsx->status_code < 200 && + e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + + /* + * Respond BYE with 200/OK + */ + if (tsx->method.id == PJSIP_BYE_METHOD) { + inv_respond_incoming_bye( inv, tsx, rdata, e ); + } else if (tsx->method.id == PJSIP_CANCEL_METHOD) { + /* + * Respond CANCEL with 200/OK too. + */ + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) return; + + status = pjsip_dlg_send_response(dlg, tsx, tdata); + if (status != PJ_SUCCESS) return; + + } + + } else if (tsx->role == PJSIP_ROLE_UAC) { + /* + * Handle 401/407/408/481/422 response + */ + handle_uac_tsx_response(inv, e); + } +} + diff --git a/pjsip/src/pjsip-ua/sip_reg.c b/pjsip/src/pjsip-ua/sip_reg.c new file mode 100644 index 0000000..bd1fb21 --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_reg.c @@ -0,0 +1,1333 @@ +/* $Id: sip_reg.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-ua/sip_regc.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_parser.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_util.h> +#include <pjsip/sip_auth_msg.h> +#include <pjsip/sip_errno.h> +#include <pj/assert.h> +#include <pj/guid.h> +#include <pj/lock.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/log.h> +#include <pj/rand.h> +#include <pj/string.h> + + +#define REFRESH_TIMER 1 +#define DELAY_BEFORE_REFRESH PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH +#define THIS_FILE "sip_reg.c" + +/* Outgoing transaction timeout when server sends 100 but never replies + * with final response. Value is in MILISECONDS! + */ +#define REGC_TSX_TIMEOUT 33000 + +enum { NOEXP = 0x1FFFFFFF }; + +static const pj_str_t XUID_PARAM_NAME = { "x-uid", 5 }; + + +/* Current/pending operation */ +enum regc_op +{ + REGC_IDLE, + REGC_REGISTERING, + REGC_UNREGISTERING +}; + +/** + * SIP client registration structure. + */ +struct pjsip_regc +{ + pj_pool_t *pool; + pjsip_endpoint *endpt; + pj_lock_t *lock; + pj_bool_t _delete_flag; + pj_bool_t has_tsx; + pj_atomic_t *busy_ctr; + enum regc_op current_op; + + pj_bool_t add_xuid_param; + + void *token; + pjsip_regc_cb *cb; + + pj_str_t str_srv_url; + pjsip_uri *srv_url; + 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; + pjsip_contact_hdr contact_hdr_list; + pjsip_contact_hdr removed_contact_hdr_list; + pjsip_expires_hdr *expires_hdr; + pj_uint32_t expires; + pj_uint32_t delay_before_refresh; + pjsip_route_hdr route_set; + pjsip_hdr hdr_list; + pjsip_host_port via_addr; + const void *via_tp; + + /* Authorization sessions. */ + pjsip_auth_clt_sess auth_sess; + + /* Auto refresh registration. */ + pj_bool_t auto_reg; + pj_time_val last_reg; + pj_time_val next_reg; + pj_timer_entry timer; + + /* Transport selector */ + pjsip_tpselector tp_sel; + + /* Last transport used. We acquire the transport to keep + * it open. + */ + pjsip_transport *last_transport; +}; + + +PJ_DEF(pj_status_t) pjsip_regc_create( pjsip_endpoint *endpt, void *token, + pjsip_regc_cb *cb, + pjsip_regc **p_regc) +{ + pj_pool_t *pool; + pjsip_regc *regc; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(endpt && cb && p_regc, PJ_EINVAL); + + pool = pjsip_endpt_create_pool(endpt, "regc%p", 1024, 1024); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + regc = PJ_POOL_ZALLOC_T(pool, pjsip_regc); + + regc->pool = pool; + regc->endpt = endpt; + regc->token = token; + regc->cb = cb; + regc->expires = PJSIP_REGC_EXPIRATION_NOT_SPECIFIED; + regc->add_xuid_param = pjsip_cfg()->regc.add_xuid_param; + + status = pj_lock_create_recursive_mutex(pool, pool->obj_name, + ®c->lock); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + + status = pj_atomic_create(pool, 0, ®c->busy_ctr); + if (status != PJ_SUCCESS) { + pj_lock_destroy(regc->lock); + pj_pool_release(pool); + return status; + } + + status = pjsip_auth_clt_init(®c->auth_sess, endpt, regc->pool, 0); + if (status != PJ_SUCCESS) + return status; + + pj_list_init(®c->route_set); + pj_list_init(®c->hdr_list); + pj_list_init(®c->contact_hdr_list); + pj_list_init(®c->removed_contact_hdr_list); + + /* Done */ + *p_regc = regc; + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_regc_destroy(pjsip_regc *regc) +{ + PJ_ASSERT_RETURN(regc, PJ_EINVAL); + + pj_lock_acquire(regc->lock); + if (regc->has_tsx || pj_atomic_get(regc->busy_ctr) != 0) { + regc->_delete_flag = 1; + regc->cb = NULL; + pj_lock_release(regc->lock); + } else { + pjsip_tpselector_dec_ref(®c->tp_sel); + if (regc->last_transport) { + pjsip_transport_dec_ref(regc->last_transport); + regc->last_transport = NULL; + } + if (regc->timer.id != 0) { + pjsip_endpt_cancel_timer(regc->endpt, ®c->timer); + regc->timer.id = 0; + } + pj_atomic_destroy(regc->busy_ctr); + pj_lock_release(regc->lock); + pj_lock_destroy(regc->lock); + regc->lock = NULL; + pjsip_endpt_release_pool(regc->endpt, regc->pool); + } + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_regc_get_info( pjsip_regc *regc, + pjsip_regc_info *info ) +{ + PJ_ASSERT_RETURN(regc && info, PJ_EINVAL); + + pj_lock_acquire(regc->lock); + + info->server_uri = regc->str_srv_url; + info->client_uri = regc->from_uri; + info->is_busy = (pj_atomic_get(regc->busy_ctr) || regc->has_tsx); + info->auto_reg = regc->auto_reg; + info->interval = regc->expires; + info->transport = regc->last_transport; + + if (regc->has_tsx) + info->next_reg = 0; + else if (regc->auto_reg == 0) + info->next_reg = 0; + else if (regc->expires < 0) + info->next_reg = regc->expires; + else { + pj_time_val now, next_reg; + + next_reg = regc->next_reg; + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(next_reg, now); + info->next_reg = next_reg.sec; + } + + pj_lock_release(regc->lock); + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_pool_t*) pjsip_regc_get_pool(pjsip_regc *regc) +{ + return regc->pool; +} + +static void set_expires( pjsip_regc *regc, pj_uint32_t expires) +{ + if (expires != regc->expires) { + regc->expires_hdr = pjsip_expires_hdr_create(regc->pool, expires); + } else { + regc->expires_hdr = NULL; + } +} + + +static pj_status_t set_contact( pjsip_regc *regc, + int contact_cnt, + const pj_str_t contact[] ) +{ + const pj_str_t CONTACT = { "Contact", 7 }; + pjsip_contact_hdr *h; + int i; + + /* Save existing contact list to removed_contact_hdr_list and + * clear contact_hdr_list. + */ + pj_list_merge_last(®c->removed_contact_hdr_list, + ®c->contact_hdr_list); + + /* Set the expiration of Contacts in to removed_contact_hdr_list + * zero. + */ + h = regc->removed_contact_hdr_list.next; + while (h != ®c->removed_contact_hdr_list) { + h->expires = 0; + h = h->next; + } + + /* Process new contacts */ + for (i=0; i<contact_cnt; ++i) { + pjsip_contact_hdr *hdr; + pj_str_t tmp; + + pj_strdup_with_null(regc->pool, &tmp, &contact[i]); + hdr = (pjsip_contact_hdr*) + pjsip_parse_hdr(regc->pool, &CONTACT, tmp.ptr, tmp.slen, NULL); + if (hdr == NULL) { + PJ_LOG(4,(THIS_FILE, "Invalid Contact: \"%.*s\"", + (int)tmp.slen, tmp.ptr)); + return PJSIP_EINVALIDURI; + } + + /* Find the new contact in old contact list. If found, remove + * the old header from the old header list. + */ + h = regc->removed_contact_hdr_list.next; + while (h != ®c->removed_contact_hdr_list) { + int rc; + + rc = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, + h->uri, hdr->uri); + if (rc == 0) { + /* Match */ + pj_list_erase(h); + break; + } + + h = h->next; + } + + /* If add_xuid_param option is enabled and Contact URI is sip/sips, + * add xuid parameter to assist matching the Contact URI in the + * REGISTER response later. + */ + if (regc->add_xuid_param && (PJSIP_URI_SCHEME_IS_SIP(hdr->uri) || + PJSIP_URI_SCHEME_IS_SIPS(hdr->uri))) + { + pjsip_param *xuid_param; + pjsip_sip_uri *sip_uri; + + xuid_param = PJ_POOL_ZALLOC_T(regc->pool, pjsip_param); + xuid_param->name = XUID_PARAM_NAME; + pj_create_unique_string(regc->pool, &xuid_param->value); + + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(hdr->uri); + pj_list_push_back(&sip_uri->other_param, xuid_param); + } + + pj_list_push_back(®c->contact_hdr_list, hdr); + } + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_regc_init( pjsip_regc *regc, + const pj_str_t *srv_url, + const pj_str_t *from_url, + const pj_str_t *to_url, + int contact_cnt, + const pj_str_t contact[], + pj_uint32_t expires) +{ + pj_str_t tmp; + pj_status_t status; + + PJ_ASSERT_RETURN(regc && srv_url && from_url && to_url && + contact_cnt && contact && expires, PJ_EINVAL); + + /* Copy server URL. */ + pj_strdup_with_null(regc->pool, ®c->str_srv_url, srv_url); + + /* Set server URL. */ + tmp = regc->str_srv_url; + regc->srv_url = pjsip_parse_uri( regc->pool, tmp.ptr, tmp.slen, 0); + if (regc->srv_url == NULL) { + return PJSIP_EINVALIDURI; + } + + /* Set "From" header. */ + pj_strdup_with_null(regc->pool, ®c->from_uri, from_url); + tmp = regc->from_uri; + regc->from_hdr = pjsip_from_hdr_create(regc->pool); + regc->from_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (!regc->from_hdr->uri) { + PJ_LOG(4,(THIS_FILE, "regc: invalid source URI %.*s", + from_url->slen, from_url->ptr)); + return PJSIP_EINVALIDURI; + } + + /* Set "To" header. */ + pj_strdup_with_null(regc->pool, &tmp, to_url); + regc->to_hdr = pjsip_to_hdr_create(regc->pool); + regc->to_hdr->uri = pjsip_parse_uri(regc->pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (!regc->to_hdr->uri) { + PJ_LOG(4,(THIS_FILE, "regc: invalid target URI %.*s", to_url->slen, to_url->ptr)); + return PJSIP_EINVALIDURI; + } + + + /* Set "Contact" header. */ + status = set_contact( regc, contact_cnt, contact); + if (status != PJ_SUCCESS) + return status; + + /* Set "Expires" header, if required. */ + set_expires( regc, expires); + regc->delay_before_refresh = DELAY_BEFORE_REFRESH; + + /* Set "Call-ID" header. */ + regc->cid_hdr = pjsip_cid_hdr_create(regc->pool); + pj_create_unique_string(regc->pool, ®c->cid_hdr->id); + + /* Set "CSeq" header. */ + regc->cseq_hdr = pjsip_cseq_hdr_create(regc->pool); + regc->cseq_hdr->cseq = pj_rand() % 0xFFFF; + pjsip_method_set( ®c->cseq_hdr->method, PJSIP_REGISTER_METHOD); + + /* Done. */ + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsip_regc_set_credentials( pjsip_regc *regc, + int count, + const pjsip_cred_info cred[] ) +{ + PJ_ASSERT_RETURN(regc && count && cred, PJ_EINVAL); + return pjsip_auth_clt_set_credentials(®c->auth_sess, count, cred); +} + +PJ_DEF(pj_status_t) pjsip_regc_set_prefs( pjsip_regc *regc, + const pjsip_auth_clt_pref *pref) +{ + PJ_ASSERT_RETURN(regc && pref, PJ_EINVAL); + return pjsip_auth_clt_set_prefs(®c->auth_sess, pref); +} + +PJ_DEF(pj_status_t) pjsip_regc_set_route_set( pjsip_regc *regc, + const pjsip_route_hdr *route_set) +{ + const pjsip_route_hdr *chdr; + + PJ_ASSERT_RETURN(regc && route_set, PJ_EINVAL); + + pj_list_init(®c->route_set); + + chdr = route_set->next; + while (chdr != route_set) { + pj_list_push_back(®c->route_set, pjsip_hdr_clone(regc->pool, chdr)); + chdr = chdr->next; + } + + return PJ_SUCCESS; +} + + +/* + * Bind client registration to a specific transport/listener. + */ +PJ_DEF(pj_status_t) pjsip_regc_set_transport( pjsip_regc *regc, + const pjsip_tpselector *sel) +{ + PJ_ASSERT_RETURN(regc && sel, PJ_EINVAL); + + pjsip_tpselector_dec_ref(®c->tp_sel); + pj_memcpy(®c->tp_sel, sel, sizeof(*sel)); + pjsip_tpselector_add_ref(®c->tp_sel); + + return PJ_SUCCESS; +} + +/* Release transport */ +PJ_DEF(pj_status_t) pjsip_regc_release_transport(pjsip_regc *regc) +{ + PJ_ASSERT_RETURN(regc, PJ_EINVAL); + if (regc->last_transport) { + pjsip_transport_dec_ref(regc->last_transport); + regc->last_transport = NULL; + } + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_regc_add_headers( pjsip_regc *regc, + const pjsip_hdr *hdr_list) +{ + const pjsip_hdr *hdr; + + PJ_ASSERT_RETURN(regc && hdr_list, PJ_EINVAL); + + //This is "add" operation, so don't remove headers. + //pj_list_init(®c->hdr_list); + + hdr = hdr_list->next; + while (hdr != hdr_list) { + pj_list_push_back(®c->hdr_list, pjsip_hdr_clone(regc->pool, hdr)); + hdr = hdr->next; + } + + return PJ_SUCCESS; +} + +static pj_status_t create_request(pjsip_regc *regc, + pjsip_tx_data **p_tdata) +{ + pj_status_t status; + pjsip_tx_data *tdata; + + PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL); + + /* Create the request. */ + status = pjsip_endpt_create_request_from_hdr( regc->endpt, + pjsip_get_register_method(), + regc->srv_url, + regc->from_hdr, + regc->to_hdr, + NULL, + regc->cid_hdr, + regc->cseq_hdr->cseq, + NULL, + &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Add cached authorization headers. */ + pjsip_auth_clt_init_req( ®c->auth_sess, tdata ); + + /* Add Route headers from route set, ideally after Via header */ + if (!pj_list_empty(®c->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 = regc->route_set.next; + while (route != ®c->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 additional request headers */ + if (!pj_list_empty(®c->hdr_list)) { + const pjsip_hdr *hdr; + + hdr = regc->hdr_list.next; + while (hdr != ®c->hdr_list) { + 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_regc_register(pjsip_regc *regc, pj_bool_t autoreg, + pjsip_tx_data **p_tdata) +{ + pjsip_msg *msg; + pjsip_contact_hdr *hdr; + const pjsip_hdr *h_allow; + pj_status_t status; + pjsip_tx_data *tdata; + + PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL); + + pj_lock_acquire(regc->lock); + + status = create_request(regc, &tdata); + if (status != PJ_SUCCESS) { + pj_lock_release(regc->lock); + return status; + } + + msg = tdata->msg; + + /* Add Contact headers. */ + hdr = regc->contact_hdr_list.next; + while (hdr != ®c->contact_hdr_list) { + pjsip_msg_add_hdr(msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, hdr)); + hdr = hdr->next; + } + + /* Also add bindings which are to be removed */ + while (!pj_list_empty(®c->removed_contact_hdr_list)) { + hdr = regc->removed_contact_hdr_list.next; + pjsip_msg_add_hdr(msg, (pjsip_hdr*) + pjsip_hdr_clone(tdata->pool, hdr)); + pj_list_erase(hdr); + } + + + if (regc->expires_hdr) + pjsip_msg_add_hdr(msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, + regc->expires_hdr)); + + if (regc->timer.id != 0) { + pjsip_endpt_cancel_timer(regc->endpt, ®c->timer); + regc->timer.id = 0; + } + + /* Add Allow header (http://trac.pjsip.org/repos/ticket/1039) */ + h_allow = pjsip_endpt_get_capability(regc->endpt, PJSIP_H_ALLOW, NULL); + if (h_allow) { + pjsip_msg_add_hdr(msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, h_allow)); + + } + + regc->auto_reg = autoreg; + + pj_lock_release(regc->lock); + + /* Done */ + *p_tdata = tdata; + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_regc_unregister(pjsip_regc *regc, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsip_msg *msg; + pjsip_hdr *hdr; + pj_status_t status; + + PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL); + + pj_lock_acquire(regc->lock); + + if (regc->timer.id != 0) { + pjsip_endpt_cancel_timer(regc->endpt, ®c->timer); + regc->timer.id = 0; + } + + status = create_request(regc, &tdata); + if (status != PJ_SUCCESS) { + pj_lock_release(regc->lock); + return status; + } + + msg = tdata->msg; + + /* Add Contact headers. */ + hdr = (pjsip_hdr*)regc->contact_hdr_list.next; + while ((void*)hdr != (void*)®c->contact_hdr_list) { + pjsip_msg_add_hdr(msg, (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, hdr)); + hdr = hdr->next; + } + + /* Also add bindings which are to be removed */ + while (!pj_list_empty(®c->removed_contact_hdr_list)) { + hdr = (pjsip_hdr*)regc->removed_contact_hdr_list.next; + pjsip_msg_add_hdr(msg, (pjsip_hdr*) + pjsip_hdr_clone(tdata->pool, hdr)); + pj_list_erase(hdr); + } + + /* Add Expires:0 header */ + hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0); + pjsip_msg_add_hdr(msg, hdr); + + pj_lock_release(regc->lock); + + *p_tdata = tdata; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsip_regc_unregister_all(pjsip_regc *regc, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsip_contact_hdr *hcontact; + pjsip_hdr *hdr; + pjsip_msg *msg; + pj_status_t status; + + PJ_ASSERT_RETURN(regc && p_tdata, PJ_EINVAL); + + pj_lock_acquire(regc->lock); + + if (regc->timer.id != 0) { + pjsip_endpt_cancel_timer(regc->endpt, ®c->timer); + regc->timer.id = 0; + } + + status = create_request(regc, &tdata); + if (status != PJ_SUCCESS) { + pj_lock_release(regc->lock); + return status; + } + + msg = tdata->msg; + + /* Clear removed_contact_hdr_list */ + pj_list_init(®c->removed_contact_hdr_list); + + /* Add Contact:* header */ + hcontact = pjsip_contact_hdr_create(tdata->pool); + hcontact->star = 1; + pjsip_msg_add_hdr(msg, (pjsip_hdr*)hcontact); + + /* Add Expires:0 header */ + hdr = (pjsip_hdr*) pjsip_expires_hdr_create(tdata->pool, 0); + pjsip_msg_add_hdr(msg, hdr); + + pj_lock_release(regc->lock); + + *p_tdata = tdata; + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_regc_update_contact( pjsip_regc *regc, + int contact_cnt, + const pj_str_t contact[] ) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(regc, PJ_EINVAL); + + pj_lock_acquire(regc->lock); + status = set_contact( regc, contact_cnt, contact ); + pj_lock_release(regc->lock); + + return status; +} + + +PJ_DEF(pj_status_t) pjsip_regc_update_expires( pjsip_regc *regc, + pj_uint32_t expires ) +{ + PJ_ASSERT_RETURN(regc, PJ_EINVAL); + + pj_lock_acquire(regc->lock); + set_expires( regc, expires ); + pj_lock_release(regc->lock); + + return PJ_SUCCESS; +} + + +static void call_callback(pjsip_regc *regc, pj_status_t status, int st_code, + const pj_str_t *reason, + pjsip_rx_data *rdata, pj_int32_t expiration, + int contact_cnt, pjsip_contact_hdr *contact[]) +{ + struct pjsip_regc_cbparam cbparam; + + + if (!regc->cb) + return; + + cbparam.regc = regc; + cbparam.token = regc->token; + cbparam.status = status; + cbparam.code = st_code; + cbparam.reason = *reason; + cbparam.rdata = rdata; + cbparam.contact_cnt = contact_cnt; + cbparam.expiration = expiration; + if (contact_cnt) { + pj_memcpy( cbparam.contact, contact, + contact_cnt*sizeof(pjsip_contact_hdr*)); + } + + (*regc->cb)(&cbparam); +} + +static void regc_refresh_timer_cb( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pjsip_regc *regc = (pjsip_regc*) entry->user_data; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_UNUSED_ARG(timer_heap); + + /* Temporarily increase busy flag to prevent regc from being deleted + * in pjsip_regc_send() or in the callback + */ + pj_atomic_inc(regc->busy_ctr); + + entry->id = 0; + status = pjsip_regc_register(regc, 1, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_regc_send(regc, tdata); + } + + if (status != PJ_SUCCESS && regc->cb) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg)); + call_callback(regc, status, 400, &reason, NULL, -1, 0, NULL); + } + + /* Delete the record if user destroy regc during the callback. */ + if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) { + pjsip_regc_destroy(regc); + } +} + +static void schedule_registration ( pjsip_regc *regc, pj_int32_t expiration ) +{ + if (regc->auto_reg && expiration > 0) { + pj_time_val delay = { 0, 0}; + + delay.sec = expiration - regc->delay_before_refresh; + if (regc->expires != PJSIP_REGC_EXPIRATION_NOT_SPECIFIED && + delay.sec > (pj_int32_t)regc->expires) + { + delay.sec = regc->expires; + } + if (delay.sec < DELAY_BEFORE_REFRESH) + delay.sec = DELAY_BEFORE_REFRESH; + regc->timer.cb = ®c_refresh_timer_cb; + regc->timer.id = REFRESH_TIMER; + regc->timer.user_data = regc; + pjsip_endpt_schedule_timer( regc->endpt, ®c->timer, &delay); + pj_gettimeofday(®c->last_reg); + regc->next_reg = regc->last_reg; + regc->next_reg.sec += delay.sec; + } +} + +PJ_DEF(pj_status_t) pjsip_regc_set_via_sent_by( pjsip_regc *regc, + pjsip_host_port *via_addr, + pjsip_transport *via_tp) +{ + PJ_ASSERT_RETURN(regc, PJ_EINVAL); + + if (!via_addr) + pj_bzero(®c->via_addr, sizeof(regc->via_addr)); + else + regc->via_addr = *via_addr; + regc->via_tp = via_tp; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjsip_regc_set_delay_before_refresh( pjsip_regc *regc, + pj_uint32_t delay ) +{ + PJ_ASSERT_RETURN(regc, PJ_EINVAL); + + if (delay > regc->expires) + return PJ_ETOOBIG; + + if (regc->delay_before_refresh != delay) + { + regc->delay_before_refresh = delay; + + if (regc->timer.id != 0) { + /* Cancel registration timer */ + pjsip_endpt_cancel_timer(regc->endpt, ®c->timer); + regc->timer.id = 0; + + /* Schedule next registration */ + schedule_registration(regc, regc->expires); + } + } + + return PJ_SUCCESS; +} + + +static pj_int32_t calculate_response_expiration(const pjsip_regc *regc, + const pjsip_rx_data *rdata, + unsigned *contact_cnt, + unsigned max_contact, + pjsip_contact_hdr *contacts[]) +{ + pj_int32_t expiration = NOEXP; + const pjsip_msg *msg = rdata->msg_info.msg; + const pjsip_hdr *hdr; + + /* Enumerate all Contact headers in the response */ + *contact_cnt = 0; + for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) { + if (hdr->type == PJSIP_H_CONTACT && + *contact_cnt < max_contact) + { + contacts[*contact_cnt] = (pjsip_contact_hdr*)hdr; + ++(*contact_cnt); + } + } + + if (regc->current_op == REGC_REGISTERING) { + pj_bool_t has_our_contact = PJ_FALSE; + const pjsip_expires_hdr *expires; + + /* Get Expires header */ + expires = (const pjsip_expires_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); + + /* Try to find the Contact URIs that we register, in the response + * to get the expires value. We'll try both with comparing the URI + * and comparing the extension param only. + */ + if (pjsip_cfg()->regc.check_contact || regc->add_xuid_param) { + unsigned i; + for (i=0; i<*contact_cnt; ++i) { + const pjsip_contact_hdr *our_hdr; + + our_hdr = (const pjsip_contact_hdr*) + regc->contact_hdr_list.next; + + /* Match with our Contact header(s) */ + while ((void*)our_hdr != (void*)®c->contact_hdr_list) { + + const pjsip_uri *uri1, *uri2; + pj_bool_t matched = PJ_FALSE; + + /* Exclude the display name when comparing the URI + * since server may not return it. + */ + uri1 = (const pjsip_uri*) + pjsip_uri_get_uri(contacts[i]->uri); + uri2 = (const pjsip_uri*) + pjsip_uri_get_uri(our_hdr->uri); + + /* First try with exact matching, according to RFC 3261 + * Section 19.1.4 URI Comparison + */ + if (pjsip_cfg()->regc.check_contact) { + matched = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, + uri1, uri2)==0; + } + + /* If no match is found, try with matching the extension + * parameter only if extension parameter was added. + */ + if (!matched && regc->add_xuid_param && + (PJSIP_URI_SCHEME_IS_SIP(uri1) || + PJSIP_URI_SCHEME_IS_SIPS(uri1)) && + (PJSIP_URI_SCHEME_IS_SIP(uri2) || + PJSIP_URI_SCHEME_IS_SIPS(uri2))) + { + const pjsip_sip_uri *sip_uri1, *sip_uri2; + const pjsip_param *p1, *p2; + + sip_uri1 = (const pjsip_sip_uri*)uri1; + sip_uri2 = (const pjsip_sip_uri*)uri2; + + p1 = pjsip_param_cfind(&sip_uri1->other_param, + &XUID_PARAM_NAME); + p2 = pjsip_param_cfind(&sip_uri2->other_param, + &XUID_PARAM_NAME); + matched = p1 && p2 && + pj_strcmp(&p1->value, &p2->value)==0; + + } + + if (matched) { + has_our_contact = PJ_TRUE; + + if (contacts[i]->expires >= 0 && + contacts[i]->expires < expiration) + { + /* Get the lowest expiration time. */ + expiration = contacts[i]->expires; + } + + break; + } + + our_hdr = our_hdr->next; + + } /* while ((void.. */ + + } /* for (i=.. */ + + /* If matching Contact header(s) are found but the + * header doesn't contain expires parameter, get the + * expiration value from the Expires header. And + * if Expires header is not present, get the expiration + * value from the request. + */ + if (has_our_contact && expiration == NOEXP) { + if (expires) { + expiration = expires->ivalue; + } else if (regc->expires_hdr) { + expiration = regc->expires_hdr->ivalue; + } else { + /* We didn't request explicit expiration value, + * and server doesn't specify it either. This + * shouldn't happen unless we have a broken + * registrar. + */ + expiration = 3600; + } + } + + } + + /* If we still couldn't get matching Contact header(s), it means + * there must be something wrong with the registrar (e.g. it may + * have modified the URI's in the response, which is prohibited). + */ + if (expiration==NOEXP) { + /* If the number of Contact headers in the response matches + * ours, they're all probably ours. Get the expiration + * from there if this is the case, or from Expires header + * if we don't have exact Contact header count, or + * from the request as the last resort. + */ + unsigned our_contact_cnt; + + our_contact_cnt = pj_list_size(®c->contact_hdr_list); + + if (*contact_cnt == our_contact_cnt && *contact_cnt && + contacts[0]->expires >= 0) + { + expiration = contacts[0]->expires; + } else if (expires) + expiration = expires->ivalue; + else if (regc->expires_hdr) + expiration = regc->expires_hdr->ivalue; + else + expiration = 3600; + } + + } else { + /* Just assume that the unregistration has been successful. */ + expiration = 0; + } + + /* Must have expiration value by now */ + pj_assert(expiration != NOEXP); + + return expiration; +} + +static void regc_tsx_callback(void *token, pjsip_event *event) +{ + pj_status_t status; + pjsip_regc *regc = (pjsip_regc*) token; + pjsip_transaction *tsx = event->body.tsx_state.tsx; + pj_bool_t handled = PJ_TRUE; + + pj_atomic_inc(regc->busy_ctr); + pj_lock_acquire(regc->lock); + + /* Decrement pending transaction counter. */ + pj_assert(regc->has_tsx); + regc->has_tsx = PJ_FALSE; + + /* Add reference to the transport */ + if (tsx->transport != regc->last_transport) { + if (regc->last_transport) { + pjsip_transport_dec_ref(regc->last_transport); + regc->last_transport = NULL; + } + + if (tsx->transport) { + regc->last_transport = tsx->transport; + pjsip_transport_add_ref(regc->last_transport); + } + } + + /* Handle 401/407 challenge (even when _delete_flag is set) */ + 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; + + /* reset current op */ + regc->current_op = REGC_IDLE; + + status = pjsip_auth_clt_reinit_req( ®c->auth_sess, + rdata, + tsx->last_tx, + &tdata); + + if (status == PJ_SUCCESS) { + status = pjsip_regc_send(regc, tdata); + } + + if (status != PJ_SUCCESS) { + + /* Only call callback if application is still interested + * in it. + */ + if (regc->_delete_flag == 0) { + /* Should be safe to release the lock temporarily. + * We do this to avoid deadlock. + */ + pj_lock_release(regc->lock); + call_callback(regc, status, tsx->status_code, + &rdata->msg_info.msg->line.status.reason, + rdata, -1, 0, NULL); + pj_lock_acquire(regc->lock); + } + } + + } else if (regc->_delete_flag) { + + /* User has called pjsip_regc_destroy(), so don't call callback. + * This regc will be destroyed later in this function. + */ + + /* Just reset current op */ + regc->current_op = REGC_IDLE; + + } else if (tsx->status_code == PJSIP_SC_INTERVAL_TOO_BRIEF && + regc->current_op == REGC_REGISTERING) + { + /* Handle 423 response automatically: + * - set requested expiration to Min-Expires header, ONLY IF + * the original request is a registration (as opposed to + * unregistration) and the requested expiration was indeed + * lower than Min-Expires) + * - resend the request + */ + pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; + pjsip_min_expires_hdr *me_hdr; + pjsip_tx_data *tdata; + pj_int32_t min_exp; + + /* reset current op */ + regc->current_op = REGC_IDLE; + + /* Update requested expiration */ + me_hdr = (pjsip_min_expires_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, + PJSIP_H_MIN_EXPIRES, NULL); + if (me_hdr) { + min_exp = me_hdr->ivalue; + } else { + /* Broken server, Min-Expires doesn't exist. + * Just guestimate then, BUT ONLY if if this is the + * first time we received such response. + */ + enum { + /* Note: changing this value would require changing couple of + * Python test scripts. + */ + UNSPECIFIED_MIN_EXPIRES = 3601 + }; + if (!regc->expires_hdr || + regc->expires_hdr->ivalue != UNSPECIFIED_MIN_EXPIRES) + { + min_exp = UNSPECIFIED_MIN_EXPIRES; + } else { + handled = PJ_FALSE; + PJ_LOG(4,(THIS_FILE, "Registration failed: 423 response " + "without Min-Expires header is invalid")); + goto handle_err; + } + } + + if (regc->expires_hdr && regc->expires_hdr->ivalue >= min_exp) { + /* But we already send with greater expiration time, why does + * the server send us with 423? Oh well, just fail the request. + */ + handled = PJ_FALSE; + PJ_LOG(4,(THIS_FILE, "Registration failed: invalid " + "Min-Expires header value in response")); + goto handle_err; + } + + set_expires(regc, min_exp); + + status = pjsip_regc_register(regc, regc->auto_reg, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_regc_send(regc, tdata); + } + + if (status != PJ_SUCCESS) { + /* Only call callback if application is still interested + * in it. + */ + if (!regc->_delete_flag) { + /* Should be safe to release the lock temporarily. + * We do this to avoid deadlock. + */ + pj_lock_release(regc->lock); + call_callback(regc, status, tsx->status_code, + &rdata->msg_info.msg->line.status.reason, + rdata, -1, 0, NULL); + pj_lock_acquire(regc->lock); + } + } + + } else { + handled = PJ_FALSE; + } + +handle_err: + if (!handled) { + pjsip_rx_data *rdata; + pj_int32_t expiration = NOEXP; + unsigned contact_cnt = 0; + pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT]; + + if (tsx->status_code/100 == 2) { + + rdata = event->body.tsx_state.src.rdata; + + /* Calculate expiration */ + expiration = calculate_response_expiration(regc, rdata, + &contact_cnt, + PJSIP_REGC_MAX_CONTACT, + contact); + + /* Schedule next registration */ + schedule_registration(regc, expiration); + + } else { + rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ? + event->body.tsx_state.src.rdata : NULL; + } + + /* Update registration */ + if (expiration==NOEXP) expiration=-1; + regc->expires = expiration; + + /* Mark operation as complete */ + regc->current_op = REGC_IDLE; + + /* Call callback. */ + /* Should be safe to release the lock temporarily. + * We do this to avoid deadlock. + */ + pj_lock_release(regc->lock); + call_callback(regc, PJ_SUCCESS, tsx->status_code, + (rdata ? &rdata->msg_info.msg->line.status.reason + : &tsx->status_text), + rdata, expiration, + contact_cnt, contact); + pj_lock_acquire(regc->lock); + } + + pj_lock_release(regc->lock); + + /* Delete the record if user destroy regc during the callback. */ + if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) { + pjsip_regc_destroy(regc); + } +} + +PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata) +{ + pj_status_t status; + pjsip_cseq_hdr *cseq_hdr; + pjsip_expires_hdr *expires_hdr; + pj_uint32_t cseq; + + pj_atomic_inc(regc->busy_ctr); + pj_lock_acquire(regc->lock); + + /* Make sure we don't have pending transaction. */ + if (regc->has_tsx) { + PJ_LOG(4,(THIS_FILE, "Unable to send request, regc has another " + "transaction pending")); + pjsip_tx_data_dec_ref( tdata ); + pj_lock_release(regc->lock); + pj_atomic_dec(regc->busy_ctr); + return PJSIP_EBUSY; + } + + pj_assert(regc->current_op == REGC_IDLE); + + /* Invalidate message buffer. */ + pjsip_tx_data_invalidate_msg(tdata); + + /* Increment CSeq */ + cseq = ++regc->cseq_hdr->cseq; + cseq_hdr = (pjsip_cseq_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); + cseq_hdr->cseq = cseq; + + /* Find Expires header */ + expires_hdr = (pjsip_expires_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_EXPIRES, NULL); + + /* Bind to transport selector */ + pjsip_tx_data_set_transport(tdata, ®c->tp_sel); + + regc->has_tsx = PJ_TRUE; + + /* Set current operation based on the value of Expires header */ + if (expires_hdr && expires_hdr->ivalue==0) + regc->current_op = REGC_UNREGISTERING; + else + regc->current_op = REGC_REGISTERING; + + /* Prevent deletion of tdata, e.g: when something wrong in sending, + * we need tdata to retrieve the transport. + */ + pjsip_tx_data_add_ref(tdata); + + /* If via_addr is set, use this address for the Via header. */ + if (regc->via_addr.host.slen > 0) { + tdata->via_addr = regc->via_addr; + tdata->via_tp = regc->via_tp; + } + + /* Need to unlock the regc temporarily while sending the message to + * prevent deadlock (https://trac.pjsip.org/repos/ticket/1247). + * It should be safe to do this since the regc's refcount has been + * incremented. + */ + pj_lock_release(regc->lock); + + /* Now send the message */ + status = pjsip_endpt_send_request(regc->endpt, tdata, REGC_TSX_TIMEOUT, + regc, ®c_tsx_callback); + if (status!=PJ_SUCCESS) { + PJ_LOG(4,(THIS_FILE, "Error sending request, status=%d", status)); + } + + /* Reacquire the lock */ + pj_lock_acquire(regc->lock); + + /* Get last transport used and add reference to it */ + if (tdata->tp_info.transport != regc->last_transport && + status==PJ_SUCCESS) + { + if (regc->last_transport) { + pjsip_transport_dec_ref(regc->last_transport); + regc->last_transport = NULL; + } + + if (tdata->tp_info.transport) { + regc->last_transport = tdata->tp_info.transport; + pjsip_transport_add_ref(regc->last_transport); + } + } + + /* Release tdata */ + pjsip_tx_data_dec_ref(tdata); + + pj_lock_release(regc->lock); + + /* Delete the record if user destroy regc during the callback. */ + if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) { + pjsip_regc_destroy(regc); + } + + return status; +} + + diff --git a/pjsip/src/pjsip-ua/sip_replaces.c b/pjsip/src/pjsip-ua/sip_replaces.c new file mode 100644 index 0000000..b961cf9 --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_replaces.c @@ -0,0 +1,384 @@ +/* $Id: sip_replaces.c 3999 2012-03-30 07:10:13Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-ua/sip_replaces.h> +#include <pjsip-ua/sip_inv.h> +#include <pjsip/print_util.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_parser.h> +#include <pjsip/sip_transport.h> +#include <pjsip/sip_ua_layer.h> +#include <pjsip/sip_util.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> + +#define THIS_FILE "sip_replaces.c" + + +/* + * Replaces header vptr. + */ +static int replaces_hdr_print( pjsip_replaces_hdr *hdr, + char *buf, pj_size_t size); +static pjsip_replaces_hdr* replaces_hdr_clone( pj_pool_t *pool, + const pjsip_replaces_hdr *hdr); +static pjsip_replaces_hdr* replaces_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_replaces_hdr*); + +static pjsip_hdr_vptr replaces_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &replaces_hdr_clone, + (pjsip_hdr_clone_fptr) &replaces_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &replaces_hdr_print, +}; + +/* Globals */ +static pjsip_endpoint *the_endpt; +static pj_bool_t is_initialized; + +PJ_DEF(pjsip_replaces_hdr*) pjsip_replaces_hdr_create(pj_pool_t *pool) +{ + pjsip_replaces_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_replaces_hdr); + hdr->type = PJSIP_H_OTHER; + hdr->name.ptr = "Replaces"; + hdr->name.slen = 8; + hdr->vptr = &replaces_hdr_vptr; + pj_list_init(hdr); + pj_list_init(&hdr->other_param); + return hdr; +} + +static int replaces_hdr_print( pjsip_replaces_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->call_id); + copy_advance_pair(p, ";to-tag=", 8, hdr->to_tag); + copy_advance_pair(p, ";from-tag=", 10, hdr->from_tag); + + if (hdr->early_only) { + const pj_str_t str_early_only = { ";early-only", 11 }; + copy_advance(p, str_early_only); + } + + 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_replaces_hdr* replaces_hdr_clone( pj_pool_t *pool, + const pjsip_replaces_hdr *rhs) +{ + pjsip_replaces_hdr *hdr = pjsip_replaces_hdr_create(pool); + pj_strdup(pool, &hdr->call_id, &rhs->call_id); + pj_strdup(pool, &hdr->to_tag, &rhs->to_tag); + pj_strdup(pool, &hdr->from_tag, &rhs->from_tag); + hdr->early_only = rhs->early_only; + pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + +static pjsip_replaces_hdr* +replaces_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_replaces_hdr *rhs ) +{ + pjsip_replaces_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_replaces_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + + +/* + * Parse Replaces header. + */ +static pjsip_hdr *parse_hdr_replaces(pjsip_parse_ctx *ctx) +{ + pjsip_replaces_hdr *hdr = pjsip_replaces_hdr_create(ctx->pool); + const pj_str_t to_tag = { "to-tag", 6 }; + const pj_str_t from_tag = { "from-tag", 8 }; + const pj_str_t early_only_tag = { "early-only", 10 }; + + /*pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->call_id);*/ + /* Get Call-ID (until ';' is found). using pjsip_TOKEN_SPEC doesn't work + * because it stops parsing when '@' character is found. + */ + pj_scan_get_until_ch(ctx->scanner, ';', &hdr->call_id); + + 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, &to_tag)==0) { + hdr->to_tag = pvalue; + } else if (pj_stricmp(&pname, &from_tag)==0) { + hdr->from_tag = pvalue; + } else if (pj_stricmp(&pname, &early_only_tag)==0) { + hdr->early_only = PJ_TRUE; + } 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; +} + + +/* Deinitialize Replaces */ +static void pjsip_replaces_deinit_module(pjsip_endpoint *endpt) +{ + PJ_TODO(provide_initialized_flag_for_each_endpoint); + PJ_UNUSED_ARG(endpt); + is_initialized = PJ_FALSE; +} + +/* + * Initialize Replaces support in PJSIP. + */ +PJ_DEF(pj_status_t) pjsip_replaces_init_module(pjsip_endpoint *endpt) +{ + pj_status_t status; + const pj_str_t STR_REPLACES = { "replaces", 8 }; + + the_endpt = endpt; + + if (is_initialized) + return PJ_SUCCESS; + + /* Register Replaces header parser */ + status = pjsip_register_hdr_parser( "Replaces", NULL, + &parse_hdr_replaces); + if (status != PJ_SUCCESS) + return status; + + /* Register "replaces" capability */ + status = pjsip_endpt_add_capability(endpt, NULL, PJSIP_H_SUPPORTED, NULL, + 1, &STR_REPLACES); + + /* Register deinit module to be executed when PJLIB shutdown */ + if (pjsip_endpt_atexit(endpt, &pjsip_replaces_deinit_module) != PJ_SUCCESS) + { + /* Failure to register this function may cause this module won't + * work properly when the stack is restarted (without quitting + * application). + */ + pj_assert(!"Failed to register Replaces deinit."); + PJ_LOG(1, (THIS_FILE, "Failed to register Replaces deinit.")); + } + + is_initialized = PJ_TRUE; + return PJ_SUCCESS; +} + + +/* + * Verify that incoming request with Replaces header can be processed. + */ +PJ_DEF(pj_status_t) pjsip_replaces_verify_request( pjsip_rx_data *rdata, + pjsip_dialog **p_dlg, + pj_bool_t lock_dlg, + pjsip_tx_data **p_tdata) +{ + const pj_str_t STR_REPLACES = { "Replaces", 8 }; + pjsip_replaces_hdr *rep_hdr; + int code = 200; + const char *warn_text = NULL; + pjsip_hdr res_hdr_list; + pjsip_dialog *dlg = NULL; + pjsip_inv_session *inv; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(rdata && p_dlg, PJ_EINVAL); + + /* Check that pjsip_replaces_init_module() has been called. */ + PJ_ASSERT_RETURN(the_endpt != NULL, PJ_EINVALIDOP); + + + /* Init output arguments */ + *p_dlg = NULL; + if (p_tdata) *p_tdata = NULL; + + pj_list_init(&res_hdr_list); + + /* Find Replaces header */ + rep_hdr = (pjsip_replaces_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_REPLACES, + NULL); + if (!rep_hdr) { + /* No Replaces header. No further processing is necessary. */ + return PJ_SUCCESS; + } + + + /* Check that there's no other Replaces header and return 400 Bad Request + * if not. + */ + if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_REPLACES, + rep_hdr->next)) { + code = PJSIP_SC_BAD_REQUEST; + warn_text = "Found multiple Replaces headers"; + goto on_return; + } + + /* Find the dialog identified by Replaces header (and always lock the + * dialog no matter what application wants). + */ + dlg = pjsip_ua_find_dialog(&rep_hdr->call_id, &rep_hdr->to_tag, + &rep_hdr->from_tag, PJ_TRUE); + + /* Respond with 481 "Call/Transaction Does Not Exist" response if + * no dialog is found. + */ + if (dlg == NULL) { + code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST; + warn_text = "No dialog found for Replaces request"; + goto on_return; + } + + /* Get the invite session within the dialog */ + inv = pjsip_dlg_get_inv_session(dlg); + + /* Return 481 if no invite session is present. */ + if (inv == NULL) { + code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST; + warn_text = "No INVITE session found for Replaces request"; + goto on_return; + } + + /* Return 603 Declined response if invite session has already + * terminated + */ + if (inv->state >= PJSIP_INV_STATE_DISCONNECTED) { + code = PJSIP_SC_DECLINE; + warn_text = "INVITE session already terminated"; + goto on_return; + } + + /* If "early-only" flag is present, check that the invite session + * has not been confirmed yet. If the session has been confirmed, + * return 486 "Busy Here" response. + */ + if (rep_hdr->early_only && inv->state >= PJSIP_INV_STATE_CONNECTING) { + code = PJSIP_SC_BUSY_HERE; + warn_text = "INVITE session already established"; + goto on_return; + } + + /* If the Replaces header field matches an early dialog that was not + * initiated by this UA, it returns a 481 (Call/Transaction Does Not + * Exist) response to the new INVITE. + */ + if (inv->state <= PJSIP_INV_STATE_EARLY && inv->role != PJSIP_ROLE_UAC) { + code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST; + warn_text = "Found early INVITE session but not initiated by this UA"; + goto on_return; + } + + + /* + * Looks like everything is okay!! + */ + *p_dlg = dlg; + status = PJ_SUCCESS; + code = 200; + +on_return: + + /* Create response if necessary */ + if (code != 200) { + /* If we have dialog we must unlock it */ + if (dlg) + pjsip_dlg_dec_lock(dlg); + + /* Create response */ + if (p_tdata) { + pjsip_tx_data *tdata; + const pjsip_hdr *h; + + status = pjsip_endpt_create_response(the_endpt, rdata, code, + NULL, &tdata); + + if (status != PJ_SUCCESS) + return status; + + /* Add response headers. */ + h = res_hdr_list.next; + while (h != &res_hdr_list) { + pjsip_hdr *cloned; + + cloned = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, h); + PJ_ASSERT_RETURN(cloned, PJ_ENOMEM); + + pjsip_msg_add_hdr(tdata->msg, cloned); + + h = h->next; + } + + /* Add warn text, if any */ + if (warn_text) { + pjsip_warning_hdr *warn_hdr; + pj_str_t warn_value = pj_str((char*)warn_text); + + warn_hdr=pjsip_warning_hdr_create(tdata->pool, 399, + pjsip_endpt_name(the_endpt), + &warn_value); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)warn_hdr); + } + + *p_tdata = tdata; + } + + /* Can not return PJ_SUCCESS when response message is produced. + * Ref: PROTOS test ~#2490 + */ + if (status == PJ_SUCCESS) + status = PJSIP_ERRNO_FROM_SIP_STATUS(code); + + } else { + /* If application doesn't want to lock the dialog, unlock it */ + if (!lock_dlg) + pjsip_dlg_dec_lock(dlg); + } + + return status; +} + + + diff --git a/pjsip/src/pjsip-ua/sip_timer.c b/pjsip/src/pjsip-ua/sip_timer.c new file mode 100644 index 0000000..2c70791 --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_timer.c @@ -0,0 +1,1062 @@ +/* $Id: sip_timer.c 3999 2012-03-30 07:10:13Z bennylp $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-ua/sip_timer.h> +#include <pjsip/print_util.h> +#include <pjsip/sip_endpoint.h> +#include <pj/log.h> +#include <pj/math.h> +#include <pj/os.h> +#include <pj/pool.h> + +#define THIS_FILE "sip_timer.c" + + +/* Constant of Session Timers */ +#define ABS_MIN_SE 90 /* Absolute Min-SE, in seconds */ + + +/* String definitions */ +static const pj_str_t STR_SE = {"Session-Expires", 15}; +static const pj_str_t STR_SHORT_SE = {"x", 1}; +static const pj_str_t STR_MIN_SE = {"Min-SE", 6}; +static const pj_str_t STR_REFRESHER = {"refresher", 9}; +static const pj_str_t STR_UAC = {"uac", 3}; +static const pj_str_t STR_UAS = {"uas", 3}; +static const pj_str_t STR_TIMER = {"timer", 5}; + + +/* Enumeration of refresher */ +enum timer_refresher { + TR_UNKNOWN, + TR_UAC, + TR_UAS +}; + +/* Structure definition of Session Timers */ +struct pjsip_timer +{ + pj_bool_t active; /**< Active/inactive flag */ + pjsip_timer_setting setting; /**< Session Timers setting */ + enum timer_refresher refresher; /**< Session refresher */ + pj_time_val last_refresh; /**< Timestamp of last + refresh */ + pj_timer_entry timer; /**< Timer entry */ + pj_bool_t use_update; /**< Use UPDATE method to + refresh the session */ + pj_bool_t with_sdp; /**< SDP in UPDATE? */ + pjsip_role_e role; /**< Role in last INVITE/ + UPDATE transaction. */ + +}; + +/* External global vars */ +extern pj_bool_t pjsip_use_compact_form; + +/* Local functions & vars */ +static void stop_timer(pjsip_inv_session *inv); +static void start_timer(pjsip_inv_session *inv); +static pj_bool_t is_initialized; +const pjsip_method pjsip_update_method = { PJSIP_OTHER_METHOD, {"UPDATE", 6}}; +/* + * Session-Expires header vptr. + */ +static int se_hdr_print(pjsip_sess_expires_hdr *hdr, + char *buf, pj_size_t size); +static pjsip_sess_expires_hdr* se_hdr_clone(pj_pool_t *pool, + const pjsip_sess_expires_hdr *hdr); +static pjsip_sess_expires_hdr* se_hdr_shallow_clone( + pj_pool_t *pool, + const pjsip_sess_expires_hdr* hdr); + +static pjsip_hdr_vptr se_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &se_hdr_clone, + (pjsip_hdr_clone_fptr) &se_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &se_hdr_print, +}; + +/* + * Min-SE header vptr. + */ +static int min_se_hdr_print(pjsip_min_se_hdr *hdr, + char *buf, pj_size_t size); +static pjsip_min_se_hdr* min_se_hdr_clone(pj_pool_t *pool, + const pjsip_min_se_hdr *hdr); +static pjsip_min_se_hdr* min_se_hdr_shallow_clone( + pj_pool_t *pool, + const pjsip_min_se_hdr* hdr); + +static pjsip_hdr_vptr min_se_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &min_se_hdr_clone, + (pjsip_hdr_clone_fptr) &min_se_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &min_se_hdr_print, +}; + +/* + * Session-Expires header vptr. + */ +static int se_hdr_print(pjsip_sess_expires_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(); + const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name; + + /* Print header name and value */ + if ((endbuf - p) < (hname->slen + 16)) + return -1; + + copy_advance(p, (*hname)); + *p++ = ':'; + *p++ = ' '; + + printed = pj_utoa(hdr->sess_expires, p); + p += printed; + + /* Print 'refresher' param */ + if (hdr->refresher.slen) + { + if ((endbuf - p) < (STR_REFRESHER.slen + 2 + hdr->refresher.slen)) + return -1; + + *p++ = ';'; + copy_advance(p, STR_REFRESHER); + *p++ = '='; + copy_advance(p, hdr->refresher); + } + + /* Print generic params */ + 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_sess_expires_hdr* se_hdr_clone(pj_pool_t *pool, + const pjsip_sess_expires_hdr *hsrc) +{ + pjsip_sess_expires_hdr *hdr = pjsip_sess_expires_hdr_create(pool); + hdr->sess_expires = hsrc->sess_expires; + pj_strdup(pool, &hdr->refresher, &hsrc->refresher); + pjsip_param_clone(pool, &hdr->other_param, &hsrc->other_param); + return hdr; +} + +static pjsip_sess_expires_hdr* se_hdr_shallow_clone( + pj_pool_t *pool, + const pjsip_sess_expires_hdr* hsrc) +{ + pjsip_sess_expires_hdr *hdr = PJ_POOL_ALLOC_T(pool,pjsip_sess_expires_hdr); + pj_memcpy(hdr, hsrc, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->other_param, &hsrc->other_param); + return hdr; +} + +/* + * Min-SE header vptr. + */ +static int min_se_hdr_print(pjsip_min_se_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(); + + /* Print header name and value */ + if ((endbuf - p) < (hdr->name.slen + 16)) + return -1; + + copy_advance(p, hdr->name); + *p++ = ':'; + *p++ = ' '; + + printed = pj_utoa(hdr->min_se, p); + p += printed; + + /* Print generic params */ + 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_min_se_hdr* min_se_hdr_clone(pj_pool_t *pool, + const pjsip_min_se_hdr *hsrc) +{ + pjsip_min_se_hdr *hdr = pjsip_min_se_hdr_create(pool); + hdr->min_se = hsrc->min_se; + pjsip_param_clone(pool, &hdr->other_param, &hsrc->other_param); + return hdr; +} + +static pjsip_min_se_hdr* min_se_hdr_shallow_clone( + pj_pool_t *pool, + const pjsip_min_se_hdr* hsrc) +{ + pjsip_min_se_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_min_se_hdr); + pj_memcpy(hdr, hsrc, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->other_param, &hsrc->other_param); + return hdr; +} + + +/* + * Parse Session-Expires header. + */ +static pjsip_hdr *parse_hdr_se(pjsip_parse_ctx *ctx) +{ + pjsip_sess_expires_hdr *hdr = pjsip_sess_expires_hdr_create(ctx->pool); + const pjsip_parser_const_t *pc = pjsip_parser_const(); + pj_str_t token; + + pj_scan_get(ctx->scanner, &pc->pjsip_DIGIT_SPEC, &token); + hdr->sess_expires = pj_strtoul(&token); + + 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, &STR_REFRESHER)==0) { + hdr->refresher = 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 Min-SE header. + */ +static pjsip_hdr *parse_hdr_min_se(pjsip_parse_ctx *ctx) +{ + pjsip_min_se_hdr *hdr = pjsip_min_se_hdr_create(ctx->pool); + const pjsip_parser_const_t *pc = pjsip_parser_const(); + pj_str_t token; + + pj_scan_get(ctx->scanner, &pc->pjsip_DIGIT_SPEC, &token); + hdr->min_se = pj_strtoul(&token); + + while (*ctx->scanner->curptr == ';') { + pj_str_t pname, pvalue; + pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param); + + pj_scan_get_char(ctx->scanner); + pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0); + + 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; +} + + +/* Add "Session-Expires" and "Min-SE" headers. Note that "Min-SE" header + * can only be added to INVITE/UPDATE request and 422 response. + */ +static void add_timer_headers(pjsip_inv_session *inv, pjsip_tx_data *tdata, + pj_bool_t add_se, pj_bool_t add_min_se) +{ + pjsip_timer *timer = inv->timer; + + /* Add Session-Expires header */ + if (add_se) { + pjsip_sess_expires_hdr *hdr; + + hdr = pjsip_sess_expires_hdr_create(tdata->pool); + hdr->sess_expires = timer->setting.sess_expires; + if (timer->refresher != TR_UNKNOWN) + hdr->refresher = (timer->refresher == TR_UAC? STR_UAC : STR_UAS); + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr); + } + + /* Add Min-SE header */ + if (add_min_se) { + pjsip_min_se_hdr *hdr; + + hdr = pjsip_min_se_hdr_create(tdata->pool); + hdr->min_se = timer->setting.min_se; + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) hdr); + } +} + +/* Timer callback. When the timer is fired, it can be time to refresh + * the session if UA is the refresher, otherwise it is time to end + * the session. + */ +static void timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) +{ + pjsip_inv_session *inv = (pjsip_inv_session*) entry->user_data; + pjsip_tx_data *tdata = NULL; + pj_status_t status; + pj_bool_t as_refresher; + + pj_assert(inv); + + inv->timer->timer.id = 0; + + PJ_UNUSED_ARG(timer_heap); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(inv->dlg); + + /* Check our role */ + as_refresher = + (inv->timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) || + (inv->timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS); + + /* Do action based on role, refresher or refreshee */ + if (as_refresher) { + pj_time_val now; + + /* As refresher, reshedule the refresh request on the following: + * - msut not send re-INVITE if another INVITE or SDP negotiation + * is in progress. + * - must not send UPDATE with SDP if SDP negotiation is in progress + */ + pjmedia_sdp_neg_state neg_state = pjmedia_sdp_neg_get_state(inv->neg); + if ( (!inv->timer->use_update && ( + inv->invite_tsx != NULL || + neg_state != PJMEDIA_SDP_NEG_STATE_DONE) + ) + || + (inv->timer->use_update && inv->timer->with_sdp && + neg_state != PJMEDIA_SDP_NEG_STATE_DONE + ) + ) + { + pj_time_val delay = {1, 0}; + + inv->timer->timer.id = 1; + pjsip_endpt_schedule_timer(inv->dlg->endpt, &inv->timer->timer, + &delay); + pjsip_dlg_dec_lock(inv->dlg); + return; + } + + /* Refresher, refresh the session */ + if (inv->timer->use_update) { + const pjmedia_sdp_session *offer = NULL; + + if (inv->timer->with_sdp) { + pjmedia_sdp_neg_get_active_local(inv->neg, &offer); + } + status = pjsip_inv_update(inv, NULL, offer, &tdata); + } else { + /* Create re-INVITE without modifying session */ + pjsip_msg_body *body; + const pjmedia_sdp_session *offer = NULL; + + pj_assert(pjmedia_sdp_neg_get_state(inv->neg) == + PJMEDIA_SDP_NEG_STATE_DONE); + + status = pjsip_inv_invite(inv, &tdata); + if (status == PJ_SUCCESS) + status = pjmedia_sdp_neg_send_local_offer(inv->pool_prov, + inv->neg, &offer); + if (status == PJ_SUCCESS) + status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer); + if (status == PJ_SUCCESS) { + status = pjsip_create_sdp_body(tdata->pool, + (pjmedia_sdp_session*)offer, &body); + tdata->msg->body = body; + } + } + + pj_gettimeofday(&now); + PJ_LOG(4, (inv->pool->obj_name, + "Refreshing session after %ds (expiration period=%ds)", + (now.sec-inv->timer->last_refresh.sec), + inv->timer->setting.sess_expires)); + } else { + + pj_time_val now; + + /* Refreshee, terminate the session */ + status = pjsip_inv_end_session(inv, PJSIP_SC_REQUEST_TIMEOUT, + NULL, &tdata); + + pj_gettimeofday(&now); + PJ_LOG(3, (inv->pool->obj_name, + "No session refresh received after %ds " + "(expiration period=%ds), stopping session now!", + (now.sec-inv->timer->last_refresh.sec), + inv->timer->setting.sess_expires)); + } + + /* Unlock dialog. */ + pjsip_dlg_dec_lock(inv->dlg); + + /* Send message, if any */ + if (tdata && status == PJ_SUCCESS) { + status = pjsip_inv_send_msg(inv, tdata); + } + + /* Print error message, if any */ + if (status != PJ_SUCCESS) { + PJ_PERROR(2, (inv->pool->obj_name, status, + "Error in %s session timer", + (as_refresher? "refreshing" : "terminating"))); + } +} + +/* Start Session Timers */ +static void start_timer(pjsip_inv_session *inv) +{ + const pj_str_t UPDATE = { "UPDATE", 6 }; + pjsip_timer *timer = inv->timer; + pj_time_val delay = {0}; + + pj_assert(inv->timer->active == PJ_TRUE); + + stop_timer(inv); + + inv->timer->use_update = + (pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, + &UPDATE) == PJSIP_DIALOG_CAP_SUPPORTED); + if (!inv->timer->use_update) { + /* INVITE always needs SDP */ + inv->timer->with_sdp = PJ_TRUE; + } + + pj_timer_entry_init(&timer->timer, + 1, /* id */ + inv, /* user data */ + timer_cb); /* callback */ + + /* Set delay based on role, refresher or refreshee */ + if ((timer->refresher == TR_UAC && inv->timer->role == PJSIP_ROLE_UAC) || + (timer->refresher == TR_UAS && inv->timer->role == PJSIP_ROLE_UAS)) + { + /* Next refresh, the delay is half of session expire */ + delay.sec = timer->setting.sess_expires / 2; + } else { + /* Send BYE if no refresh received until this timer fired, delay + * is the minimum of 32 seconds and one third of the session interval + * before session expiration. + */ + delay.sec = timer->setting.sess_expires - + timer->setting.sess_expires/3; + delay.sec = PJ_MAX((long)timer->setting.sess_expires-32, delay.sec); + } + + /* Schedule the timer */ + pjsip_endpt_schedule_timer(inv->dlg->endpt, &timer->timer, &delay); + + /* Update last refresh time */ + pj_gettimeofday(&timer->last_refresh); +} + +/* Stop Session Timers */ +static void stop_timer(pjsip_inv_session *inv) +{ + if (inv->timer->timer.id != 0) { + pjsip_endpt_cancel_timer(inv->dlg->endpt, &inv->timer->timer); + inv->timer->timer.id = 0; + } +} + +/* Deinitialize Session Timers */ +static void pjsip_timer_deinit_module(pjsip_endpoint *endpt) +{ + PJ_TODO(provide_initialized_flag_for_each_endpoint); + PJ_UNUSED_ARG(endpt); + is_initialized = PJ_FALSE; +} + +/* + * Initialize Session Timers support in PJSIP. + */ +PJ_DEF(pj_status_t) pjsip_timer_init_module(pjsip_endpoint *endpt) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(endpt, PJ_EINVAL); + + if (is_initialized) + return PJ_SUCCESS; + + /* Register Session-Expires header parser */ + status = pjsip_register_hdr_parser( STR_SE.ptr, STR_SHORT_SE.ptr, + &parse_hdr_se); + if (status != PJ_SUCCESS) + return status; + + /* Register Min-SE header parser */ + status = pjsip_register_hdr_parser( STR_MIN_SE.ptr, NULL, + &parse_hdr_min_se); + if (status != PJ_SUCCESS) + return status; + + /* Register 'timer' capability to endpoint */ + status = pjsip_endpt_add_capability(endpt, NULL, PJSIP_H_SUPPORTED, + NULL, 1, &STR_TIMER); + if (status != PJ_SUCCESS) + return status; + + /* Register deinit module to be executed when PJLIB shutdown */ + if (pjsip_endpt_atexit(endpt, &pjsip_timer_deinit_module) != PJ_SUCCESS) { + /* Failure to register this function may cause this module won't + * work properly when the stack is restarted (without quitting + * application). + */ + pj_assert(!"Failed to register Session Timer deinit."); + PJ_LOG(1, (THIS_FILE, "Failed to register Session Timer deinit.")); + } + + is_initialized = PJ_TRUE; + + return PJ_SUCCESS; +} + + +/* + * Initialize Session Timers setting with default values. + */ +PJ_DEF(pj_status_t) pjsip_timer_setting_default(pjsip_timer_setting *setting) +{ + pj_bzero(setting, sizeof(pjsip_timer_setting)); + + setting->sess_expires = PJSIP_SESS_TIMER_DEF_SE; + setting->min_se = ABS_MIN_SE; + + return PJ_SUCCESS; +} + +/* + * Initialize Session Timers in an INVITE session. + */ +PJ_DEF(pj_status_t) pjsip_timer_init_session( + pjsip_inv_session *inv, + const pjsip_timer_setting *setting) +{ + pjsip_timer_setting *s; + + pj_assert(is_initialized); + PJ_ASSERT_RETURN(inv, PJ_EINVAL); + + /* Allocate and/or reset Session Timers structure */ + if (!inv->timer) + inv->timer = PJ_POOL_ZALLOC_T(inv->pool, pjsip_timer); + else + pj_bzero(inv->timer, sizeof(pjsip_timer)); + + s = &inv->timer->setting; + + /* Init Session Timers setting */ + if (setting) { + PJ_ASSERT_RETURN(setting->min_se >= ABS_MIN_SE, + PJ_ETOOSMALL); + PJ_ASSERT_RETURN(setting->sess_expires >= setting->min_se, + PJ_EINVAL); + + pj_memcpy(s, setting, sizeof(*s)); + } else { + pjsip_timer_setting_default(s); + } + + return PJ_SUCCESS; +} + + +/* + * Create Session-Expires header. + */ +PJ_DEF(pjsip_sess_expires_hdr*) pjsip_sess_expires_hdr_create( + pj_pool_t *pool) +{ + pjsip_sess_expires_hdr *hdr = PJ_POOL_ZALLOC_T(pool, + pjsip_sess_expires_hdr); + + pj_assert(is_initialized); + + hdr->type = PJSIP_H_OTHER; + hdr->name = STR_SE; + hdr->sname = STR_SHORT_SE; + hdr->vptr = &se_hdr_vptr; + pj_list_init(hdr); + pj_list_init(&hdr->other_param); + return hdr; +} + + +/* + * Create Min-SE header. + */ +PJ_DEF(pjsip_min_se_hdr*) pjsip_min_se_hdr_create(pj_pool_t *pool) +{ + pjsip_min_se_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_min_se_hdr); + + pj_assert(is_initialized); + + hdr->type = PJSIP_H_OTHER; + hdr->name = STR_MIN_SE; + hdr->vptr = &min_se_hdr_vptr; + pj_list_init(hdr); + pj_list_init(&hdr->other_param); + return hdr; +} + + +/* + * This function generates headers for Session Timers for intial and + * refresh INVITE or UPDATE. + */ +PJ_DEF(pj_status_t) pjsip_timer_update_req(pjsip_inv_session *inv, + pjsip_tx_data *tdata) +{ + PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL); + + /* Check if Session Timers is supported */ + if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0) + return PJ_SUCCESS; + + pj_assert(is_initialized); + + /* Make sure Session Timers is initialized */ + if (inv->timer == NULL) + pjsip_timer_init_session(inv, NULL); + + /* If refresher role (i.e: ours or peer) has been set/negotiated, + * better to keep it. + */ + if (inv->timer->refresher != TR_UNKNOWN) { + pj_bool_t as_refresher; + + /* Check our refresher role */ + as_refresher = + (inv->timer->refresher==TR_UAC && inv->timer->role==PJSIP_ROLE_UAC) || + (inv->timer->refresher==TR_UAS && inv->timer->role==PJSIP_ROLE_UAS); + + /* Update transaction role */ + inv->timer->role = PJSIP_ROLE_UAC; + + /* Update refresher role */ + inv->timer->refresher = as_refresher? TR_UAC : TR_UAS; + } + + /* Add Session Timers headers */ + add_timer_headers(inv, tdata, PJ_TRUE, PJ_TRUE); + + return PJ_SUCCESS; +} + +/* + * This function will handle Session Timers part of INVITE/UPDATE + * responses with code: + * - 422 (Session Interval Too Small) + * - 2xx final response + */ +PJ_DEF(pj_status_t) pjsip_timer_process_resp(pjsip_inv_session *inv, + const pjsip_rx_data *rdata, + pjsip_status_code *st_code) +{ + const pjsip_msg *msg; + + PJ_ASSERT_ON_FAIL(inv && rdata, + {if(st_code)*st_code=PJSIP_SC_INTERNAL_SERVER_ERROR;return PJ_EINVAL;}); + + /* Check if Session Timers is supported */ + if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0) + return PJ_SUCCESS; + + pj_assert(is_initialized); + + msg = rdata->msg_info.msg; + pj_assert(msg->type == PJSIP_RESPONSE_MSG); + + /* Only process response of INVITE or UPDATE */ + if (rdata->msg_info.cseq->method.id != PJSIP_INVITE_METHOD && + pjsip_method_cmp(&rdata->msg_info.cseq->method, &pjsip_update_method)) + { + return PJ_SUCCESS; + } + + if (msg->line.status.code == PJSIP_SC_SESSION_TIMER_TOO_SMALL) { + /* Our Session-Expires is too small, let's update it based on + * Min-SE header in the response. + */ + pjsip_tx_data *tdata; + pjsip_min_se_hdr *min_se_hdr; + pjsip_hdr *hdr; + pjsip_via_hdr *via; + + /* Get Min-SE value from response */ + min_se_hdr = (pjsip_min_se_hdr*) + pjsip_msg_find_hdr_by_name(msg, &STR_MIN_SE, NULL); + if (min_se_hdr == NULL) { + /* Response 422 should contain Min-SE header */ + return PJ_SUCCESS; + } + + /* Session Timers should have been initialized here */ + pj_assert(inv->timer); + + /* Update Min-SE */ + inv->timer->setting.min_se = PJ_MAX(min_se_hdr->min_se, + inv->timer->setting.min_se); + + /* Update Session Timers setting */ + if (inv->timer->setting.sess_expires < inv->timer->setting.min_se) + inv->timer->setting.sess_expires = inv->timer->setting.min_se; + + /* Prepare to restart the request */ + + /* Get the original INVITE request. */ + tdata = inv->invite_req; + + /* Remove branch param in Via header. */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + pj_assert(via); + via->branch_param.slen = 0; + + /* Restore strict route set. + * See http://trac.pjsip.org/repos/ticket/492 + */ + pjsip_restore_strict_route_set(tdata); + + /* Must invalidate the message! */ + pjsip_tx_data_invalidate_msg(tdata); + + pjsip_tx_data_add_ref(tdata); + + /* Update Session Timers headers */ + hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_name(tdata->msg, + &STR_MIN_SE, NULL); + if (hdr != NULL) pj_list_erase(hdr); + + hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_names(tdata->msg, &STR_SE, + &STR_SHORT_SE, NULL); + if (hdr != NULL) pj_list_erase(hdr); + + add_timer_headers(inv, tdata, PJ_TRUE, PJ_TRUE); + + /* Restart UAC */ + pjsip_inv_uac_restart(inv, PJ_FALSE); + pjsip_inv_send_msg(inv, tdata); + + return PJ_SUCCESS; + + } else if (msg->line.status.code/100 == 2) { + + pjsip_sess_expires_hdr *se_hdr; + + /* Find Session-Expires header */ + se_hdr = (pjsip_sess_expires_hdr*) pjsip_msg_find_hdr_by_names( + msg, &STR_SE, + &STR_SHORT_SE, NULL); + if (se_hdr == NULL) { + /* Remote doesn't support/want Session Timers, check if local + * require or force to use Session Timers. + */ + if (inv->options & PJSIP_INV_REQUIRE_TIMER) { + if (st_code) + *st_code = PJSIP_SC_EXTENSION_REQUIRED; + pjsip_timer_end_session(inv); + return PJSIP_ERRNO_FROM_SIP_STATUS( + PJSIP_SC_EXTENSION_REQUIRED); + } + + if ((inv->options & PJSIP_INV_ALWAYS_USE_TIMER) == 0) { + /* Session Timers not forced */ + pjsip_timer_end_session(inv); + return PJ_SUCCESS; + } + } + + /* Make sure Session Timers is initialized */ + if (inv->timer == NULL) + pjsip_timer_init_session(inv, NULL); + + /* Session expiration period specified by remote is lower than our + * Min-SE. + */ + if (se_hdr && + se_hdr->sess_expires < inv->timer->setting.min_se) + { + /* See ticket #954, instead of returning non-PJ_SUCCESS (which + * may cause disconnecting call/dialog), let's just accept the + * SE and update our local SE, as long as it isn't less than 90s. + */ + if (se_hdr->sess_expires >= ABS_MIN_SE) { + PJ_LOG(3, (inv->pool->obj_name, + "Peer responds with bad Session-Expires, %ds, " + "which is less than Min-SE specified in request, " + "%ds. Well, let's just accept and use it.", + se_hdr->sess_expires, inv->timer->setting.min_se)); + + inv->timer->setting.sess_expires = se_hdr->sess_expires; + inv->timer->setting.min_se = se_hdr->sess_expires; + } + + //if (st_code) + // *st_code = PJSIP_SC_SESSION_TIMER_TOO_SMALL; + //pjsip_timer_end_session(inv); + //return PJSIP_ERRNO_FROM_SIP_STATUS( + // PJSIP_SC_SESSION_TIMER_TOO_SMALL); + } + + /* Update SE. Session-Expires in response cannot be lower than Min-SE. + * Session-Expires in response can only be equal or lower than in + * request. + */ + if (se_hdr && + se_hdr->sess_expires <= inv->timer->setting.sess_expires && + se_hdr->sess_expires >= inv->timer->setting.min_se) + { + /* Good SE from remote, update local SE */ + inv->timer->setting.sess_expires = se_hdr->sess_expires; + } + + /* Set the refresher */ + if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAC) == 0) + inv->timer->refresher = TR_UAC; + else if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAS) == 0) + inv->timer->refresher = TR_UAS; + else + /* UAS should set the refresher, however, there is a case that + * UAS doesn't support/want Session Timers but the UAC insists + * to use Session Timers. + */ + inv->timer->refresher = TR_UAC; + + /* Remember our role in this transaction */ + inv->timer->role = PJSIP_ROLE_UAC; + + /* Finally, set active flag and start the Session Timers */ + inv->timer->active = PJ_TRUE; + start_timer(inv); + + } else if (pjsip_method_cmp(&rdata->msg_info.cseq->method, + &pjsip_update_method) == 0 && + msg->line.status.code >= 400 && msg->line.status.code < 600) + { + /* This is to handle error response to previous UPDATE that was + * sent without SDP. In this case, retry sending UPDATE but + * with SDP this time. + * Note: the additional expressions are to check that the + * UPDATE was really the one sent by us, not by other + * call components (e.g. to change codec) + */ + if (inv->timer->timer.id == 0 && inv->timer->use_update && + inv->timer->with_sdp == PJ_FALSE) + { + inv->timer->with_sdp = PJ_TRUE; + timer_cb(NULL, &inv->timer->timer); + } + } + + return PJ_SUCCESS; +} + +/* + * Handle incoming INVITE or UPDATE request. + */ +PJ_DEF(pj_status_t) pjsip_timer_process_req(pjsip_inv_session *inv, + const pjsip_rx_data *rdata, + pjsip_status_code *st_code) +{ + pjsip_min_se_hdr *min_se_hdr; + pjsip_sess_expires_hdr *se_hdr; + const pjsip_msg *msg; + unsigned min_se; + + PJ_ASSERT_ON_FAIL(inv && rdata, + {if(st_code)*st_code=PJSIP_SC_INTERNAL_SERVER_ERROR;return PJ_EINVAL;}); + + /* Check if Session Timers is supported */ + if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0) + return PJ_SUCCESS; + + pj_assert(is_initialized); + + msg = rdata->msg_info.msg; + pj_assert(msg->type == PJSIP_REQUEST_MSG); + + /* Only process INVITE or UPDATE request */ + if (msg->line.req.method.id != PJSIP_INVITE_METHOD && + pjsip_method_cmp(&rdata->msg_info.cseq->method, &pjsip_update_method)) + { + return PJ_SUCCESS; + } + + /* Find Session-Expires header */ + se_hdr = (pjsip_sess_expires_hdr*) pjsip_msg_find_hdr_by_names( + msg, &STR_SE, &STR_SHORT_SE, NULL); + if (se_hdr == NULL) { + /* Remote doesn't support/want Session Timers, check if local + * require or force to use Session Timers. Note that Supported and + * Require headers negotiation should have been verified by invite + * session. + */ + if ((inv->options & + (PJSIP_INV_REQUIRE_TIMER | PJSIP_INV_ALWAYS_USE_TIMER)) == 0) + { + /* Session Timers not forced/required */ + pjsip_timer_end_session(inv); + return PJ_SUCCESS; + } + } + + /* Make sure Session Timers is initialized */ + if (inv->timer == NULL) + pjsip_timer_init_session(inv, NULL); + + /* Find Min-SE header */ + min_se_hdr = (pjsip_min_se_hdr*) pjsip_msg_find_hdr_by_name(msg, + &STR_MIN_SE, NULL); + /* Update Min-SE */ + min_se = inv->timer->setting.min_se; + if (min_se_hdr) + min_se = PJ_MAX(min_se_hdr->min_se, min_se); + + /* Validate SE. Session-Expires cannot be lower than Min-SE + * (or 90 seconds if Min-SE is not set). + */ + if (se_hdr && se_hdr->sess_expires < min_se) { + if (st_code) + *st_code = PJSIP_SC_SESSION_TIMER_TOO_SMALL; + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_SESSION_TIMER_TOO_SMALL); + } + + /* Update SE. Note that there is a case that SE is not available in the + * request (which means remote doesn't want/support it), but local insists + * to use Session Timers. + */ + if (se_hdr) { + /* Update SE as specified by peer. */ + inv->timer->setting.sess_expires = se_hdr->sess_expires; + } else if (inv->timer->setting.sess_expires < min_se) { + /* There is no SE in the request (remote support Session Timers but + * doesn't want to use it, it just specify Min-SE) and local SE is + * lower than Min-SE specified by remote. + */ + inv->timer->setting.sess_expires = min_se; + } + + /* Set the refresher */ + if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAC) == 0) + inv->timer->refresher = TR_UAC; + else if (se_hdr && pj_stricmp(&se_hdr->refresher, &STR_UAS) == 0) + inv->timer->refresher = TR_UAS; + else { + /* If refresher role (i.e: ours or peer) has been set/negotiated, + * better to keep it. + */ + if (inv->timer->refresher != TR_UNKNOWN) { + pj_bool_t as_refresher; + + /* Check our refresher role */ + as_refresher = + (inv->timer->refresher==TR_UAC && inv->timer->role==PJSIP_ROLE_UAC) || + (inv->timer->refresher==TR_UAS && inv->timer->role==PJSIP_ROLE_UAS); + + /* Update refresher role */ + inv->timer->refresher = as_refresher? TR_UAS : TR_UAC; + } else { + /* If UAC support timer (currently check the existance of + * Session-Expires header in the request), set UAC as refresher. + */ + inv->timer->refresher = se_hdr? TR_UAC : TR_UAS; + } + } + + /* Remember our role in this transaction */ + inv->timer->role = PJSIP_ROLE_UAS; + + /* Set active flag */ + inv->timer->active = PJ_TRUE; + + return PJ_SUCCESS; +} + +/* + * Handle outgoing response with status code 2xx & 422. + */ +PJ_DEF(pj_status_t) pjsip_timer_update_resp(pjsip_inv_session *inv, + pjsip_tx_data *tdata) +{ + pjsip_msg *msg; + + /* Check if Session Timers is supported */ + if ((inv->options & PJSIP_INV_SUPPORT_TIMER) == 0) + return PJ_SUCCESS; + + pj_assert(is_initialized); + PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL); + + msg = tdata->msg; + + if (msg->line.status.code/100 == 2) + { + if (inv->timer && inv->timer->active) { + /* Add Session-Expires header and start the timer */ + add_timer_headers(inv, tdata, PJ_TRUE, PJ_FALSE); + start_timer(inv); + } + } + else if (msg->line.status.code == PJSIP_SC_SESSION_TIMER_TOO_SMALL) + { + /* Add Min-SE header */ + add_timer_headers(inv, tdata, PJ_FALSE, PJ_TRUE); + } + + return PJ_SUCCESS; +} + + +/* + * End the Session Timers. + */ +PJ_DEF(pj_status_t) pjsip_timer_end_session(pjsip_inv_session *inv) +{ + PJ_ASSERT_RETURN(inv, PJ_EINVAL); + + if (inv->timer) { + /* Reset active flag */ + inv->timer->active = PJ_FALSE; + + /* Stop Session Timers */ + stop_timer(inv); + } + + return PJ_SUCCESS; +} diff --git a/pjsip/src/pjsip-ua/sip_xfer.c b/pjsip/src/pjsip-ua/sip_xfer.c new file mode 100644 index 0000000..cfa3e10 --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_xfer.c @@ -0,0 +1,630 @@ +/* $Id: sip_xfer.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-ua/sip_xfer.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip/sip_dialog.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_transport.h> +#include <pj/assert.h> +#include <pj/pool.h> +#include <pj/string.h> + +/* Subscription expiration */ +#ifndef PJSIP_XFER_EXPIRES +# define PJSIP_XFER_EXPIRES 600 +#endif + + +/* + * Refer module (mod-refer) + */ +static struct pjsip_module mod_xfer = +{ + NULL, NULL, /* prev, next. */ + { "mod-refer", 9 }, /* 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() */ +}; + + +/* Declare PJSIP_REFER_METHOD, so that if somebody declares this in + * sip_msg.h we can catch the error here. + */ +enum +{ + PJSIP_REFER_METHOD = PJSIP_OTHER_METHOD +}; + +PJ_DEF_DATA(const pjsip_method) pjsip_refer_method = { + (pjsip_method_e) PJSIP_REFER_METHOD, + { "REFER", 5} +}; + +PJ_DEF(const pjsip_method*) pjsip_get_refer_method() +{ + return &pjsip_refer_method; +} + +/* + * String constants + */ +const pj_str_t STR_REFER = { "refer", 5 }; +const pj_str_t STR_MESSAGE = { "message", 7 }; +const pj_str_t STR_SIPFRAG = { "sipfrag", 7 }; +const pj_str_t STR_SIPFRAG_VERSION = {";version=2.0", 12 }; + + +/* + * Transfer struct. + */ +struct pjsip_xfer +{ + pjsip_evsub *sub; /**< Event subscribtion record. */ + pjsip_dialog *dlg; /**< The dialog. */ + pjsip_evsub_user user_cb; /**< The user callback. */ + pj_str_t refer_to_uri; /**< The full Refer-To URI. */ + int last_st_code; /**< st_code sent in last NOTIFY */ + pj_str_t last_st_text; /**< st_text sent in last NOTIFY */ +}; + + +typedef struct pjsip_xfer pjsip_xfer; + + + +/* + * Forward decl for evsub callback. + */ +static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); +static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event); +static void xfer_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 xfer_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 xfer_on_evsub_client_refresh(pjsip_evsub *sub); +static void xfer_on_evsub_server_timeout(pjsip_evsub *sub); + + +/* + * Event subscription callback for xference. + */ +static pjsip_evsub_user xfer_user = +{ + &xfer_on_evsub_state, + &xfer_on_evsub_tsx_state, + &xfer_on_evsub_rx_refresh, + &xfer_on_evsub_rx_notify, + &xfer_on_evsub_client_refresh, + &xfer_on_evsub_server_timeout, +}; + + + + +/* + * Initialize the REFER subsystem. + */ +PJ_DEF(pj_status_t) pjsip_xfer_init_module(pjsip_endpoint *endpt) +{ + const pj_str_t accept = { "message/sipfrag;version=2.0", 27 }; + pj_status_t status; + + PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(mod_xfer.id == -1, PJ_EINVALIDOP); + + status = pjsip_endpt_register_module(endpt, &mod_xfer); + if (status != PJ_SUCCESS) + return status; + + status = pjsip_endpt_add_capability( endpt, &mod_xfer, PJSIP_H_ALLOW, + NULL, 1, + &pjsip_get_refer_method()->name); + if (status != PJ_SUCCESS) + return status; + + status = pjsip_evsub_register_pkg(&mod_xfer, &STR_REFER, + PJSIP_XFER_EXPIRES, 1, &accept); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + + +/* + * Create transferer (sender of REFER request). + * + */ +PJ_DEF(pj_status_t) pjsip_xfer_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_evsub **p_evsub ) +{ + pj_status_t status; + pjsip_xfer *xfer; + 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, &xfer_user, &STR_REFER, + PJSIP_EVSUB_NO_EVENT_ID, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create xfer session */ + xfer = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_xfer); + xfer->dlg = dlg; + xfer->sub = sub; + if (user_cb) + pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer); + + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; + +} + + + + +/* + * Create transferee (receiver of REFER request). + * + */ +PJ_DEF(pj_status_t) pjsip_xfer_create_uas( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_rx_data *rdata, + pjsip_evsub **p_evsub ) +{ + pjsip_evsub *sub; + pjsip_xfer *xfer; + const pj_str_t STR_EVENT = {"Event", 5 }; + pjsip_event_hdr *event_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); + + /* Check that request is REFER */ + PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, + pjsip_get_refer_method())==0, + PJSIP_ENOTREFER); + + /* Lock dialog */ + pjsip_dlg_inc_lock(dlg); + + /* The evsub framework expects an Event header in the request, + * while a REFER request conveniently doesn't have one (pun intended!). + * So create a dummy Event header. + */ + if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, + &STR_EVENT, NULL)==NULL) + { + event_hdr = pjsip_event_hdr_create(rdata->tp_info.pool); + event_hdr->event_type = STR_REFER; + pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr*)event_hdr); + } + + /* Create server subscription */ + status = pjsip_evsub_create_uas( dlg, &xfer_user, rdata, + PJSIP_EVSUB_NO_EVENT_ID, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create server xfer subscription */ + xfer = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_xfer); + xfer->dlg = dlg; + xfer->sub = sub; + if (user_cb) + pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer); + + /* Done: */ + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + + +/* + * Call this function to create request to initiate REFER subscription. + * + */ +PJ_DEF(pj_status_t) pjsip_xfer_initiate( pjsip_evsub *sub, + const pj_str_t *refer_to_uri, + pjsip_tx_data **p_tdata) +{ + pjsip_xfer *xfer; + const pj_str_t refer_to = { "Refer-To", 8}; + pjsip_tx_data *tdata; + pjsip_generic_string_hdr *hdr; + pj_status_t status; + + /* sub and p_tdata argument must be valid. */ + PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL); + + + /* Get the xfer object. */ + xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION); + + /* refer_to_uri argument MAY be NULL for subsequent REFER requests, + * but it MUST be specified in the first REFER. + */ + PJ_ASSERT_RETURN((refer_to_uri || xfer->refer_to_uri.slen), PJ_EINVAL); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(xfer->dlg); + + /* Create basic REFER request */ + status = pjsip_evsub_initiate(sub, pjsip_get_refer_method(), -1, + &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Save Refer-To URI. */ + if (refer_to_uri == NULL) { + refer_to_uri = &xfer->refer_to_uri; + } else { + pj_strdup(xfer->dlg->pool, &xfer->refer_to_uri, refer_to_uri); + } + + /* Create and add Refer-To header. */ + hdr = pjsip_generic_string_hdr_create(tdata->pool, &refer_to, + refer_to_uri); + if (!hdr) { + pjsip_tx_data_dec_ref(tdata); + status = PJ_ENOMEM; + goto on_return; + } + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); + + + /* Done. */ + *p_tdata = tdata; + + status = PJ_SUCCESS; + +on_return: + pjsip_dlg_dec_lock(xfer->dlg); + return status; +} + + +/* + * Accept the incoming REFER request by sending 2xx response. + * + */ +PJ_DEF(pj_status_t) pjsip_xfer_accept( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list ) +{ + /* + * Don't need to add custom headers, so just call basic + * evsub response. + */ + return pjsip_evsub_accept( sub, rdata, st_code, hdr_list ); +} + + +/* + * For notifier, create NOTIFY request to subscriber, and set the state + * of the subscription. + */ +PJ_DEF(pj_status_t) pjsip_xfer_notify( pjsip_evsub *sub, + pjsip_evsub_state state, + int xfer_st_code, + const pj_str_t *xfer_st_text, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsip_xfer *xfer; + pjsip_param *param; + const pj_str_t reason = { "noresource", 10 }; + char *body; + int bodylen; + pjsip_msg_body *msg_body; + pj_status_t status; + + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub, PJ_EINVAL); + + /* Get the xfer object. */ + xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION); + + + /* Lock object. */ + pjsip_dlg_inc_lock(xfer->dlg); + + /* Create the NOTIFY request. + * Note that reason is only used when state is TERMINATED, and + * the defined termination reason for REFER is "noresource". + */ + status = pjsip_evsub_notify( sub, state, NULL, &reason, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Check status text */ + if (xfer_st_text==NULL || xfer_st_text->slen==0) + xfer_st_text = pjsip_get_status_text(xfer_st_code); + + /* Save st_code and st_text, for current_notify() */ + xfer->last_st_code = xfer_st_code; + pj_strdup(xfer->dlg->pool, &xfer->last_st_text, xfer_st_text); + + /* Create sipfrag content. */ + body = (char*) pj_pool_alloc(tdata->pool, 128); + bodylen = pj_ansi_snprintf(body, 128, "SIP/2.0 %u %.*s\r\n", + xfer_st_code, + (int)xfer_st_text->slen, + xfer_st_text->ptr); + PJ_ASSERT_ON_FAIL(bodylen > 0 && bodylen < 128, + {status=PJ_EBUG; pjsip_tx_data_dec_ref(tdata); + goto on_return; }); + + + /* Create SIP message body. */ + msg_body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body); + pjsip_media_type_init(&msg_body->content_type, (pj_str_t*)&STR_MESSAGE, + (pj_str_t*)&STR_SIPFRAG); + msg_body->data = body; + msg_body->len = bodylen; + msg_body->print_body = &pjsip_print_text_body; + msg_body->clone_data = &pjsip_clone_text_data; + + param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param); + param->name = pj_str("version"); + param->value = pj_str("2.0"); + pj_list_push_back(&msg_body->content_type.param, param); + + /* Attach sipfrag body. */ + tdata->msg->body = msg_body; + + + /* Done. */ + *p_tdata = tdata; + + +on_return: + pjsip_dlg_dec_lock(xfer->dlg); + return status; + +} + + +/* + * Send current state and the last sipfrag body. + */ +PJ_DEF(pj_status_t) pjsip_xfer_current_notify( pjsip_evsub *sub, + pjsip_tx_data **p_tdata ) +{ + pjsip_xfer *xfer; + pj_status_t status; + + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub, PJ_EINVAL); + + /* Get the xfer object. */ + xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION); + + pjsip_dlg_inc_lock(xfer->dlg); + + status = pjsip_xfer_notify(sub, pjsip_evsub_get_state(sub), + xfer->last_st_code, &xfer->last_st_text, + p_tdata); + + pjsip_dlg_dec_lock(xfer->dlg); + + return status; +} + + +/* + * Send request message. + */ +PJ_DEF(pj_status_t) pjsip_xfer_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 xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsip_xfer *xfer; + + xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_evsub_state) + (*xfer->user_cb.on_evsub_state)(sub, event); + +} + +/* + * Called when transaction state has changed. + */ +static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event) +{ + pjsip_xfer *xfer; + + xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_tsx_state) + (*xfer->user_cb.on_tsx_state)(sub, tsx, event); +} + +/* + * Called when REFER is received to refresh subscription. + */ +static void xfer_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_xfer *xfer; + + xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_rx_refresh) { + (*xfer->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 + * (implementor == "us" from evsub point of view. + */ + pjsip_tx_data *tdata; + pj_status_t status; + + if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) { + status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, + xfer->last_st_code, + &xfer->last_st_text, + &tdata); + } else { + status = pjsip_xfer_current_notify(sub, &tdata); + } + + if (status == PJ_SUCCESS) + pjsip_xfer_send_request(sub, tdata); + } +} + + +/* + * Called when NOTIFY is received. + */ +static void xfer_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_xfer *xfer; + + xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_rx_notify) + (*xfer->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 xfer_on_evsub_client_refresh(pjsip_evsub *sub) +{ + pjsip_xfer *xfer; + + xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_client_refresh) { + (*xfer->user_cb.on_client_refresh)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_evsub_initiate(sub, NULL, PJSIP_XFER_EXPIRES, &tdata); + if (status == PJ_SUCCESS) + pjsip_xfer_send_request(sub, tdata); + } +} + + +/* + * Called when no refresh is received after the interval. + */ +static void xfer_on_evsub_server_timeout(pjsip_evsub *sub) +{ + pjsip_xfer *xfer; + + xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_server_timeout) { + (*xfer->user_cb.on_server_timeout)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + xfer->last_st_code, + &xfer->last_st_text, &tdata); + if (status == PJ_SUCCESS) + pjsip_xfer_send_request(sub, tdata); + } +} + diff --git a/pjsip/src/pjsip/sip_auth_aka.c b/pjsip/src/pjsip/sip_auth_aka.c new file mode 100644 index 0000000..b3c2dde --- /dev/null +++ b/pjsip/src/pjsip/sip_auth_aka.c @@ -0,0 +1,204 @@ +/* $Id: sip_auth_aka.c 3999 2012-03-30 07:10:13Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_auth_aka.h> +#include <pjsip/sip_errno.h> +#include <pjlib-util/base64.h> +#include <pjlib-util/md5.h> +#include <pjlib-util/hmac_md5.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> + +#if PJSIP_HAS_DIGEST_AKA_AUTH + +#include "../../third_party/milenage/milenage.h" + +/* + * Create MD5-AKA1 digest response. + */ +PJ_DEF(pj_status_t) pjsip_auth_create_aka_response( + pj_pool_t *pool, + const pjsip_digest_challenge*chal, + const pjsip_cred_info *cred, + const pj_str_t *method, + pjsip_digest_credential *auth) +{ + pj_str_t nonce_bin; + int aka_version; + const pj_str_t pjsip_AKAv1_MD5 = { "AKAv1-MD5", 9 }; + const pj_str_t pjsip_AKAv2_MD5 = { "AKAv2-MD5", 9 }; + pj_uint8_t *chal_rand, *chal_sqnxoraka, *chal_mac; + pj_uint8_t k[PJSIP_AKA_KLEN]; + pj_uint8_t op[PJSIP_AKA_OPLEN]; + pj_uint8_t amf[PJSIP_AKA_AMFLEN]; + pj_uint8_t res[PJSIP_AKA_RESLEN]; + pj_uint8_t ck[PJSIP_AKA_CKLEN]; + pj_uint8_t ik[PJSIP_AKA_IKLEN]; + pj_uint8_t ak[PJSIP_AKA_AKLEN]; + pj_uint8_t sqn[PJSIP_AKA_SQNLEN]; + pj_uint8_t xmac[PJSIP_AKA_MACLEN]; + pjsip_cred_info aka_cred; + int i, len; + pj_status_t status; + + /* Check the algorithm is supported. */ + if (chal->algorithm.slen==0 || pj_stricmp2(&chal->algorithm, "md5") == 0) { + /* + * A normal MD5 authentication is requested. Fallbackt to the usual + * MD5 digest creation. + */ + pjsip_auth_create_digest(&auth->response, &auth->nonce, &auth->nc, + &auth->cnonce, &auth->qop, &auth->uri, + &auth->realm, cred, method); + return PJ_SUCCESS; + + } else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5) == 0) { + /* + * AKA version 1 is requested. + */ + aka_version = 1; + + } else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv2_MD5) == 0) { + /* + * AKA version 2 is requested. + */ + aka_version = 2; + + } else { + /* Unsupported algorithm */ + return PJSIP_EINVALIDALGORITHM; + } + + /* Decode nonce */ + nonce_bin.slen = len = PJ_BASE64_TO_BASE256_LEN(chal->nonce.slen); + nonce_bin.ptr = pj_pool_alloc(pool, nonce_bin.slen + 1); + status = pj_base64_decode(&chal->nonce, (pj_uint8_t*)nonce_bin.ptr, &len); + nonce_bin.slen = len; + if (status != PJ_SUCCESS) + return PJSIP_EAUTHINNONCE; + + if (nonce_bin.slen < PJSIP_AKA_RANDLEN + PJSIP_AKA_AUTNLEN) + return PJSIP_EAUTHINNONCE; + + /* Get RAND, AUTN, and MAC */ + chal_rand = (pj_uint8_t*)(nonce_bin.ptr + 0); + chal_sqnxoraka = (pj_uint8_t*) (nonce_bin.ptr + PJSIP_AKA_RANDLEN); + chal_mac = (pj_uint8_t*) (nonce_bin.ptr + PJSIP_AKA_RANDLEN + + PJSIP_AKA_SQNLEN + PJSIP_AKA_AMFLEN); + + /* Copy k. op, and amf */ + pj_bzero(k, sizeof(k)); + pj_bzero(op, sizeof(op)); + pj_bzero(amf, sizeof(amf)); + + if (cred->ext.aka.k.slen) + pj_memcpy(k, cred->ext.aka.k.ptr, cred->ext.aka.k.slen); + if (cred->ext.aka.op.slen) + pj_memcpy(op, cred->ext.aka.op.ptr, cred->ext.aka.op.slen); + if (cred->ext.aka.amf.slen) + pj_memcpy(amf, cred->ext.aka.amf.ptr, cred->ext.aka.amf.slen); + + /* Given key K and random challenge RAND, compute response RES, + * confidentiality key CK, integrity key IK and anonymity key AK. + */ + f2345(k, chal_rand, res, ck, ik, ak, op); + + /* Compute sequence number SQN */ + for (i=0; i<PJSIP_AKA_SQNLEN; ++i) + sqn[i] = (pj_uint8_t) (chal_sqnxoraka[i] ^ ak[i]); + + /* Verify MAC in the challenge */ + /* Compute XMAC */ + f1(k, chal_rand, sqn, amf, xmac, op); + + if (pj_memcmp(chal_mac, xmac, PJSIP_AKA_MACLEN) != 0) { + return PJSIP_EAUTHINNONCE; + } + + /* Build a temporary credential info to create MD5 digest, using + * "res" as the password. + */ + pj_memcpy(&aka_cred, cred, sizeof(aka_cred)); + aka_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; + + /* Create a response */ + if (aka_version == 1) { + /* + * For AKAv1, the password is RES + */ + aka_cred.data.ptr = (char*)res; + aka_cred.data.slen = PJSIP_AKA_RESLEN; + + pjsip_auth_create_digest(&auth->response, &chal->nonce, + &auth->nc, &auth->cnonce, &auth->qop, + &auth->uri, &chal->realm, &aka_cred, method); + + } else if (aka_version == 2) { + + /* + * For AKAv2, password is base64 encoded [1] parameters: + * PRF(RES||IK||CK,"http-digest-akav2-password") + * + * The pseudo-random function (PRF) is HMAC-MD5 in this case. + */ + + pj_str_t resikck; + const pj_str_t AKAv2_Passwd = { "http-digest-akav2-password", 26 }; + pj_uint8_t hmac_digest[16]; + char tmp_buf[48]; + int hmac64_len; + + resikck.slen = PJSIP_AKA_RESLEN + PJSIP_AKA_IKLEN + PJSIP_AKA_CKLEN; + pj_assert(resikck.slen <= PJ_ARRAY_SIZE(tmp_buf)); + resikck.ptr = tmp_buf; + pj_memcpy(resikck.ptr + 0, res, PJSIP_AKA_RESLEN); + pj_memcpy(resikck.ptr + PJSIP_AKA_RESLEN, ik, PJSIP_AKA_IKLEN); + pj_memcpy(resikck.ptr + PJSIP_AKA_RESLEN + PJSIP_AKA_IKLEN, + ck, PJSIP_AKA_CKLEN); + + pj_hmac_md5((const pj_uint8_t*)AKAv2_Passwd.ptr, AKAv2_Passwd.slen, + (const pj_uint8_t*)resikck.ptr, resikck.slen, + hmac_digest); + + aka_cred.data.slen = hmac64_len = + PJ_BASE256_TO_BASE64_LEN(PJ_ARRAY_SIZE(hmac_digest)); + pj_assert(aka_cred.data.slen+1 <= PJ_ARRAY_SIZE(tmp_buf)); + aka_cred.data.ptr = tmp_buf; + pj_base64_encode(hmac_digest, PJ_ARRAY_SIZE(hmac_digest), + aka_cred.data.ptr, &len); + aka_cred.data.slen = hmac64_len; + + pjsip_auth_create_digest(&auth->response, &chal->nonce, + &auth->nc, &auth->cnonce, &auth->qop, + &auth->uri, &chal->realm, &aka_cred, method); + + } else { + pj_assert(!"Bug!"); + return PJ_EBUG; + } + + /* Done */ + return PJ_SUCCESS; +} + + +#endif /* PJSIP_HAS_DIGEST_AKA_AUTH */ + diff --git a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c new file mode 100644 index 0000000..ae850b1 --- /dev/null +++ b/pjsip/src/pjsip/sip_auth_client.c @@ -0,0 +1,1189 @@ +/* $Id: sip_auth_client.c 3999 2012-03-30 07:10:13Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <pjsip/sip_auth.h> +#include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */ +#include <pjsip/sip_auth_aka.h> +#include <pjsip/sip_transport.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_util.h> +#include <pjlib-util/md5.h> +#include <pj/log.h> +#include <pj/string.h> +#include <pj/pool.h> +#include <pj/guid.h> +#include <pj/assert.h> +#include <pj/ctype.h> + + + +/* A macro just to get rid of type mismatch between char and unsigned char */ +#define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, len) + +/* Logging. */ +#define THIS_FILE "sip_auth_client.c" +#if 0 +# define AUTH_TRACE_(expr) PJ_LOG(3, expr) +#else +# define AUTH_TRACE_(expr) +#endif + +#define PASSWD_MASK 0x000F +#define EXT_MASK 0x00F0 + + +static void dup_bin(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src) +{ + dst->slen = src->slen; + + if (dst->slen) { + dst->ptr = (char*) pj_pool_alloc(pool, src->slen); + pj_memcpy(dst->ptr, src->ptr, src->slen); + } else { + dst->ptr = NULL; + } +} + +PJ_DEF(void) pjsip_cred_info_dup(pj_pool_t *pool, + pjsip_cred_info *dst, + const pjsip_cred_info *src) +{ + pj_memcpy(dst, src, sizeof(pjsip_cred_info)); + + pj_strdup_with_null(pool, &dst->realm, &src->realm); + pj_strdup_with_null(pool, &dst->scheme, &src->scheme); + pj_strdup_with_null(pool, &dst->username, &src->username); + pj_strdup_with_null(pool, &dst->data, &src->data); + + if ((dst->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { + dup_bin(pool, &dst->ext.aka.k, &src->ext.aka.k); + dup_bin(pool, &dst->ext.aka.op, &src->ext.aka.op); + dup_bin(pool, &dst->ext.aka.amf, &src->ext.aka.amf); + } +} + + +PJ_DEF(int) pjsip_cred_info_cmp(const pjsip_cred_info *cred1, + const pjsip_cred_info *cred2) +{ + int result; + + result = pj_strcmp(&cred1->realm, &cred2->realm); + if (result) goto on_return; + result = pj_strcmp(&cred1->scheme, &cred2->scheme); + if (result) goto on_return; + result = pj_strcmp(&cred1->username, &cred2->username); + if (result) goto on_return; + result = pj_strcmp(&cred1->data, &cred2->data); + if (result) goto on_return; + result = (cred1->data_type != cred2->data_type); + if (result) goto on_return; + + if ((cred1->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { + result = pj_strcmp(&cred1->ext.aka.k, &cred2->ext.aka.k); + if (result) goto on_return; + result = pj_strcmp(&cred1->ext.aka.op, &cred2->ext.aka.op); + if (result) goto on_return; + result = pj_strcmp(&cred1->ext.aka.amf, &cred2->ext.aka.amf); + if (result) goto on_return; + } + +on_return: + return result; +} + +PJ_DEF(void) pjsip_auth_clt_pref_dup( pj_pool_t *pool, + pjsip_auth_clt_pref *dst, + const pjsip_auth_clt_pref *src) +{ + pj_memcpy(dst, src, sizeof(pjsip_auth_clt_pref)); + pj_strdup_with_null(pool, &dst->algorithm, &src->algorithm); +} + + +/* Transform digest to string. + * output must be at least PJSIP_MD5STRLEN+1 bytes. + * + * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED! + */ +static void digest2str(const unsigned char digest[], char *output) +{ + int i; + for (i = 0; i<16; ++i) { + pj_val_to_hex_digit(digest[i], output); + output += 2; + } +} + + +/* + * Create response digest based on the parameters and store the + * digest ASCII in 'result'. + */ +PJ_DEF(void) pjsip_auth_create_digest( pj_str_t *result, + const pj_str_t *nonce, + const pj_str_t *nc, + const pj_str_t *cnonce, + const pj_str_t *qop, + const pj_str_t *uri, + const pj_str_t *realm, + const pjsip_cred_info *cred_info, + const pj_str_t *method) +{ + char ha1[PJSIP_MD5STRLEN]; + char ha2[PJSIP_MD5STRLEN]; + unsigned char digest[16]; + pj_md5_context pms; + + pj_assert(result->slen >= PJSIP_MD5STRLEN); + + AUTH_TRACE_((THIS_FILE, "Begin creating digest")); + + if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) { + /*** + *** ha1 = MD5(username ":" realm ":" password) + ***/ + pj_md5_init(&pms); + MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen); + MD5_APPEND( &pms, ":", 1); + MD5_APPEND( &pms, realm->ptr, realm->slen); + MD5_APPEND( &pms, ":", 1); + MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen); + pj_md5_final(&pms, digest); + + digest2str(digest, ha1); + + } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) { + pj_assert(cred_info->data.slen == 32); + pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen ); + } else { + pj_assert(!"Invalid data_type"); + } + + AUTH_TRACE_((THIS_FILE, " ha1=%.32s", ha1)); + + /*** + *** ha2 = MD5(method ":" req_uri) + ***/ + pj_md5_init(&pms); + MD5_APPEND( &pms, method->ptr, method->slen); + MD5_APPEND( &pms, ":", 1); + MD5_APPEND( &pms, uri->ptr, uri->slen); + pj_md5_final(&pms, digest); + digest2str(digest, ha2); + + AUTH_TRACE_((THIS_FILE, " ha2=%.32s", ha2)); + + /*** + *** When qop is not used: + *** response = MD5(ha1 ":" nonce ":" ha2) + *** + *** When qop=auth is used: + *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) + ***/ + pj_md5_init(&pms); + MD5_APPEND( &pms, ha1, PJSIP_MD5STRLEN); + MD5_APPEND( &pms, ":", 1); + MD5_APPEND( &pms, nonce->ptr, nonce->slen); + if (qop && qop->slen != 0) { + MD5_APPEND( &pms, ":", 1); + MD5_APPEND( &pms, nc->ptr, nc->slen); + MD5_APPEND( &pms, ":", 1); + MD5_APPEND( &pms, cnonce->ptr, cnonce->slen); + MD5_APPEND( &pms, ":", 1); + MD5_APPEND( &pms, qop->ptr, qop->slen); + } + MD5_APPEND( &pms, ":", 1); + MD5_APPEND( &pms, ha2, PJSIP_MD5STRLEN); + + /* This is the final response digest. */ + pj_md5_final(&pms, digest); + + /* Convert digest to string and store in chal->response. */ + result->slen = PJSIP_MD5STRLEN; + digest2str(digest, result->ptr); + + AUTH_TRACE_((THIS_FILE, " digest=%.32s", result->ptr)); + AUTH_TRACE_((THIS_FILE, "Digest created")); +} + +/* + * Finds out if qop offer contains "auth" token. + */ +static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer) +{ + pj_str_t qop; + char *p; + + pj_strdup_with_null( pool, &qop, qop_offer); + p = qop.ptr; + while (*p) { + *p = (char)pj_tolower(*p); + ++p; + } + + p = qop.ptr; + while (*p) { + if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') { + int e = *(p+4); + if (e=='"' || e==',' || e==0) + return PJ_TRUE; + else + p += 4; + } else { + ++p; + } + } + + return PJ_FALSE; +} + +/* + * Generate response digest. + * Most of the parameters to generate the digest (i.e. username, realm, uri, + * and nonce) are expected to be in the credential. Additional parameters (i.e. + * password and method param) should be supplied in the argument. + * + * The resulting digest will be stored in cred->response. + * The pool is used to allocate 32 bytes to store the digest in cred->response. + */ +static pj_status_t respond_digest( pj_pool_t *pool, + pjsip_digest_credential *cred, + const pjsip_digest_challenge *chal, + const pj_str_t *uri, + const pjsip_cred_info *cred_info, + const pj_str_t *cnonce, + pj_uint32_t nc, + const pj_str_t *method) +{ + const pj_str_t pjsip_AKAv1_MD5_STR = { "AKAv1-MD5", 9 }; + + /* Check algorithm is supported. We support MD5 and AKAv1-MD5. */ + if (chal->algorithm.slen==0 || + (pj_stricmp(&chal->algorithm, &pjsip_MD5_STR)==0 || + pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5_STR)==0)) + { + ; + } + else { + PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"", + chal->algorithm.slen, chal->algorithm.ptr)); + return PJSIP_EINVALIDALGORITHM; + } + + /* Build digest credential from arguments. */ + pj_strdup(pool, &cred->username, &cred_info->username); + pj_strdup(pool, &cred->realm, &chal->realm); + pj_strdup(pool, &cred->nonce, &chal->nonce); + pj_strdup(pool, &cred->uri, uri); + pj_strdup(pool, &cred->algorithm, &chal->algorithm); + pj_strdup(pool, &cred->opaque, &chal->opaque); + + /* Allocate memory. */ + cred->response.ptr = (char*) pj_pool_alloc(pool, PJSIP_MD5STRLEN); + cred->response.slen = PJSIP_MD5STRLEN; + + if (chal->qop.slen == 0) { + /* Server doesn't require quality of protection. */ + + if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { + /* Call application callback to create the response digest */ + return (*cred_info->ext.aka.cb)(pool, chal, cred_info, + method, cred); + } + else { + /* Convert digest to string and store in chal->response. */ + pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL, + NULL, NULL, uri, &chal->realm, + cred_info, method); + } + + } else if (has_auth_qop(pool, &chal->qop)) { + /* Server requires quality of protection. + * We respond with selecting "qop=auth" protection. + */ + cred->qop = pjsip_AUTH_STR; + cred->nc.ptr = (char*) pj_pool_alloc(pool, 16); + cred->nc.slen = pj_ansi_snprintf(cred->nc.ptr, 16, "%08u", nc); + + if (cnonce && cnonce->slen) { + pj_strdup(pool, &cred->cnonce, cnonce); + } else { + pj_str_t dummy_cnonce = { "b39971", 6}; + pj_strdup(pool, &cred->cnonce, &dummy_cnonce); + } + + if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { + /* Call application callback to create the response digest */ + return (*cred_info->ext.aka.cb)(pool, chal, cred_info, + method, cred); + } + else { + pjsip_auth_create_digest( &cred->response, &cred->nonce, + &cred->nc, cnonce, &pjsip_AUTH_STR, + uri, &chal->realm, cred_info, method ); + } + + } else { + /* Server requires quality protection that we don't support. */ + PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s", + chal->qop.slen, chal->qop.ptr)); + return PJSIP_EINVALIDQOP; + } + + return PJ_SUCCESS; +} + +#if defined(PJSIP_AUTH_QOP_SUPPORT) && PJSIP_AUTH_QOP_SUPPORT!=0 +/* + * Update authentication session with a challenge. + */ +static void update_digest_session( pj_pool_t *ses_pool, + pjsip_cached_auth *cached_auth, + const pjsip_www_authenticate_hdr *hdr ) +{ + if (hdr->challenge.digest.qop.slen == 0) { +#if PJSIP_AUTH_AUTO_SEND_NEXT!=0 + if (!cached_auth->last_chal || pj_stricmp2(&hdr->scheme, "digest")) { + cached_auth->last_chal = (pjsip_www_authenticate_hdr*) + pjsip_hdr_clone(ses_pool, hdr); + } else { + /* Only update if the new challenge is "significantly different" + * than the one in the cache, to reduce memory usage. + */ + const pjsip_digest_challenge *d1 = + &cached_auth->last_chal->challenge.digest; + const pjsip_digest_challenge *d2 = &hdr->challenge.digest; + + if (pj_strcmp(&d1->domain, &d2->domain) || + pj_strcmp(&d1->realm, &d2->realm) || + pj_strcmp(&d1->nonce, &d2->nonce) || + pj_strcmp(&d1->opaque, &d2->opaque) || + pj_strcmp(&d1->algorithm, &d2->algorithm) || + pj_strcmp(&d1->qop, &d2->qop)) + { + cached_auth->last_chal = (pjsip_www_authenticate_hdr*) + pjsip_hdr_clone(ses_pool, hdr); + } + } +#endif + return; + } + + /* Initialize cnonce and qop if not present. */ + if (cached_auth->cnonce.slen == 0) { + /* Save the whole challenge */ + cached_auth->last_chal = (pjsip_www_authenticate_hdr*) + pjsip_hdr_clone(ses_pool, hdr); + + /* Create cnonce */ + pj_create_unique_string( ses_pool, &cached_auth->cnonce ); + + /* Initialize nonce-count */ + cached_auth->nc = 1; + + /* Save realm. */ + /* Note: allow empty realm (http://trac.pjsip.org/repos/ticket/1061) + pj_assert(cached_auth->realm.slen != 0); + */ + if (cached_auth->realm.slen == 0) { + pj_strdup(ses_pool, &cached_auth->realm, + &hdr->challenge.digest.realm); + } + + } else { + /* Update last_nonce and nonce-count */ + if (!pj_strcmp(&hdr->challenge.digest.nonce, + &cached_auth->last_chal->challenge.digest.nonce)) + { + /* Same nonce, increment nonce-count */ + ++cached_auth->nc; + } else { + /* Server gives new nonce. */ + pj_strdup(ses_pool, &cached_auth->last_chal->challenge.digest.nonce, + &hdr->challenge.digest.nonce); + /* Has the opaque changed? */ + if (pj_strcmp(&cached_auth->last_chal->challenge.digest.opaque, + &hdr->challenge.digest.opaque)) + { + pj_strdup(ses_pool, + &cached_auth->last_chal->challenge.digest.opaque, + &hdr->challenge.digest.opaque); + } + cached_auth->nc = 1; + } + } +} +#endif /* PJSIP_AUTH_QOP_SUPPORT */ + + +/* Find cached authentication in the list for the specified realm. */ +static pjsip_cached_auth *find_cached_auth( pjsip_auth_clt_sess *sess, + const pj_str_t *realm ) +{ + pjsip_cached_auth *auth = sess->cached_auth.next; + while (auth != &sess->cached_auth) { + if (pj_stricmp(&auth->realm, realm) == 0) + return auth; + auth = auth->next; + } + + return NULL; +} + +/* Find credential to use for the specified realm and auth scheme. */ +static const pjsip_cred_info* auth_find_cred( const pjsip_auth_clt_sess *sess, + const pj_str_t *realm, + const pj_str_t *auth_scheme) +{ + unsigned i; + int wildcard = -1; + + PJ_UNUSED_ARG(auth_scheme); + + for (i=0; i<sess->cred_cnt; ++i) { + if (pj_stricmp(&sess->cred_info[i].realm, realm) == 0) + return &sess->cred_info[i]; + else if (sess->cred_info[i].realm.slen == 1 && + sess->cred_info[i].realm.ptr[0] == '*') + { + wildcard = i; + } + } + + /* No matching realm. See if we have credential with wildcard ('*') + * as the realm. + */ + if (wildcard != -1) + return &sess->cred_info[wildcard]; + + /* Nothing is suitable */ + return NULL; +} + + +/* Init client session. */ +PJ_DEF(pj_status_t) pjsip_auth_clt_init( pjsip_auth_clt_sess *sess, + pjsip_endpoint *endpt, + pj_pool_t *pool, + unsigned options) +{ + PJ_ASSERT_RETURN(sess && endpt && pool && (options==0), PJ_EINVAL); + + sess->pool = pool; + sess->endpt = endpt; + sess->cred_cnt = 0; + sess->cred_info = NULL; + pj_list_init(&sess->cached_auth); + + return PJ_SUCCESS; +} + + +/* Clone session. */ +PJ_DEF(pj_status_t) pjsip_auth_clt_clone( pj_pool_t *pool, + pjsip_auth_clt_sess *sess, + const pjsip_auth_clt_sess *rhs ) +{ + unsigned i; + + PJ_ASSERT_RETURN(pool && sess && rhs, PJ_EINVAL); + + pjsip_auth_clt_init(sess, (pjsip_endpoint*)rhs->endpt, pool, 0); + + sess->cred_cnt = rhs->cred_cnt; + sess->cred_info = (pjsip_cred_info*) + pj_pool_alloc(pool, + sess->cred_cnt*sizeof(pjsip_cred_info)); + for (i=0; i<rhs->cred_cnt; ++i) { + pj_strdup(pool, &sess->cred_info[i].realm, &rhs->cred_info[i].realm); + pj_strdup(pool, &sess->cred_info[i].scheme, &rhs->cred_info[i].scheme); + pj_strdup(pool, &sess->cred_info[i].username, + &rhs->cred_info[i].username); + sess->cred_info[i].data_type = rhs->cred_info[i].data_type; + pj_strdup(pool, &sess->cred_info[i].data, &rhs->cred_info[i].data); + } + + /* TODO note: + * Cloning the full authentication client is quite a big task. + * We do only the necessary bits here, i.e. cloning the credentials. + * The drawback of this basic approach is, a forked dialog will have to + * re-authenticate itself on the next request because it has lost the + * cached authentication headers. + */ + PJ_TODO(FULL_CLONE_OF_AUTH_CLIENT_SESSION); + + return PJ_SUCCESS; +} + + +/* Set client credentials. */ +PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess, + int cred_cnt, + const pjsip_cred_info *c) +{ + PJ_ASSERT_RETURN(sess && c, PJ_EINVAL); + + if (cred_cnt == 0) { + sess->cred_cnt = 0; + } else { + int i; + sess->cred_info = (pjsip_cred_info*) + pj_pool_alloc(sess->pool, cred_cnt * sizeof(*c)); + for (i=0; i<cred_cnt; ++i) { + sess->cred_info[i].data_type = c[i].data_type; + + /* When data_type is PJSIP_CRED_DATA_EXT_AKA, + * callback must be specified. + */ + if ((c[i].data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) { + +#if !PJSIP_HAS_DIGEST_AKA_AUTH + if (!PJSIP_HAS_DIGEST_AKA_AUTH) { + pj_assert(!"PJSIP_HAS_DIGEST_AKA_AUTH is not enabled"); + return PJSIP_EAUTHINAKACRED; + } +#endif + + /* Callback must be specified */ + PJ_ASSERT_RETURN(c[i].ext.aka.cb != NULL, PJ_EINVAL); + + /* Verify K len */ + PJ_ASSERT_RETURN(c[i].ext.aka.k.slen <= PJSIP_AKA_KLEN, + PJSIP_EAUTHINAKACRED); + + /* Verify OP len */ + PJ_ASSERT_RETURN(c[i].ext.aka.op.slen <= PJSIP_AKA_OPLEN, + PJSIP_EAUTHINAKACRED); + + /* Verify AMF len */ + PJ_ASSERT_RETURN(c[i].ext.aka.amf.slen <= PJSIP_AKA_AMFLEN, + PJSIP_EAUTHINAKACRED); + + sess->cred_info[i].ext.aka.cb = c[i].ext.aka.cb; + pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.k, + &c[i].ext.aka.k); + pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.op, + &c[i].ext.aka.op); + pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.amf, + &c[i].ext.aka.amf); + } + + pj_strdup(sess->pool, &sess->cred_info[i].scheme, &c[i].scheme); + pj_strdup(sess->pool, &sess->cred_info[i].realm, &c[i].realm); + pj_strdup(sess->pool, &sess->cred_info[i].username, &c[i].username); + pj_strdup(sess->pool, &sess->cred_info[i].data, &c[i].data); + } + sess->cred_cnt = cred_cnt; + } + + return PJ_SUCCESS; +} + + +/* + * Set the preference for the client authentication session. + */ +PJ_DEF(pj_status_t) pjsip_auth_clt_set_prefs(pjsip_auth_clt_sess *sess, + const pjsip_auth_clt_pref *p) +{ + PJ_ASSERT_RETURN(sess && p, PJ_EINVAL); + + pj_memcpy(&sess->pref, p, sizeof(*p)); + pj_strdup(sess->pool, &sess->pref.algorithm, &p->algorithm); + //if (sess->pref.algorithm.slen == 0) + // sess->pref.algorithm = pj_str("md5"); + + return PJ_SUCCESS; +} + + +/* + * Get the preference for the client authentication session. + */ +PJ_DEF(pj_status_t) pjsip_auth_clt_get_prefs(pjsip_auth_clt_sess *sess, + pjsip_auth_clt_pref *p) +{ + PJ_ASSERT_RETURN(sess && p, PJ_EINVAL); + + pj_memcpy(p, &sess->pref, sizeof(pjsip_auth_clt_pref)); + return PJ_SUCCESS; +} + + +/* + * Create Authorization/Proxy-Authorization response header based on the challege + * in WWW-Authenticate/Proxy-Authenticate header. + */ +static pj_status_t auth_respond( pj_pool_t *req_pool, + const pjsip_www_authenticate_hdr *hdr, + const pjsip_uri *uri, + const pjsip_cred_info *cred_info, + const pjsip_method *method, + pj_pool_t *sess_pool, + pjsip_cached_auth *cached_auth, + pjsip_authorization_hdr **p_h_auth) +{ + pjsip_authorization_hdr *hauth; + char tmp[PJSIP_MAX_URL_SIZE]; + pj_str_t uri_str; + pj_pool_t *pool; + pj_status_t status; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(req_pool && hdr && uri && cred_info && method && + sess_pool && cached_auth && p_h_auth, PJ_EINVAL); + + /* Print URL in the original request. */ + uri_str.ptr = tmp; + uri_str.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, tmp,sizeof(tmp)); + if (uri_str.slen < 1) { + pj_assert(!"URL is too long!"); + return PJSIP_EURITOOLONG; + } + +# if (PJSIP_AUTH_HEADER_CACHING) + { + pool = sess_pool; + PJ_UNUSED_ARG(req_pool); + } +# else + { + pool = req_pool; + PJ_UNUSED_ARG(sess_pool); + } +# endif + + if (hdr->type == PJSIP_H_WWW_AUTHENTICATE) + hauth = pjsip_authorization_hdr_create(pool); + else if (hdr->type == PJSIP_H_PROXY_AUTHENTICATE) + hauth = pjsip_proxy_authorization_hdr_create(pool); + else { + pj_assert(!"Invalid response header!"); + return PJSIP_EINVALIDHDR; + } + + /* Only support digest scheme at the moment. */ + if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) { + pj_str_t *cnonce = NULL; + pj_uint32_t nc = 1; + + /* Update the session (nonce-count etc) if required. */ +# if PJSIP_AUTH_QOP_SUPPORT + { + if (cached_auth) { + update_digest_session( sess_pool, cached_auth, hdr ); + + cnonce = &cached_auth->cnonce; + nc = cached_auth->nc; + } + } +# endif /* PJSIP_AUTH_QOP_SUPPORT */ + + hauth->scheme = pjsip_DIGEST_STR; + status = respond_digest( pool, &hauth->credential.digest, + &hdr->challenge.digest, &uri_str, cred_info, + cnonce, nc, &method->name); + if (status != PJ_SUCCESS) + return status; + + /* Set qop type in auth session the first time only. */ + if (hdr->challenge.digest.qop.slen != 0 && cached_auth) { + if (cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) { + pj_str_t *qop_val = &hauth->credential.digest.qop; + if (!pj_strcmp(qop_val, &pjsip_AUTH_STR)) { + cached_auth->qop_value = PJSIP_AUTH_QOP_AUTH; + } else { + cached_auth->qop_value = PJSIP_AUTH_QOP_UNKNOWN; + } + } + } + } else { + return PJSIP_EINVALIDAUTHSCHEME; + } + + /* Keep the new authorization header in the cache, only + * if no qop is not present. + */ +# if PJSIP_AUTH_HEADER_CACHING + { + if (hauth && cached_auth && cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) { + pjsip_cached_auth_hdr *cached_hdr; + + /* Delete old header with the same method. */ + cached_hdr = cached_auth->cached_hdr.next; + while (cached_hdr != &cached_auth->cached_hdr) { + if (pjsip_method_cmp(method, &cached_hdr->method)==0) + break; + cached_hdr = cached_hdr->next; + } + + /* Save the header to the list. */ + if (cached_hdr != &cached_auth->cached_hdr) { + cached_hdr->hdr = hauth; + } else { + cached_hdr = pj_pool_alloc(pool, sizeof(*cached_hdr)); + pjsip_method_copy( pool, &cached_hdr->method, method); + cached_hdr->hdr = hauth; + pj_list_insert_before( &cached_auth->cached_hdr, cached_hdr ); + } + } + +# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0 + if (hdr != cached_auth->last_chal) { + cached_auth->last_chal = pjsip_hdr_clone(sess_pool, hdr); + } +# endif + } +# endif + + *p_h_auth = hauth; + return PJ_SUCCESS; + +} + + +#if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0 +static pj_status_t new_auth_for_req( pjsip_tx_data *tdata, + pjsip_auth_clt_sess *sess, + pjsip_cached_auth *auth, + pjsip_authorization_hdr **p_h_auth) +{ + const pjsip_cred_info *cred; + pjsip_authorization_hdr *hauth; + pj_status_t status; + + PJ_ASSERT_RETURN(tdata && sess && auth, PJ_EINVAL); + PJ_ASSERT_RETURN(auth->last_chal != NULL, PJSIP_EAUTHNOPREVCHAL); + + cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme ); + if (!cred) + return PJSIP_ENOCREDENTIAL; + + status = auth_respond( tdata->pool, auth->last_chal, + tdata->msg->line.req.uri, + cred, &tdata->msg->line.req.method, + sess->pool, auth, &hauth); + if (status != PJ_SUCCESS) + return status; + + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth); + + if (p_h_auth) + *p_h_auth = hauth; + + return PJ_SUCCESS; +} +#endif + + +/* Find credential in list of (Proxy-)Authorization headers */ +static pjsip_authorization_hdr* get_header_for_realm(const pjsip_hdr *hdr_list, + const pj_str_t *realm) +{ + pjsip_authorization_hdr *h; + + h = (pjsip_authorization_hdr*)hdr_list->next; + while (h != (pjsip_authorization_hdr*)hdr_list) { + if (pj_stricmp(&h->credential.digest.realm, realm)==0) + return h; + h = h->next; + } + + return NULL; +} + + +/* Initialize outgoing request. */ +PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess, + pjsip_tx_data *tdata ) +{ + const pjsip_method *method; + pjsip_cached_auth *auth; + pjsip_hdr added; + + PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED); + PJ_ASSERT_RETURN(tdata->msg->type==PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Init list */ + pj_list_init(&added); + + /* Get the method. */ + method = &tdata->msg->line.req.method; + + auth = sess->cached_auth.next; + while (auth != &sess->cached_auth) { + /* Reset stale counter */ + auth->stale_cnt = 0; + + if (auth->qop_value == PJSIP_AUTH_QOP_NONE) { +# if defined(PJSIP_AUTH_HEADER_CACHING) && \ + PJSIP_AUTH_HEADER_CACHING!=0 + { + pjsip_cached_auth_hdr *entry = auth->cached_hdr.next; + while (entry != &auth->cached_hdr) { + if (pjsip_method_cmp(&entry->method, method)==0) { + pjsip_authorization_hdr *hauth; + hauth = pjsip_hdr_shallow_clone(tdata->pool, entry->hdr); + //pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth); + pj_list_push_back(&added, hauth); + break; + } + entry = entry->next; + } + +# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \ + PJSIP_AUTH_AUTO_SEND_NEXT!=0 + { + if (entry == &auth->cached_hdr) + new_auth_for_req( tdata, sess, auth, NULL); + } +# endif + + } +# elif defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \ + PJSIP_AUTH_AUTO_SEND_NEXT!=0 + { + new_auth_for_req( tdata, sess, auth, NULL); + } +# endif + + } +# if defined(PJSIP_AUTH_QOP_SUPPORT) && \ + defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \ + (PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT) + else if (auth->qop_value == PJSIP_AUTH_QOP_AUTH) { + /* For qop="auth", we have to re-create the authorization header. + */ + const pjsip_cred_info *cred; + pjsip_authorization_hdr *hauth; + pj_status_t status; + + cred = auth_find_cred(sess, &auth->realm, + &auth->last_chal->scheme); + if (!cred) { + auth = auth->next; + continue; + } + + status = auth_respond( tdata->pool, auth->last_chal, + tdata->msg->line.req.uri, + cred, + &tdata->msg->line.req.method, + sess->pool, auth, &hauth); + if (status != PJ_SUCCESS) + return status; + + //pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth); + pj_list_push_back(&added, hauth); + } +# endif /* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */ + + auth = auth->next; + } + + if (sess->pref.initial_auth == PJ_FALSE) { + pjsip_hdr *h; + + /* Don't want to send initial empty Authorization header, so + * just send whatever available in the list (maybe empty). + */ + + h = added.next; + while (h != &added) { + pjsip_hdr *next = h->next; + pjsip_msg_add_hdr(tdata->msg, h); + h = next; + } + } else { + /* For each realm, add either the cached authorization header + * or add an empty authorization header. + */ + unsigned i; + char *uri_str; + int len; + + uri_str = (char*)pj_pool_alloc(tdata->pool, PJSIP_MAX_URL_SIZE); + len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, tdata->msg->line.req.uri, + uri_str, PJSIP_MAX_URL_SIZE); + if (len < 1 || len >= PJSIP_MAX_URL_SIZE) + return PJSIP_EURITOOLONG; + + for (i=0; i<sess->cred_cnt; ++i) { + pjsip_cred_info *c = &sess->cred_info[i]; + pjsip_authorization_hdr *h; + + h = get_header_for_realm(&added, &c->realm); + if (h) { + pj_list_erase(h); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)h); + } else { + pjsip_authorization_hdr *hs; + + hs = pjsip_authorization_hdr_create(tdata->pool); + pj_strdup(tdata->pool, &hs->scheme, &c->scheme); + pj_strdup(tdata->pool, &hs->credential.digest.username, + &c->username); + pj_strdup(tdata->pool, &hs->credential.digest.realm, + &c->realm); + pj_strdup2(tdata->pool, &hs->credential.digest.uri, + uri_str); + pj_strdup(tdata->pool, &hs->credential.digest.algorithm, + &sess->pref.algorithm); + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hs); + } + } + } + + return PJ_SUCCESS; +} + + +/* Process authorization challenge */ +static pj_status_t process_auth( pj_pool_t *req_pool, + const pjsip_www_authenticate_hdr *hchal, + const pjsip_uri *uri, + pjsip_tx_data *tdata, + pjsip_auth_clt_sess *sess, + pjsip_cached_auth *cached_auth, + pjsip_authorization_hdr **h_auth) +{ + const pjsip_cred_info *cred; + pjsip_authorization_hdr *sent_auth = NULL; + pjsip_hdr *hdr; + pj_status_t status; + + /* See if we have sent authorization header for this realm */ + hdr = tdata->msg->hdr.next; + while (hdr != &tdata->msg->hdr) { + if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE && + hdr->type == PJSIP_H_AUTHORIZATION) || + (hchal->type == PJSIP_H_PROXY_AUTHENTICATE && + hdr->type == PJSIP_H_PROXY_AUTHORIZATION)) + { + sent_auth = (pjsip_authorization_hdr*) hdr; + if (pj_stricmp(&hchal->challenge.common.realm, + &sent_auth->credential.common.realm )==0) + { + /* If this authorization has empty response, remove it. */ + if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 && + sent_auth->credential.digest.response.slen == 0) + { + /* This is empty authorization, remove it. */ + hdr = hdr->next; + pj_list_erase(sent_auth); + continue; + } else { + /* Found previous authorization attempt */ + break; + } + } + } + hdr = hdr->next; + } + + /* If we have sent, see if server rejected because of stale nonce or + * other causes. + */ + if (hdr != &tdata->msg->hdr) { + pj_bool_t stale; + + /* Detect "stale" state */ + stale = hchal->challenge.digest.stale; + if (!stale) { + /* If stale is false, check is nonce has changed. Some servers + * (broken ones!) want to change nonce but they fail to set + * stale to true. + */ + stale = pj_strcmp(&hchal->challenge.digest.nonce, + &sent_auth->credential.digest.nonce); + } + + if (stale == PJ_FALSE) { + /* Our credential is rejected. No point in trying to re-supply + * the same credential. + */ + PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: " + "server rejected with stale=false", + sent_auth->credential.digest.username.slen, + sent_auth->credential.digest.username.ptr, + sent_auth->credential.digest.realm.slen, + sent_auth->credential.digest.realm.ptr)); + return PJSIP_EFAILEDCREDENTIAL; + } + + cached_auth->stale_cnt++; + if (cached_auth->stale_cnt >= PJSIP_MAX_STALE_COUNT) { + /* Our credential is rejected. No point in trying to re-supply + * the same credential. + */ + PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: " + "maximum number of stale retries exceeded", + sent_auth->credential.digest.username.slen, + sent_auth->credential.digest.username.ptr, + sent_auth->credential.digest.realm.slen, + sent_auth->credential.digest.realm.ptr)); + return PJSIP_EAUTHSTALECOUNT; + } + + /* Otherwise remove old, stale authorization header from the mesasge. + * We will supply a new one. + */ + pj_list_erase(sent_auth); + } + + /* Find credential to be used for the challenge. */ + cred = auth_find_cred( sess, &hchal->challenge.common.realm, + &hchal->scheme); + if (!cred) { + const pj_str_t *realm = &hchal->challenge.common.realm; + PJ_LOG(4,(THIS_FILE, + "Unable to set auth for %s: can not find credential for %.*s/%.*s", + tdata->obj_name, + realm->slen, realm->ptr, + hchal->scheme.slen, hchal->scheme.ptr)); + return PJSIP_ENOCREDENTIAL; + } + + /* Respond to authorization challenge. */ + status = auth_respond( req_pool, hchal, uri, cred, + &tdata->msg->line.req.method, + sess->pool, cached_auth, h_auth); + return status; +} + + +/* Reinitialize outgoing request after 401/407 response is received. + * The purpose of this function is: + * - to add a Authorization/Proxy-Authorization header. + * - to put the newly created Authorization/Proxy-Authorization header + * in cached_list. + */ +PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess, + const pjsip_rx_data *rdata, + pjsip_tx_data *old_request, + pjsip_tx_data **new_request ) +{ + pjsip_tx_data *tdata; + const pjsip_hdr *hdr; + unsigned chal_cnt; + pjsip_via_hdr *via; + pj_status_t status; + + PJ_ASSERT_RETURN(sess && rdata && old_request && new_request, + PJ_EINVAL); + PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED); + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG, + PJSIP_ENOTRESPONSEMSG); + PJ_ASSERT_RETURN(old_request->msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + PJ_ASSERT_RETURN(rdata->msg_info.msg->line.status.code == 401 || + rdata->msg_info.msg->line.status.code == 407, + PJSIP_EINVALIDSTATUS); + + tdata = old_request; + tdata->auth_retry = PJ_FALSE; + + /* + * Respond to each authentication challenge. + */ + hdr = rdata->msg_info.msg->hdr.next; + chal_cnt = 0; + while (hdr != &rdata->msg_info.msg->hdr) { + pjsip_cached_auth *cached_auth; + const pjsip_www_authenticate_hdr *hchal; + pjsip_authorization_hdr *hauth; + + /* Find WWW-Authenticate or Proxy-Authenticate header. */ + while (hdr != &rdata->msg_info.msg->hdr && + hdr->type != PJSIP_H_WWW_AUTHENTICATE && + hdr->type != PJSIP_H_PROXY_AUTHENTICATE) + { + hdr = hdr->next; + } + if (hdr == &rdata->msg_info.msg->hdr) + break; + + hchal = (const pjsip_www_authenticate_hdr*) hdr; + ++chal_cnt; + + /* Find authentication session for this realm, create a new one + * if not present. + */ + cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm ); + if (!cached_auth) { + cached_auth = PJ_POOL_ZALLOC_T( sess->pool, pjsip_cached_auth); + pj_strdup( sess->pool, &cached_auth->realm, &hchal->challenge.common.realm); + cached_auth->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE); +# if (PJSIP_AUTH_HEADER_CACHING) + { + pj_list_init(&cached_auth->cached_hdr); + } +# endif + pj_list_insert_before( &sess->cached_auth, cached_auth ); + } + + /* Create authorization header for this challenge, and update + * authorization session. + */ + status = process_auth( tdata->pool, hchal, tdata->msg->line.req.uri, + tdata, sess, cached_auth, &hauth); + if (status != PJ_SUCCESS) + return status; + + /* Add to the message. */ + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth); + + /* Process next header. */ + hdr = hdr->next; + } + + /* Check if challenge is present */ + if (chal_cnt == 0) + return PJSIP_EAUTHNOCHAL; + + /* Remove branch param in Via header. */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->branch_param.slen = 0; + + /* Restore strict route set. + * See http://trac.pjsip.org/repos/ticket/492 + */ + pjsip_restore_strict_route_set(tdata); + + /* Must invalidate the message! */ + pjsip_tx_data_invalidate_msg(tdata); + + /* Retrying.. */ + tdata->auth_retry = PJ_TRUE; + + /* Increment reference counter. */ + pjsip_tx_data_add_ref(tdata); + + /* Done. */ + *new_request = tdata; + return PJ_SUCCESS; + +} + diff --git a/pjsip/src/pjsip/sip_auth_msg.c b/pjsip/src/pjsip/sip_auth_msg.c new file mode 100644 index 0000000..5167b46 --- /dev/null +++ b/pjsip/src/pjsip/sip_auth_msg.c @@ -0,0 +1,343 @@ +/* $Id: sip_auth_msg.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_auth_msg.h> +#include <pjsip/sip_auth_parser.h> +#include <pjsip/sip_parser.h> +#include <pj/pool.h> +#include <pj/list.h> +#include <pj/string.h> +#include <pj/assert.h> +#include <pjsip/print_util.h> + +/////////////////////////////////////////////////////////////////////////////// +/* + * Authorization and Proxy-Authorization header. + */ +static pjsip_authorization_hdr* pjsip_authorization_hdr_clone( pj_pool_t *pool, + const pjsip_authorization_hdr *hdr); +static pjsip_authorization_hdr* pjsip_authorization_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_authorization_hdr *hdr); +static int pjsip_authorization_hdr_print( pjsip_authorization_hdr *hdr, + char *buf, pj_size_t size); + +static pjsip_hdr_vptr authorization_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_authorization_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_authorization_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_authorization_hdr_print, +}; + + +PJ_DEF(pjsip_authorization_hdr*) pjsip_authorization_hdr_create(pj_pool_t *pool) +{ + pjsip_authorization_hdr *hdr; + hdr = PJ_POOL_ZALLOC_T(pool, pjsip_authorization_hdr); + init_hdr(hdr, PJSIP_H_AUTHORIZATION, &authorization_hdr_vptr); + pj_list_init(&hdr->credential.common.other_param); + return hdr; +} + +PJ_DEF(pjsip_proxy_authorization_hdr*) pjsip_proxy_authorization_hdr_create(pj_pool_t *pool) +{ + pjsip_proxy_authorization_hdr *hdr; + hdr = PJ_POOL_ZALLOC_T(pool, pjsip_proxy_authorization_hdr); + init_hdr(hdr, PJSIP_H_PROXY_AUTHORIZATION, &authorization_hdr_vptr); + pj_list_init(&hdr->credential.common.other_param); + return hdr; +} + +static int print_digest_credential(pjsip_digest_credential *cred, char *buf, pj_size_t size) +{ + int printed; + char *startbuf = buf; + char *endbuf = buf + size; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + copy_advance_pair_quote_cond(buf, "username=", 9, cred->username, '"', '"'); + copy_advance_pair_quote_cond(buf, ", realm=", 8, cred->realm, '"', '"'); + copy_advance_pair_quote(buf, ", nonce=", 8, cred->nonce, '"', '"'); + copy_advance_pair_quote_cond(buf, ", uri=", 6, cred->uri, '"', '"'); + copy_advance_pair_quote(buf, ", response=", 11, cred->response, '"', '"'); + copy_advance_pair(buf, ", algorithm=", 12, cred->algorithm); + copy_advance_pair_quote_cond(buf, ", cnonce=", 9, cred->cnonce, '"', '"'); + copy_advance_pair_quote_cond(buf, ", opaque=", 9, cred->opaque, '"', '"'); + //Note: there's no dbl-quote in qop in Authorization header + // (unlike WWW-Authenticate) + //copy_advance_pair_quote_cond(buf, ", qop=", 6, cred->qop, '"', '"'); + copy_advance_pair(buf, ", qop=", 6, cred->qop); + copy_advance_pair(buf, ", nc=", 5, cred->nc); + + printed = pjsip_param_print_on(&cred->other_param, buf, endbuf-buf, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, ','); + if (printed < 0) + return -1; + buf += printed; + + return (int) (buf-startbuf); +} + +static int print_pgp_credential(pjsip_pgp_credential *cred, char *buf, pj_size_t size) +{ + PJ_UNUSED_ARG(cred); + PJ_UNUSED_ARG(buf); + PJ_UNUSED_ARG(size); + return -1; +} + +static int pjsip_authorization_hdr_print( pjsip_authorization_hdr *hdr, + char *buf, pj_size_t size) +{ + int printed; + char *startbuf = buf; + char *endbuf = buf + size; + + copy_advance(buf, hdr->name); + *buf++ = ':'; + *buf++ = ' '; + + copy_advance(buf, hdr->scheme); + *buf++ = ' '; + + if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) + { + printed = print_digest_credential(&hdr->credential.digest, buf, endbuf - buf); + } + else if (pj_stricmp(&hdr->scheme, &pjsip_PGP_STR) == 0) + { + printed = print_pgp_credential(&hdr->credential.pgp, buf, endbuf - buf); + } + else { + pj_assert(0); + return -1; + } + + if (printed == -1) + return -1; + + buf += printed; + *buf = '\0'; + return (int)(buf-startbuf); +} + +static pjsip_authorization_hdr* pjsip_authorization_hdr_clone( pj_pool_t *pool, + const pjsip_authorization_hdr *rhs) +{ + /* This function also serves Proxy-Authorization header. */ + pjsip_authorization_hdr *hdr; + if (rhs->type == PJSIP_H_AUTHORIZATION) + hdr = pjsip_authorization_hdr_create(pool); + else + hdr = pjsip_proxy_authorization_hdr_create(pool); + + pj_strdup(pool, &hdr->scheme, &rhs->scheme); + + if (pj_stricmp2(&hdr->scheme, "digest") == 0) { + pj_strdup(pool, &hdr->credential.digest.username, &rhs->credential.digest.username); + pj_strdup(pool, &hdr->credential.digest.realm, &rhs->credential.digest.realm); + pj_strdup(pool, &hdr->credential.digest.nonce, &rhs->credential.digest.nonce); + pj_strdup(pool, &hdr->credential.digest.uri, &rhs->credential.digest.uri); + pj_strdup(pool, &hdr->credential.digest.response, &rhs->credential.digest.response); + pj_strdup(pool, &hdr->credential.digest.algorithm, &rhs->credential.digest.algorithm); + pj_strdup(pool, &hdr->credential.digest.cnonce, &rhs->credential.digest.cnonce); + pj_strdup(pool, &hdr->credential.digest.opaque, &rhs->credential.digest.opaque); + pj_strdup(pool, &hdr->credential.digest.qop, &rhs->credential.digest.qop); + pj_strdup(pool, &hdr->credential.digest.nc, &rhs->credential.digest.nc); + pjsip_param_clone(pool, &hdr->credential.digest.other_param, &rhs->credential.digest.other_param); + } else if (pj_stricmp2(&hdr->scheme, "pgp") == 0) { + pj_assert(0); + return NULL; + } else { + pj_assert(0); + return NULL; + } + + return hdr; +} + +static pjsip_authorization_hdr* +pjsip_authorization_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_authorization_hdr *rhs) +{ + /* This function also serves Proxy-Authorization header. */ + pjsip_authorization_hdr *hdr; + hdr = PJ_POOL_ALLOC_T(pool, pjsip_authorization_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->credential.common.other_param, + &rhs->credential.common.other_param); + return hdr; +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Proxy-Authenticate and WWW-Authenticate header. + */ +static int pjsip_www_authenticate_hdr_print( pjsip_www_authenticate_hdr *hdr, + char *buf, pj_size_t size); +static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_clone( pj_pool_t *pool, + const pjsip_www_authenticate_hdr *hdr); +static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_www_authenticate_hdr *hdr); + +static pjsip_hdr_vptr www_authenticate_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_www_authenticate_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_www_authenticate_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_www_authenticate_hdr_print, +}; + + +PJ_DEF(pjsip_www_authenticate_hdr*) pjsip_www_authenticate_hdr_create(pj_pool_t *pool) +{ + pjsip_www_authenticate_hdr *hdr; + hdr = PJ_POOL_ZALLOC_T(pool, pjsip_www_authenticate_hdr); + init_hdr(hdr, PJSIP_H_WWW_AUTHENTICATE, &www_authenticate_hdr_vptr); + pj_list_init(&hdr->challenge.common.other_param); + return hdr; +} + + +PJ_DEF(pjsip_proxy_authenticate_hdr*) pjsip_proxy_authenticate_hdr_create(pj_pool_t *pool) +{ + pjsip_proxy_authenticate_hdr *hdr; + hdr = PJ_POOL_ZALLOC_T(pool, pjsip_proxy_authenticate_hdr); + init_hdr(hdr, PJSIP_H_PROXY_AUTHENTICATE, &www_authenticate_hdr_vptr); + pj_list_init(&hdr->challenge.common.other_param); + return hdr; +} + +static int print_digest_challenge( pjsip_digest_challenge *chal, + char *buf, pj_size_t size) +{ + int printed; + char *startbuf = buf; + char *endbuf = buf + size; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + /* Allow empty realm, see http://trac.pjsip.org/repos/ticket/1061 */ + copy_advance_pair_quote(buf, " realm=", 7, chal->realm, '"', '"'); + copy_advance_pair_quote_cond(buf, ",domain=", 8, chal->domain, '"', '"'); + copy_advance_pair_quote_cond(buf, ",nonce=", 7, chal->nonce, '"', '"'); + copy_advance_pair_quote_cond(buf, ",opaque=", 8, chal->opaque, '"', '"'); + if (chal->stale) { + pj_str_t true_str = { "true", 4 }; + copy_advance_pair(buf, ",stale=", 7, true_str); + } + copy_advance_pair(buf, ",algorithm=", 11, chal->algorithm); + copy_advance_pair_quote_cond(buf, ",qop=", 5, chal->qop, '"', '"'); + + printed = pjsip_param_print_on(&chal->other_param, buf, endbuf-buf, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, ','); + if (printed < 0) + return -1; + buf += printed; + + return (int)(buf-startbuf); +} + +static int print_pgp_challenge( pjsip_pgp_challenge *chal, + char *buf, pj_size_t size) +{ + PJ_UNUSED_ARG(chal); + PJ_UNUSED_ARG(buf); + PJ_UNUSED_ARG(size); + return -1; +} + +static int pjsip_www_authenticate_hdr_print( pjsip_www_authenticate_hdr *hdr, + char *buf, pj_size_t size) +{ + int printed; + char *startbuf = buf; + char *endbuf = buf + size; + + copy_advance(buf, hdr->name); + *buf++ = ':'; + *buf++ = ' '; + + copy_advance(buf, hdr->scheme); + *buf++ = ' '; + + if (pj_stricmp2(&hdr->scheme, "digest") == 0) + printed = print_digest_challenge(&hdr->challenge.digest, buf, endbuf - buf); + else if (pj_stricmp2(&hdr->scheme, "pgp") == 0) + printed = print_pgp_challenge(&hdr->challenge.pgp, buf, endbuf - buf); + else { + pj_assert(0); + return -1; + } + + if (printed == -1) + return -1; + + buf += printed; + *buf = '\0'; + return (int)(buf-startbuf); +} + +static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_clone( pj_pool_t *pool, + const pjsip_www_authenticate_hdr *rhs) +{ + /* This function also serves Proxy-Authenticate header. */ + pjsip_www_authenticate_hdr *hdr; + if (rhs->type == PJSIP_H_WWW_AUTHENTICATE) + hdr = pjsip_www_authenticate_hdr_create(pool); + else + hdr = pjsip_proxy_authenticate_hdr_create(pool); + + pj_strdup(pool, &hdr->scheme, &rhs->scheme); + + if (pj_stricmp2(&hdr->scheme, "digest") == 0) { + pj_strdup(pool, &hdr->challenge.digest.realm, &rhs->challenge.digest.realm); + pj_strdup(pool, &hdr->challenge.digest.domain, &rhs->challenge.digest.domain); + pj_strdup(pool, &hdr->challenge.digest.nonce, &rhs->challenge.digest.nonce); + pj_strdup(pool, &hdr->challenge.digest.opaque, &rhs->challenge.digest.opaque); + hdr->challenge.digest.stale = rhs->challenge.digest.stale; + pj_strdup(pool, &hdr->challenge.digest.algorithm, &rhs->challenge.digest.algorithm); + pj_strdup(pool, &hdr->challenge.digest.qop, &rhs->challenge.digest.qop); + pjsip_param_clone(pool, &hdr->challenge.digest.other_param, + &rhs->challenge.digest.other_param); + } else if (pj_stricmp2(&hdr->scheme, "pgp") == 0) { + pj_assert(0); + return NULL; + } else { + pj_assert(0); + return NULL; + } + + return hdr; + +} + +static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_www_authenticate_hdr *rhs) +{ + /* This function also serves Proxy-Authenticate header. */ + pjsip_www_authenticate_hdr *hdr; + hdr = PJ_POOL_ALLOC_T(pool, pjsip_www_authenticate_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->challenge.common.other_param, + &rhs->challenge.common.other_param); + return hdr; +} + + diff --git a/pjsip/src/pjsip/sip_auth_parser.c b/pjsip/src/pjsip/sip_auth_parser.c new file mode 100644 index 0000000..823372c --- /dev/null +++ b/pjsip/src/pjsip/sip_auth_parser.c @@ -0,0 +1,308 @@ +/* $Id: sip_auth_parser.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_auth_parser.h> +#include <pjsip/sip_auth_msg.h> +#include <pjsip/sip_parser.h> +#include <pj/assert.h> +#include <pj/string.h> +#include <pj/except.h> +#include <pj/pool.h> + +static pjsip_hdr* parse_hdr_authorization ( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_proxy_authorization ( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_www_authenticate ( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_proxy_authenticate ( pjsip_parse_ctx *ctx ); + +static void parse_digest_credential ( pj_scanner *scanner, pj_pool_t *pool, + pjsip_digest_credential *cred); +static void parse_pgp_credential ( pj_scanner *scanner, pj_pool_t *pool, + pjsip_pgp_credential *cred); +static void parse_digest_challenge ( pj_scanner *scanner, pj_pool_t *pool, + pjsip_digest_challenge *chal); +static void parse_pgp_challenge ( pj_scanner *scanner, pj_pool_t *pool, + pjsip_pgp_challenge *chal); + +const pj_str_t pjsip_USERNAME_STR = { "username", 8 }, + pjsip_REALM_STR = { "realm", 5}, + pjsip_NONCE_STR = { "nonce", 5}, + pjsip_URI_STR = { "uri", 3 }, + pjsip_RESPONSE_STR = { "response", 8 }, + pjsip_ALGORITHM_STR = { "algorithm", 9 }, + pjsip_DOMAIN_STR = { "domain", 6 }, + pjsip_STALE_STR = { "stale", 5}, + pjsip_QOP_STR = { "qop", 3}, + pjsip_CNONCE_STR = { "cnonce", 6}, + pjsip_OPAQUE_STR = { "opaque", 6}, + pjsip_NC_STR = { "nc", 2}, + pjsip_TRUE_STR = { "true", 4}, + pjsip_QUOTED_TRUE_STR = { "\"true\"", 6}, + pjsip_FALSE_STR = { "false", 5}, + pjsip_QUOTED_FALSE_STR = { "\"false\"", 7}, + pjsip_DIGEST_STR = { "Digest", 6}, + pjsip_QUOTED_DIGEST_STR = { "\"Digest\"", 8}, + pjsip_PGP_STR = { "PGP", 3 }, + pjsip_QUOTED_PGP_STR = { "\"PGP\"", 5 }, + pjsip_MD5_STR = { "md5", 3 }, + pjsip_QUOTED_MD5_STR = { "\"md5\"", 5}, + pjsip_AUTH_STR = { "auth", 4}, + pjsip_QUOTED_AUTH_STR = { "\"auth\"", 6 }; + + +static void parse_digest_credential( pj_scanner *scanner, pj_pool_t *pool, + pjsip_digest_credential *cred) +{ + pj_list_init(&cred->other_param); + + for (;;) { + pj_str_t name, value; + + pjsip_parse_param_imp(scanner, pool, &name, &value, + PJSIP_PARSE_REMOVE_QUOTE); + + if (!pj_stricmp(&name, &pjsip_USERNAME_STR)) { + cred->username = value; + + } else if (!pj_stricmp(&name, &pjsip_REALM_STR)) { + cred->realm = value; + + } else if (!pj_stricmp(&name, &pjsip_NONCE_STR)) { + cred->nonce = value; + + } else if (!pj_stricmp(&name, &pjsip_URI_STR)) { + cred->uri = value; + + } else if (!pj_stricmp(&name, &pjsip_RESPONSE_STR)) { + cred->response = value; + + } else if (!pj_stricmp(&name, &pjsip_ALGORITHM_STR)) { + cred->algorithm = value; + + } else if (!pj_stricmp(&name, &pjsip_CNONCE_STR)) { + cred->cnonce = value; + + } else if (!pj_stricmp(&name, &pjsip_OPAQUE_STR)) { + cred->opaque = value; + + } else if (!pj_stricmp(&name, &pjsip_QOP_STR)) { + cred->qop = value; + + } else if (!pj_stricmp(&name, &pjsip_NC_STR)) { + cred->nc = value; + + } else { + pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param); + p->name = name; + p->value = value; + pj_list_insert_before(&cred->other_param, p); + } + + /* Eat comma */ + if (!pj_scan_is_eof(scanner) && *scanner->curptr == ',') + pj_scan_get_char(scanner); + else + break; + } +} + +static void parse_pgp_credential( pj_scanner *scanner, pj_pool_t *pool, + pjsip_pgp_credential *cred) +{ + PJ_UNUSED_ARG(scanner); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(cred); + + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); +} + +static void parse_digest_challenge( pj_scanner *scanner, pj_pool_t *pool, + pjsip_digest_challenge *chal) +{ + pj_list_init(&chal->other_param); + + for (;;) { + pj_str_t name, value; + + pjsip_parse_param_imp(scanner, pool, &name, &value, + PJSIP_PARSE_REMOVE_QUOTE); + + if (!pj_stricmp(&name, &pjsip_REALM_STR)) { + chal->realm = value; + + } else if (!pj_stricmp(&name, &pjsip_DOMAIN_STR)) { + chal->domain = value; + + } else if (!pj_stricmp(&name, &pjsip_NONCE_STR)) { + chal->nonce = value; + + } else if (!pj_stricmp(&name, &pjsip_OPAQUE_STR)) { + chal->opaque = value; + + } else if (!pj_stricmp(&name, &pjsip_STALE_STR)) { + if (!pj_stricmp(&value, &pjsip_TRUE_STR) || + !pj_stricmp(&value, &pjsip_QUOTED_TRUE_STR)) + { + chal->stale = 1; + } + + } else if (!pj_stricmp(&name, &pjsip_ALGORITHM_STR)) { + chal->algorithm = value; + + + } else if (!pj_stricmp(&name, &pjsip_QOP_STR)) { + chal->qop = value; + + } else { + pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param); + p->name = name; + p->value = value; + pj_list_insert_before(&chal->other_param, p); + } + + /* Eat comma */ + if (!pj_scan_is_eof(scanner) && *scanner->curptr == ',') + pj_scan_get_char(scanner); + else + break; + } +} + +static void parse_pgp_challenge( pj_scanner *scanner, pj_pool_t *pool, + pjsip_pgp_challenge *chal) +{ + PJ_UNUSED_ARG(scanner); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(chal); + + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); +} + +static void int_parse_hdr_authorization( pj_scanner *scanner, pj_pool_t *pool, + pjsip_authorization_hdr *hdr) +{ + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + if (*scanner->curptr == '"') { + pj_scan_get_quote(scanner, '"', '"', &hdr->scheme); + hdr->scheme.ptr++; + hdr->scheme.slen -= 2; + } else { + pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &hdr->scheme); + } + + if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) { + + parse_digest_credential(scanner, pool, &hdr->credential.digest); + + } else if (!pj_stricmp(&hdr->scheme, &pjsip_PGP_STR)) { + + parse_pgp_credential( scanner, pool, &hdr->credential.pgp); + + } else { + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + } + + pjsip_parse_end_hdr_imp( scanner ); +} + +static void int_parse_hdr_authenticate( pj_scanner *scanner, pj_pool_t *pool, + pjsip_www_authenticate_hdr *hdr) +{ + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + if (*scanner->curptr == '"') { + pj_scan_get_quote(scanner, '"', '"', &hdr->scheme); + hdr->scheme.ptr++; + hdr->scheme.slen -= 2; + } else { + pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &hdr->scheme); + } + + if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) { + + parse_digest_challenge(scanner, pool, &hdr->challenge.digest); + + } else if (!pj_stricmp(&hdr->scheme, &pjsip_PGP_STR)) { + + parse_pgp_challenge(scanner, pool, &hdr->challenge.pgp); + + } else { + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + } + + pjsip_parse_end_hdr_imp( scanner ); +} + + +static pjsip_hdr* parse_hdr_authorization( pjsip_parse_ctx *ctx ) +{ + pjsip_authorization_hdr *hdr = pjsip_authorization_hdr_create(ctx->pool); + int_parse_hdr_authorization(ctx->scanner, ctx->pool, hdr); + return (pjsip_hdr*)hdr; +} + +static pjsip_hdr* parse_hdr_proxy_authorization( pjsip_parse_ctx *ctx ) +{ + pjsip_proxy_authorization_hdr *hdr = + pjsip_proxy_authorization_hdr_create(ctx->pool); + int_parse_hdr_authorization(ctx->scanner, ctx->pool, hdr); + return (pjsip_hdr*)hdr; +} + +static pjsip_hdr* parse_hdr_www_authenticate( pjsip_parse_ctx *ctx ) +{ + pjsip_www_authenticate_hdr *hdr = + pjsip_www_authenticate_hdr_create(ctx->pool); + int_parse_hdr_authenticate(ctx->scanner, ctx->pool, hdr); + return (pjsip_hdr*)hdr; +} + +static pjsip_hdr* parse_hdr_proxy_authenticate( pjsip_parse_ctx *ctx ) +{ + pjsip_proxy_authenticate_hdr *hdr = + pjsip_proxy_authenticate_hdr_create(ctx->pool); + int_parse_hdr_authenticate(ctx->scanner, ctx->pool, hdr); + return (pjsip_hdr*)hdr; +} + + +PJ_DEF(pj_status_t) pjsip_auth_init_parser() +{ + pj_status_t status; + + status = pjsip_register_hdr_parser( "Authorization", NULL, + &parse_hdr_authorization); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + status = pjsip_register_hdr_parser( "Proxy-Authorization", NULL, + &parse_hdr_proxy_authorization); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + status = pjsip_register_hdr_parser( "WWW-Authenticate", NULL, + &parse_hdr_www_authenticate); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + status = pjsip_register_hdr_parser( "Proxy-Authenticate", NULL, + &parse_hdr_proxy_authenticate); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + + return PJ_SUCCESS; +} + +PJ_DEF(void) pjsip_auth_deinit_parser() +{ +} + diff --git a/pjsip/src/pjsip/sip_auth_parser_wrap.cpp b/pjsip/src/pjsip/sip_auth_parser_wrap.cpp new file mode 100644 index 0000000..8d4f8a3 --- /dev/null +++ b/pjsip/src/pjsip/sip_auth_parser_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: sip_auth_parser_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * 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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "sip_auth_parser.c" diff --git a/pjsip/src/pjsip/sip_auth_server.c b/pjsip/src/pjsip/sip_auth_server.c new file mode 100644 index 0000000..248e6cc --- /dev/null +++ b/pjsip/src/pjsip/sip_auth_server.c @@ -0,0 +1,225 @@ +/* $Id: sip_auth_server.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <pjsip/sip_auth.h> +#include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */ +#include <pjsip/sip_auth_msg.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_transport.h> +#include <pj/string.h> +#include <pj/assert.h> + + +/* + * Initialize server authorization session data structure to serve the + * specified realm and to use lookup_func function to look for the credential + * info. + */ +PJ_DEF(pj_status_t) pjsip_auth_srv_init( pj_pool_t *pool, + pjsip_auth_srv *auth_srv, + const pj_str_t *realm, + pjsip_auth_lookup_cred *lookup, + unsigned options ) +{ + PJ_ASSERT_RETURN(pool && auth_srv && realm && lookup, PJ_EINVAL); + + pj_strdup( pool, &auth_srv->realm, realm); + auth_srv->lookup = lookup; + auth_srv->is_proxy = (options & PJSIP_AUTH_SRV_IS_PROXY); + + return PJ_SUCCESS; +} + + +/* Verify incoming Authorization/Proxy-Authorization header against the + * specified credential. + */ +static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr, + const pj_str_t *method, + const pjsip_cred_info *cred_info ) +{ + if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) { + char digest_buf[PJSIP_MD5STRLEN]; + pj_str_t digest; + const pjsip_digest_credential *dig = &hdr->credential.digest; + + /* Check that username and realm match. + * These checks should have been performed before entering this + * function. + */ + PJ_ASSERT_RETURN(pj_strcmp(&dig->username, &cred_info->username) == 0, + PJ_EINVALIDOP); + PJ_ASSERT_RETURN(pj_strcmp(&dig->realm, &cred_info->realm) == 0, + PJ_EINVALIDOP); + + /* Prepare for our digest calculation. */ + digest.ptr = digest_buf; + digest.slen = PJSIP_MD5STRLEN; + + /* Create digest for comparison. */ + pjsip_auth_create_digest(&digest, + &hdr->credential.digest.nonce, + &hdr->credential.digest.nc, + &hdr->credential.digest.cnonce, + &hdr->credential.digest.qop, + &hdr->credential.digest.uri, + &cred_info->realm, + cred_info, + method ); + + /* Compare digest. */ + return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ? + PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST; + + } else { + pj_assert(!"Unsupported authentication scheme"); + return PJSIP_EINVALIDAUTHSCHEME; + } +} + + +/* + * Request the authorization server framework to verify the authorization + * information in the specified request in rdata. + */ +PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv, + pjsip_rx_data *rdata, + int *status_code) +{ + pjsip_authorization_hdr *h_auth; + pjsip_msg *msg = rdata->msg_info.msg; + pjsip_hdr_e htype; + pj_str_t acc_name; + pjsip_cred_info cred_info; + pj_status_t status; + + PJ_ASSERT_RETURN(auth_srv && rdata, PJ_EINVAL); + PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG); + + htype = auth_srv->is_proxy ? PJSIP_H_PROXY_AUTHORIZATION : + PJSIP_H_AUTHORIZATION; + + /* Initialize status with 200. */ + *status_code = 200; + + /* Find authorization header for our realm. */ + h_auth = (pjsip_authorization_hdr*) pjsip_msg_find_hdr(msg, htype, NULL); + while (h_auth) { + if (!pj_stricmp(&h_auth->credential.common.realm, &auth_srv->realm)) + break; + + h_auth = h_auth->next; + if (h_auth == (void*) &msg->hdr) { + h_auth = NULL; + break; + } + + h_auth=(pjsip_authorization_hdr*)pjsip_msg_find_hdr(msg,htype,h_auth); + } + + if (!h_auth) { + *status_code = auth_srv->is_proxy ? 407 : 401; + return PJSIP_EAUTHNOAUTH; + } + + /* Check authorization scheme. */ + if (pj_stricmp(&h_auth->scheme, &pjsip_DIGEST_STR) == 0) + acc_name = h_auth->credential.digest.username; + else { + *status_code = auth_srv->is_proxy ? 407 : 401; + return PJSIP_EINVALIDAUTHSCHEME; + } + + /* Find the credential information for the account. */ + status = (*auth_srv->lookup)(rdata->tp_info.pool, &auth_srv->realm, + &acc_name, &cred_info); + if (status != PJ_SUCCESS) { + *status_code = PJSIP_SC_FORBIDDEN; + return status; + } + + /* Authenticate with the specified credential. */ + status = pjsip_auth_verify(h_auth, &msg->line.req.method.name, + &cred_info); + if (status != PJ_SUCCESS) { + *status_code = PJSIP_SC_FORBIDDEN; + } + return status; +} + + +/* + * Add authentication challenge headers to the outgoing response in tdata. + * Application may specify its customized nonce and opaque for the challenge, + * or can leave the value to NULL to make the function fills them in with + * random characters. + */ +PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv, + const pj_str_t *qop, + const pj_str_t *nonce, + const pj_str_t *opaque, + pj_bool_t stale, + pjsip_tx_data *tdata) +{ + pjsip_www_authenticate_hdr *hdr; + char nonce_buf[16]; + pj_str_t random; + + PJ_ASSERT_RETURN( auth_srv && tdata, PJ_EINVAL ); + + random.ptr = nonce_buf; + random.slen = sizeof(nonce_buf); + + /* Create the header. */ + if (auth_srv->is_proxy) + hdr = pjsip_proxy_authenticate_hdr_create(tdata->pool); + else + hdr = pjsip_www_authenticate_hdr_create(tdata->pool); + + /* Initialize header. + * Note: only support digest authentication now. + */ + hdr->scheme = pjsip_DIGEST_STR; + hdr->challenge.digest.algorithm = pjsip_MD5_STR; + if (nonce) { + pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, nonce); + } else { + pj_create_random_string(nonce_buf, sizeof(nonce_buf)); + pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, &random); + } + if (opaque) { + pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, opaque); + } else { + pj_create_random_string(nonce_buf, sizeof(nonce_buf)); + pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random); + } + if (qop) { + pj_strdup(tdata->pool, &hdr->challenge.digest.qop, qop); + } else { + hdr->challenge.digest.qop.slen = 0; + } + pj_strdup(tdata->pool, &hdr->challenge.digest.realm, &auth_srv->realm); + hdr->challenge.digest.stale = stale; + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsip/sip_config.c b/pjsip/src/pjsip/sip_config.c new file mode 100644 index 0000000..8742c8a --- /dev/null +++ b/pjsip/src/pjsip/sip_config.c @@ -0,0 +1,54 @@ +/* $Id: sip_config.c 3999 2012-03-30 07:10:13Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <pjsip/sip_config.h> + +/* pjsip configuration instance, initialized with default values */ +pjsip_cfg_t pjsip_sip_cfg_var = +{ + /* Global settings */ + { + PJSIP_ALLOW_PORT_IN_FROMTO_HDR, + 0, + PJSIP_DONT_SWITCH_TO_TCP + }, + + /* Transaction settings */ + { + PJSIP_MAX_TSX_COUNT, + PJSIP_T1_TIMEOUT, + PJSIP_T2_TIMEOUT, + PJSIP_T4_TIMEOUT, + PJSIP_TD_TIMEOUT + }, + + /* Client registration client */ + { + PJSIP_REGISTER_CLIENT_CHECK_CONTACT + } +}; + + +#ifdef PJ_DLL +PJ_DEF(pjsip_cfg_t*) pjsip_cfg(void) +{ + return &pjsip_sip_cfg_var; +} +#endif diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c new file mode 100644 index 0000000..008cad2 --- /dev/null +++ b/pjsip/src/pjsip/sip_dialog.c @@ -0,0 +1,2250 @@ +/* $Id: sip_dialog.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_dialog.h> +#include <pjsip/sip_ua_layer.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_parser.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_util.h> +#include <pjsip/sip_transaction.h> +#include <pj/assert.h> +#include <pj/os.h> +#include <pj/string.h> +#include <pj/pool.h> +#include <pj/guid.h> +#include <pj/rand.h> +#include <pj/array.h> +#include <pj/except.h> +#include <pj/hash.h> +#include <pj/log.h> + +#define THIS_FILE "sip_dialog.c" + +long pjsip_dlg_lock_tls_id; + +/* Config */ +pj_bool_t pjsip_include_allow_hdr_in_dlg = PJSIP_INCLUDE_ALLOW_HDR_IN_DLG; + +/* Contact header string */ +static const pj_str_t HCONTACT = { "Contact", 7 }; + + +PJ_DEF(pj_bool_t) pjsip_method_creates_dialog(const pjsip_method *m) +{ + const pjsip_method subscribe = { PJSIP_OTHER_METHOD, {"SUBSCRIBE", 9}}; + const pjsip_method refer = { PJSIP_OTHER_METHOD, {"REFER", 5}}; + const pjsip_method notify = { PJSIP_OTHER_METHOD, {"NOTIFY", 6}}; + const pjsip_method update = { PJSIP_OTHER_METHOD, {"UPDATE", 6}}; + + return m->id == PJSIP_INVITE_METHOD || + (pjsip_method_cmp(m, &subscribe)==0) || + (pjsip_method_cmp(m, &refer)==0) || + (pjsip_method_cmp(m, ¬ify)==0) || + (pjsip_method_cmp(m, &update)==0); +} + +static pj_status_t create_dialog( pjsip_user_agent *ua, + pjsip_dialog **p_dlg) +{ + pjsip_endpoint *endpt; + pj_pool_t *pool; + pjsip_dialog *dlg; + pj_status_t status; + + endpt = pjsip_ua_get_endpt(ua); + if (!endpt) + return PJ_EINVALIDOP; + + pool = pjsip_endpt_create_pool(endpt, "dlg%p", + PJSIP_POOL_LEN_DIALOG, + PJSIP_POOL_INC_DIALOG); + if (!pool) + return PJ_ENOMEM; + + dlg = PJ_POOL_ZALLOC_T(pool, pjsip_dialog); + PJ_ASSERT_RETURN(dlg != NULL, PJ_ENOMEM); + + dlg->pool = pool; + pj_ansi_snprintf(dlg->obj_name, sizeof(dlg->obj_name), "dlg%p", dlg); + dlg->ua = ua; + dlg->endpt = endpt; + dlg->state = PJSIP_DIALOG_STATE_NULL; + dlg->add_allow = pjsip_include_allow_hdr_in_dlg; + + pj_list_init(&dlg->inv_hdr); + pj_list_init(&dlg->rem_cap_hdr); + + status = pj_mutex_create_recursive(pool, dlg->obj_name, &dlg->mutex_); + if (status != PJ_SUCCESS) + goto on_error; + + pjsip_target_set_init(&dlg->target_set); + + *p_dlg = dlg; + return PJ_SUCCESS; + +on_error: + if (dlg->mutex_) + pj_mutex_destroy(dlg->mutex_); + pjsip_endpt_release_pool(endpt, pool); + return status; +} + +static void destroy_dialog( pjsip_dialog *dlg ) +{ + if (dlg->mutex_) { + pj_mutex_destroy(dlg->mutex_); + dlg->mutex_ = NULL; + } + if (dlg->tp_sel.type != PJSIP_TPSELECTOR_NONE) { + pjsip_tpselector_dec_ref(&dlg->tp_sel); + pj_bzero(&dlg->tp_sel, sizeof(pjsip_tpselector)); + } + pjsip_endpt_release_pool(dlg->endpt, dlg->pool); +} + + +/* + * Create an UAC dialog. + */ +PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua, + const pj_str_t *local_uri, + const pj_str_t *local_contact, + const pj_str_t *remote_uri, + const pj_str_t *target, + pjsip_dialog **p_dlg) +{ + pj_status_t status; + pj_str_t tmp; + pjsip_dialog *dlg; + + /* Check arguments. */ + PJ_ASSERT_RETURN(ua && local_uri && remote_uri && p_dlg, PJ_EINVAL); + + /* Create dialog instance. */ + status = create_dialog(ua, &dlg); + if (status != PJ_SUCCESS) + return status; + + /* Parse target. */ + pj_strdup_with_null(dlg->pool, &tmp, target ? target : remote_uri); + dlg->target = pjsip_parse_uri(dlg->pool, tmp.ptr, tmp.slen, 0); + if (!dlg->target) { + status = PJSIP_EINVALIDURI; + goto on_error; + } + + /* Put any header param in the target URI into INVITE header list. */ + if (PJSIP_URI_SCHEME_IS_SIP(dlg->target) || + PJSIP_URI_SCHEME_IS_SIPS(dlg->target)) + { + pjsip_param *param; + pjsip_sip_uri *uri = (pjsip_sip_uri*)pjsip_uri_get_uri(dlg->target); + + param = uri->header_param.next; + while (param != &uri->header_param) { + pjsip_hdr *hdr; + int c; + + c = param->value.ptr[param->value.slen]; + param->value.ptr[param->value.slen] = '\0'; + + hdr = (pjsip_hdr*) + pjsip_parse_hdr(dlg->pool, ¶m->name, param->value.ptr, + param->value.slen, NULL); + + param->value.ptr[param->value.slen] = (char)c; + + if (hdr == NULL) { + status = PJSIP_EINVALIDURI; + goto on_error; + } + pj_list_push_back(&dlg->inv_hdr, hdr); + + param = param->next; + } + + /* Now must remove any header params from URL, since that would + * create another header in pjsip_endpt_create_request(). + */ + pj_list_init(&uri->header_param); + } + + /* Add target to the target set */ + pjsip_target_set_add_uri(&dlg->target_set, dlg->pool, dlg->target, 0); + + /* Init local info. */ + dlg->local.info = pjsip_from_hdr_create(dlg->pool); + pj_strdup_with_null(dlg->pool, &dlg->local.info_str, local_uri); + dlg->local.info->uri = pjsip_parse_uri(dlg->pool, + dlg->local.info_str.ptr, + dlg->local.info_str.slen, 0); + if (!dlg->local.info->uri) { + status = PJSIP_EINVALIDURI; + goto on_error; + } + + /* Generate local tag. */ + pj_create_unique_string(dlg->pool, &dlg->local.info->tag); + + /* Calculate hash value of local tag. */ + dlg->local.tag_hval = pj_hash_calc(0, dlg->local.info->tag.ptr, + dlg->local.info->tag.slen); + + /* Randomize local CSeq. */ + dlg->local.first_cseq = pj_rand() & 0x7FFF; + dlg->local.cseq = dlg->local.first_cseq; + + /* Init local contact. */ + pj_strdup_with_null(dlg->pool, &tmp, + local_contact ? local_contact : local_uri); + dlg->local.contact = (pjsip_contact_hdr*) + pjsip_parse_hdr(dlg->pool, &HCONTACT, tmp.ptr, + tmp.slen, NULL); + if (!dlg->local.contact) { + status = PJSIP_EINVALIDURI; + goto on_error; + } + + /* Init remote info. */ + dlg->remote.info = pjsip_to_hdr_create(dlg->pool); + pj_strdup_with_null(dlg->pool, &dlg->remote.info_str, remote_uri); + dlg->remote.info->uri = pjsip_parse_uri(dlg->pool, + dlg->remote.info_str.ptr, + dlg->remote.info_str.slen, 0); + if (!dlg->remote.info->uri) { + status = PJSIP_EINVALIDURI; + goto on_error; + } + + /* Remove header param from remote.info_str, if any */ + if (PJSIP_URI_SCHEME_IS_SIP(dlg->remote.info->uri) || + PJSIP_URI_SCHEME_IS_SIPS(dlg->remote.info->uri)) + { + pjsip_sip_uri *sip_uri = (pjsip_sip_uri *) + pjsip_uri_get_uri(dlg->remote.info->uri); + if (!pj_list_empty(&sip_uri->header_param)) { + pj_str_t tmp; + + /* Remove all header param */ + pj_list_init(&sip_uri->header_param); + + /* Print URI */ + tmp.ptr = (char*) pj_pool_alloc(dlg->pool, + dlg->remote.info_str.slen); + tmp.slen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, + sip_uri, tmp.ptr, + dlg->remote.info_str.slen); + + if (tmp.slen < 1) { + status = PJSIP_EURITOOLONG; + goto on_error; + } + + /* Assign remote.info_str */ + dlg->remote.info_str = tmp; + } + } + + + /* Initialize remote's CSeq to -1. */ + dlg->remote.cseq = dlg->remote.first_cseq = -1; + + /* Initial role is UAC. */ + dlg->role = PJSIP_ROLE_UAC; + + /* Secure? */ + dlg->secure = PJSIP_URI_SCHEME_IS_SIPS(dlg->target); + + /* Generate Call-ID header. */ + dlg->call_id = pjsip_cid_hdr_create(dlg->pool); + pj_create_unique_string(dlg->pool, &dlg->call_id->id); + + /* Initial route set is empty. */ + pj_list_init(&dlg->route_set); + + /* Init client authentication session. */ + status = pjsip_auth_clt_init(&dlg->auth_sess, dlg->endpt, + dlg->pool, 0); + if (status != PJ_SUCCESS) + goto on_error; + + /* Register this dialog to user agent. */ + status = pjsip_ua_register_dlg( ua, dlg ); + if (status != PJ_SUCCESS) + goto on_error; + + + /* Done! */ + *p_dlg = dlg; + + + PJ_LOG(5,(dlg->obj_name, "UAC dialog created")); + + return PJ_SUCCESS; + +on_error: + destroy_dialog(dlg); + return status; +} + + +/* + * Create UAS dialog. + */ +PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua, + pjsip_rx_data *rdata, + const pj_str_t *contact, + pjsip_dialog **p_dlg) +{ + pj_status_t status; + pjsip_hdr *pos = NULL; + pjsip_contact_hdr *contact_hdr; + pjsip_rr_hdr *rr; + pjsip_transaction *tsx = NULL; + pj_str_t tmp; + enum { TMP_LEN=128}; + pj_ssize_t len; + pjsip_dialog *dlg; + + /* Check arguments. */ + PJ_ASSERT_RETURN(ua && rdata && p_dlg, PJ_EINVAL); + + /* rdata must have request message. */ + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Request must not have To tag. + * This should have been checked in the user agent (or application?). + */ + PJ_ASSERT_RETURN(rdata->msg_info.to->tag.slen == 0, PJ_EINVALIDOP); + + /* The request must be a dialog establishing request. */ + PJ_ASSERT_RETURN( + pjsip_method_creates_dialog(&rdata->msg_info.msg->line.req.method), + PJ_EINVALIDOP); + + /* Create dialog instance. */ + status = create_dialog(ua, &dlg); + if (status != PJ_SUCCESS) + return status; + + /* Temprary string for getting the string representation of + * both local and remote URI. + */ + tmp.ptr = (char*) pj_pool_alloc(rdata->tp_info.pool, TMP_LEN); + + /* Init local info from the To header. */ + dlg->local.info = (pjsip_fromto_hdr*) + pjsip_hdr_clone(dlg->pool, rdata->msg_info.to); + pjsip_fromto_hdr_set_from(dlg->local.info); + + /* Generate local tag. */ + pj_create_unique_string(dlg->pool, &dlg->local.info->tag); + + + /* Print the local info. */ + len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, + dlg->local.info->uri, tmp.ptr, TMP_LEN); + if (len < 1) { + pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->"); + tmp.slen = pj_ansi_strlen(tmp.ptr); + } else + tmp.slen = len; + + /* Save the local info. */ + pj_strdup(dlg->pool, &dlg->local.info_str, &tmp); + + /* Calculate hash value of local tag. */ + dlg->local.tag_hval = pj_hash_calc(0, dlg->local.info->tag.ptr, + dlg->local.info->tag.slen); + + + /* Randomize local cseq */ + dlg->local.first_cseq = pj_rand() & 0x7FFF; + dlg->local.cseq = dlg->local.first_cseq; + + /* Init local contact. */ + /* TODO: + * Section 12.1.1, paragraph about using SIPS URI in Contact. + * If the request that initiated the dialog contained a SIPS URI + * in the Request-URI or in the top Record-Route header field value, + * if there was any, or the Contact header field if there was no + * Record-Route header field, the Contact header field in the response + * MUST be a SIPS URI. + */ + if (contact) { + pj_str_t tmp; + + pj_strdup_with_null(dlg->pool, &tmp, contact); + dlg->local.contact = (pjsip_contact_hdr*) + pjsip_parse_hdr(dlg->pool, &HCONTACT, tmp.ptr, + tmp.slen, NULL); + if (!dlg->local.contact) { + status = PJSIP_EINVALIDURI; + goto on_error; + } + + } else { + dlg->local.contact = pjsip_contact_hdr_create(dlg->pool); + dlg->local.contact->uri = dlg->local.info->uri; + } + + /* Init remote info from the From header. */ + dlg->remote.info = (pjsip_fromto_hdr*) + pjsip_hdr_clone(dlg->pool, rdata->msg_info.from); + pjsip_fromto_hdr_set_to(dlg->remote.info); + + /* Print the remote info. */ + len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, + dlg->remote.info->uri, tmp.ptr, TMP_LEN); + if (len < 1) { + pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->"); + tmp.slen = pj_ansi_strlen(tmp.ptr); + } else + tmp.slen = len; + + /* Save the remote info. */ + pj_strdup(dlg->pool, &dlg->remote.info_str, &tmp); + + + /* Init remote's contact from Contact header. + * Iterate the Contact URI until we find sip: or sips: scheme. + */ + do { + contact_hdr = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, + pos); + if (contact_hdr) { + if (!contact_hdr->uri || + (!PJSIP_URI_SCHEME_IS_SIP(contact_hdr->uri) && + !PJSIP_URI_SCHEME_IS_SIPS(contact_hdr->uri))) + { + pos = (pjsip_hdr*)contact_hdr->next; + if (pos == &rdata->msg_info.msg->hdr) + contact_hdr = NULL; + } else { + break; + } + } + } while (contact_hdr); + + if (!contact_hdr) { + status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST); + goto on_error; + } + + dlg->remote.contact = (pjsip_contact_hdr*) + pjsip_hdr_clone(dlg->pool, (pjsip_hdr*)contact_hdr); + + /* Init remote's CSeq from CSeq header */ + dlg->remote.cseq = dlg->remote.first_cseq = rdata->msg_info.cseq->cseq; + + /* Set initial target to remote's Contact. */ + dlg->target = dlg->remote.contact->uri; + + /* Initial role is UAS */ + dlg->role = PJSIP_ROLE_UAS; + + /* Secure? + * RFC 3261 Section 12.1.1: + * If the request arrived over TLS, and the Request-URI contained a + * SIPS URI, the 'secure' flag is set to TRUE. + */ + dlg->secure = PJSIP_TRANSPORT_IS_SECURE(rdata->tp_info.transport) && + PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.msg->line.req.uri); + + /* Call-ID */ + dlg->call_id = (pjsip_cid_hdr*) + pjsip_hdr_clone(dlg->pool, rdata->msg_info.cid); + + /* Route set. + * RFC 3261 Section 12.1.1: + * The route set MUST be set to the list of URIs in the Record-Route + * header field from the request, taken in order and preserving all URI + * parameters. If no Record-Route header field is present in the request, + * the route set MUST be set to the empty set. + */ + pj_list_init(&dlg->route_set); + rr = rdata->msg_info.record_route; + while (rr != NULL) { + pjsip_route_hdr *route; + + /* Clone the Record-Route, change the type to Route header. */ + route = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, rr); + pjsip_routing_hdr_set_route(route); + + /* Add to route set. */ + pj_list_push_back(&dlg->route_set, route); + + /* Find next Record-Route header. */ + rr = rr->next; + if (rr == (void*)&rdata->msg_info.msg->hdr) + break; + rr = (pjsip_route_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, + PJSIP_H_RECORD_ROUTE, rr); + } + dlg->route_set_frozen = PJ_TRUE; + + /* Init client authentication session. */ + status = pjsip_auth_clt_init(&dlg->auth_sess, dlg->endpt, + dlg->pool, 0); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create UAS transaction for this request. */ + status = pjsip_tsx_create_uas(dlg->ua, rdata, &tsx); + if (status != PJ_SUCCESS) + goto on_error; + + /* Associate this dialog to the transaction. */ + tsx->mod_data[dlg->ua->id] = dlg; + + /* Increment tsx counter */ + ++dlg->tsx_count; + + /* Calculate hash value of remote tag. */ + dlg->remote.tag_hval = pj_hash_calc(0, dlg->remote.info->tag.ptr, + dlg->remote.info->tag.slen); + + /* Update remote capabilities info */ + pjsip_dlg_update_remote_cap(dlg, rdata->msg_info.msg, PJ_TRUE); + + /* Register this dialog to user agent. */ + status = pjsip_ua_register_dlg( ua, dlg ); + if (status != PJ_SUCCESS) + goto on_error; + + /* Put this dialog in rdata's mod_data */ + rdata->endpt_info.mod_data[ua->id] = dlg; + + PJ_TODO(DIALOG_APP_TIMER); + + /* Feed the first request to the transaction. */ + pjsip_tsx_recv_msg(tsx, rdata); + + /* Done. */ + *p_dlg = dlg; + PJ_LOG(5,(dlg->obj_name, "UAS dialog created")); + return PJ_SUCCESS; + +on_error: + if (tsx) { + pjsip_tsx_terminate(tsx, 500); + pj_assert(dlg->tsx_count>0); + --dlg->tsx_count; + } + + destroy_dialog(dlg); + return status; +} + + +/* + * Bind dialog to a specific transport/listener. + */ +PJ_DEF(pj_status_t) pjsip_dlg_set_transport( pjsip_dialog *dlg, + const pjsip_tpselector *sel) +{ + /* Validate */ + PJ_ASSERT_RETURN(dlg && sel, PJ_EINVAL); + + /* Start locking the dialog. */ + pjsip_dlg_inc_lock(dlg); + + /* Decrement reference counter of previous transport selector */ + pjsip_tpselector_dec_ref(&dlg->tp_sel); + + /* Copy transport selector structure .*/ + pj_memcpy(&dlg->tp_sel, sel, sizeof(*sel)); + + /* Increment reference counter */ + pjsip_tpselector_add_ref(&dlg->tp_sel); + + /* Unlock dialog. */ + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} + +/* + * Set "sent-by" field of Via header. + */ +PJ_DEF(pj_status_t) pjsip_dlg_set_via_sent_by( pjsip_dialog *dlg, + pjsip_host_port *via_addr, + pjsip_transport *via_tp) +{ + PJ_ASSERT_RETURN(dlg, PJ_EINVAL); + + if (!via_addr) + pj_bzero(&dlg->via_addr, sizeof(dlg->via_addr)); + else + dlg->via_addr = *via_addr; + dlg->via_tp = via_tp; + + return PJ_SUCCESS; +} + + +/* + * Create forked dialog from a response. + */ +PJ_DEF(pj_status_t) pjsip_dlg_fork( const pjsip_dialog *first_dlg, + const pjsip_rx_data *rdata, + pjsip_dialog **new_dlg ) +{ + pjsip_dialog *dlg; + const pjsip_msg *msg = rdata->msg_info.msg; + const pjsip_hdr *end_hdr, *hdr; + const pjsip_contact_hdr *contact; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(first_dlg && rdata && new_dlg, PJ_EINVAL); + + /* rdata must be response message. */ + PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG, + PJSIP_ENOTRESPONSEMSG); + + /* Status code MUST be 1xx (but not 100), or 2xx */ + status = msg->line.status.code; + PJ_ASSERT_RETURN( (status/100==1 && status!=100) || + (status/100==2), PJ_EBUG); + + /* To tag must present in the response. */ + PJ_ASSERT_RETURN(rdata->msg_info.to->tag.slen != 0, PJSIP_EMISSINGTAG); + + /* Find Contact header in the response */ + contact = (const pjsip_contact_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL); + if (contact == NULL || contact->uri == NULL) + return PJSIP_EMISSINGHDR; + + /* Create the dialog. */ + status = create_dialog((pjsip_user_agent*)first_dlg->ua, &dlg); + if (status != PJ_SUCCESS) + return status; + + /* Set remote target from the response. */ + dlg->target = (pjsip_uri*) pjsip_uri_clone(dlg->pool, contact->uri); + + /* Clone local info. */ + dlg->local.info = (pjsip_fromto_hdr*) + pjsip_hdr_clone(dlg->pool, first_dlg->local.info); + + /* Clone local tag. */ + pj_strdup(dlg->pool, &dlg->local.info->tag, &first_dlg->local.info->tag); + dlg->local.tag_hval = first_dlg->local.tag_hval; + + /* Clone local CSeq. */ + dlg->local.first_cseq = first_dlg->local.first_cseq; + dlg->local.cseq = first_dlg->local.cseq; + + /* Clone local Contact. */ + dlg->local.contact = (pjsip_contact_hdr*) + pjsip_hdr_clone(dlg->pool, first_dlg->local.contact); + + /* Clone remote info. */ + dlg->remote.info = (pjsip_fromto_hdr*) + pjsip_hdr_clone(dlg->pool, first_dlg->remote.info); + + /* Set remote tag from the response. */ + pj_strdup(dlg->pool, &dlg->remote.info->tag, &rdata->msg_info.to->tag); + + /* Initialize remote's CSeq to -1. */ + dlg->remote.cseq = dlg->remote.first_cseq = -1; + + /* Initial role is UAC. */ + dlg->role = PJSIP_ROLE_UAC; + + /* Dialog state depends on the response. */ + status = msg->line.status.code/100; + if (status == 1 || status == 2) + dlg->state = PJSIP_DIALOG_STATE_ESTABLISHED; + else { + pj_assert(!"Invalid status code"); + dlg->state = PJSIP_DIALOG_STATE_NULL; + } + + /* Secure? */ + dlg->secure = PJSIP_URI_SCHEME_IS_SIPS(dlg->target); + + /* Clone Call-ID header. */ + dlg->call_id = (pjsip_cid_hdr*) + pjsip_hdr_clone(dlg->pool, first_dlg->call_id); + + /* Get route-set from the response. */ + pj_list_init(&dlg->route_set); + end_hdr = &msg->hdr; + for (hdr=msg->hdr.prev; hdr!=end_hdr; hdr=hdr->prev) { + if (hdr->type == PJSIP_H_RECORD_ROUTE) { + pjsip_route_hdr *r; + r = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, hdr); + pjsip_routing_hdr_set_route(r); + pj_list_push_back(&dlg->route_set, r); + } + } + + //dlg->route_set_frozen = PJ_TRUE; + + /* Clone client authentication session. */ + status = pjsip_auth_clt_clone(dlg->pool, &dlg->auth_sess, + &first_dlg->auth_sess); + if (status != PJ_SUCCESS) + goto on_error; + + /* Register this dialog to user agent. */ + status = pjsip_ua_register_dlg(dlg->ua, dlg ); + if (status != PJ_SUCCESS) + goto on_error; + + + /* Done! */ + *new_dlg = dlg; + + PJ_LOG(5,(dlg->obj_name, "Forked dialog created")); + return PJ_SUCCESS; + +on_error: + destroy_dialog(dlg); + return status; +} + + +/* + * Destroy dialog. + */ +static pj_status_t unregister_and_destroy_dialog( pjsip_dialog *dlg ) +{ + pj_status_t status; + + /* Lock must have been held. */ + + /* Check dialog state. */ + /* Number of sessions must be zero. */ + PJ_ASSERT_RETURN(dlg->sess_count==0, PJ_EINVALIDOP); + + /* MUST not have pending transactions. */ + PJ_ASSERT_RETURN(dlg->tsx_count==0, PJ_EINVALIDOP); + + /* Unregister from user agent. */ + status = pjsip_ua_unregister_dlg(dlg->ua, dlg); + if (status != PJ_SUCCESS) { + pj_assert(!"Unexpected failed unregistration!"); + return status; + } + + /* Log */ + PJ_LOG(5,(dlg->obj_name, "Dialog destroyed")); + + /* Destroy this dialog. */ + destroy_dialog(dlg); + + return PJ_SUCCESS; +} + + +/* + * Forcefully terminate dialog. + */ +PJ_DEF(pj_status_t) pjsip_dlg_terminate( pjsip_dialog *dlg ) +{ + /* Number of sessions must be zero. */ + PJ_ASSERT_RETURN(dlg->sess_count==0, PJ_EINVALIDOP); + + /* MUST not have pending transactions. */ + PJ_ASSERT_RETURN(dlg->tsx_count==0, PJ_EINVALIDOP); + + return unregister_and_destroy_dialog(dlg); +} + + +/* + * Set route_set + */ +PJ_DEF(pj_status_t) pjsip_dlg_set_route_set( pjsip_dialog *dlg, + const pjsip_route_hdr *route_set ) +{ + pjsip_route_hdr *r; + + PJ_ASSERT_RETURN(dlg, PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); + + /* Clear route set. */ + pj_list_init(&dlg->route_set); + + if (!route_set) { + pjsip_dlg_dec_lock(dlg); + return PJ_SUCCESS; + } + + r = route_set->next; + while (r != route_set) { + pjsip_route_hdr *new_r; + + new_r = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, r); + pj_list_push_back(&dlg->route_set, new_r); + + r = r->next; + } + + pjsip_dlg_dec_lock(dlg); + return PJ_SUCCESS; +} + + +/* + * Increment session counter. + */ +PJ_DEF(pj_status_t) pjsip_dlg_inc_session( pjsip_dialog *dlg, + pjsip_module *mod ) +{ + PJ_ASSERT_RETURN(dlg && mod, PJ_EINVAL); + + pj_log_push_indent(); + + pjsip_dlg_inc_lock(dlg); + ++dlg->sess_count; + pjsip_dlg_dec_lock(dlg); + + PJ_LOG(5,(dlg->obj_name, "Session count inc to %d by %.*s", + dlg->sess_count, (int)mod->name.slen, mod->name.ptr)); + + pj_log_pop_indent(); + return PJ_SUCCESS; +} + +/* + * Lock dialog and increment session counter temporarily + * to prevent it from being deleted. In addition, it must lock + * the user agent's dialog table first, to prevent deadlock. + */ +PJ_DEF(void) pjsip_dlg_inc_lock(pjsip_dialog *dlg) +{ + PJ_LOG(6,(dlg->obj_name, "Entering pjsip_dlg_inc_lock(), sess_count=%d", + dlg->sess_count)); + + pj_mutex_lock(dlg->mutex_); + dlg->sess_count++; + + PJ_LOG(6,(dlg->obj_name, "Leaving pjsip_dlg_inc_lock(), sess_count=%d", + dlg->sess_count)); +} + +/* Try to acquire dialog's mutex, but bail out if mutex can not be + * acquired immediately. + */ +PJ_DEF(pj_status_t) pjsip_dlg_try_inc_lock(pjsip_dialog *dlg) +{ + pj_status_t status; + + PJ_LOG(6,(dlg->obj_name,"Entering pjsip_dlg_try_inc_lock(), sess_count=%d", + dlg->sess_count)); + + status = pj_mutex_trylock(dlg->mutex_); + if (status != PJ_SUCCESS) { + PJ_LOG(6,(dlg->obj_name, "pjsip_dlg_try_inc_lock() failed")); + return status; + } + + dlg->sess_count++; + + PJ_LOG(6,(dlg->obj_name, "Leaving pjsip_dlg_try_inc_lock(), sess_count=%d", + dlg->sess_count)); + + return PJ_SUCCESS; +} + + +/* + * Unlock dialog and decrement session counter. + * It may delete the dialog! + */ +PJ_DEF(void) pjsip_dlg_dec_lock(pjsip_dialog *dlg) +{ + PJ_ASSERT_ON_FAIL(dlg!=NULL, return); + + PJ_LOG(6,(dlg->obj_name, "Entering pjsip_dlg_dec_lock(), sess_count=%d", + dlg->sess_count)); + + pj_assert(dlg->sess_count > 0); + --dlg->sess_count; + + if (dlg->sess_count==0 && dlg->tsx_count==0) { + pj_mutex_unlock(dlg->mutex_); + pj_mutex_lock(dlg->mutex_); + unregister_and_destroy_dialog(dlg); + } else { + pj_mutex_unlock(dlg->mutex_); + } + + PJ_LOG(6,(THIS_FILE, "Leaving pjsip_dlg_dec_lock() (dlg=%p)", dlg)); +} + + + +/* + * Decrement session counter. + */ +PJ_DEF(pj_status_t) pjsip_dlg_dec_session( pjsip_dialog *dlg, + pjsip_module *mod) +{ + PJ_ASSERT_RETURN(dlg, PJ_EINVAL); + + pj_log_push_indent(); + + PJ_LOG(5,(dlg->obj_name, "Session count dec to %d by %.*s", + dlg->sess_count-1, (int)mod->name.slen, mod->name.ptr)); + + pjsip_dlg_inc_lock(dlg); + --dlg->sess_count; + pjsip_dlg_dec_lock(dlg); + + pj_log_pop_indent(); + return PJ_SUCCESS; +} + +/* + * Check if the module is registered as a usage + */ +PJ_DEF(pj_bool_t) pjsip_dlg_has_usage( pjsip_dialog *dlg, + pjsip_module *mod) +{ + unsigned index; + pj_bool_t found = PJ_FALSE; + + pjsip_dlg_inc_lock(dlg); + for (index=0; index<dlg->usage_cnt; ++index) { + if (dlg->usage[index] == mod) { + found = PJ_TRUE; + break; + } + } + pjsip_dlg_dec_lock(dlg); + + return found; +} + +/* + * Add usage. + */ +PJ_DEF(pj_status_t) pjsip_dlg_add_usage( pjsip_dialog *dlg, + pjsip_module *mod, + void *mod_data ) +{ + unsigned index; + + PJ_ASSERT_RETURN(dlg && mod, PJ_EINVAL); + PJ_ASSERT_RETURN(mod->id >= 0 && mod->id < PJSIP_MAX_MODULE, + PJ_EINVAL); + PJ_ASSERT_RETURN(dlg->usage_cnt < PJSIP_MAX_MODULE, PJ_EBUG); + + PJ_LOG(5,(dlg->obj_name, + "Module %.*s added as dialog usage, data=%p", + (int)mod->name.slen, mod->name.ptr, mod_data)); + + pjsip_dlg_inc_lock(dlg); + + /* Usages are sorted on priority, lowest number first. + * Find position to put the new module, also makes sure that + * this module has not been registered before. + */ + for (index=0; index<dlg->usage_cnt; ++index) { + if (dlg->usage[index] == mod) { + /* Module may be registered more than once in the same dialog. + * For example, when call transfer fails, application may retry + * call transfer on the same dialog. + * So return PJ_SUCCESS here. + */ + PJ_LOG(4,(dlg->obj_name, + "Module %.*s already registered as dialog usage, " + "updating the data %p", + (int)mod->name.slen, mod->name.ptr, mod_data)); + dlg->mod_data[mod->id] = mod_data; + + pjsip_dlg_dec_lock(dlg); + return PJ_SUCCESS; + + //pj_assert(!"This module is already registered"); + //pjsip_dlg_dec_lock(dlg); + //return PJSIP_ETYPEEXISTS; + } + + if (dlg->usage[index]->priority > mod->priority) + break; + } + + /* index holds position to put the module. + * Insert module at this index. + */ + pj_array_insert(dlg->usage, sizeof(dlg->usage[0]), dlg->usage_cnt, + index, &mod); + + /* Set module data. */ + dlg->mod_data[mod->id] = mod_data; + + /* Increment count. */ + ++dlg->usage_cnt; + + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} + + +/* + * Attach module specific data to the dialog. Application can also set + * the value directly by accessing dlg->mod_data[module_id]. + */ +PJ_DEF(pj_status_t) pjsip_dlg_set_mod_data( pjsip_dialog *dlg, + int mod_id, + void *data ) +{ + PJ_ASSERT_RETURN(dlg, PJ_EINVAL); + PJ_ASSERT_RETURN(mod_id >= 0 && mod_id < PJSIP_MAX_MODULE, + PJ_EINVAL); + dlg->mod_data[mod_id] = data; + return PJ_SUCCESS; +} + +/** + * Get module specific data previously attached to the dialog. Application + * can also get value directly by accessing dlg->mod_data[module_id]. + */ +PJ_DEF(void*) pjsip_dlg_get_mod_data( pjsip_dialog *dlg, + int mod_id) +{ + PJ_ASSERT_RETURN(dlg, NULL); + PJ_ASSERT_RETURN(mod_id >= 0 && mod_id < PJSIP_MAX_MODULE, + NULL); + return dlg->mod_data[mod_id]; +} + + +/* + * Create a new request within dialog (i.e. after the dialog session has been + * established). The construction of such requests follows the rule in + * RFC3261 section 12.2.1. + */ +static pj_status_t dlg_create_request_throw( pjsip_dialog *dlg, + const pjsip_method *method, + int cseq, + pjsip_tx_data **p_tdata ) +{ + pjsip_tx_data *tdata; + pjsip_contact_hdr *contact; + pjsip_route_hdr *route, *end_list; + pj_status_t status; + + /* Contact Header field. + * Contact can only be present in requests that establish dialog (in the + * core SIP spec, only INVITE). + */ + if (pjsip_method_creates_dialog(method)) + contact = dlg->local.contact; + else + contact = NULL; + + /* + * Create the request by cloning from the headers in the + * dialog. + */ + status = pjsip_endpt_create_request_from_hdr(dlg->endpt, + method, + dlg->target, + dlg->local.info, + dlg->remote.info, + contact, + dlg->call_id, + cseq, + NULL, + &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Just copy dialog route-set to Route header. + * The transaction will do the processing as specified in Section 12.2.1 + * of RFC 3261 in function tsx_process_route() in sip_transaction.c. + */ + route = dlg->route_set.next; + end_list = &dlg->route_set; + for (; route != end_list; route = route->next ) { + pjsip_route_hdr *r; + r = (pjsip_route_hdr*) pjsip_hdr_shallow_clone( tdata->pool, route ); + pjsip_routing_hdr_set_route(r); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)r); + } + + /* Copy authorization headers, if request is not ACK or CANCEL. */ + if (method->id != PJSIP_ACK_METHOD && method->id != PJSIP_CANCEL_METHOD) { + status = pjsip_auth_clt_init_req( &dlg->auth_sess, tdata ); + if (status != PJ_SUCCESS) + return status; + } + + /* Done. */ + *p_tdata = tdata; + + return PJ_SUCCESS; +} + + + +/* + * Create outgoing request. + */ +PJ_DEF(pj_status_t) pjsip_dlg_create_request( pjsip_dialog *dlg, + const pjsip_method *method, + int cseq, + pjsip_tx_data **p_tdata) +{ + pj_status_t status; + pjsip_tx_data *tdata = NULL; + PJ_USE_EXCEPTION; + + PJ_ASSERT_RETURN(dlg && method && p_tdata, PJ_EINVAL); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(dlg); + + /* Use outgoing CSeq and increment it by one. */ + if (cseq < 0) + cseq = dlg->local.cseq + 1; + + /* Keep compiler happy */ + status = PJ_EBUG; + + /* Create the request. */ + PJ_TRY { + status = dlg_create_request_throw(dlg, method, cseq, &tdata); + } + PJ_CATCH_ANY { + status = PJ_ENOMEM; + } + PJ_END; + + /* Failed! Delete transmit data. */ + if (status != PJ_SUCCESS && tdata) { + pjsip_tx_data_dec_ref( tdata ); + tdata = NULL; + } + + /* Unlock dialog. */ + pjsip_dlg_dec_lock(dlg); + + *p_tdata = tdata; + + return status; +} + + +/* + * Send request statefully, and update dialog'c CSeq. + */ +PJ_DEF(pj_status_t) pjsip_dlg_send_request( pjsip_dialog *dlg, + pjsip_tx_data *tdata, + int mod_data_id, + void *mod_data) +{ + pjsip_transaction *tsx; + pjsip_msg *msg = tdata->msg; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(dlg && tdata && tdata->msg, PJ_EINVAL); + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + pj_log_push_indent(); + PJ_LOG(5,(dlg->obj_name, "Sending %s", + pjsip_tx_data_get_info(tdata))); + + /* Lock and increment session */ + pjsip_dlg_inc_lock(dlg); + + /* If via_addr is set, use this address for the Via header. */ + if (dlg->via_addr.host.slen > 0) { + tdata->via_addr = dlg->via_addr; + tdata->via_tp = dlg->via_tp; + } + + /* Update dialog's CSeq and message's CSeq if request is not + * ACK nor CANCEL. + */ + if (msg->line.req.method.id != PJSIP_CANCEL_METHOD && + msg->line.req.method.id != PJSIP_ACK_METHOD) + { + pjsip_cseq_hdr *ch; + + ch = PJSIP_MSG_CSEQ_HDR(msg); + PJ_ASSERT_RETURN(ch!=NULL, PJ_EBUG); + + ch->cseq = dlg->local.cseq++; + + /* Force the whole message to be re-printed. */ + pjsip_tx_data_invalidate_msg( tdata ); + } + + /* Create a new transaction if method is not ACK. + * The transaction user is the user agent module. + */ + if (msg->line.req.method.id != PJSIP_ACK_METHOD) { + int tsx_count; + + status = pjsip_tsx_create_uac(dlg->ua, tdata, &tsx); + if (status != PJ_SUCCESS) + goto on_error; + + /* Set transport selector */ + status = pjsip_tsx_set_transport(tsx, &dlg->tp_sel); + pj_assert(status == PJ_SUCCESS); + + /* Attach this dialog to the transaction, so that user agent + * will dispatch events to this dialog. + */ + tsx->mod_data[dlg->ua->id] = dlg; + + /* Copy optional caller's mod_data, if present */ + if (mod_data_id >= 0 && mod_data_id < PJSIP_MAX_MODULE) + tsx->mod_data[mod_data_id] = mod_data; + + /* Increment transaction counter. */ + tsx_count = ++dlg->tsx_count; + + /* Send the message. */ + status = pjsip_tsx_send_msg(tsx, tdata); + if (status != PJ_SUCCESS) { + if (dlg->tsx_count == tsx_count) + pjsip_tsx_terminate(tsx, tsx->status_code); + goto on_error; + } + + } else { + /* Set transport selector */ + pjsip_tx_data_set_transport(tdata, &dlg->tp_sel); + + /* Send request */ + status = pjsip_endpt_send_request_stateless(dlg->endpt, tdata, + NULL, NULL); + if (status != PJ_SUCCESS) + goto on_error; + + } + + /* Unlock dialog, may destroy dialog. */ + pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + /* Unlock dialog, may destroy dialog. */ + pjsip_dlg_dec_lock(dlg); + + /* Whatever happen delete the message. */ + pjsip_tx_data_dec_ref( tdata ); + pj_log_pop_indent(); + return status; +} + +/* Add standard headers for certain types of response */ +static void dlg_beautify_response(pjsip_dialog *dlg, + pj_bool_t add_headers, + int st_code, + pjsip_tx_data *tdata) +{ + pjsip_cseq_hdr *cseq; + int st_class; + const pjsip_hdr *c_hdr; + pjsip_hdr *hdr; + + cseq = PJSIP_MSG_CSEQ_HDR(tdata->msg); + pj_assert(cseq != NULL); + + st_class = st_code / 100; + + /* Contact, Allow, Supported header. */ + if (add_headers && pjsip_method_creates_dialog(&cseq->method)) { + /* Add Contact header for 1xx, 2xx, 3xx and 485 response. */ + if (st_class==2 || st_class==3 || (st_class==1 && st_code != 100) || + st_code==485) + { + /* Add contact header only if one is not present. */ + if (pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL) == 0 && + pjsip_msg_find_hdr_by_name(tdata->msg, &HCONTACT, NULL) == 0) + { + hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, + dlg->local.contact); + pjsip_msg_add_hdr(tdata->msg, hdr); + } + } + + /* Add Allow header in 18x, 2xx and 405 response. */ + if ((((st_code/10==18 || st_class==2) && dlg->add_allow) + || st_code==405) && + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ALLOW, NULL)==NULL) + { + c_hdr = pjsip_endpt_get_capability(dlg->endpt, + PJSIP_H_ALLOW, NULL); + if (c_hdr) { + hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, c_hdr); + pjsip_msg_add_hdr(tdata->msg, hdr); + } + } + + /* Add Supported header in 2xx response. */ + if (st_class==2 && + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_SUPPORTED, NULL)==NULL) + { + c_hdr = pjsip_endpt_get_capability(dlg->endpt, + PJSIP_H_SUPPORTED, NULL); + if (c_hdr) { + hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, c_hdr); + pjsip_msg_add_hdr(tdata->msg, hdr); + } + } + + } + + /* Add To tag in all responses except 100 */ + if (st_code != 100) { + pjsip_to_hdr *to; + + to = PJSIP_MSG_TO_HDR(tdata->msg); + pj_assert(to != NULL); + + to->tag = dlg->local.info->tag; + + if (dlg->state == PJSIP_DIALOG_STATE_NULL) + dlg->state = PJSIP_DIALOG_STATE_ESTABLISHED; + } +} + + +/* + * Create response. + */ +PJ_DEF(pj_status_t) pjsip_dlg_create_response( pjsip_dialog *dlg, + pjsip_rx_data *rdata, + int st_code, + const pj_str_t *st_text, + pjsip_tx_data **p_tdata) +{ + pj_status_t status; + pjsip_tx_data *tdata; + + /* Create generic response. + * This will initialize response's Via, To, From, Call-ID, CSeq + * and Record-Route headers from the request. + */ + status = pjsip_endpt_create_response(dlg->endpt, + rdata, st_code, st_text, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Lock the dialog. */ + pjsip_dlg_inc_lock(dlg); + + dlg_beautify_response(dlg, PJ_FALSE, st_code, tdata); + + /* Unlock the dialog. */ + pjsip_dlg_dec_lock(dlg); + + /* Done. */ + *p_tdata = tdata; + return PJ_SUCCESS; +} + +/* + * Modify response. + */ +PJ_DEF(pj_status_t) pjsip_dlg_modify_response( pjsip_dialog *dlg, + pjsip_tx_data *tdata, + int st_code, + const pj_str_t *st_text) +{ + pjsip_hdr *hdr; + + PJ_ASSERT_RETURN(dlg && tdata && tdata->msg, PJ_EINVAL); + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG, + PJSIP_ENOTRESPONSEMSG); + PJ_ASSERT_RETURN(st_code >= 100 && st_code <= 699, PJ_EINVAL); + + /* Lock and increment session */ + pjsip_dlg_inc_lock(dlg); + + /* Replace status code and reason */ + tdata->msg->line.status.code = st_code; + if (st_text) { + pj_strdup(tdata->pool, &tdata->msg->line.status.reason, st_text); + } else { + tdata->msg->line.status.reason = *pjsip_get_status_text(st_code); + } + + /* Remove existing Contact header (without this, when dialog sent + * 180 and then 302, the Contact in 302 will not get updated). + */ + hdr = (pjsip_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL); + if (hdr) + pj_list_erase(hdr); + + /* Add tag etc. if necessary */ + dlg_beautify_response(dlg, st_code/100 <= 2, st_code, tdata); + + + /* Must add reference counter, since tsx_send_msg() will decrement it */ + pjsip_tx_data_add_ref(tdata); + + /* Force to re-print message. */ + pjsip_tx_data_invalidate_msg(tdata); + + /* Unlock dialog and dec session, may destroy dialog. */ + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} + +/* + * Send response statefully. + */ +PJ_DEF(pj_status_t) pjsip_dlg_send_response( pjsip_dialog *dlg, + pjsip_transaction *tsx, + pjsip_tx_data *tdata) +{ + pj_status_t status; + + /* Sanity check. */ + PJ_ASSERT_RETURN(dlg && tsx && tdata && tdata->msg, PJ_EINVAL); + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG, + PJSIP_ENOTRESPONSEMSG); + + /* The transaction must belong to this dialog. */ + PJ_ASSERT_RETURN(tsx->mod_data[dlg->ua->id] == dlg, PJ_EINVALIDOP); + + pj_log_push_indent(); + + PJ_LOG(5,(dlg->obj_name, "Sending %s", + pjsip_tx_data_get_info(tdata))); + + /* Check that transaction method and cseq match the response. + * This operation is sloooww (search CSeq header twice), that's why + * we only do it in debug mode. + */ +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + PJ_ASSERT_RETURN( PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq == tsx->cseq && + pjsip_method_cmp(&PJSIP_MSG_CSEQ_HDR(tdata->msg)->method, + &tsx->method)==0, + PJ_EINVALIDOP); +#endif + + /* Must acquire dialog first, to prevent deadlock */ + pjsip_dlg_inc_lock(dlg); + + /* Last chance to add mandatory headers before the response is + * sent. + */ + dlg_beautify_response(dlg, PJ_TRUE, tdata->msg->line.status.code, tdata); + + /* If the dialog is locked to transport, make sure that transaction + * is locked to the same transport too. + */ + if (dlg->tp_sel.type != tsx->tp_sel.type || + dlg->tp_sel.u.ptr != tsx->tp_sel.u.ptr) + { + status = pjsip_tsx_set_transport(tsx, &dlg->tp_sel); + pj_assert(status == PJ_SUCCESS); + } + + /* Ask transaction to send the response */ + status = pjsip_tsx_send_msg(tsx, tdata); + + /* This function must decrement transmit data request counter + * regardless of the operation status. The transaction only + * decrements the counter if the operation is successful. + */ + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + } + + pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + + return status; +} + + +/* + * Combo function to create and send response statefully. + */ +PJ_DEF(pj_status_t) pjsip_dlg_respond( pjsip_dialog *dlg, + pjsip_rx_data *rdata, + int st_code, + const pj_str_t *st_text, + const pjsip_hdr *hdr_list, + const pjsip_msg_body *body ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + + /* Sanity check. */ + PJ_ASSERT_RETURN(dlg && rdata && rdata->msg_info.msg, PJ_EINVAL); + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* The transaction must belong to this dialog. */ + PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata) && + pjsip_rdata_get_tsx(rdata)->mod_data[dlg->ua->id] == dlg, + PJ_EINVALIDOP); + + /* Create the response. */ + status = pjsip_dlg_create_response(dlg, rdata, st_code, st_text, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Add additional header, if any */ + if (hdr_list) { + const pjsip_hdr *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; + } + } + + /* Add the message body, if any. */ + if (body) { + tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body); + } + + /* Send the response. */ + return pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata); +} + + +/* This function is called by user agent upon receiving incoming request + * message. + */ +void pjsip_dlg_on_rx_request( pjsip_dialog *dlg, pjsip_rx_data *rdata ) +{ + pj_status_t status; + pjsip_transaction *tsx = NULL; + pj_bool_t processed = PJ_FALSE; + unsigned i; + + PJ_LOG(5,(dlg->obj_name, "Received %s", + pjsip_rx_data_get_info(rdata))); + pj_log_push_indent(); + + /* Lock dialog and increment session. */ + pjsip_dlg_inc_lock(dlg); + + /* Check CSeq */ + if (rdata->msg_info.cseq->cseq <= dlg->remote.cseq && + rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD && + rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD) + { + /* Invalid CSeq. + * Respond statelessly with 500 (Internal Server Error) + */ + pj_str_t warn_text; + + /* Unlock dialog and dec session, may destroy dialog. */ + pjsip_dlg_dec_lock(dlg); + + pj_assert(pjsip_rdata_get_tsx(rdata) == NULL); + warn_text = pj_str("Invalid CSeq"); + pjsip_endpt_respond_stateless(dlg->endpt, + rdata, 500, &warn_text, NULL, NULL); + pj_log_pop_indent(); + return; + } + + /* Update CSeq. */ + dlg->remote.cseq = rdata->msg_info.cseq->cseq; + + /* Update To tag if necessary. + * This only happens if UAS sends a new request before answering + * our request (e.g. UAS sends NOTIFY before answering our + * SUBSCRIBE request). + */ + if (dlg->remote.info->tag.slen == 0) { + pj_strdup(dlg->pool, &dlg->remote.info->tag, + &rdata->msg_info.from->tag); + } + + /* Create UAS transaction for this request. */ + if (pjsip_rdata_get_tsx(rdata) == NULL && + rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) + { + status = pjsip_tsx_create_uas(dlg->ua, rdata, &tsx); + if (status != PJ_SUCCESS) { + /* Once case for this is when re-INVITE contains same + * Via branch value as previous INVITE (ticket #965). + */ + char errmsg[PJ_ERR_MSG_SIZE]; + pj_str_t reason; + + reason = pj_strerror(status, errmsg, sizeof(errmsg)); + pjsip_endpt_respond_stateless(dlg->endpt, rdata, 500, &reason, + NULL, NULL); + goto on_return; + } + + /* Put this dialog in the transaction data. */ + tsx->mod_data[dlg->ua->id] = dlg; + + /* Add transaction count. */ + ++dlg->tsx_count; + } + + /* Update the target URI if this is a target refresh request. + * We have passed the basic checking for the request, I think we + * should update the target URI regardless of whether the request + * is accepted or not (e.g. when re-INVITE is answered with 488, + * we would still need to update the target URI, otherwise our + * target URI would be wrong, wouldn't it). + */ + if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method)) { + pjsip_contact_hdr *contact; + + contact = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, + NULL); + if (contact && contact->uri && + (dlg->remote.contact==NULL || + pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI, + dlg->remote.contact->uri, + contact->uri))) + { + dlg->remote.contact = (pjsip_contact_hdr*) + pjsip_hdr_clone(dlg->pool, contact); + dlg->target = dlg->remote.contact->uri; + } + } + + /* Report the request to dialog usages. */ + for (i=0; i<dlg->usage_cnt; ++i) { + + if (!dlg->usage[i]->on_rx_request) + continue; + + processed = (*dlg->usage[i]->on_rx_request)(rdata); + + if (processed) + break; + } + + /* Feed the first request to the transaction. */ + if (tsx) + pjsip_tsx_recv_msg(tsx, rdata); + + /* If no dialog usages has claimed the processing of the transaction, + * and if transaction has not sent final response, respond with + * 500/Internal Server Error. + */ + if (!processed && tsx && tsx->status_code < 200) { + pjsip_tx_data *tdata; + const pj_str_t reason = { "Unhandled by dialog usages", 26}; + + PJ_LOG(4,(tsx->obj_name, "%s was unhandled by " + "dialog usages, sending 500 response", + pjsip_rx_data_get_info(rdata))); + + status = pjsip_dlg_create_response(dlg, rdata, 500, &reason, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(dlg, tsx, tdata); + } + } + +on_return: + /* Unlock dialog and dec session, may destroy dialog. */ + pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); +} + +/* Update route-set from incoming message */ +static void dlg_update_routeset(pjsip_dialog *dlg, const pjsip_rx_data *rdata) +{ + const pjsip_hdr *hdr, *end_hdr; + pj_int32_t msg_cseq; + const pjsip_msg *msg; + + msg = rdata->msg_info.msg; + msg_cseq = rdata->msg_info.cseq->cseq; + + /* Ignore if route set has been frozen */ + if (dlg->route_set_frozen) + return; + + /* Only update route set if this message belongs to the same + * transaction as the initial transaction that establishes dialog. + */ + if (dlg->role == PJSIP_ROLE_UAC) { + + /* Ignore subsequent request from remote */ + if (msg->type != PJSIP_RESPONSE_MSG) + return; + + /* Ignore subsequent responses with higher CSeq than initial CSeq. + * Unfortunately this would be broken when the first request is + * challenged! + */ + //if (msg_cseq != dlg->local.first_cseq) + // return; + + } else { + + /* For callee dialog, route set should have been set by initial + * request and it will have been rejected by dlg->route_set_frozen + * check above. + */ + pj_assert(!"Should not happen"); + + } + + /* Based on the checks above, we should only get response message here */ + pj_assert(msg->type == PJSIP_RESPONSE_MSG); + + /* Ignore if this is not 1xx or 2xx response */ + if (msg->line.status.code >= 300) + return; + + /* Reset route set */ + pj_list_init(&dlg->route_set); + + /* Update route set */ + end_hdr = &msg->hdr; + for (hdr=msg->hdr.prev; hdr!=end_hdr; hdr=hdr->prev) { + if (hdr->type == PJSIP_H_RECORD_ROUTE) { + pjsip_route_hdr *r; + r = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, hdr); + pjsip_routing_hdr_set_route(r); + pj_list_push_back(&dlg->route_set, r); + } + } + + PJ_LOG(5,(dlg->obj_name, "Route-set updated")); + + /* Freeze the route set only when the route set comes in 2xx response. + * If it is in 1xx response, prepare to recompute the route set when + * the 2xx response comes in. + * + * There is a debate whether route set should be frozen when the dialog + * is established with reliable provisional response, but I think + * it is safer to not freeze the route set (thus recompute the route set + * upon receiving 2xx response). Also RFC 3261 says so in 13.2.2.4. + * + * The pjsip_method_creates_dialog() check protects from wrongly + * freezing the route set upon receiving 200/OK response for PRACK. + */ + if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) && + PJSIP_IS_STATUS_IN_CLASS(msg->line.status.code, 200)) + { + dlg->route_set_frozen = PJ_TRUE; + PJ_LOG(5,(dlg->obj_name, "Route-set frozen")); + } +} + + +/* This function is called by user agent upon receiving incoming response + * message. + */ +void pjsip_dlg_on_rx_response( pjsip_dialog *dlg, pjsip_rx_data *rdata ) +{ + unsigned i; + int res_code; + + PJ_LOG(5,(dlg->obj_name, "Received %s", + pjsip_rx_data_get_info(rdata))); + pj_log_push_indent(); + + /* Lock the dialog and inc session. */ + pjsip_dlg_inc_lock(dlg); + + /* Check that rdata already has dialog in mod_data. */ + pj_assert(pjsip_rdata_get_dlg(rdata) == dlg); + + /* Keep the response's status code */ + res_code = rdata->msg_info.msg->line.status.code; + + /* When we receive response that establishes dialog, update To tag, + * route set and dialog target. + * + * The second condition of the "if" is a workaround for forking. + * Originally, the dialog takes the first To tag seen and set it as + * the remote tag. If the tag in 2xx response is different than this + * tag, ACK will be sent with wrong To tag and incoming request with + * this tag will be rejected with 481. + * + * The workaround for this is to take the To tag received in the + * 2xx response and set it as remote tag. + * + * New update: + * We also need to update the dialog for 1xx responses, to handle the + * case when 100rel is used, otherwise PRACK will be sent to the + * wrong target. + */ + if ((dlg->state == PJSIP_DIALOG_STATE_NULL && + pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) && + (res_code > 100 && res_code < 300) && + rdata->msg_info.to->tag.slen) + || + (dlg->role==PJSIP_ROLE_UAC && + !dlg->uac_has_2xx && + res_code > 100 && + res_code/100 <= 2 && + pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) && + pj_strcmp(&dlg->remote.info->tag, &rdata->msg_info.to->tag))) + { + pjsip_contact_hdr *contact; + + /* Update remote capability info, when To tags in the dialog remote + * info and the incoming response are different, e.g: first response + * with To-tag or forking, apply strict update. + */ + pjsip_dlg_update_remote_cap(dlg, rdata->msg_info.msg, + pj_strcmp(&dlg->remote.info->tag, + &rdata->msg_info.to->tag)); + + /* Update To tag. */ + pj_strdup(dlg->pool, &dlg->remote.info->tag, &rdata->msg_info.to->tag); + /* No need to update remote's tag_hval since its never used. */ + + /* RFC 3271 Section 12.1.2: + * The route set MUST be set to the list of URIs in the Record-Route + * header field from the response, taken in reverse order and + * preserving all URI parameters. If no Record-Route header field + * is present in the response, the route set MUST be set to the + * empty set. This route set, even if empty, overrides any pre-existing + * route set for future requests in this dialog. + */ + dlg_update_routeset(dlg, rdata); + + /* The remote target MUST be set to the URI from the Contact header + * field of the response. + */ + contact = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, + NULL); + if (contact && contact->uri && + (dlg->remote.contact==NULL || + pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI, + dlg->remote.contact->uri, + contact->uri))) + { + dlg->remote.contact = (pjsip_contact_hdr*) + pjsip_hdr_clone(dlg->pool, contact); + dlg->target = dlg->remote.contact->uri; + } + + dlg->state = PJSIP_DIALOG_STATE_ESTABLISHED; + + /* Prevent dialog from being updated just in case more 2xx + * gets through this dialog (it shouldn't happen). + */ + if (dlg->role==PJSIP_ROLE_UAC && !dlg->uac_has_2xx && + res_code/100==2) + { + dlg->uac_has_2xx = PJ_TRUE; + } + } + + /* Update remote target (again) when receiving 2xx response messages + * that's defined as target refresh. + * + * Also upon receiving 2xx response, recheck again the route set. + * This is for compatibility with RFC 2543, as described in Section + * 13.2.2.4 of RFC 3261: + + If the dialog identifier in the 2xx response matches the dialog + identifier of an existing dialog, the dialog MUST be transitioned to + the "confirmed" state, and the route set for the dialog MUST be + recomputed based on the 2xx response using the procedures of Section + 12.2.1.2. + + Note that the only piece of state that is recomputed is the route + set. Other pieces of state such as the highest sequence numbers + (remote and local) sent within the dialog are not recomputed. The + route set only is recomputed for backwards compatibility. RFC + 2543 did not mandate mirroring of the Record-Route header field in + a 1xx, only 2xx. + */ + if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) && + res_code/100 == 2) + { + pjsip_contact_hdr *contact; + + contact = (pjsip_contact_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, + PJSIP_H_CONTACT, + NULL); + if (contact && contact->uri && + (dlg->remote.contact==NULL || + pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI, + dlg->remote.contact->uri, + contact->uri))) + { + dlg->remote.contact = (pjsip_contact_hdr*) + pjsip_hdr_clone(dlg->pool, contact); + dlg->target = dlg->remote.contact->uri; + } + + dlg_update_routeset(dlg, rdata); + + /* Update remote capability info after the first 2xx response + * (ticket #1539). Note that the remote capability retrieved here + * will be assumed to remain unchanged for the duration of the dialog. + */ + if (dlg->role==PJSIP_ROLE_UAC && !dlg->uac_has_2xx) { + pjsip_dlg_update_remote_cap(dlg, rdata->msg_info.msg, PJ_FALSE); + dlg->uac_has_2xx = PJ_TRUE; + } + } + + /* Pass to dialog usages. */ + for (i=0; i<dlg->usage_cnt; ++i) { + pj_bool_t processed; + + if (!dlg->usage[i]->on_rx_response) + continue; + + processed = (*dlg->usage[i]->on_rx_response)(rdata); + + if (processed) + break; + } + + /* Handle the case of forked response, when the application creates + * the forked dialog but not the invite session. In this case, the + * forked 200/OK response will be unhandled, and we must send ACK + * here. + */ + if (dlg->usage_cnt==0) { + pj_status_t status; + + if (rdata->msg_info.cseq->method.id==PJSIP_INVITE_METHOD && + rdata->msg_info.msg->line.status.code/100 == 2) + { + pjsip_tx_data *ack; + + status = pjsip_dlg_create_request(dlg, &pjsip_ack_method, + rdata->msg_info.cseq->cseq, + &ack); + if (status == PJ_SUCCESS) + status = pjsip_dlg_send_request(dlg, ack, -1, NULL); + } else if (rdata->msg_info.msg->line.status.code==401 || + rdata->msg_info.msg->line.status.code==407) + { + pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata); + pjsip_tx_data *tdata; + + status = pjsip_auth_clt_reinit_req( &dlg->auth_sess, + rdata, tsx->last_tx, + &tdata); + + if (status == PJ_SUCCESS) { + /* Re-send request. */ + status = pjsip_dlg_send_request(dlg, tdata, -1, NULL); + } + } + } + + /* Unhandled response does not necessarily mean error because + dialog usages may choose to process the transaction state instead. + if (i==dlg->usage_cnt) { + PJ_LOG(4,(dlg->obj_name, "%s was not claimed by any dialog usages", + pjsip_rx_data_get_info(rdata))); + } + */ + + /* Unlock dialog and dec session, may destroy dialog. */ + pjsip_dlg_dec_lock(dlg); + + pj_log_pop_indent(); +} + +/* This function is called by user agent upon receiving transaction + * state notification. + */ +void pjsip_dlg_on_tsx_state( pjsip_dialog *dlg, + pjsip_transaction *tsx, + pjsip_event *e ) +{ + unsigned i; + + PJ_LOG(5,(dlg->obj_name, "Transaction %s state changed to %s", + tsx->obj_name, pjsip_tsx_state_str(tsx->state))); + pj_log_push_indent(); + + /* Lock the dialog and increment session. */ + pjsip_dlg_inc_lock(dlg); + + /* Pass to dialog usages. */ + for (i=0; i<dlg->usage_cnt; ++i) { + + if (!dlg->usage[i]->on_tsx_state) + continue; + + (*dlg->usage[i]->on_tsx_state)(tsx, e); + } + + + /* It is possible that the transaction is terminated and this function + * is called while we're calling on_tsx_state(). So only decrement + * the tsx_count if we're still attached to the transaction. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED && + tsx->mod_data[dlg->ua->id] == dlg) + { + pj_assert(dlg->tsx_count>0); + --dlg->tsx_count; + tsx->mod_data[dlg->ua->id] = NULL; + } + + /* Unlock dialog and dec session, may destroy dialog. */ + pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); +} + + +/* + * Check if the specified capability is supported by remote. + */ +PJ_DEF(pjsip_dialog_cap_status) pjsip_dlg_remote_has_cap( + pjsip_dialog *dlg, + int htype, + const pj_str_t *hname, + const pj_str_t *token) +{ + const pjsip_generic_array_hdr *hdr; + pjsip_dialog_cap_status cap_status = PJSIP_DIALOG_CAP_UNSUPPORTED; + unsigned i; + + PJ_ASSERT_RETURN(dlg && token, PJSIP_DIALOG_CAP_UNKNOWN); + + pjsip_dlg_inc_lock(dlg); + + hdr = (const pjsip_generic_array_hdr*) + pjsip_dlg_get_remote_cap_hdr(dlg, htype, hname); + if (!hdr) { + cap_status = PJSIP_DIALOG_CAP_UNKNOWN; + } else { + for (i=0; i<hdr->count; ++i) { + if (!pj_stricmp(&hdr->values[i], token)) { + cap_status = PJSIP_DIALOG_CAP_SUPPORTED; + break; + } + } + } + + pjsip_dlg_dec_lock(dlg); + + return cap_status; +} + + +/* + * Update remote capability of ACCEPT, ALLOW, and SUPPORTED from + * the received message. + */ +PJ_DEF(pj_status_t) pjsip_dlg_update_remote_cap(pjsip_dialog *dlg, + const pjsip_msg *msg, + pj_bool_t strict) +{ + pjsip_hdr_e htypes[] = + { PJSIP_H_ACCEPT, PJSIP_H_ALLOW, PJSIP_H_SUPPORTED }; + unsigned i; + + PJ_ASSERT_RETURN(dlg && msg, PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); + + /* Retrieve all specified capability header types */ + for (i = 0; i < PJ_ARRAY_SIZE(htypes); ++i) { + const pjsip_generic_array_hdr *hdr; + pj_status_t status; + + /* Find this capability type in the message */ + hdr = (const pjsip_generic_array_hdr*) + pjsip_msg_find_hdr(msg, htypes[i], NULL); + if (!hdr) { + /* Not found. + * If strict update is specified, remote this capability type + * from the capability list. + */ + if (strict) + pjsip_dlg_remove_remote_cap_hdr(dlg, htypes[i], NULL); + } else { + /* Found, a capability type may be specified in multiple headers, + * so combine all the capability tags/values into a temporary + * header. + */ + pjsip_generic_array_hdr tmp_hdr; + + /* Init temporary header */ + pjsip_generic_array_hdr_init(dlg->pool, &tmp_hdr, NULL); + pj_memcpy(&tmp_hdr, hdr, sizeof(pjsip_hdr)); + + while (hdr) { + unsigned j; + + /* Append the header content to temporary header */ + for(j=0; j<hdr->count && + tmp_hdr.count<PJSIP_GENERIC_ARRAY_MAX_COUNT; ++j) + { + tmp_hdr.values[tmp_hdr.count++] = hdr->values[j]; + } + + /* Get the next header for this capability */ + hdr = (const pjsip_generic_array_hdr*) + pjsip_msg_find_hdr(msg, htypes[i], hdr->next); + } + + /* Save this capability */ + status = pjsip_dlg_set_remote_cap_hdr(dlg, &tmp_hdr); + if (status != PJ_SUCCESS) { + pjsip_dlg_dec_lock(dlg); + return status; + } + } + } + + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} + + +/* + * Get the value of the specified capability header field of remote. + */ +PJ_DEF(const pjsip_hdr*) pjsip_dlg_get_remote_cap_hdr(pjsip_dialog *dlg, + int htype, + const pj_str_t *hname) +{ + pjsip_hdr *hdr; + + /* Check arguments. */ + PJ_ASSERT_RETURN(dlg, NULL); + PJ_ASSERT_RETURN((htype != PJSIP_H_OTHER) || (hname && hname->slen), + NULL); + + pjsip_dlg_inc_lock(dlg); + + hdr = dlg->rem_cap_hdr.next; + while (hdr != &dlg->rem_cap_hdr) { + if ((htype != PJSIP_H_OTHER && htype == hdr->type) || + (htype == PJSIP_H_OTHER && pj_stricmp(&hdr->name, hname) == 0)) + { + pjsip_dlg_dec_lock(dlg); + return hdr; + } + hdr = hdr->next; + } + + pjsip_dlg_dec_lock(dlg); + + return NULL; +} + + +/* + * Set remote capability header from a SIP header containing array + * of capability tags/values. + */ +PJ_DEF(pj_status_t) pjsip_dlg_set_remote_cap_hdr( + pjsip_dialog *dlg, + const pjsip_generic_array_hdr *cap_hdr) +{ + pjsip_generic_array_hdr *hdr; + + /* Check arguments. */ + PJ_ASSERT_RETURN(dlg && cap_hdr, PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); + + /* Find the header. */ + hdr = (pjsip_generic_array_hdr*) + pjsip_dlg_get_remote_cap_hdr(dlg, cap_hdr->type, &cap_hdr->name); + + /* Quick compare if the capability is up to date */ + if (hdr && hdr->count == cap_hdr->count) { + unsigned i; + pj_bool_t uptodate = PJ_TRUE; + + for (i=0; i<hdr->count; ++i) { + if (pj_stricmp(&hdr->values[i], &cap_hdr->values[i])) + uptodate = PJ_FALSE; + } + + /* Capability is up to date, just return PJ_SUCCESS */ + if (uptodate) { + pjsip_dlg_dec_lock(dlg); + return PJ_SUCCESS; + } + } + + /* Remove existing capability header if any */ + if (hdr) + pj_list_erase(hdr); + + /* Add the new capability header */ + hdr = (pjsip_generic_array_hdr*) pjsip_hdr_clone(dlg->pool, cap_hdr); + hdr->type = cap_hdr->type; + pj_strdup(dlg->pool, &hdr->name, &cap_hdr->name); + pj_list_push_back(&dlg->rem_cap_hdr, hdr); + + pjsip_dlg_dec_lock(dlg); + + /* Done. */ + return PJ_SUCCESS; +} + +/* + * Remove a remote capability header. + */ +PJ_DEF(pj_status_t) pjsip_dlg_remove_remote_cap_hdr(pjsip_dialog *dlg, + int htype, + const pj_str_t *hname) +{ + pjsip_generic_array_hdr *hdr; + + /* Check arguments. */ + PJ_ASSERT_RETURN(dlg, PJ_EINVAL); + PJ_ASSERT_RETURN((htype != PJSIP_H_OTHER) || (hname && hname->slen), + PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); + + hdr = (pjsip_generic_array_hdr*) + pjsip_dlg_get_remote_cap_hdr(dlg, htype, hname); + if (!hdr) { + pjsip_dlg_dec_lock(dlg); + return PJ_ENOTFOUND; + } + + pj_list_erase(hdr); + + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} diff --git a/pjsip/src/pjsip/sip_dialog_wrap.cpp b/pjsip/src/pjsip/sip_dialog_wrap.cpp new file mode 100644 index 0000000..5bf3935 --- /dev/null +++ b/pjsip/src/pjsip/sip_dialog_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: sip_dialog_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * 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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "sip_dialog.c" diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c new file mode 100644 index 0000000..2510d14 --- /dev/null +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -0,0 +1,1245 @@ +/* $Id: sip_endpoint.c 4154 2012-06-05 10:41:17Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_private.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_resolve.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_util.h> +#include <pjsip/sip_errno.h> +#include <pj/except.h> +#include <pj/log.h> +#include <pj/string.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/hash.h> +#include <pj/assert.h> +#include <pj/errno.h> +#include <pj/lock.h> + +#define PJSIP_EX_NO_MEMORY pj_NO_MEMORY_EXCEPTION() +#define THIS_FILE "sip_endpoint.c" + +#define MAX_METHODS 32 + + +/* List of SIP endpoint exit callback. */ +typedef struct exit_cb +{ + PJ_DECL_LIST_MEMBER (struct exit_cb); + pjsip_endpt_exit_callback func; +} exit_cb; + + +/** + * The SIP endpoint. + */ +struct pjsip_endpoint +{ + /** Pool to allocate memory for the endpoint. */ + pj_pool_t *pool; + + /** Mutex for the pool, hash table, and event list/queue. */ + pj_mutex_t *mutex; + + /** Pool factory. */ + pj_pool_factory *pf; + + /** Name. */ + pj_str_t name; + + /** Timer heap. */ + pj_timer_heap_t *timer_heap; + + /** Transport manager. */ + pjsip_tpmgr *transport_mgr; + + /** Ioqueue. */ + pj_ioqueue_t *ioqueue; + + /** Last ioqueue err */ + pj_status_t ioq_last_err; + + /** DNS Resolver. */ + pjsip_resolver_t *resolver; + + /** Modules lock. */ + pj_rwmutex_t *mod_mutex; + + /** Modules. */ + pjsip_module *modules[PJSIP_MAX_MODULE]; + + /** Module list, sorted by priority. */ + pjsip_module module_list; + + /** Capability header list. */ + pjsip_hdr cap_hdr; + + /** Additional request headers. */ + pjsip_hdr req_hdr; + + /** List of exit callback. */ + exit_cb exit_cb_list; +}; + + +#if defined(PJSIP_SAFE_MODULE) && PJSIP_SAFE_MODULE!=0 +# define LOCK_MODULE_ACCESS(ept) pj_rwmutex_lock_read(ept->mod_mutex) +# define UNLOCK_MODULE_ACCESS(ept) pj_rwmutex_unlock_read(ept->mod_mutex) +#else +# define LOCK_MODULE_ACCESS(endpt) +# define UNLOCK_MODULE_ACCESS(endpt) +#endif + + + +/* + * Prototypes. + */ +static void endpt_on_rx_msg( pjsip_endpoint*, + pj_status_t, pjsip_rx_data*); +static pj_status_t endpt_on_tx_msg( pjsip_endpoint *endpt, + pjsip_tx_data *tdata ); +static pj_status_t unload_module(pjsip_endpoint *endpt, + pjsip_module *mod); + +/* Defined in sip_parser.c */ +void init_sip_parser(void); +void deinit_sip_parser(void); + +/* Defined in sip_tel_uri.c */ +pj_status_t pjsip_tel_uri_subsys_init(void); + + +/* + * This is the global handler for memory allocation failure, for pools that + * are created by the endpoint (by default, all pools ARE allocated by + * endpoint). The error is handled by throwing exception, and hopefully, + * the exception will be handled by the application (or this library). + */ +static void pool_callback( pj_pool_t *pool, pj_size_t size ) +{ + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(size); + + PJ_THROW(PJSIP_EX_NO_MEMORY); +} + + +/* Compare module name, used for searching module based on name. */ +static int cmp_mod_name(void *name, const void *mod) +{ + return pj_stricmp((const pj_str_t*)name, &((pjsip_module*)mod)->name); +} + +/* + * Register new module to the endpoint. + * The endpoint will then call the load and start function in the module to + * properly initialize the module, and assign a unique module ID for the + * module. + */ +PJ_DEF(pj_status_t) pjsip_endpt_register_module( pjsip_endpoint *endpt, + pjsip_module *mod ) +{ + pj_status_t status = PJ_SUCCESS; + pjsip_module *m; + unsigned i; + + pj_rwmutex_lock_write(endpt->mod_mutex); + + /* Make sure that this module has not been registered. */ + PJ_ASSERT_ON_FAIL( pj_list_find_node(&endpt->module_list, mod) == NULL, + {status = PJ_EEXISTS; goto on_return;}); + + /* Make sure that no module with the same name has been registered. */ + PJ_ASSERT_ON_FAIL( pj_list_search(&endpt->module_list, &mod->name, + &cmp_mod_name)==NULL, + {status = PJ_EEXISTS; goto on_return; }); + + /* Find unused ID for this module. */ + for (i=0; i<PJ_ARRAY_SIZE(endpt->modules); ++i) { + if (endpt->modules[i] == NULL) + break; + } + if (i == PJ_ARRAY_SIZE(endpt->modules)) { + pj_assert(!"Too many modules registered!"); + status = PJ_ETOOMANY; + goto on_return; + } + + /* Assign the ID. */ + mod->id = i; + + /* Try to load the module. */ + if (mod->load) { + status = (*mod->load)(endpt); + if (status != PJ_SUCCESS) + goto on_return; + } + + /* Try to start the module. */ + if (mod->start) { + status = (*mod->start)(); + if (status != PJ_SUCCESS) + goto on_return; + } + + /* Save the module. */ + endpt->modules[i] = mod; + + /* Put in the module list, sorted by priority. */ + m = endpt->module_list.next; + while (m != &endpt->module_list) { + if (m->priority > mod->priority) + break; + m = m->next; + } + pj_list_insert_before(m, mod); + + /* Done. */ + + PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" registered", + (int)mod->name.slen, mod->name.ptr)); + +on_return: + pj_rwmutex_unlock_write(endpt->mod_mutex); + return status; +} + +/* + * Unregister a module from the endpoint. + * The endpoint will then call the stop and unload function in the module to + * properly shutdown the module. + */ +PJ_DEF(pj_status_t) pjsip_endpt_unregister_module( pjsip_endpoint *endpt, + pjsip_module *mod ) +{ + pj_status_t status; + + pj_rwmutex_lock_write(endpt->mod_mutex); + + /* Make sure the module exists in the list. */ + PJ_ASSERT_ON_FAIL( pj_list_find_node(&endpt->module_list, mod) == mod, + {status = PJ_ENOTFOUND;goto on_return;} ); + + /* Make sure the module exists in the array. */ + PJ_ASSERT_ON_FAIL( mod->id>=0 && + mod->id<(int)PJ_ARRAY_SIZE(endpt->modules) && + endpt->modules[mod->id] == mod, + {status = PJ_ENOTFOUND; goto on_return;}); + + /* Try to stop the module. */ + if (mod->stop) { + status = (*mod->stop)(); + if (status != PJ_SUCCESS) goto on_return; + } + + /* Unload module */ + status = unload_module(endpt, mod); + +on_return: + pj_rwmutex_unlock_write(endpt->mod_mutex); + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(3,(THIS_FILE, "Module \"%.*s\" can not be unregistered: %s", + (int)mod->name.slen, mod->name.ptr, errmsg)); + } + + return status; +} + +static pj_status_t unload_module(pjsip_endpoint *endpt, + pjsip_module *mod) +{ + pj_status_t status; + + /* Try to unload the module. */ + if (mod->unload) { + status = (*mod->unload)(); + if (status != PJ_SUCCESS) + return status; + } + + /* Module MUST NOT set module ID to -1. */ + pj_assert(mod->id >= 0); + + /* Remove module from array. */ + endpt->modules[mod->id] = NULL; + + /* Remove module from list. */ + pj_list_erase(mod); + + /* Set module Id to -1. */ + mod->id = -1; + + /* Done. */ + status = PJ_SUCCESS; + + PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" unregistered", + (int)mod->name.slen, mod->name.ptr)); + + return status; +} + + +/* + * Get the value of the specified capability header field. + */ +PJ_DEF(const pjsip_hdr*) pjsip_endpt_get_capability( pjsip_endpoint *endpt, + int htype, + const pj_str_t *hname) +{ + pjsip_hdr *hdr = endpt->cap_hdr.next; + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt != NULL, NULL); + PJ_ASSERT_RETURN(htype != PJSIP_H_OTHER || hname, NULL); + + if (htype != PJSIP_H_OTHER) { + while (hdr != &endpt->cap_hdr) { + if (hdr->type == htype) + return hdr; + hdr = hdr->next; + } + } + return NULL; +} + + +/* + * Check if the specified capability is supported. + */ +PJ_DEF(pj_bool_t) pjsip_endpt_has_capability( pjsip_endpoint *endpt, + int htype, + const pj_str_t *hname, + const pj_str_t *token) +{ + const pjsip_generic_array_hdr *hdr; + unsigned i; + + hdr = (const pjsip_generic_array_hdr*) + pjsip_endpt_get_capability(endpt, htype, hname); + if (!hdr) + return PJ_FALSE; + + PJ_ASSERT_RETURN(token != NULL, PJ_FALSE); + + for (i=0; i<hdr->count; ++i) { + if (!pj_stricmp(&hdr->values[i], token)) + return PJ_TRUE; + } + + return PJ_FALSE; +} + +/* + * Add or register new capabilities as indicated by the tags to the + * appropriate header fields in the endpoint. + */ +PJ_DEF(pj_status_t) pjsip_endpt_add_capability( pjsip_endpoint *endpt, + pjsip_module *mod, + int htype, + const pj_str_t *hname, + unsigned count, + const pj_str_t tags[]) +{ + pjsip_generic_array_hdr *hdr; + unsigned i; + + PJ_UNUSED_ARG(mod); + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt!=NULL && count>0 && tags, PJ_EINVAL); + PJ_ASSERT_RETURN(htype==PJSIP_H_ACCEPT || + htype==PJSIP_H_ALLOW || + htype==PJSIP_H_SUPPORTED, + PJ_EINVAL); + + /* Find the header. */ + hdr = (pjsip_generic_array_hdr*) pjsip_endpt_get_capability(endpt, + htype, hname); + + /* Create the header when it's not present */ + if (hdr == NULL) { + switch (htype) { + case PJSIP_H_ACCEPT: + hdr = pjsip_accept_hdr_create(endpt->pool); + break; + case PJSIP_H_ALLOW: + hdr = pjsip_allow_hdr_create(endpt->pool); + break; + case PJSIP_H_SUPPORTED: + hdr = pjsip_supported_hdr_create(endpt->pool); + break; + default: + return PJ_EINVAL; + } + + if (hdr) { + pj_list_push_back(&endpt->cap_hdr, hdr); + } + } + + /* Add the tags to the header. */ + for (i=0; i<count; ++i) { + pj_strdup(endpt->pool, &hdr->values[hdr->count], &tags[i]); + ++hdr->count; + } + + /* Done. */ + return PJ_SUCCESS; +} + +/* + * Get additional headers to be put in outgoing request message. + */ +PJ_DEF(const pjsip_hdr*) pjsip_endpt_get_request_headers(pjsip_endpoint *endpt) +{ + return &endpt->req_hdr; +} + + +/* + * Initialize endpoint. + */ +PJ_DEF(pj_status_t) pjsip_endpt_create(pj_pool_factory *pf, + const char *name, + pjsip_endpoint **p_endpt) +{ + pj_status_t status; + pj_pool_t *pool; + pjsip_endpoint *endpt; + pjsip_max_fwd_hdr *mf_hdr; + pj_lock_t *lock = NULL; + + + status = pj_register_strerror(PJSIP_ERRNO_START, PJ_ERRNO_SPACE_SIZE, + &pjsip_strerror); + pj_assert(status == PJ_SUCCESS); + + PJ_LOG(5, (THIS_FILE, "Creating endpoint instance...")); + + *p_endpt = NULL; + + /* Create pool */ + pool = pj_pool_create(pf, "pept%p", + PJSIP_POOL_LEN_ENDPT, PJSIP_POOL_INC_ENDPT, + &pool_callback); + if (!pool) + return PJ_ENOMEM; + + /* Create endpoint. */ + endpt = PJ_POOL_ZALLOC_T(pool, pjsip_endpoint); + endpt->pool = pool; + endpt->pf = pf; + + /* Init modules list. */ + pj_list_init(&endpt->module_list); + + /* Initialize exit callback list. */ + pj_list_init(&endpt->exit_cb_list); + + /* Create R/W mutex for module manipulation. */ + status = pj_rwmutex_create(endpt->pool, "ept%p", &endpt->mod_mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init parser. */ + init_sip_parser(); + + /* Init tel: uri */ + pjsip_tel_uri_subsys_init(); + + /* Get name. */ + if (name != NULL) { + pj_str_t temp; + pj_strdup_with_null(endpt->pool, &endpt->name, pj_cstr(&temp, name)); + } else { + pj_strdup_with_null(endpt->pool, &endpt->name, pj_gethostname()); + } + + /* Create mutex for the events, etc. */ + status = pj_mutex_create_recursive( endpt->pool, "ept%p", &endpt->mutex ); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Create timer heap to manage all timers within this endpoint. */ + status = pj_timer_heap_create( endpt->pool, PJSIP_MAX_TIMER_COUNT, + &endpt->timer_heap); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Set recursive lock for the timer heap. */ + status = pj_lock_create_recursive_mutex( endpt->pool, "edpt%p", &lock); + if (status != PJ_SUCCESS) { + goto on_error; + } + pj_timer_heap_set_lock(endpt->timer_heap, lock, PJ_TRUE); + + /* Set maximum timed out entries to process in a single poll. */ + pj_timer_heap_set_max_timed_out_per_poll(endpt->timer_heap, + PJSIP_MAX_TIMED_OUT_ENTRIES); + + /* Create ioqueue. */ + status = pj_ioqueue_create( endpt->pool, PJSIP_MAX_TRANSPORTS, &endpt->ioqueue); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Create transport manager. */ + status = pjsip_tpmgr_create( endpt->pool, endpt, + &endpt_on_rx_msg, + &endpt_on_tx_msg, + &endpt->transport_mgr); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Create asynchronous DNS resolver. */ + status = pjsip_resolver_create(endpt->pool, &endpt->resolver); + if (status != PJ_SUCCESS) { + PJ_LOG(4, (THIS_FILE, "Error creating resolver instance")); + goto on_error; + } + + /* Initialize request headers. */ + pj_list_init(&endpt->req_hdr); + + /* Add "Max-Forwards" for request header. */ + mf_hdr = pjsip_max_fwd_hdr_create(endpt->pool, + PJSIP_MAX_FORWARDS_VALUE); + pj_list_insert_before( &endpt->req_hdr, mf_hdr); + + /* Initialize capability header list. */ + pj_list_init(&endpt->cap_hdr); + + + /* Done. */ + *p_endpt = endpt; + return status; + +on_error: + if (endpt->transport_mgr) { + pjsip_tpmgr_destroy(endpt->transport_mgr); + endpt->transport_mgr = NULL; + } + if (endpt->ioqueue) { + pj_ioqueue_destroy(endpt->ioqueue); + endpt->ioqueue = NULL; + } + if (endpt->timer_heap) { + pj_timer_heap_destroy(endpt->timer_heap); + endpt->timer_heap = NULL; + } + if (endpt->mutex) { + pj_mutex_destroy(endpt->mutex); + endpt->mutex = NULL; + } + if (endpt->mod_mutex) { + pj_rwmutex_destroy(endpt->mod_mutex); + endpt->mod_mutex = NULL; + } + pj_pool_release( endpt->pool ); + + PJ_LOG(4, (THIS_FILE, "Error creating endpoint")); + return status; +} + +/* + * Destroy endpoint. + */ +PJ_DEF(void) pjsip_endpt_destroy(pjsip_endpoint *endpt) +{ + pjsip_module *mod; + exit_cb *ecb; + + PJ_LOG(5, (THIS_FILE, "Destroying endpoing instance..")); + + /* Phase 1: stop all modules */ + mod = endpt->module_list.prev; + while (mod != &endpt->module_list) { + pjsip_module *prev = mod->prev; + if (mod->stop) { + (*mod->stop)(); + } + mod = prev; + } + + /* Phase 2: unload modules. */ + mod = endpt->module_list.prev; + while (mod != &endpt->module_list) { + pjsip_module *prev = mod->prev; + unload_module(endpt, mod); + mod = prev; + } + + /* Destroy resolver */ + pjsip_resolver_destroy(endpt->resolver); + + /* Shutdown and destroy all transports. */ + pjsip_tpmgr_destroy(endpt->transport_mgr); + + /* Destroy ioqueue */ + pj_ioqueue_destroy(endpt->ioqueue); + + /* Destroy timer heap */ +#if PJ_TIMER_DEBUG + pj_timer_heap_dump(endpt->timer_heap); +#endif + pj_timer_heap_destroy(endpt->timer_heap); + + /* Call all registered exit callbacks */ + ecb = endpt->exit_cb_list.next; + while (ecb != &endpt->exit_cb_list) { + (*ecb->func)(endpt); + ecb = ecb->next; + } + + /* Delete endpoint mutex. */ + pj_mutex_destroy(endpt->mutex); + + /* Deinit parser */ + deinit_sip_parser(); + + /* Delete module's mutex */ + pj_rwmutex_destroy(endpt->mod_mutex); + + /* Finally destroy pool. */ + pj_pool_release(endpt->pool); + + PJ_LOG(4, (THIS_FILE, "Endpoint %p destroyed", endpt)); +} + +/* + * Get endpoint name. + */ +PJ_DEF(const pj_str_t*) pjsip_endpt_name(const pjsip_endpoint *endpt) +{ + return &endpt->name; +} + + +/* + * Create new pool. + */ +PJ_DEF(pj_pool_t*) pjsip_endpt_create_pool( pjsip_endpoint *endpt, + const char *pool_name, + pj_size_t initial, + pj_size_t increment ) +{ + pj_pool_t *pool; + + /* Lock endpoint mutex. */ + /* No need to lock mutex. Factory is thread safe. + pj_mutex_lock(endpt->mutex); + */ + + /* Create pool */ + pool = pj_pool_create( endpt->pf, pool_name, + initial, increment, &pool_callback); + + /* Unlock mutex. */ + /* No need to lock mutex. Factory is thread safe. + pj_mutex_unlock(endpt->mutex); + */ + + if (!pool) { + PJ_LOG(4, (THIS_FILE, "Unable to create pool %s!", pool_name)); + } + + return pool; +} + +/* + * Return back pool to endpoint's pool manager to be either destroyed or + * recycled. + */ +PJ_DEF(void) pjsip_endpt_release_pool( pjsip_endpoint *endpt, pj_pool_t *pool ) +{ + PJ_LOG(6, (THIS_FILE, "Releasing pool %s", pj_pool_getobjname(pool))); + + /* Don't need to acquire mutex since pool factory is thread safe + pj_mutex_lock(endpt->mutex); + */ + pj_pool_release( pool ); + + PJ_UNUSED_ARG(endpt); + /* + pj_mutex_unlock(endpt->mutex); + */ +} + + +PJ_DEF(pj_status_t) pjsip_endpt_handle_events2(pjsip_endpoint *endpt, + const pj_time_val *max_timeout, + unsigned *p_count) +{ + /* timeout is 'out' var. This just to make compiler happy. */ + pj_time_val timeout = { 0, 0}; + unsigned count = 0, net_event_count = 0; + int c; + + PJ_LOG(6, (THIS_FILE, "pjsip_endpt_handle_events()")); + + /* Poll the timer. The timer heap has its own mutex for better + * granularity, so we don't need to lock end endpoint. + */ + timeout.sec = timeout.msec = 0; + c = pj_timer_heap_poll( endpt->timer_heap, &timeout ); + if (c > 0) + count += c; + + /* timer_heap_poll should never ever returns negative value, or otherwise + * ioqueue_poll() will block forever! + */ + pj_assert(timeout.sec >= 0 && timeout.msec >= 0); + if (timeout.msec >= 1000) timeout.msec = 999; + + /* If caller specifies maximum time to wait, then compare the value with + * the timeout to wait from timer, and use the minimum value. + */ + if (max_timeout && PJ_TIME_VAL_GT(timeout, *max_timeout)) { + timeout = *max_timeout; + } + + /* Poll ioqueue. + * Repeat polling the ioqueue while we have immediate events, because + * timer heap may process more than one events, so if we only process + * one network events at a time (such as when IOCP backend is used), + * the ioqueue may have trouble keeping up with the request rate. + * + * For example, for each send() request, one network event will be + * reported by ioqueue for the send() completion. If we don't poll + * the ioqueue often enough, the send() completion will not be + * reported in timely manner. + */ + do { + c = pj_ioqueue_poll( endpt->ioqueue, &timeout); + if (c < 0) { + pj_status_t err = pj_get_netos_error(); + pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout)); + if (p_count) + *p_count = count; + return err; + } else if (c == 0) { + break; + } else { + net_event_count += c; + timeout.sec = timeout.msec = 0; + } + } while (c > 0 && net_event_count < PJSIP_MAX_NET_EVENTS); + + count += net_event_count; + if (p_count) + *p_count = count; + + return PJ_SUCCESS; +} + +/* + * Handle events. + */ +PJ_DEF(pj_status_t) pjsip_endpt_handle_events(pjsip_endpoint *endpt, + const pj_time_val *max_timeout) +{ + return pjsip_endpt_handle_events2(endpt, max_timeout, NULL); +} + +/* + * Schedule timer. + */ +#if PJ_TIMER_DEBUG +PJ_DEF(pj_status_t) pjsip_endpt_schedule_timer_dbg(pjsip_endpoint *endpt, + pj_timer_entry *entry, + const pj_time_val *delay, + const char *src_file, + int src_line) +{ + PJ_LOG(6, (THIS_FILE, "pjsip_endpt_schedule_timer(entry=%p, delay=%u.%u)", + entry, delay->sec, delay->msec)); + return pj_timer_heap_schedule_dbg(endpt->timer_heap, entry, delay, + src_file, src_line); +} +#else +PJ_DEF(pj_status_t) pjsip_endpt_schedule_timer( pjsip_endpoint *endpt, + pj_timer_entry *entry, + const pj_time_val *delay ) +{ + PJ_LOG(6, (THIS_FILE, "pjsip_endpt_schedule_timer(entry=%p, delay=%u.%u)", + entry, delay->sec, delay->msec)); + return pj_timer_heap_schedule( endpt->timer_heap, entry, delay ); +} +#endif + +/* + * Cancel the previously registered timer. + */ +PJ_DEF(void) pjsip_endpt_cancel_timer( pjsip_endpoint *endpt, + pj_timer_entry *entry ) +{ + PJ_LOG(6, (THIS_FILE, "pjsip_endpt_cancel_timer(entry=%p)", entry)); + pj_timer_heap_cancel( endpt->timer_heap, entry ); +} + +/* + * Get the timer heap instance of the SIP endpoint. + */ +PJ_DEF(pj_timer_heap_t*) pjsip_endpt_get_timer_heap(pjsip_endpoint *endpt) +{ + return endpt->timer_heap; +} + +/* + * This is the callback that is called by the transport manager when it + * receives a message from the network. + */ +static void endpt_on_rx_msg( pjsip_endpoint *endpt, + pj_status_t status, + pjsip_rx_data *rdata ) +{ + pjsip_msg *msg = rdata->msg_info.msg; + + if (status != PJ_SUCCESS) { + char info[30]; + char errmsg[PJ_ERR_MSG_SIZE]; + + info[0] = '\0'; + + if (status == PJSIP_EMISSINGHDR) { + pj_str_t p; + + p.ptr = info; p.slen = 0; + + if (rdata->msg_info.cid == NULL || rdata->msg_info.cid->id.slen) + pj_strcpy2(&p, "Call-ID"); + if (rdata->msg_info.from == NULL) + pj_strcpy2(&p, " From"); + if (rdata->msg_info.to == NULL) + pj_strcpy2(&p, " To"); + if (rdata->msg_info.via == NULL) + pj_strcpy2(&p, " Via"); + if (rdata->msg_info.cseq == NULL) + pj_strcpy2(&p, " CSeq"); + + p.ptr[p.slen] = '\0'; + } + + pj_strerror(status, errmsg, sizeof(errmsg)); + + PJ_LOG(1, (THIS_FILE, + "Error processing packet from %s:%d: %s %s [code %d]:\n" + "%.*s\n" + "-- end of packet.", + rdata->pkt_info.src_name, + rdata->pkt_info.src_port, + errmsg, + info, + status, + (int)rdata->msg_info.len, + rdata->msg_info.msg_buf)); + return; + } + + PJ_LOG(5, (THIS_FILE, "Processing incoming message: %s", + pjsip_rx_data_get_info(rdata))); + pj_log_push_indent(); + +#if defined(PJSIP_CHECK_VIA_SENT_BY) && PJSIP_CHECK_VIA_SENT_BY != 0 + /* For response, check that the value in Via sent-by match the transport. + * If not matched, silently drop the response. + * Ref: RFC3261 Section 18.1.2 Receiving Response + */ + if (msg->type == PJSIP_RESPONSE_MSG) { + const pj_str_t *local_addr; + int port = rdata->msg_info.via->sent_by.port; + pj_bool_t mismatch = PJ_FALSE; + if (port == 0) { + pjsip_transport_type_e type; + type = (pjsip_transport_type_e)rdata->tp_info.transport->key.type; + port = pjsip_transport_get_default_port_for_type(type); + } + local_addr = &rdata->tp_info.transport->local_name.host; + + if (pj_strcmp(&rdata->msg_info.via->sent_by.host, local_addr) != 0) { + + /* The RFC says that we should drop response when sent-by + * address mismatch. But it could happen (e.g. with SER) when + * endpoint with private IP is sending request to public + * server. + + mismatch = PJ_TRUE; + + */ + + } else if (port != rdata->tp_info.transport->local_name.port) { + /* Port or address mismatch, we should discard response */ + /* But we saw one implementation (we don't want to name it to + * protect the innocence) which put wrong sent-by port although + * the "rport" parameter is correct. + * So we discard the response only if the port doesn't match + * both the port in sent-by and rport. We try to be lenient here! + */ + if (rdata->msg_info.via->rport_param != + rdata->tp_info.transport->local_name.port) + mismatch = PJ_TRUE; + else { + PJ_LOG(4,(THIS_FILE, "Message %s from %s has mismatch port in " + "sent-by but the rport parameter is " + "correct", + pjsip_rx_data_get_info(rdata), + rdata->pkt_info.src_name)); + } + } + + if (mismatch) { + PJ_TODO(ENDPT_REPORT_WHEN_DROPPING_MESSAGE); + PJ_LOG(4,(THIS_FILE, "Dropping response %s from %s:%d because " + "sent-by is mismatch", + pjsip_rx_data_get_info(rdata), + rdata->pkt_info.src_name, + rdata->pkt_info.src_port)); + pj_log_pop_indent(); + return; + } + } +#endif + + + /* Distribute to modules, starting from modules with highest priority */ + LOCK_MODULE_ACCESS(endpt); + + if (msg->type == PJSIP_REQUEST_MSG) { + pjsip_module *mod; + pj_bool_t handled = PJ_FALSE; + + mod = endpt->module_list.next; + while (mod != &endpt->module_list) { + if (mod->on_rx_request) + handled = (*mod->on_rx_request)(rdata); + if (handled) + break; + mod = mod->next; + } + + /* No module is able to handle the request. */ + if (!handled) { + PJ_TODO(ENDPT_RESPOND_UNHANDLED_REQUEST); + PJ_LOG(4,(THIS_FILE, "Message %s from %s:%d was dropped/unhandled by" + " any modules", + pjsip_rx_data_get_info(rdata), + rdata->pkt_info.src_name, + rdata->pkt_info.src_port)); + } + + } else { + pjsip_module *mod; + pj_bool_t handled = PJ_FALSE; + + mod = endpt->module_list.next; + while (mod != &endpt->module_list) { + if (mod->on_rx_response) + handled = (*mod->on_rx_response)(rdata); + if (handled) + break; + mod = mod->next; + } + + if (!handled) { + PJ_LOG(4,(THIS_FILE, "Message %s from %s:%d was dropped/unhandled" + " by any modules", + pjsip_rx_data_get_info(rdata), + rdata->pkt_info.src_name, + rdata->pkt_info.src_port)); + } + } + + UNLOCK_MODULE_ACCESS(endpt); + + /* Must clear mod_data before returning rdata to transport, since + * rdata may be reused. + */ + pj_bzero(&rdata->endpt_info, sizeof(rdata->endpt_info)); + + pj_log_pop_indent(); +} + +/* + * This callback is called by transport manager before message is sent. + * Modules may inspect the message before it's actually sent. + */ +static pj_status_t endpt_on_tx_msg( pjsip_endpoint *endpt, + pjsip_tx_data *tdata ) +{ + pj_status_t status = PJ_SUCCESS; + pjsip_module *mod; + + /* Distribute to modules, starting from modules with LOWEST priority */ + LOCK_MODULE_ACCESS(endpt); + + mod = endpt->module_list.prev; + if (tdata->msg->type == PJSIP_REQUEST_MSG) { + while (mod != &endpt->module_list) { + if (mod->on_tx_request) + status = (*mod->on_tx_request)(tdata); + if (status != PJ_SUCCESS) + break; + mod = mod->prev; + } + + } else { + while (mod != &endpt->module_list) { + if (mod->on_tx_response) + status = (*mod->on_tx_response)(tdata); + if (status != PJ_SUCCESS) + break; + mod = mod->prev; + } + } + + UNLOCK_MODULE_ACCESS(endpt); + + return status; +} + + +/* + * Create transmit data buffer. + */ +PJ_DEF(pj_status_t) pjsip_endpt_create_tdata( pjsip_endpoint *endpt, + pjsip_tx_data **p_tdata) +{ + return pjsip_tx_data_create(endpt->transport_mgr, p_tdata); +} + +/* + * Create the DNS resolver instance. + */ +PJ_DEF(pj_status_t) pjsip_endpt_create_resolver(pjsip_endpoint *endpt, + pj_dns_resolver **p_resv) +{ +#if PJSIP_HAS_RESOLVER + PJ_ASSERT_RETURN(endpt && p_resv, PJ_EINVAL); + return pj_dns_resolver_create( endpt->pf, NULL, 0, endpt->timer_heap, + endpt->ioqueue, p_resv); +#else + PJ_UNUSED_ARG(endpt); + PJ_UNUSED_ARG(p_resv); + pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)"); + return PJ_EINVALIDOP; +#endif +} + +/* + * Set DNS resolver to be used by the SIP resolver. + */ +PJ_DEF(pj_status_t) pjsip_endpt_set_resolver( pjsip_endpoint *endpt, + pj_dns_resolver *resv) +{ + return pjsip_resolver_set_resolver(endpt->resolver, resv); +} + +/* + * Get the DNS resolver being used by the SIP resolver. + */ +PJ_DEF(pj_dns_resolver*) pjsip_endpt_get_resolver(pjsip_endpoint *endpt) +{ + PJ_ASSERT_RETURN(endpt, NULL); + return pjsip_resolver_get_resolver(endpt->resolver); +} + +/* + * Resolve + */ +PJ_DEF(void) pjsip_endpt_resolve( pjsip_endpoint *endpt, + pj_pool_t *pool, + pjsip_host_info *target, + void *token, + pjsip_resolver_callback *cb) +{ + pjsip_resolve( endpt->resolver, pool, target, token, cb); +} + +/* + * Get transport manager. + */ +PJ_DEF(pjsip_tpmgr*) pjsip_endpt_get_tpmgr(pjsip_endpoint *endpt) +{ + return endpt->transport_mgr; +} + +/* + * Get ioqueue instance. + */ +PJ_DEF(pj_ioqueue_t*) pjsip_endpt_get_ioqueue(pjsip_endpoint *endpt) +{ + return endpt->ioqueue; +} + +/* + * Find/create transport. + */ +PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport(pjsip_endpoint *endpt, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + const pjsip_tpselector *sel, + pjsip_transport **transport) +{ + return pjsip_tpmgr_acquire_transport(endpt->transport_mgr, type, + remote, addr_len, sel, transport); +} + + +/* + * Find/create transport. + */ +PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport2(pjsip_endpoint *endpt, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + const pjsip_tpselector *sel, + pjsip_tx_data *tdata, + pjsip_transport **transport) +{ + return pjsip_tpmgr_acquire_transport2(endpt->transport_mgr, type, remote, + addr_len, sel, tdata, transport); +} + + +/* + * Report error. + */ +PJ_DEF(void) pjsip_endpt_log_error( pjsip_endpoint *endpt, + const char *sender, + pj_status_t error_code, + const char *format, + ... ) +{ +#if PJ_LOG_MAX_LEVEL > 0 + char newformat[256]; + int len; + va_list marker; + + va_start(marker, format); + + PJ_UNUSED_ARG(endpt); + + len = pj_ansi_strlen(format); + if (len < (int)sizeof(newformat)-30) { + pj_str_t errstr; + + pj_ansi_strcpy(newformat, format); + pj_ansi_snprintf(newformat+len, sizeof(newformat)-len-1, + ": [err %d] ", error_code); + len += pj_ansi_strlen(newformat+len); + + errstr = pj_strerror( error_code, newformat+len, + sizeof(newformat)-len-1); + + len += errstr.slen; + newformat[len] = '\0'; + + pj_log(sender, 1, newformat, marker); + } else { + pj_log(sender, 1, format, marker); + } + + va_end(marker); +#else + PJ_UNUSED_ARG(format); + PJ_UNUSED_ARG(error_code); + PJ_UNUSED_ARG(sender); + PJ_UNUSED_ARG(endpt); +#endif +} + + +/* + * Dump endpoint. + */ +PJ_DEF(void) pjsip_endpt_dump( pjsip_endpoint *endpt, pj_bool_t detail ) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + PJ_LOG(5, (THIS_FILE, "pjsip_endpt_dump()")); + + /* Lock mutex. */ + pj_mutex_lock(endpt->mutex); + + PJ_LOG(3, (THIS_FILE, "Dumping endpoint %p:", endpt)); + + /* Dumping pool factory. */ + pj_pool_factory_dump(endpt->pf, detail); + + /* Pool health. */ + PJ_LOG(3, (THIS_FILE," Endpoint pool capacity=%u, used_size=%u", + pj_pool_get_capacity(endpt->pool), + pj_pool_get_used_size(endpt->pool))); + + /* Resolver */ +#if PJSIP_HAS_RESOLVER + if (pjsip_endpt_get_resolver(endpt)) { + pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), detail); + } +#endif + + /* Transports. + */ + pjsip_tpmgr_dump_transports( endpt->transport_mgr ); + + /* Timer. */ +#if PJ_TIMER_DEBUG + pj_timer_heap_dump(endpt->timer_heap); +#else + PJ_LOG(3,(THIS_FILE, " Timer heap has %u entries", + pj_timer_heap_count(endpt->timer_heap))); +#endif + + /* Unlock mutex. */ + pj_mutex_unlock(endpt->mutex); +#else + PJ_UNUSED_ARG(endpt); + PJ_UNUSED_ARG(detail); + PJ_LOG(3,(THIS_FILE, "pjsip_end_dump: can't dump because it's disabled.")); +#endif +} + + +PJ_DEF(pj_status_t) pjsip_endpt_atexit( pjsip_endpoint *endpt, + pjsip_endpt_exit_callback func) +{ + exit_cb *new_cb; + + PJ_ASSERT_RETURN(endpt && func, PJ_EINVAL); + + new_cb = PJ_POOL_ZALLOC_T(endpt->pool, exit_cb); + new_cb->func = func; + + pj_mutex_lock(endpt->mutex); + pj_list_push_back(&endpt->exit_cb_list, new_cb); + pj_mutex_unlock(endpt->mutex); + + return PJ_SUCCESS; +} diff --git a/pjsip/src/pjsip/sip_endpoint_wrap.cpp b/pjsip/src/pjsip/sip_endpoint_wrap.cpp new file mode 100644 index 0000000..1386023 --- /dev/null +++ b/pjsip/src/pjsip/sip_endpoint_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: sip_endpoint_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * 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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "sip_endpoint.c" diff --git a/pjsip/src/pjsip/sip_errno.c b/pjsip/src/pjsip/sip_errno.c new file mode 100644 index 0000000..211db39 --- /dev/null +++ b/pjsip/src/pjsip/sip_errno.c @@ -0,0 +1,211 @@ +/* $Id: sip_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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_errno.h> +#include <pjsip/sip_msg.h> +#include <pj/string.h> +#include <pj/errno.h> + +/* PJSIP's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + */ + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + +static const struct +{ + int code; + const char *msg; +} err_str[] = +{ + /* Generic SIP errors */ + PJ_BUILD_ERR( PJSIP_EBUSY, "Object is busy" ), + PJ_BUILD_ERR( PJSIP_ETYPEEXISTS , "Object with the same type exists" ), + PJ_BUILD_ERR( PJSIP_ESHUTDOWN, "SIP stack shutting down" ), + PJ_BUILD_ERR( PJSIP_ENOTINITIALIZED,"SIP object is not initialized." ), + PJ_BUILD_ERR( PJSIP_ENOROUTESET, "Missing route set (for tel: URI)" ), + + /* Messaging errors */ + PJ_BUILD_ERR( PJSIP_EINVALIDMSG, "Invalid message/syntax error" ), + PJ_BUILD_ERR( PJSIP_ENOTREQUESTMSG, "Expecting request message"), + PJ_BUILD_ERR( PJSIP_ENOTRESPONSEMSG,"Expecting response message"), + PJ_BUILD_ERR( PJSIP_EMSGTOOLONG, "Message too long" ), + PJ_BUILD_ERR( PJSIP_EPARTIALMSG, "Partial message" ), + + PJ_BUILD_ERR( PJSIP_EINVALIDSTATUS, "Invalid/unexpected SIP status code"), + + PJ_BUILD_ERR( PJSIP_EINVALIDURI, "Invalid URI" ), + PJ_BUILD_ERR( PJSIP_EINVALIDSCHEME, "Invalid URI scheme" ), + PJ_BUILD_ERR( PJSIP_EMISSINGREQURI, "Missing Request-URI" ), + PJ_BUILD_ERR( PJSIP_EINVALIDREQURI, "Invalid Request URI" ), + PJ_BUILD_ERR( PJSIP_EURITOOLONG, "URI is too long" ), + + PJ_BUILD_ERR( PJSIP_EMISSINGHDR, "Missing required header(s)" ), + PJ_BUILD_ERR( PJSIP_EINVALIDHDR, "Invalid header field"), + PJ_BUILD_ERR( PJSIP_EINVALIDVIA, "Invalid Via header" ), + PJ_BUILD_ERR( PJSIP_EMULTIPLEVIA, "Multiple Via headers in response" ), + + PJ_BUILD_ERR( PJSIP_EMISSINGBODY, "Missing message body" ), + PJ_BUILD_ERR( PJSIP_EINVALIDMETHOD, "Invalid/unexpected method" ), + + /* Transport errors */ + PJ_BUILD_ERR( PJSIP_EUNSUPTRANSPORT,"Unsupported transport"), + PJ_BUILD_ERR( PJSIP_EPENDINGTX, "Transmit buffer already pending"), + PJ_BUILD_ERR( PJSIP_ERXOVERFLOW, "Rx buffer overflow"), + PJ_BUILD_ERR( PJSIP_EBUFDESTROYED, "Buffer destroyed"), + PJ_BUILD_ERR( PJSIP_ETPNOTSUITABLE, "Unsuitable transport selected"), + PJ_BUILD_ERR( PJSIP_ETPNOTAVAIL, "Transport not available for use"), + + /* Transaction errors */ + PJ_BUILD_ERR( PJSIP_ETSXDESTROYED, "Transaction has been destroyed"), + PJ_BUILD_ERR( PJSIP_ENOTSX, "No transaction is associated with the object " + "(expecting stateful processing)" ), + + /* URI comparison status */ + PJ_BUILD_ERR( PJSIP_ECMPSCHEME, "URI scheme mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPUSER, "URI user part mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPPASSWD, "URI password part mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPHOST, "URI host part mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPPORT, "URI port mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPTRANSPORTPRM,"URI transport param mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPTTLPARAM, "URI ttl param mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPUSERPARAM, "URI user param mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPMETHODPARAM,"URI method param mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPMADDRPARAM, "URI maddr param mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPOTHERPARAM, "URI other param mismatch" ), + PJ_BUILD_ERR( PJSIP_ECMPHEADERPARAM,"URI header parameter mismatch" ), + + /* Authentication. */ + PJ_BUILD_ERR( PJSIP_EFAILEDCREDENTIAL, "Credential failed to authenticate"), + PJ_BUILD_ERR( PJSIP_ENOCREDENTIAL, "No suitable credential"), + PJ_BUILD_ERR( PJSIP_EINVALIDALGORITHM, "Invalid/unsupported digest algorithm" ), + PJ_BUILD_ERR( PJSIP_EINVALIDQOP, "Invalid/unsupported digest qop" ), + PJ_BUILD_ERR( PJSIP_EINVALIDAUTHSCHEME,"Unsupported authentication scheme" ), + PJ_BUILD_ERR( PJSIP_EAUTHNOPREVCHAL, "No previous challenge" ), + PJ_BUILD_ERR( PJSIP_EAUTHNOAUTH, "No suitable authorization header" ), + PJ_BUILD_ERR( PJSIP_EAUTHACCNOTFOUND, "Account or credential not found" ), + PJ_BUILD_ERR( PJSIP_EAUTHACCDISABLED, "Account or credential is disabled" ), + PJ_BUILD_ERR( PJSIP_EAUTHINVALIDREALM, "Invalid authorization realm"), + PJ_BUILD_ERR( PJSIP_EAUTHINVALIDDIGEST,"Invalid authorization digest" ), + PJ_BUILD_ERR( PJSIP_EAUTHSTALECOUNT, "Maximum number of stale retries exceeded"), + PJ_BUILD_ERR( PJSIP_EAUTHINNONCE, "Invalid nonce value in authentication challenge"), + PJ_BUILD_ERR( PJSIP_EAUTHINAKACRED, "Invalid AKA credential"), + PJ_BUILD_ERR( PJSIP_EAUTHNOCHAL, "No challenge is found"), + + /* UA/dialog layer. */ + PJ_BUILD_ERR( PJSIP_EMISSINGTAG, "Missing From/To tag parameter" ), + PJ_BUILD_ERR( PJSIP_ENOTREFER, "Expecting REFER request") , + PJ_BUILD_ERR( PJSIP_ENOREFERSESSION,"Not associated with REFER subscription"), + + /* Invite session. */ + PJ_BUILD_ERR( PJSIP_ESESSIONTERMINATED, "INVITE session already terminated" ), + PJ_BUILD_ERR( PJSIP_ESESSIONSTATE, "Invalid INVITE session state" ), + PJ_BUILD_ERR( PJSIP_ESESSIONINSECURE, "Require secure session/transport"), + + /* SSL errors */ + PJ_BUILD_ERR( PJSIP_TLS_EUNKNOWN, "Unknown TLS error" ), + PJ_BUILD_ERR( PJSIP_TLS_EINVMETHOD, "Invalid SSL protocol method" ), + PJ_BUILD_ERR( PJSIP_TLS_ECACERT, "Error loading/verifying SSL CA list file"), + PJ_BUILD_ERR( PJSIP_TLS_ECERTFILE, "Error loading SSL certificate chain file"), + PJ_BUILD_ERR( PJSIP_TLS_EKEYFILE, "Error adding private key from SSL certificate file"), + PJ_BUILD_ERR( PJSIP_TLS_ECIPHER, "Error setting SSL cipher list"), + PJ_BUILD_ERR( PJSIP_TLS_ECTX, "Error creating SSL context"), + PJ_BUILD_ERR( PJSIP_TLS_ESSLCONN, "Error creating SSL connection object"), + PJ_BUILD_ERR( PJSIP_TLS_ECONNECT, "Unknown error when performing SSL connect()"), + PJ_BUILD_ERR( PJSIP_TLS_EACCEPT, "Unknown error when performing SSL accept()"), + PJ_BUILD_ERR( PJSIP_TLS_ESEND, "Unknown error when sending SSL data"), + PJ_BUILD_ERR( PJSIP_TLS_EREAD, "Unknown error when reading SSL data"), + PJ_BUILD_ERR( PJSIP_TLS_ETIMEDOUT, "SSL negotiation has timed out"), + PJ_BUILD_ERR( PJSIP_TLS_ECERTVERIF, "SSL certificate verification error"), +}; + + +#endif /* PJ_HAS_ERROR_STRING */ + + +/* + * pjsip_strerror() + */ +PJ_DEF(pj_str_t) pjsip_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_ERRNO_START && statcode < PJSIP_ERRNO_START+800) + { + /* Status code. */ + const pj_str_t *status_text = + pjsip_get_status_text(PJSIP_ERRNO_TO_SIP_STATUS(statcode)); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, status_text, bufsize); + return errstr; + } + else if (statcode >= PJSIP_ERRNO_START_PJSIP && + statcode < PJSIP_ERRNO_START_PJSIP + 1000) + { + /* 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 error %d", + statcode); + + return errstr; + +} + diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c new file mode 100644 index 0000000..edbf367 --- /dev/null +++ b/pjsip/src/pjsip/sip_msg.c @@ -0,0 +1,2218 @@ +/* $Id: sip_msg.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_msg.h> +#include <pjsip/sip_parser.h> +#include <pjsip/print_util.h> +#include <pjsip/sip_errno.h> +#include <pj/ctype.h> +#include <pj/guid.h> +#include <pj/string.h> +#include <pj/pool.h> +#include <pj/assert.h> +#include <pjlib-util/string.h> + +PJ_DEF_DATA(const pjsip_method) pjsip_invite_method = + { PJSIP_INVITE_METHOD, { "INVITE",6 }}; + +PJ_DEF_DATA(const pjsip_method) pjsip_cancel_method = + { PJSIP_CANCEL_METHOD, { "CANCEL",6 }}; + +PJ_DEF_DATA(const pjsip_method) pjsip_ack_method = + { PJSIP_ACK_METHOD, { "ACK",3}}; + +PJ_DEF_DATA(const pjsip_method) pjsip_bye_method = + { PJSIP_BYE_METHOD, { "BYE",3}}; + +PJ_DEF_DATA(const pjsip_method) pjsip_register_method = + { PJSIP_REGISTER_METHOD, { "REGISTER", 8}}; + +PJ_DEF_DATA(const pjsip_method) pjsip_options_method = + { PJSIP_OPTIONS_METHOD, { "OPTIONS",7}}; + + +/** INVITE method constant. */ +PJ_DEF(const pjsip_method*) pjsip_get_invite_method(void) +{ + return &pjsip_invite_method; +} + +/** CANCEL method constant. */ +PJ_DEF(const pjsip_method*) pjsip_get_cancel_method(void) +{ + return &pjsip_cancel_method; +} + +/** ACK method constant. */ +PJ_DEF(const pjsip_method*) pjsip_get_ack_method(void) +{ + return &pjsip_ack_method; +} + +/** BYE method constant. */ +PJ_DEF(const pjsip_method*) pjsip_get_bye_method(void) +{ + return &pjsip_bye_method; +} + +/** REGISTER method constant.*/ +PJ_DEF(const pjsip_method*) pjsip_get_register_method(void) +{ + return &pjsip_register_method; +} + +/** OPTIONS method constant. */ +PJ_DEF(const pjsip_method*) pjsip_get_options_method(void) +{ + return &pjsip_options_method; +} + + +static const pj_str_t *method_names[] = +{ + &pjsip_invite_method.name, + &pjsip_cancel_method.name, + &pjsip_ack_method.name, + &pjsip_bye_method.name, + &pjsip_register_method.name, + &pjsip_options_method.name +}; + +const pjsip_hdr_name_info_t pjsip_hdr_names[] = +{ + { "Accept", 6, NULL }, // PJSIP_H_ACCEPT, + { "Accept-Encoding", 15, NULL }, // PJSIP_H_ACCEPT_ENCODING, + { "Accept-Language", 15, NULL }, // PJSIP_H_ACCEPT_LANGUAGE, + { "Alert-Info", 10, NULL }, // PJSIP_H_ALERT_INFO, + { "Allow", 5, NULL }, // PJSIP_H_ALLOW, + { "Authentication-Info",19, NULL }, // PJSIP_H_AUTHENTICATION_INFO, + { "Authorization", 13, NULL }, // PJSIP_H_AUTHORIZATION, + { "Call-ID", 7, "i" }, // PJSIP_H_CALL_ID, + { "Call-Info", 9, NULL }, // PJSIP_H_CALL_INFO, + { "Contact", 7, "m" }, // PJSIP_H_CONTACT, + { "Content-Disposition",19, NULL }, // PJSIP_H_CONTENT_DISPOSITION, + { "Content-Encoding", 16, "e" }, // PJSIP_H_CONTENT_ENCODING, + { "Content-Language", 16, NULL }, // PJSIP_H_CONTENT_LANGUAGE, + { "Content-Length", 14, "l" }, // PJSIP_H_CONTENT_LENGTH, + { "Content-Type", 12, "c" }, // PJSIP_H_CONTENT_TYPE, + { "CSeq", 4, NULL }, // PJSIP_H_CSEQ, + { "Date", 4, NULL }, // PJSIP_H_DATE, + { "Error-Info", 10, NULL }, // PJSIP_H_ERROR_INFO, + { "Expires", 7, NULL }, // PJSIP_H_EXPIRES, + { "From", 4, "f" }, // PJSIP_H_FROM, + { "In-Reply-To", 11, NULL }, // PJSIP_H_IN_REPLY_TO, + { "Max-Forwards", 12, NULL }, // PJSIP_H_MAX_FORWARDS, + { "MIME-Version", 12, NULL }, // PJSIP_H_MIME_VERSION, + { "Min-Expires", 11, NULL }, // PJSIP_H_MIN_EXPIRES, + { "Organization", 12, NULL }, // PJSIP_H_ORGANIZATION, + { "Priority", 8, NULL }, // PJSIP_H_PRIORITY, + { "Proxy-Authenticate", 18, NULL }, // PJSIP_H_PROXY_AUTHENTICATE, + { "Proxy-Authorization",19, NULL }, // PJSIP_H_PROXY_AUTHORIZATION, + { "Proxy-Require", 13, NULL }, // PJSIP_H_PROXY_REQUIRE, + { "Record-Route", 12, NULL }, // PJSIP_H_RECORD_ROUTE, + { "Reply-To", 8, NULL }, // PJSIP_H_REPLY_TO, + { "Require", 7, NULL }, // PJSIP_H_REQUIRE, + { "Retry-After", 11, NULL }, // PJSIP_H_RETRY_AFTER, + { "Route", 5, NULL }, // PJSIP_H_ROUTE, + { "Server", 6, NULL }, // PJSIP_H_SERVER, + { "Subject", 7, "s" }, // PJSIP_H_SUBJECT, + { "Supported", 9, "k" }, // PJSIP_H_SUPPORTED, + { "Timestamp", 9, NULL }, // PJSIP_H_TIMESTAMP, + { "To", 2, "t" }, // PJSIP_H_TO, + { "Unsupported", 11, NULL }, // PJSIP_H_UNSUPPORTED, + { "User-Agent", 10, NULL }, // PJSIP_H_USER_AGENT, + { "Via", 3, "v" }, // PJSIP_H_VIA, + { "Warning", 7, NULL }, // PJSIP_H_WARNING, + { "WWW-Authenticate", 16, NULL }, // PJSIP_H_WWW_AUTHENTICATE, + + { "_Unknown-Header", 15, NULL }, // PJSIP_H_OTHER, +}; + +pj_bool_t pjsip_use_compact_form = PJSIP_ENCODE_SHORT_HNAME; + +static pj_str_t status_phrase[710]; +static int print_media_type(char *buf, unsigned len, + const pjsip_media_type *media); + +static int init_status_phrase() +{ + unsigned i; + pj_str_t default_reason_phrase = { "Default status message", 22}; + + for (i=0; i<PJ_ARRAY_SIZE(status_phrase); ++i) + status_phrase[i] = default_reason_phrase; + + pj_strset2( &status_phrase[100], "Trying"); + pj_strset2( &status_phrase[180], "Ringing"); + pj_strset2( &status_phrase[181], "Call Is Being Forwarded"); + pj_strset2( &status_phrase[182], "Queued"); + pj_strset2( &status_phrase[183], "Session Progress"); + + pj_strset2( &status_phrase[200], "OK"); + pj_strset2( &status_phrase[202], "Accepted"); + + pj_strset2( &status_phrase[300], "Multiple Choices"); + pj_strset2( &status_phrase[301], "Moved Permanently"); + pj_strset2( &status_phrase[302], "Moved Temporarily"); + pj_strset2( &status_phrase[305], "Use Proxy"); + pj_strset2( &status_phrase[380], "Alternative Service"); + + pj_strset2( &status_phrase[400], "Bad Request"); + pj_strset2( &status_phrase[401], "Unauthorized"); + pj_strset2( &status_phrase[402], "Payment Required"); + pj_strset2( &status_phrase[403], "Forbidden"); + pj_strset2( &status_phrase[404], "Not Found"); + pj_strset2( &status_phrase[405], "Method Not Allowed"); + pj_strset2( &status_phrase[406], "Not Acceptable"); + pj_strset2( &status_phrase[407], "Proxy Authentication Required"); + pj_strset2( &status_phrase[408], "Request Timeout"); + pj_strset2( &status_phrase[410], "Gone"); + pj_strset2( &status_phrase[413], "Request Entity Too Large"); + pj_strset2( &status_phrase[414], "Request URI Too Long"); + pj_strset2( &status_phrase[415], "Unsupported Media Type"); + pj_strset2( &status_phrase[416], "Unsupported URI Scheme"); + pj_strset2( &status_phrase[420], "Bad Extension"); + pj_strset2( &status_phrase[421], "Extension Required"); + pj_strset2( &status_phrase[422], "Session Timer Too Small"); + pj_strset2( &status_phrase[423], "Interval Too Brief"); + pj_strset2( &status_phrase[480], "Temporarily Unavailable"); + pj_strset2( &status_phrase[481], "Call/Transaction Does Not Exist"); + pj_strset2( &status_phrase[482], "Loop Detected"); + pj_strset2( &status_phrase[483], "Too Many Hops"); + pj_strset2( &status_phrase[484], "Address Incompleted"); + pj_strset2( &status_phrase[485], "Ambiguous"); + pj_strset2( &status_phrase[486], "Busy Here"); + pj_strset2( &status_phrase[487], "Request Terminated"); + pj_strset2( &status_phrase[488], "Not Acceptable Here"); + pj_strset2( &status_phrase[489], "Bad Event"); + pj_strset2( &status_phrase[490], "Request Updated"); + pj_strset2( &status_phrase[491], "Request Pending"); + pj_strset2( &status_phrase[493], "Undecipherable"); + + pj_strset2( &status_phrase[500], "Internal Server Error"); + pj_strset2( &status_phrase[501], "Not Implemented"); + pj_strset2( &status_phrase[502], "Bad Gateway"); + pj_strset2( &status_phrase[503], "Service Unavailable"); + pj_strset2( &status_phrase[504], "Server Timeout"); + pj_strset2( &status_phrase[505], "Version Not Supported"); + pj_strset2( &status_phrase[513], "Message Too Large"); + pj_strset2( &status_phrase[580], "Precondition Failure"); + + pj_strset2( &status_phrase[600], "Busy Everywhere"); + pj_strset2( &status_phrase[603], "Decline"); + pj_strset2( &status_phrase[604], "Does Not Exist Anywhere"); + pj_strset2( &status_phrase[606], "Not Acceptable"); + + pj_strset2( &status_phrase[701], "No response from destination server"); + pj_strset2( &status_phrase[702], "Unable to resolve destination server"); + pj_strset2( &status_phrase[703], "Error sending message to destination server"); + + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Method. + */ + +PJ_DEF(void) pjsip_method_init( pjsip_method *m, + pj_pool_t *pool, + const pj_str_t *str) +{ + pj_str_t dup; + pjsip_method_init_np(m, pj_strdup(pool, &dup, str)); +} + +PJ_DEF(void) pjsip_method_set( pjsip_method *m, pjsip_method_e me ) +{ + pj_assert(me < PJSIP_OTHER_METHOD); + m->id = me; + m->name = *method_names[me]; +} + +PJ_DEF(void) pjsip_method_init_np(pjsip_method *m, + pj_str_t *str) +{ + unsigned i; + for (i=0; i<PJ_ARRAY_SIZE(method_names); ++i) { + if (pj_memcmp(str->ptr, method_names[i]->ptr, str->slen)==0 || + pj_stricmp(str, method_names[i])==0) + { + m->id = (pjsip_method_e)i; + m->name = *method_names[i]; + return; + } + } + m->id = PJSIP_OTHER_METHOD; + m->name = *str; +} + +PJ_DEF(void) pjsip_method_copy( pj_pool_t *pool, + pjsip_method *method, + const pjsip_method *rhs ) +{ + method->id = rhs->id; + if (rhs->id != PJSIP_OTHER_METHOD) { + method->name = rhs->name; + } else { + pj_strdup(pool, &method->name, &rhs->name); + } +} + + +PJ_DEF(int) pjsip_method_cmp( const pjsip_method *m1, const pjsip_method *m2) +{ + if (m1->id == m2->id) { + if (m1->id != PJSIP_OTHER_METHOD) + return 0; + /* Method comparison is case sensitive! */ + return pj_strcmp(&m1->name, &m2->name); + } + + return ( m1->id < m2->id ) ? -1 : 1; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Message. + */ + +PJ_DEF(pjsip_msg*) pjsip_msg_create( pj_pool_t *pool, pjsip_msg_type_e type) +{ + pjsip_msg *msg = PJ_POOL_ALLOC_T(pool, pjsip_msg); + pj_list_init(&msg->hdr); + msg->type = type; + msg->body = NULL; + return msg; +} + +PJ_DEF(pjsip_msg*) pjsip_msg_clone( pj_pool_t *pool, const pjsip_msg *src) +{ + pjsip_msg *dst; + const pjsip_hdr *sh; + + dst = pjsip_msg_create(pool, src->type); + + /* Clone request/status line */ + if (src->type == PJSIP_REQUEST_MSG) { + pjsip_method_copy(pool, &dst->line.req.method, &src->line.req.method); + dst->line.req.uri = (pjsip_uri*) pjsip_uri_clone(pool, + src->line.req.uri); + } else { + dst->line.status.code = src->line.status.code; + pj_strdup(pool, &dst->line.status.reason, &src->line.status.reason); + } + + /* Clone headers */ + sh = src->hdr.next; + while (sh != &src->hdr) { + pjsip_hdr *dh = (pjsip_hdr*) pjsip_hdr_clone(pool, sh); + pjsip_msg_add_hdr(dst, dh); + sh = sh->next; + } + + /* Clone message body */ + if (src->body) { + dst->body = pjsip_msg_body_clone(pool, src->body); + } + + return dst; +} + +PJ_DEF(void*) pjsip_msg_find_hdr( const pjsip_msg *msg, + pjsip_hdr_e hdr_type, const void *start) +{ + const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=&msg->hdr; + + if (hdr == NULL) { + hdr = msg->hdr.next; + } + for (; hdr!=end; hdr = hdr->next) { + if (hdr->type == hdr_type) + return (void*)hdr; + } + return NULL; +} + +PJ_DEF(void*) pjsip_msg_find_hdr_by_name( const pjsip_msg *msg, + const pj_str_t *name, + const void *start) +{ + const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr; + + if (hdr == NULL) { + hdr = msg->hdr.next; + } + for (; hdr!=end; hdr = hdr->next) { + if (pj_stricmp(&hdr->name, name) == 0) + return (void*)hdr; + } + return NULL; +} + +PJ_DEF(void*) pjsip_msg_find_hdr_by_names( const pjsip_msg *msg, + const pj_str_t *name, + const pj_str_t *sname, + const void *start) +{ + const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr; + + if (hdr == NULL) { + hdr = msg->hdr.next; + } + for (; hdr!=end; hdr = hdr->next) { + if (pj_stricmp(&hdr->name, name) == 0) + return (void*)hdr; + if (pj_stricmp(&hdr->name, sname) == 0) + return (void*)hdr; + } + return NULL; +} + +PJ_DEF(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg, + pjsip_hdr_e hdr_type, void *start) +{ + pjsip_hdr *hdr = (pjsip_hdr*) pjsip_msg_find_hdr(msg, hdr_type, start); + if (hdr) { + pj_list_erase(hdr); + } + return hdr; +} + +PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg, + char *buf, pj_size_t size) +{ + char *p=buf, *end=buf+size; + int len; + pjsip_hdr *hdr; + pj_str_t clen_hdr = { "Content-Length: ", 16}; + + if (pjsip_use_compact_form) { + clen_hdr.ptr = "l: "; + clen_hdr.slen = 3; + } + + /* Get a wild guess on how many bytes are typically needed. + * We'll check this later in detail, but this serves as a quick check. + */ + if (size < 256) + return -1; + + /* Print request line or status line depending on message type */ + if (msg->type == PJSIP_REQUEST_MSG) { + pjsip_uri *uri; + + /* Add method. */ + len = msg->line.req.method.name.slen; + pj_memcpy(p, msg->line.req.method.name.ptr, len); + p += len; + *p++ = ' '; + + /* Add URI */ + uri = (pjsip_uri*) pjsip_uri_get_uri(msg->line.req.uri); + len = pjsip_uri_print( PJSIP_URI_IN_REQ_URI, uri, p, end-p); + if (len < 1) + return -1; + p += len; + + /* Add ' SIP/2.0' */ + if (end-p < 16) + return -1; + pj_memcpy(p, " SIP/2.0\r\n", 10); + p += 10; + + } else { + + /* Add 'SIP/2.0 ' */ + pj_memcpy(p, "SIP/2.0 ", 8); + p += 8; + + /* Add status code. */ + len = pj_utoa(msg->line.status.code, p); + p += len; + *p++ = ' '; + + /* Add reason text. */ + len = msg->line.status.reason.slen; + pj_memcpy(p, msg->line.status.reason.ptr, len ); + p += len; + + /* Add newline. */ + *p++ = '\r'; + *p++ = '\n'; + } + + /* Print each of the headers. */ + for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) { + len = pjsip_hdr_print_on(hdr, p, end-p); + if (len < 0) + return -1; + + if (len > 0) { + p += len; + if (p+3 >= end) + return -1; + + *p++ = '\r'; + *p++ = '\n'; + } + } + + /* Process message body. */ + if (msg->body) { + enum { CLEN_SPACE = 5 }; + char *clen_pos = NULL; + + /* Automaticly adds Content-Type and Content-Length headers, only + * if content_type is set in the message body. + */ + if (msg->body->content_type.type.slen) { + pj_str_t ctype_hdr = { "Content-Type: ", 14}; + const pjsip_media_type *media = &msg->body->content_type; + + if (pjsip_use_compact_form) { + ctype_hdr.ptr = "c: "; + ctype_hdr.slen = 3; + } + + /* Add Content-Type header. */ + if ( (end-p) < 24 + media->type.slen + media->subtype.slen) { + return -1; + } + pj_memcpy(p, ctype_hdr.ptr, ctype_hdr.slen); + p += ctype_hdr.slen; + p += print_media_type(p, end-p, media); + *p++ = '\r'; + *p++ = '\n'; + + /* Add Content-Length header. */ + if ((end-p) < clen_hdr.slen + 12 + 2) { + return -1; + } + pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen); + p += clen_hdr.slen; + + /* Print blanks after "Content-Length:", this is where we'll put + * the content length value after we know the length of the + * body. + */ + pj_memset(p, ' ', CLEN_SPACE); + clen_pos = p; + p += CLEN_SPACE; + *p++ = '\r'; + *p++ = '\n'; + } + + /* Add blank newline. */ + *p++ = '\r'; + *p++ = '\n'; + + /* Print the message body itself. */ + len = (*msg->body->print_body)(msg->body, p, end-p); + if (len < 0) { + return -1; + } + p += len; + + /* Now that we have the length of the body, print this to the + * Content-Length header. + */ + if (clen_pos) { + char tmp[16]; + len = pj_utoa(len, tmp); + if (len > CLEN_SPACE) len = CLEN_SPACE; + pj_memcpy(clen_pos+CLEN_SPACE-len, tmp, len); + } + + } else { + /* There's no message body. + * Add Content-Length with zero value. + */ + if ((end-p) < clen_hdr.slen+8) { + return -1; + } + pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen); + p += clen_hdr.slen; + *p++ = ' '; + *p++ = '0'; + *p++ = '\r'; + *p++ = '\n'; + *p++ = '\r'; + *p++ = '\n'; + } + + *p = '\0'; + return p-buf; +} + +/////////////////////////////////////////////////////////////////////////////// +PJ_DEF(void*) pjsip_hdr_clone( pj_pool_t *pool, const void *hdr_ptr ) +{ + const pjsip_hdr *hdr = (const pjsip_hdr*) hdr_ptr; + return (*hdr->vptr->clone)(pool, hdr_ptr); +} + + +PJ_DEF(void*) pjsip_hdr_shallow_clone( pj_pool_t *pool, const void *hdr_ptr ) +{ + const pjsip_hdr *hdr = (const pjsip_hdr*) hdr_ptr; + return (*hdr->vptr->shallow_clone)(pool, hdr_ptr); +} + +PJ_DEF(int) pjsip_hdr_print_on( void *hdr_ptr, char *buf, pj_size_t len) +{ + pjsip_hdr *hdr = (pjsip_hdr*) hdr_ptr; + return (*hdr->vptr->print_on)(hdr_ptr, buf, len); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Status/Reason Phrase + */ + +PJ_DEF(const pj_str_t*) pjsip_get_status_text(int code) +{ + static int is_initialized; + if (is_initialized == 0) { + is_initialized = 1; + init_status_phrase(); + } + + return (code>=100 && + code<(int)(sizeof(status_phrase)/sizeof(status_phrase[0]))) ? + &status_phrase[code] : &status_phrase[0]; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Media type + */ +/* + * Init media type. + */ +PJ_DEF(void) pjsip_media_type_init( pjsip_media_type *mt, + pj_str_t *type, + pj_str_t *subtype) +{ + pj_bzero(mt, sizeof(*mt)); + pj_list_init(&mt->param); + if (type) + mt->type = *type; + if (subtype) + mt->subtype = *subtype; +} + +PJ_DEF(void) pjsip_media_type_init2( pjsip_media_type *mt, + char *type, + char *subtype) +{ + pj_str_t s_type, s_subtype; + + if (type) { + s_type = pj_str(type); + } else { + s_type.ptr = NULL; + s_type.slen = 0; + } + + if (subtype) { + s_subtype = pj_str(subtype); + } else { + s_subtype.ptr = NULL; + s_subtype.slen = 0; + } + + pjsip_media_type_init(mt, &s_type, &s_subtype); +} + +/* + * Compare two media types. + */ +PJ_DEF(int) pjsip_media_type_cmp( const pjsip_media_type *mt1, + const pjsip_media_type *mt2, + pj_bool_t cmp_param) +{ + int rc; + + PJ_ASSERT_RETURN(mt1 && mt2, 1); + + rc = pj_stricmp(&mt1->type, &mt2->type); + if (rc) return rc; + + rc = pj_stricmp(&mt1->subtype, &mt2->subtype); + if (rc) return rc; + + if (cmp_param) { + rc = pjsip_param_cmp(&mt1->param, &mt2->param, (cmp_param==1)); + } + + return rc; +} + +PJ_DEF(void) pjsip_media_type_cp( pj_pool_t *pool, + pjsip_media_type *dst, + const pjsip_media_type *src) +{ + PJ_ASSERT_ON_FAIL(pool && dst && src, return); + pj_strdup(pool, &dst->type, &src->type); + pj_strdup(pool, &dst->subtype, &src->subtype); + pjsip_param_clone(pool, &dst->param, &src->param); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Generic pjsip_hdr_names/hvalue header. + */ + +static int pjsip_generic_string_hdr_print( pjsip_generic_string_hdr *hdr, + char *buf, pj_size_t size); +static pjsip_generic_string_hdr* pjsip_generic_string_hdr_clone( pj_pool_t *pool, + const pjsip_generic_string_hdr *hdr); +static pjsip_generic_string_hdr* pjsip_generic_string_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_generic_string_hdr *hdr ); + +static pjsip_hdr_vptr generic_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_generic_string_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_generic_string_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_generic_string_hdr_print, +}; + + +PJ_DEF(void) pjsip_generic_string_hdr_init2(pjsip_generic_string_hdr *hdr, + pj_str_t *hname, + pj_str_t *hvalue) +{ + init_hdr(hdr, PJSIP_H_OTHER, &generic_hdr_vptr); + if (hname) { + hdr->name = *hname; + hdr->sname = *hname; + } + if (hvalue) { + hdr->hvalue = *hvalue; + } else { + hdr->hvalue.ptr = NULL; + hdr->hvalue.slen = 0; + } +} + + +PJ_DEF(pjsip_generic_string_hdr*) pjsip_generic_string_hdr_init(pj_pool_t *pool, + void *mem, + const pj_str_t *hnames, + const pj_str_t *hvalue) +{ + pjsip_generic_string_hdr *hdr = (pjsip_generic_string_hdr*) mem; + pj_str_t dup_hname, dup_hval; + + if (hnames) { + pj_strdup(pool, &dup_hname, hnames); + } else { + dup_hname.slen = 0; + } + + if (hvalue) { + pj_strdup(pool, &dup_hval, hvalue); + } else { + dup_hval.slen = 0; + } + + pjsip_generic_string_hdr_init2(hdr, &dup_hname, &dup_hval); + return hdr; +} + +PJ_DEF(pjsip_generic_string_hdr*) pjsip_generic_string_hdr_create(pj_pool_t *pool, + const pj_str_t *hnames, + const pj_str_t *hvalue) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_generic_string_hdr)); + return pjsip_generic_string_hdr_init(pool, mem, hnames, hvalue); +} + +static int pjsip_generic_string_hdr_print( pjsip_generic_string_hdr *hdr, + char *buf, pj_size_t size) +{ + char *p = buf; + const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name; + + if ((pj_ssize_t)size < hname->slen + hdr->hvalue.slen + 5) + return -1; + + pj_memcpy(p, hname->ptr, hname->slen); + p += hname->slen; + *p++ = ':'; + *p++ = ' '; + pj_memcpy(p, hdr->hvalue.ptr, hdr->hvalue.slen); + p += hdr->hvalue.slen; + *p = '\0'; + + return p - buf; +} + +static pjsip_generic_string_hdr* pjsip_generic_string_hdr_clone( pj_pool_t *pool, + const pjsip_generic_string_hdr *rhs) +{ + pjsip_generic_string_hdr *hdr; + + hdr = pjsip_generic_string_hdr_create(pool, &rhs->name, &rhs->hvalue); + + hdr->type = rhs->type; + pj_strdup(pool, &hdr->sname, &rhs->sname); + return hdr; +} + +static pjsip_generic_string_hdr* pjsip_generic_string_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_generic_string_hdr *rhs ) +{ + pjsip_generic_string_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_string_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + return hdr; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Generic pjsip_hdr_names/integer value header. + */ + +static int pjsip_generic_int_hdr_print( pjsip_generic_int_hdr *hdr, + char *buf, pj_size_t size); +static pjsip_generic_int_hdr* pjsip_generic_int_hdr_clone( pj_pool_t *pool, + const pjsip_generic_int_hdr *hdr); +static pjsip_generic_int_hdr* pjsip_generic_int_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_generic_int_hdr *hdr ); + +static pjsip_hdr_vptr generic_int_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_generic_int_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_generic_int_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_generic_int_hdr_print, +}; + +PJ_DEF(pjsip_generic_int_hdr*) pjsip_generic_int_hdr_init( pj_pool_t *pool, + void *mem, + const pj_str_t *hnames, + int value) +{ + pjsip_generic_int_hdr *hdr = (pjsip_generic_int_hdr*) mem; + + init_hdr(hdr, PJSIP_H_OTHER, &generic_int_hdr_vptr); + if (hnames) { + pj_strdup(pool, &hdr->name, hnames); + hdr->sname = hdr->name; + } + hdr->ivalue = value; + return hdr; +} + +PJ_DEF(pjsip_generic_int_hdr*) pjsip_generic_int_hdr_create( pj_pool_t *pool, + const pj_str_t *hnames, + int value) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_generic_int_hdr)); + return pjsip_generic_int_hdr_init(pool, mem, hnames, value); +} + +static int pjsip_generic_int_hdr_print( pjsip_generic_int_hdr *hdr, + char *buf, pj_size_t size) +{ + char *p = buf; + const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name; + + if ((pj_ssize_t)size < hname->slen + 15) + return -1; + + pj_memcpy(p, hname->ptr, hname->slen); + p += hname->slen; + *p++ = ':'; + *p++ = ' '; + + p += pj_utoa(hdr->ivalue, p); + + return p - buf; +} + +static pjsip_generic_int_hdr* pjsip_generic_int_hdr_clone( pj_pool_t *pool, + const pjsip_generic_int_hdr *rhs) +{ + pjsip_generic_int_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_int_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + return hdr; +} + +static pjsip_generic_int_hdr* pjsip_generic_int_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_generic_int_hdr *rhs ) +{ + pjsip_generic_int_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_int_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + return hdr; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Generic array header. + */ +static int pjsip_generic_array_hdr_print( pjsip_generic_array_hdr *hdr, char *buf, pj_size_t size); +static pjsip_generic_array_hdr* pjsip_generic_array_hdr_clone( pj_pool_t *pool, + const pjsip_generic_array_hdr *hdr); +static pjsip_generic_array_hdr* pjsip_generic_array_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_generic_array_hdr *hdr); + +static pjsip_hdr_vptr generic_array_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_generic_array_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_generic_array_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_generic_array_hdr_print, +}; + + +PJ_DEF(pjsip_generic_array_hdr*) pjsip_generic_array_hdr_init( pj_pool_t *pool, + void *mem, + const pj_str_t *hnames) +{ + pjsip_generic_array_hdr *hdr = (pjsip_generic_array_hdr*) mem; + + init_hdr(hdr, PJSIP_H_OTHER, &generic_array_hdr_vptr); + if (hnames) { + pj_strdup(pool, &hdr->name, hnames); + hdr->sname = hdr->name; + } + hdr->count = 0; + return hdr; +} + +PJ_DEF(pjsip_generic_array_hdr*) pjsip_generic_array_hdr_create( pj_pool_t *pool, + const pj_str_t *hnames) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_generic_array_hdr)); + return pjsip_generic_array_hdr_init(pool, mem, hnames); + +} + +static int pjsip_generic_array_hdr_print( pjsip_generic_array_hdr *hdr, + char *buf, pj_size_t size) +{ + char *p = buf, *endbuf = buf+size; + const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name; + + copy_advance(p, (*hname)); + *p++ = ':'; + *p++ = ' '; + + if (hdr->count > 0) { + unsigned i; + int printed; + copy_advance(p, hdr->values[0]); + for (i=1; i<hdr->count; ++i) { + copy_advance_pair(p, ", ", 2, hdr->values[i]); + } + } + + return p - buf; +} + +static pjsip_generic_array_hdr* pjsip_generic_array_hdr_clone( pj_pool_t *pool, + const pjsip_generic_array_hdr *rhs) +{ + unsigned i; + pjsip_generic_array_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_array_hdr); + + pj_memcpy(hdr, rhs, sizeof(*hdr)); + for (i=0; i<rhs->count; ++i) { + pj_strdup(pool, &hdr->values[i], &rhs->values[i]); + } + + return hdr; +} + + +static pjsip_generic_array_hdr* pjsip_generic_array_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_generic_array_hdr *rhs) +{ + pjsip_generic_array_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_array_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + return hdr; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Accept header. + */ +PJ_DEF(pjsip_accept_hdr*) pjsip_accept_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_accept_hdr *hdr = (pjsip_accept_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_ACCEPT, &generic_array_hdr_vptr); + hdr->count = 0; + return hdr; +} + +PJ_DEF(pjsip_accept_hdr*) pjsip_accept_hdr_create(pj_pool_t *pool) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_accept_hdr)); + return pjsip_accept_hdr_init(pool, mem); +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Allow header. + */ + +PJ_DEF(pjsip_allow_hdr*) pjsip_allow_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_allow_hdr *hdr = (pjsip_allow_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_ALLOW, &generic_array_hdr_vptr); + hdr->count = 0; + return hdr; +} + +PJ_DEF(pjsip_allow_hdr*) pjsip_allow_hdr_create(pj_pool_t *pool) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_allow_hdr)); + return pjsip_allow_hdr_init(pool, mem); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Call-ID header. + */ + +PJ_DEF(pjsip_cid_hdr*) pjsip_cid_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_cid_hdr *hdr = (pjsip_cid_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_CALL_ID, &generic_hdr_vptr); + return hdr; + +} + +PJ_DEF(pjsip_cid_hdr*) pjsip_cid_hdr_create( pj_pool_t *pool ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_cid_hdr)); + return pjsip_cid_hdr_init(pool, mem); +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Content-Length header. + */ +static int pjsip_clen_hdr_print( pjsip_clen_hdr *hdr, char *buf, pj_size_t size); +static pjsip_clen_hdr* pjsip_clen_hdr_clone( pj_pool_t *pool, const pjsip_clen_hdr *hdr); +#define pjsip_clen_hdr_shallow_clone pjsip_clen_hdr_clone + +static pjsip_hdr_vptr clen_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_clen_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_clen_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_clen_hdr_print, +}; + +PJ_DEF(pjsip_clen_hdr*) pjsip_clen_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_clen_hdr *hdr = (pjsip_clen_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_CONTENT_LENGTH, &clen_hdr_vptr); + hdr->len = 0; + return hdr; +} + +PJ_DEF(pjsip_clen_hdr*) pjsip_clen_hdr_create( pj_pool_t *pool ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_clen_hdr)); + return pjsip_clen_hdr_init(pool, mem); +} + +static int pjsip_clen_hdr_print( pjsip_clen_hdr *hdr, + char *buf, pj_size_t size) +{ + char *p = buf; + int len; + const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name; + + if ((pj_ssize_t)size < hname->slen + 14) + return -1; + + pj_memcpy(p, hname->ptr, hname->slen); + p += hname->slen; + *p++ = ':'; + *p++ = ' '; + + len = pj_utoa(hdr->len, p); + p += len; + *p = '\0'; + + return p-buf; +} + +static pjsip_clen_hdr* pjsip_clen_hdr_clone( pj_pool_t *pool, const pjsip_clen_hdr *rhs) +{ + pjsip_clen_hdr *hdr = pjsip_clen_hdr_create(pool); + hdr->len = rhs->len; + return hdr; +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * CSeq header. + */ +static int pjsip_cseq_hdr_print( pjsip_cseq_hdr *hdr, char *buf, pj_size_t size); +static pjsip_cseq_hdr* pjsip_cseq_hdr_clone( pj_pool_t *pool, const pjsip_cseq_hdr *hdr); +static pjsip_cseq_hdr* pjsip_cseq_hdr_shallow_clone( pj_pool_t *pool, const pjsip_cseq_hdr *hdr ); + +static pjsip_hdr_vptr cseq_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_cseq_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_cseq_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_cseq_hdr_print, +}; + +PJ_DEF(pjsip_cseq_hdr*) pjsip_cseq_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_cseq_hdr *hdr = (pjsip_cseq_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_CSEQ, &cseq_hdr_vptr); + hdr->cseq = 0; + hdr->method.id = PJSIP_OTHER_METHOD; + hdr->method.name.ptr = NULL; + hdr->method.name.slen = 0; + return hdr; +} + +PJ_DEF(pjsip_cseq_hdr*) pjsip_cseq_hdr_create( pj_pool_t *pool ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_cseq_hdr)); + return pjsip_cseq_hdr_init(pool, mem); +} + +static int pjsip_cseq_hdr_print( pjsip_cseq_hdr *hdr, char *buf, pj_size_t size) +{ + char *p = buf; + int len; + /* CSeq doesn't have compact form */ + + if ((pj_ssize_t)size < hdr->name.slen + hdr->method.name.slen + 15) + return -1; + + pj_memcpy(p, hdr->name.ptr, hdr->name.slen); + p += hdr->name.slen; + *p++ = ':'; + *p++ = ' '; + + len = pj_utoa(hdr->cseq, p); + p += len; + *p++ = ' '; + + pj_memcpy(p, hdr->method.name.ptr, hdr->method.name.slen); + p += hdr->method.name.slen; + + *p = '\0'; + + return p-buf; +} + +static pjsip_cseq_hdr* pjsip_cseq_hdr_clone( pj_pool_t *pool, + const pjsip_cseq_hdr *rhs) +{ + pjsip_cseq_hdr *hdr = pjsip_cseq_hdr_create(pool); + hdr->cseq = rhs->cseq; + pjsip_method_copy(pool, &hdr->method, &rhs->method); + return hdr; +} + +static pjsip_cseq_hdr* pjsip_cseq_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_cseq_hdr *rhs ) +{ + pjsip_cseq_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_cseq_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + return hdr; +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Contact header. + */ +static int pjsip_contact_hdr_print( pjsip_contact_hdr *hdr, char *buf, pj_size_t size); +static pjsip_contact_hdr* pjsip_contact_hdr_clone( pj_pool_t *pool, const pjsip_contact_hdr *hdr); +static pjsip_contact_hdr* pjsip_contact_hdr_shallow_clone( pj_pool_t *pool, const pjsip_contact_hdr *); + +static pjsip_hdr_vptr contact_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_contact_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_contact_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_contact_hdr_print, +}; + +PJ_DEF(pjsip_contact_hdr*) pjsip_contact_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + pj_bzero(mem, sizeof(pjsip_contact_hdr)); + init_hdr(hdr, PJSIP_H_CONTACT, &contact_hdr_vptr); + hdr->expires = -1; + pj_list_init(&hdr->other_param); + return hdr; +} + +PJ_DEF(pjsip_contact_hdr*) pjsip_contact_hdr_create( pj_pool_t *pool ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_contact_hdr)); + return pjsip_contact_hdr_init(pool, mem); +} + +static int pjsip_contact_hdr_print( pjsip_contact_hdr *hdr, char *buf, + pj_size_t size) +{ + const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + if (hdr->star) { + char *p = buf; + if ((pj_ssize_t)size < hname->slen + 6) + return -1; + pj_memcpy(p, hname->ptr, hname->slen); + p += hname->slen; + *p++ = ':'; + *p++ = ' '; + *p++ = '*'; + return p - buf; + + } else { + int printed; + char *startbuf = buf; + char *endbuf = buf + size; + + copy_advance(buf, (*hname)); + *buf++ = ':'; + *buf++ = ' '; + + printed = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, hdr->uri, + buf, endbuf-buf); + if (printed < 1) + return -1; + + buf += printed; + + if (hdr->q1000) { + unsigned frac; + + if (buf+19 >= endbuf) + return -1; + + /* + printed = sprintf(buf, ";q=%u.%03u", + hdr->q1000/1000, hdr->q1000 % 1000); + */ + pj_memcpy(buf, ";q=", 3); + printed = pj_utoa(hdr->q1000/1000, buf+3); + buf += printed + 3; + frac = hdr->q1000 % 1000; + if (frac != 0) { + *buf++ = '.'; + if ((frac % 100)==0) frac /= 100; + if ((frac % 10)==0) frac /= 10; + printed = pj_utoa(frac, buf); + buf += printed; + } + } + + if (hdr->expires >= 0) { + if (buf+23 >= endbuf) + return -1; + + pj_memcpy(buf, ";expires=", 9); + printed = pj_utoa(hdr->expires, buf+9); + buf += printed + 9; + } + + printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, + ';'); + if (printed < 0) + return printed; + buf += printed; + + return buf-startbuf; + } +} + +static pjsip_contact_hdr* pjsip_contact_hdr_clone(pj_pool_t *pool, + const pjsip_contact_hdr *rhs) +{ + pjsip_contact_hdr *hdr = pjsip_contact_hdr_create(pool); + + hdr->star = rhs->star; + if (hdr->star) + return hdr; + + hdr->uri = (pjsip_uri*) pjsip_uri_clone(pool, rhs->uri); + hdr->q1000 = rhs->q1000; + hdr->expires = rhs->expires; + pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + +static pjsip_contact_hdr* +pjsip_contact_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_contact_hdr *rhs) +{ + pjsip_contact_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_contact_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Content-Type header.. + */ +static int pjsip_ctype_hdr_print( pjsip_ctype_hdr *hdr, char *buf, + pj_size_t size); +static pjsip_ctype_hdr* pjsip_ctype_hdr_clone(pj_pool_t *pool, + const pjsip_ctype_hdr *hdr); +#define pjsip_ctype_hdr_shallow_clone pjsip_ctype_hdr_clone + +static pjsip_hdr_vptr ctype_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_ctype_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_ctype_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_ctype_hdr_print, +}; + +PJ_DEF(pjsip_ctype_hdr*) pjsip_ctype_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_ctype_hdr *hdr = (pjsip_ctype_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + pj_bzero(mem, sizeof(pjsip_ctype_hdr)); + init_hdr(hdr, PJSIP_H_CONTENT_TYPE, &ctype_hdr_vptr); + pj_list_init(&hdr->media.param); + return hdr; + +} + +PJ_DEF(pjsip_ctype_hdr*) pjsip_ctype_hdr_create( pj_pool_t *pool ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_ctype_hdr)); + return pjsip_ctype_hdr_init(pool, mem); +} + +static int print_media_type(char *buf, unsigned len, + const pjsip_media_type *media) +{ + char *p = buf; + pj_ssize_t printed; + const pjsip_parser_const_t *pc; + + pj_memcpy(p, media->type.ptr, media->type.slen); + p += media->type.slen; + *p++ = '/'; + pj_memcpy(p, media->subtype.ptr, media->subtype.slen); + p += media->subtype.slen; + + pc = pjsip_parser_const(); + printed = pjsip_param_print_on(&media->param, p, buf+len-p, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, ';'); + if (printed < 0) + return -1; + + p += printed; + + return p-buf; +} + + +PJ_DEF(int) pjsip_media_type_print(char *buf, unsigned len, + const pjsip_media_type *media) +{ + return print_media_type(buf, len, media); +} + +static int pjsip_ctype_hdr_print( pjsip_ctype_hdr *hdr, + char *buf, pj_size_t size) +{ + char *p = buf; + int len; + const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name; + + if ((pj_ssize_t)size < hname->slen + + hdr->media.type.slen + hdr->media.subtype.slen + 8) + { + return -1; + } + + pj_memcpy(p, hname->ptr, hname->slen); + p += hname->slen; + *p++ = ':'; + *p++ = ' '; + + len = print_media_type(p, buf+size-p, &hdr->media); + p += len; + + *p = '\0'; + return p-buf; +} + +static pjsip_ctype_hdr* pjsip_ctype_hdr_clone( pj_pool_t *pool, + const pjsip_ctype_hdr *rhs) +{ + pjsip_ctype_hdr *hdr = pjsip_ctype_hdr_create(pool); + pj_strdup(pool, &hdr->media.type, &rhs->media.type); + pj_strdup(pool, &hdr->media.subtype, &rhs->media.subtype); + pjsip_param_clone(pool, &hdr->media.param, &rhs->media.param); + return hdr; +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Expires header. + */ +PJ_DEF(pjsip_expires_hdr*) pjsip_expires_hdr_init( pj_pool_t *pool, + void *mem, + int value) +{ + pjsip_expires_hdr *hdr = (pjsip_expires_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_EXPIRES, &generic_int_hdr_vptr); + hdr->ivalue = value; + return hdr; + +} + +PJ_DEF(pjsip_expires_hdr*) pjsip_expires_hdr_create( pj_pool_t *pool, + int value ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_expires_hdr)); + return pjsip_expires_hdr_init(pool, mem, value); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * To or From header. + */ +static int pjsip_fromto_hdr_print( pjsip_fromto_hdr *hdr, + char *buf, pj_size_t size); +static pjsip_fromto_hdr* pjsip_fromto_hdr_clone( pj_pool_t *pool, + const pjsip_fromto_hdr *hdr); +static pjsip_fromto_hdr* pjsip_fromto_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_fromto_hdr *hdr); + + +static pjsip_hdr_vptr fromto_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_fromto_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_fromto_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_fromto_hdr_print, +}; + +PJ_DEF(pjsip_from_hdr*) pjsip_from_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_from_hdr *hdr = (pjsip_from_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + pj_bzero(mem, sizeof(pjsip_from_hdr)); + init_hdr(hdr, PJSIP_H_FROM, &fromto_hdr_vptr); + pj_list_init(&hdr->other_param); + return hdr; +} + +PJ_DEF(pjsip_from_hdr*) pjsip_from_hdr_create( pj_pool_t *pool ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_from_hdr)); + return pjsip_from_hdr_init(pool, mem); +} + +PJ_DEF(pjsip_to_hdr*) pjsip_to_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_to_hdr *hdr = (pjsip_to_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + pj_bzero(mem, sizeof(pjsip_to_hdr)); + init_hdr(hdr, PJSIP_H_TO, &fromto_hdr_vptr); + pj_list_init(&hdr->other_param); + return hdr; + +} + +PJ_DEF(pjsip_to_hdr*) pjsip_to_hdr_create( pj_pool_t *pool ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_to_hdr)); + return pjsip_to_hdr_init(pool, mem); +} + +PJ_DEF(pjsip_from_hdr*) pjsip_fromto_hdr_set_from( pjsip_fromto_hdr *hdr ) +{ + hdr->type = PJSIP_H_FROM; + hdr->name.ptr = pjsip_hdr_names[PJSIP_H_FROM].name; + hdr->name.slen = pjsip_hdr_names[PJSIP_H_FROM].name_len; + hdr->sname.ptr = pjsip_hdr_names[PJSIP_H_FROM].sname; + hdr->sname.slen = 1; + return hdr; +} + +PJ_DEF(pjsip_to_hdr*) pjsip_fromto_hdr_set_to( pjsip_fromto_hdr *hdr ) +{ + hdr->type = PJSIP_H_TO; + hdr->name.ptr = pjsip_hdr_names[PJSIP_H_TO].name; + hdr->name.slen = pjsip_hdr_names[PJSIP_H_TO].name_len; + hdr->sname.ptr = pjsip_hdr_names[PJSIP_H_TO].sname; + hdr->sname.slen = 1; + return hdr; +} + +static int pjsip_fromto_hdr_print( pjsip_fromto_hdr *hdr, + char *buf, pj_size_t size) +{ + int printed; + char *startbuf = buf; + char *endbuf = buf + size; + const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + copy_advance(buf, (*hname)); + *buf++ = ':'; + *buf++ = ' '; + + printed = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, hdr->uri, + buf, endbuf-buf); + if (printed < 1) + return -1; + + buf += printed; + + copy_advance_pair_escape(buf, ";tag=", 5, hdr->tag, + pc->pjsip_TOKEN_SPEC); + + printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, ';'); + if (printed < 0) + return -1; + buf += printed; + + return buf-startbuf; +} + +static pjsip_fromto_hdr* pjsip_fromto_hdr_clone( pj_pool_t *pool, + const pjsip_fromto_hdr *rhs) +{ + pjsip_fromto_hdr *hdr = pjsip_from_hdr_create(pool); + + hdr->type = rhs->type; + hdr->name = rhs->name; + hdr->sname = rhs->sname; + hdr->uri = (pjsip_uri*) pjsip_uri_clone(pool, rhs->uri); + pj_strdup( pool, &hdr->tag, &rhs->tag); + pjsip_param_clone( pool, &hdr->other_param, &rhs->other_param); + + return hdr; +} + +static pjsip_fromto_hdr* +pjsip_fromto_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_fromto_hdr *rhs) +{ + pjsip_fromto_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_fromto_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone( pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Max-Forwards header. + */ +PJ_DEF(pjsip_max_fwd_hdr*) pjsip_max_fwd_hdr_init( pj_pool_t *pool, + void *mem, + int value) +{ + pjsip_max_fwd_hdr *hdr = (pjsip_max_fwd_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_MAX_FORWARDS, &generic_int_hdr_vptr); + hdr->ivalue = value; + return hdr; + +} + +PJ_DEF(pjsip_max_fwd_hdr*) pjsip_max_fwd_hdr_create(pj_pool_t *pool, + int value) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_max_fwd_hdr)); + return pjsip_max_fwd_hdr_init(pool, mem, value); +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Min-Expires header. + */ +PJ_DEF(pjsip_min_expires_hdr*) pjsip_min_expires_hdr_init( pj_pool_t *pool, + void *mem, + int value ) +{ + pjsip_min_expires_hdr *hdr = (pjsip_min_expires_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_MIN_EXPIRES, &generic_int_hdr_vptr); + hdr->ivalue = value; + return hdr; +} + +PJ_DEF(pjsip_min_expires_hdr*) pjsip_min_expires_hdr_create(pj_pool_t *pool, + int value ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_min_expires_hdr)); + return pjsip_min_expires_hdr_init(pool, mem, value ); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Record-Route and Route header. + */ +static int pjsip_routing_hdr_print( pjsip_routing_hdr *r, char *buf, pj_size_t size ); +static pjsip_routing_hdr* pjsip_routing_hdr_clone( pj_pool_t *pool, const pjsip_routing_hdr *r ); +static pjsip_routing_hdr* pjsip_routing_hdr_shallow_clone( pj_pool_t *pool, const pjsip_routing_hdr *r ); + +static pjsip_hdr_vptr routing_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_routing_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_routing_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_routing_hdr_print, +}; + +PJ_DEF(pjsip_rr_hdr*) pjsip_rr_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_rr_hdr *hdr = (pjsip_rr_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_RECORD_ROUTE, &routing_hdr_vptr); + pjsip_name_addr_init(&hdr->name_addr); + pj_list_init(&hdr->other_param); + return hdr; + +} + +PJ_DEF(pjsip_rr_hdr*) pjsip_rr_hdr_create( pj_pool_t *pool ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_rr_hdr)); + return pjsip_rr_hdr_init(pool, mem); +} + +PJ_DEF(pjsip_route_hdr*) pjsip_route_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_route_hdr *hdr = (pjsip_route_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_ROUTE, &routing_hdr_vptr); + pjsip_name_addr_init(&hdr->name_addr); + pj_list_init(&hdr->other_param); + return hdr; +} + +PJ_DEF(pjsip_route_hdr*) pjsip_route_hdr_create( pj_pool_t *pool ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_route_hdr)); + return pjsip_route_hdr_init(pool, mem); +} + +PJ_DEF(pjsip_rr_hdr*) pjsip_routing_hdr_set_rr( pjsip_routing_hdr *hdr ) +{ + hdr->type = PJSIP_H_RECORD_ROUTE; + hdr->name.ptr = pjsip_hdr_names[PJSIP_H_RECORD_ROUTE].name; + hdr->name.slen = pjsip_hdr_names[PJSIP_H_RECORD_ROUTE].name_len; + hdr->sname = hdr->name; + return hdr; +} + +PJ_DEF(pjsip_route_hdr*) pjsip_routing_hdr_set_route( pjsip_routing_hdr *hdr ) +{ + hdr->type = PJSIP_H_ROUTE; + hdr->name.ptr = pjsip_hdr_names[PJSIP_H_ROUTE].name; + hdr->name.slen = pjsip_hdr_names[PJSIP_H_ROUTE].name_len; + hdr->sname = hdr->name; + return hdr; +} + +static int pjsip_routing_hdr_print( pjsip_routing_hdr *hdr, + char *buf, pj_size_t size ) +{ + int printed; + char *startbuf = buf; + char *endbuf = buf + size; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + pjsip_sip_uri *sip_uri; + pjsip_param *p; + + /* Check the proprietary param 'hide', don't print this header + * if it exists in the route URI. + */ + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(hdr->name_addr.uri); + p = sip_uri->other_param.next; + while (p != &sip_uri->other_param) { + const pj_str_t st_hide = {"hide", 4}; + + if (pj_stricmp(&p->name, &st_hide) == 0) { + /* Check if param 'hide' is specified without 'lr'. */ + pj_assert(sip_uri->lr_param != 0); + return 0; + } + p = p->next; + } + + /* Route and Record-Route don't compact forms */ + + copy_advance(buf, hdr->name); + *buf++ = ':'; + *buf++ = ' '; + + printed = pjsip_uri_print(PJSIP_URI_IN_ROUTING_HDR, &hdr->name_addr, buf, + endbuf-buf); + if (printed < 1) + return -1; + buf += printed; + + printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, ';'); + if (printed < 0) + return -1; + buf += printed; + + return buf-startbuf; +} + +static pjsip_routing_hdr* pjsip_routing_hdr_clone( pj_pool_t *pool, + const pjsip_routing_hdr *rhs ) +{ + pjsip_routing_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_routing_hdr); + + init_hdr(hdr, rhs->type, rhs->vptr); + pjsip_name_addr_init(&hdr->name_addr); + pjsip_name_addr_assign(pool, &hdr->name_addr, &rhs->name_addr); + pjsip_param_clone( pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + +static pjsip_routing_hdr* pjsip_routing_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_routing_hdr *rhs ) +{ + pjsip_routing_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_routing_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone( pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Require header. + */ +PJ_DEF(pjsip_require_hdr*) pjsip_require_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_require_hdr *hdr = (pjsip_require_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_REQUIRE, &generic_array_hdr_vptr); + hdr->count = 0; + return hdr; +} + +PJ_DEF(pjsip_require_hdr*) pjsip_require_hdr_create(pj_pool_t *pool) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_require_hdr)); + return pjsip_require_hdr_init(pool, mem); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Retry-After header. + */ +static int pjsip_retry_after_hdr_print(pjsip_retry_after_hdr *r, + char *buf, pj_size_t size ); +static pjsip_retry_after_hdr* pjsip_retry_after_hdr_clone(pj_pool_t *pool, + const pjsip_retry_after_hdr *r); +static pjsip_retry_after_hdr* +pjsip_retry_after_hdr_shallow_clone(pj_pool_t *pool, + const pjsip_retry_after_hdr *r ); + +static pjsip_hdr_vptr retry_after_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_retry_after_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_retry_after_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_retry_after_hdr_print, +}; + + +PJ_DEF(pjsip_retry_after_hdr*) pjsip_retry_after_hdr_init( pj_pool_t *pool, + void *mem, + int value ) +{ + pjsip_retry_after_hdr *hdr = (pjsip_retry_after_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_RETRY_AFTER, &retry_after_hdr_vptr); + hdr->ivalue = value; + hdr->comment.slen = 0; + pj_list_init(&hdr->param); + return hdr; +} + +PJ_DEF(pjsip_retry_after_hdr*) pjsip_retry_after_hdr_create(pj_pool_t *pool, + int value ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_retry_after_hdr)); + return pjsip_retry_after_hdr_init(pool, mem, value ); +} + + +static int pjsip_retry_after_hdr_print(pjsip_retry_after_hdr *hdr, + char *buf, pj_size_t size) +{ + char *p = buf; + char *endbuf = buf + size; + const pj_str_t *hname = &hdr->name; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + int printed; + + if ((pj_ssize_t)size < hdr->name.slen + 2+11) + return -1; + + pj_memcpy(p, hdr->name.ptr, hdr->name.slen); + p += hname->slen; + *p++ = ':'; + *p++ = ' '; + + p += pj_utoa(hdr->ivalue, p); + + if (hdr->comment.slen) { + pj_bool_t enclosed; + + if (endbuf-p < hdr->comment.slen + 3) + return -1; + + enclosed = (*hdr->comment.ptr == '('); + if (!enclosed) + *p++ = '('; + pj_memcpy(p, hdr->comment.ptr, hdr->comment.slen); + p += hdr->comment.slen; + if (!enclosed) + *p++ = ')'; + + if (!pj_list_empty(&hdr->param)) + *p++ = ' '; + } + + printed = pjsip_param_print_on(&hdr->param, p, endbuf-p, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, + ';'); + if (printed < 0) + return printed; + + p += printed; + + return p - buf; +} + +static pjsip_retry_after_hdr* pjsip_retry_after_hdr_clone(pj_pool_t *pool, + const pjsip_retry_after_hdr *rhs) +{ + pjsip_retry_after_hdr *hdr = pjsip_retry_after_hdr_create(pool, rhs->ivalue); + pj_strdup(pool, &hdr->comment, &rhs->comment); + pjsip_param_clone(pool, &hdr->param, &rhs->param); + return hdr; +} + +static pjsip_retry_after_hdr* +pjsip_retry_after_hdr_shallow_clone(pj_pool_t *pool, + const pjsip_retry_after_hdr *rhs) +{ + pjsip_retry_after_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_retry_after_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->param, &rhs->param); + return hdr; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Supported header. + */ +PJ_DEF(pjsip_supported_hdr*) pjsip_supported_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_supported_hdr *hdr = (pjsip_supported_hdr*) mem; + + PJ_UNUSED_ARG(pool); + init_hdr(hdr, PJSIP_H_SUPPORTED, &generic_array_hdr_vptr); + hdr->count = 0; + return hdr; +} + +PJ_DEF(pjsip_supported_hdr*) pjsip_supported_hdr_create(pj_pool_t *pool) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_supported_hdr)); + return pjsip_supported_hdr_init(pool, mem); +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Unsupported header. + */ +PJ_DEF(pjsip_unsupported_hdr*) pjsip_unsupported_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_unsupported_hdr *hdr = (pjsip_unsupported_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + init_hdr(hdr, PJSIP_H_UNSUPPORTED, &generic_array_hdr_vptr); + hdr->count = 0; + return hdr; +} + +PJ_DEF(pjsip_unsupported_hdr*) pjsip_unsupported_hdr_create(pj_pool_t *pool) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_unsupported_hdr)); + return pjsip_unsupported_hdr_init(pool, mem); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Via header. + */ +static int pjsip_via_hdr_print( pjsip_via_hdr *hdr, char *buf, pj_size_t size); +static pjsip_via_hdr* pjsip_via_hdr_clone( pj_pool_t *pool, const pjsip_via_hdr *hdr); +static pjsip_via_hdr* pjsip_via_hdr_shallow_clone( pj_pool_t *pool, const pjsip_via_hdr *hdr ); + +static pjsip_hdr_vptr via_hdr_vptr = +{ + (pjsip_hdr_clone_fptr) &pjsip_via_hdr_clone, + (pjsip_hdr_clone_fptr) &pjsip_via_hdr_shallow_clone, + (pjsip_hdr_print_fptr) &pjsip_via_hdr_print, +}; + +PJ_DEF(pjsip_via_hdr*) pjsip_via_hdr_init( pj_pool_t *pool, + void *mem ) +{ + pjsip_via_hdr *hdr = (pjsip_via_hdr*) mem; + + PJ_UNUSED_ARG(pool); + + pj_bzero(mem, sizeof(pjsip_via_hdr)); + init_hdr(hdr, PJSIP_H_VIA, &via_hdr_vptr); + hdr->ttl_param = -1; + hdr->rport_param = -1; + pj_list_init(&hdr->other_param); + return hdr; + +} + +PJ_DEF(pjsip_via_hdr*) pjsip_via_hdr_create( pj_pool_t *pool ) +{ + void *mem = pj_pool_alloc(pool, sizeof(pjsip_via_hdr)); + return pjsip_via_hdr_init(pool, mem); +} + +static int pjsip_via_hdr_print( pjsip_via_hdr *hdr, + char *buf, pj_size_t size) +{ + int printed; + char *startbuf = buf; + char *endbuf = buf + size; + pj_str_t sip_ver = { "SIP/2.0/", 8 }; + const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + if ((pj_ssize_t)size < hname->slen + sip_ver.slen + + hdr->transport.slen + hdr->sent_by.host.slen + 12) + { + return -1; + } + + /* pjsip_hdr_names */ + copy_advance(buf, (*hname)); + *buf++ = ':'; + *buf++ = ' '; + + /* SIP/2.0/transport host:port */ + pj_memcpy(buf, sip_ver.ptr, sip_ver.slen); + buf += sip_ver.slen; + //pj_memcpy(buf, hdr->transport.ptr, hdr->transport.slen); + /* Convert transport type to UPPERCASE (some endpoints want that) */ + { + int i; + for (i=0; i<hdr->transport.slen; ++i) { + buf[i] = (char)pj_toupper(hdr->transport.ptr[i]); + } + } + buf += hdr->transport.slen; + *buf++ = ' '; + + /* Check if host contains IPv6 */ + if (pj_memchr(hdr->sent_by.host.ptr, ':', hdr->sent_by.host.slen)) { + copy_advance_pair_quote_cond(buf, "", 0, hdr->sent_by.host, '[', ']'); + } else { + copy_advance_check(buf, hdr->sent_by.host); + } + + if (hdr->sent_by.port != 0) { + *buf++ = ':'; + printed = pj_utoa(hdr->sent_by.port, buf); + buf += printed; + } + + if (hdr->ttl_param >= 0) { + size = endbuf-buf; + if (size < 14) + return -1; + pj_memcpy(buf, ";ttl=", 5); + printed = pj_utoa(hdr->ttl_param, buf+5); + buf += printed + 5; + } + + if (hdr->rport_param >= 0) { + size = endbuf-buf; + if (size < 14) + return -1; + pj_memcpy(buf, ";rport", 6); + buf += 6; + if (hdr->rport_param > 0) { + *buf++ = '='; + buf += pj_utoa(hdr->rport_param, buf); + } + } + + + if (hdr->maddr_param.slen) { + /* Detect IPv6 IP address */ + if (pj_memchr(hdr->maddr_param.ptr, ':', hdr->maddr_param.slen)) { + copy_advance_pair_quote_cond(buf, ";maddr=", 7, hdr->maddr_param, + '[', ']'); + } else { + copy_advance_pair(buf, ";maddr=", 7, hdr->maddr_param); + } + } + + copy_advance_pair(buf, ";received=", 10, hdr->recvd_param); + copy_advance_pair_escape(buf, ";branch=", 8, hdr->branch_param, + pc->pjsip_TOKEN_SPEC); + + printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf, + &pc->pjsip_TOKEN_SPEC, + &pc->pjsip_TOKEN_SPEC, ';'); + if (printed < 0) + return -1; + buf += printed; + + return buf-startbuf; +} + +static pjsip_via_hdr* pjsip_via_hdr_clone( pj_pool_t *pool, + const pjsip_via_hdr *rhs) +{ + pjsip_via_hdr *hdr = pjsip_via_hdr_create(pool); + pj_strdup(pool, &hdr->transport, &rhs->transport); + pj_strdup(pool, &hdr->sent_by.host, &rhs->sent_by.host); + hdr->sent_by.port = rhs->sent_by.port; + hdr->ttl_param = rhs->ttl_param; + hdr->rport_param = rhs->rport_param; + pj_strdup(pool, &hdr->maddr_param, &rhs->maddr_param); + pj_strdup(pool, &hdr->recvd_param, &rhs->recvd_param); + pj_strdup(pool, &hdr->branch_param, &rhs->branch_param); + pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + +static pjsip_via_hdr* pjsip_via_hdr_shallow_clone( pj_pool_t *pool, + const pjsip_via_hdr *rhs ) +{ + pjsip_via_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_via_hdr); + pj_memcpy(hdr, rhs, sizeof(*hdr)); + pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param); + return hdr; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Warning header. + */ +PJ_DEF(pjsip_warning_hdr*) pjsip_warning_hdr_create( pj_pool_t *pool, + int code, + const pj_str_t *host, + const pj_str_t *text) +{ + const pj_str_t str_warning = { "Warning", 7 }; + pj_str_t hvalue; + + hvalue.ptr = (char*) pj_pool_alloc(pool, 10 + /* code */ + host->slen + 2 + /* host */ + text->slen + 2); /* text */ + hvalue.slen = pj_ansi_sprintf(hvalue.ptr, "%u %.*s \"%.*s\"", + code, (int)host->slen, host->ptr, + (int)text->slen, text->ptr); + + return pjsip_generic_string_hdr_create(pool, &str_warning, &hvalue); +} + +PJ_DEF(pjsip_warning_hdr*) pjsip_warning_hdr_create_from_status(pj_pool_t *pool, + const pj_str_t *host, + pj_status_t status) +{ + char errbuf[PJ_ERR_MSG_SIZE]; + pj_str_t text; + + text = pj_strerror(status, errbuf, sizeof(errbuf)); + return pjsip_warning_hdr_create(pool, 399, host, &text); +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Message body manipulations. + */ +PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size) +{ + if (size < msg_body->len) + return -1; + pj_memcpy(buf, msg_body->data, msg_body->len); + return msg_body->len; +} + +PJ_DEF(void*) pjsip_clone_text_data( pj_pool_t *pool, const void *data, + unsigned len) +{ + char *newdata = ""; + + if (len) { + newdata = (char*) pj_pool_alloc(pool, len); + pj_memcpy(newdata, data, len); + } + return newdata; +} + +PJ_DEF(pj_status_t) pjsip_msg_body_copy( pj_pool_t *pool, + pjsip_msg_body *dst_body, + const pjsip_msg_body *src_body ) +{ + /* First check if clone_data field is initialized. */ + PJ_ASSERT_RETURN( src_body->clone_data!=NULL, PJ_EINVAL ); + + /* Duplicate content-type */ + pjsip_media_type_cp(pool, &dst_body->content_type, + &src_body->content_type); + + /* Duplicate data. */ + dst_body->data = (*src_body->clone_data)(pool, src_body->data, + src_body->len ); + + /* Length. */ + dst_body->len = src_body->len; + + /* Function pointers. */ + dst_body->print_body = src_body->print_body; + dst_body->clone_data = src_body->clone_data; + + return PJ_SUCCESS; +} + + +PJ_DEF(pjsip_msg_body*) pjsip_msg_body_clone( pj_pool_t *pool, + const pjsip_msg_body *body ) +{ + pjsip_msg_body *new_body; + pj_status_t status; + + new_body = PJ_POOL_ALLOC_T(pool, pjsip_msg_body); + PJ_ASSERT_RETURN(new_body, NULL); + + status = pjsip_msg_body_copy(pool, new_body, body); + + return (status==PJ_SUCCESS) ? new_body : NULL; +} + + +PJ_DEF(pjsip_msg_body*) pjsip_msg_body_create( pj_pool_t *pool, + const pj_str_t *type, + const pj_str_t *subtype, + const pj_str_t *text ) +{ + pjsip_msg_body *body; + + PJ_ASSERT_RETURN(pool && type && subtype && text, NULL); + + body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + PJ_ASSERT_RETURN(body != NULL, NULL); + + pj_strdup(pool, &body->content_type.type, type); + pj_strdup(pool, &body->content_type.subtype, subtype); + pj_list_init(&body->content_type.param); + + body->data = pj_pool_alloc(pool, text->slen); + pj_memcpy(body->data, text->ptr, text->slen); + body->len = text->slen; + + body->clone_data = &pjsip_clone_text_data; + body->print_body = &pjsip_print_text_body; + + return body; +} + diff --git a/pjsip/src/pjsip/sip_multipart.c b/pjsip/src/pjsip/sip_multipart.c new file mode 100644 index 0000000..8fa9d75 --- /dev/null +++ b/pjsip/src/pjsip/sip_multipart.c @@ -0,0 +1,658 @@ +/* $Id: sip_multipart.c 3841 2011-10-24 09:28:13Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_multipart.h> +#include <pjsip/sip_parser.h> +#include <pjlib-util/scanner.h> +#include <pj/assert.h> +#include <pj/ctype.h> +#include <pj/errno.h> +#include <pj/except.h> +#include <pj/guid.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> + +#define THIS_FILE "sip_multipart.c" + +#define IS_SPACE(c) ((c)==' ' || (c)=='\t') + +#if 0 +# define TRACE_(x) PJ_LOG(4,x) +#else +# define TRACE_(x) +#endif + +extern pj_bool_t pjsip_use_compact_form; + +/* Type of "data" in multipart pjsip_msg_body */ +struct multipart_data +{ + pj_str_t boundary; + pjsip_multipart_part part_head; +}; + + +static int multipart_print_body(struct pjsip_msg_body *msg_body, + char *buf, pj_size_t size) +{ + const struct multipart_data *m_data; + pj_str_t clen_hdr = { "Content-Length: ", 16}; + pjsip_multipart_part *part; + char *p = buf, *end = buf+size; + +#define SIZE_LEFT() (end-p) + + m_data = (const struct multipart_data*)msg_body->data; + + PJ_ASSERT_RETURN(m_data && !pj_list_empty(&m_data->part_head), PJ_EINVAL); + + part = m_data->part_head.next; + while (part != &m_data->part_head) { + enum { CLEN_SPACE = 5 }; + char *clen_pos; + const pjsip_hdr *hdr; + + clen_pos = NULL; + + /* Print delimiter */ + if (SIZE_LEFT() <= (m_data->boundary.slen+8) << 1) + return -1; + *p++ = 13; *p++ = 10; *p++ = '-'; *p++ = '-'; + pj_memcpy(p, m_data->boundary.ptr, m_data->boundary.slen); + p += m_data->boundary.slen; + *p++ = 13; *p++ = 10; + + /* Print optional headers */ + hdr = part->hdr.next; + while (hdr != &part->hdr) { + int printed = pjsip_hdr_print_on((pjsip_hdr*)hdr, p, + SIZE_LEFT()-2); + if (printed < 0) + return -1; + p += printed; + *p++ = '\r'; + *p++ = '\n'; + hdr = hdr->next; + } + + /* Automaticly adds Content-Type and Content-Length headers, only + * if content_type is set in the message body. + */ + if (part->body && part->body->content_type.type.slen) { + pj_str_t ctype_hdr = { "Content-Type: ", 14}; + const pjsip_media_type *media = &part->body->content_type; + + if (pjsip_use_compact_form) { + ctype_hdr.ptr = "c: "; + ctype_hdr.slen = 3; + } + + /* Add Content-Type header. */ + if ( (end-p) < 24 + media->type.slen + media->subtype.slen) { + return -1; + } + pj_memcpy(p, ctype_hdr.ptr, ctype_hdr.slen); + p += ctype_hdr.slen; + p += pjsip_media_type_print(p, end-p, media); + *p++ = '\r'; + *p++ = '\n'; + + /* Add Content-Length header. */ + if ((end-p) < clen_hdr.slen + 12 + 2) { + return -1; + } + pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen); + p += clen_hdr.slen; + + /* Print blanks after "Content-Length:", this is where we'll put + * the content length value after we know the length of the + * body. + */ + pj_memset(p, ' ', CLEN_SPACE); + clen_pos = p; + p += CLEN_SPACE; + *p++ = '\r'; + *p++ = '\n'; + } + + /* Empty newline */ + *p++ = 13; *p++ = 10; + + /* Print the body */ + pj_assert(part->body != NULL); + if (part->body) { + int printed = part->body->print_body(part->body, p, SIZE_LEFT()); + if (printed < 0) + return -1; + p += printed; + + /* Now that we have the length of the body, print this to the + * Content-Length header. + */ + if (clen_pos) { + char tmp[16]; + int len; + + len = pj_utoa(printed, tmp); + if (len > CLEN_SPACE) len = CLEN_SPACE; + pj_memcpy(clen_pos+CLEN_SPACE-len, tmp, len); + } + } + + part = part->next; + } + + /* Print closing delimiter */ + if (SIZE_LEFT() < m_data->boundary.slen+8) + return -1; + *p++ = 13; *p++ = 10; *p++ = '-'; *p++ = '-'; + pj_memcpy(p, m_data->boundary.ptr, m_data->boundary.slen); + p += m_data->boundary.slen; + *p++ = '-'; *p++ = '-'; *p++ = 13; *p++ = 10; + +#undef SIZE_LEFT + + return p - buf; +} + +static void* multipart_clone_data(pj_pool_t *pool, const void *data, + unsigned len) +{ + const struct multipart_data *src; + struct multipart_data *dst; + const pjsip_multipart_part *src_part; + + PJ_UNUSED_ARG(len); + + src = (const struct multipart_data*) data; + dst = PJ_POOL_ALLOC_T(pool, struct multipart_data); + + pj_strdup(pool, &dst->boundary, &src->boundary); + + src_part = src->part_head.next; + while (src_part != &src->part_head) { + pjsip_multipart_part *dst_part; + const pjsip_hdr *src_hdr; + + dst_part = pjsip_multipart_create_part(pool); + + src_hdr = src_part->hdr.next; + while (src_hdr != &src_part->hdr) { + pjsip_hdr *dst_hdr = (pjsip_hdr*)pjsip_hdr_clone(pool, src_hdr); + pj_list_push_back(&dst_part->hdr, dst_hdr); + src_hdr = src_hdr->next; + } + + dst_part->body = pjsip_msg_body_clone(pool, src_part->body); + + pj_list_push_back(&dst->part_head, dst_part); + + src_part = src_part->next; + } + + return (void*)dst; +} + +/* + * Create an empty multipart body. + */ +PJ_DEF(pjsip_msg_body*) pjsip_multipart_create( pj_pool_t *pool, + const pjsip_media_type *ctype, + const pj_str_t *boundary) +{ + pjsip_msg_body *body; + pjsip_param *ctype_param; + struct multipart_data *mp_data; + pj_str_t STR_BOUNDARY = { "boundary", 8 }; + + PJ_ASSERT_RETURN(pool, NULL); + + body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + + /* content-type */ + if (ctype && ctype->type.slen) { + pjsip_media_type_cp(pool, &body->content_type, ctype); + } else { + pj_str_t STR_MULTIPART = {"multipart", 9}; + pj_str_t STR_MIXED = { "mixed", 5 }; + + pjsip_media_type_init(&body->content_type, + &STR_MULTIPART, &STR_MIXED); + } + + /* multipart data */ + mp_data = PJ_POOL_ZALLOC_T(pool, struct multipart_data); + pj_list_init(&mp_data->part_head); + if (boundary) { + pj_strdup(pool, &mp_data->boundary, boundary); + } else { + pj_create_unique_string(pool, &mp_data->boundary); + } + body->data = mp_data; + + /* Add ";boundary" parameter to content_type parameter. */ + ctype_param = pjsip_param_find(&body->content_type.param, &STR_BOUNDARY); + if (!ctype_param) { + ctype_param = PJ_POOL_ALLOC_T(pool, pjsip_param); + ctype_param->name = STR_BOUNDARY; + pj_list_push_back(&body->content_type.param, ctype_param); + } + ctype_param->value = mp_data->boundary; + + /* function pointers */ + body->print_body = &multipart_print_body; + body->clone_data = &multipart_clone_data; + + return body; +} + +/* + * Create an empty multipart part. + */ +PJ_DEF(pjsip_multipart_part*) pjsip_multipart_create_part(pj_pool_t *pool) +{ + pjsip_multipart_part *mp; + + mp = PJ_POOL_ZALLOC_T(pool, pjsip_multipart_part); + pj_list_init(&mp->hdr); + + return mp; +} + + +/* + * Deep clone. + */ +PJ_DEF(pjsip_multipart_part*) +pjsip_multipart_clone_part(pj_pool_t *pool, + const pjsip_multipart_part *src) +{ + pjsip_multipart_part *dst; + const pjsip_hdr *hdr; + + dst = pjsip_multipart_create_part(pool); + + hdr = src->hdr.next; + while (hdr != &src->hdr) { + pj_list_push_back(&dst->hdr, pjsip_hdr_clone(pool, hdr)); + hdr = hdr->next; + } + + dst->body = pjsip_msg_body_clone(pool, src->body); + + return dst; +} + + +/* + * Add a part into multipart bodies. + */ +PJ_DEF(pj_status_t) pjsip_multipart_add_part( pj_pool_t *pool, + pjsip_msg_body *mp, + pjsip_multipart_part *part) +{ + struct multipart_data *m_data; + + /* All params must be specified */ + PJ_ASSERT_RETURN(pool && mp && part, PJ_EINVAL); + + /* mp must really point to an actual multipart msg body */ + PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, PJ_EINVAL); + + /* The multipart part must contain a valid message body */ + PJ_ASSERT_RETURN(part->body && part->body->print_body, PJ_EINVAL); + + m_data = (struct multipart_data*)mp->data; + pj_list_push_back(&m_data->part_head, part); + + PJ_UNUSED_ARG(pool); + + return PJ_SUCCESS; +} + +/* + * Get the first part of multipart bodies. + */ +PJ_DEF(pjsip_multipart_part*) +pjsip_multipart_get_first_part(const pjsip_msg_body *mp) +{ + struct multipart_data *m_data; + + /* Must specify mandatory params */ + PJ_ASSERT_RETURN(mp, NULL); + + /* mp must really point to an actual multipart msg body */ + PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL); + + m_data = (struct multipart_data*)mp->data; + if (pj_list_empty(&m_data->part_head)) + return NULL; + + return m_data->part_head.next; +} + +/* + * Get the next part after the specified part. + */ +PJ_DEF(pjsip_multipart_part*) +pjsip_multipart_get_next_part(const pjsip_msg_body *mp, + pjsip_multipart_part *part) +{ + struct multipart_data *m_data; + + /* Must specify mandatory params */ + PJ_ASSERT_RETURN(mp && part, NULL); + + /* mp must really point to an actual multipart msg body */ + PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL); + + m_data = (struct multipart_data*)mp->data; + + /* the part parameter must be really member of the list */ + PJ_ASSERT_RETURN(pj_list_find_node(&m_data->part_head, part) != NULL, + NULL); + + if (part->next == &m_data->part_head) + return NULL; + + return part->next; +} + +/* + * Find a body inside multipart bodies which has the specified content type. + */ +PJ_DEF(pjsip_multipart_part*) +pjsip_multipart_find_part( const pjsip_msg_body *mp, + const pjsip_media_type *content_type, + const pjsip_multipart_part *start) +{ + struct multipart_data *m_data; + pjsip_multipart_part *part; + + /* Must specify mandatory params */ + PJ_ASSERT_RETURN(mp && content_type, NULL); + + /* mp must really point to an actual multipart msg body */ + PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL); + + m_data = (struct multipart_data*)mp->data; + + if (start) + part = start->next; + else + part = m_data->part_head.next; + + while (part != &m_data->part_head) { + if (pjsip_media_type_cmp(&part->body->content_type, + content_type, 0)==0) + { + return part; + } + part = part->next; + } + + return NULL; +} + +/* Parse a multipart part. "pct" is parent content-type */ +static pjsip_multipart_part *parse_multipart_part(pj_pool_t *pool, + char *start, + pj_size_t len, + const pjsip_media_type *pct) +{ + pjsip_multipart_part *part = pjsip_multipart_create_part(pool); + char *p = start, *end = start+len, *end_hdr = NULL, *start_body = NULL; + pjsip_ctype_hdr *ctype_hdr = NULL; + + TRACE_((THIS_FILE, "Parsing part: begin--\n%.*s\n--end", + (int)len, start)); + + /* Find the end of header area, by looking at an empty line */ + for (;;) { + while (p!=end && *p!='\n') ++p; + if (p==end) { + start_body = end; + break; + } + if ((p==start) || (p==start+1 && *(p-1)=='\r')) { + /* Empty header section */ + end_hdr = start; + start_body = ++p; + break; + } else if (p==end-1) { + /* Empty body section */ + end_hdr = end; + start_body = ++p; + } else if ((p>=start+1 && *(p-1)=='\n') || + (p>=start+2 && *(p-1)=='\r' && *(p-2)=='\n')) + { + /* Found it */ + end_hdr = (*(p-1)=='\r') ? (p-1) : p; + start_body = ++p; + break; + } else { + ++p; + } + } + + /* Parse the headers */ + if (end_hdr-start > 0) { + pjsip_hdr *hdr; + pj_status_t status; + + status = pjsip_parse_headers(pool, start, end_hdr-start, + &part->hdr, 0); + if (status != PJ_SUCCESS) { + PJ_PERROR(2,(THIS_FILE, status, "Warning: error parsing multipart" + " header")); + } + + /* Find Content-Type header */ + hdr = part->hdr.next; + while (hdr != &part->hdr) { + TRACE_((THIS_FILE, "Header parsed: %.*s", (int)hdr->name.slen, + hdr->name.ptr)); + if (hdr->type == PJSIP_H_CONTENT_TYPE) { + ctype_hdr = (pjsip_ctype_hdr*)hdr; + } + hdr = hdr->next; + } + } + + /* Assign the body */ + part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + if (ctype_hdr) { + pjsip_media_type_cp(pool, &part->body->content_type, &ctype_hdr->media); + } else if (pct && pj_stricmp2(&pct->subtype, "digest")==0) { + part->body->content_type.type = pj_str("message"); + part->body->content_type.subtype = pj_str("rfc822"); + } else { + part->body->content_type.type = pj_str("text"); + part->body->content_type.subtype = pj_str("plain"); + } + + if (start_body < end) { + part->body->data = start_body; + part->body->len = end - start_body; + } else { + part->body->data = (void*)""; + part->body->len = 0; + } + TRACE_((THIS_FILE, "Body parsed: \"%.*s\"", (int)part->body->len, + part->body->data)); + part->body->print_body = &pjsip_print_text_body; + part->body->clone_data = &pjsip_clone_text_data; + + return part; +} + +/* Public function to parse multipart message bodies into its parts */ +PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool, + char *buf, pj_size_t len, + const pjsip_media_type *ctype, + unsigned options) +{ + pj_str_t boundary, delim; + char *curptr, *endptr; + const pjsip_param *ctype_param; + const pj_str_t STR_BOUNDARY = { "boundary", 8 }; + pjsip_msg_body *body = NULL; + + PJ_ASSERT_RETURN(pool && buf && len && ctype && !options, NULL); + + TRACE_((THIS_FILE, "Started parsing multipart body")); + + /* Get the boundary value in the ctype */ + boundary.ptr = NULL; + boundary.slen = 0; + ctype_param = pjsip_param_find(&ctype->param, &STR_BOUNDARY); + if (ctype_param) { + boundary = ctype_param->value; + if (boundary.slen>2 && *boundary.ptr=='"') { + /* Remove quote */ + boundary.ptr++; + boundary.slen -= 2; + } + TRACE_((THIS_FILE, "Boundary is specified: '%.*s'", (int)boundary.slen, + boundary.ptr)); + } + + if (!boundary.slen) { + /* Boundary not found or not specified. Try to be clever, get + * the boundary from the body. + */ + char *p=buf, *end=buf+len; + + PJ_LOG(4,(THIS_FILE, "Warning: boundary parameter not found or " + "not specified when parsing multipart body")); + + /* Find the first "--". This "--" must be right after a CRLF, unless + * it really appears at the start of the buffer. + */ + for (;;) { + while (p!=end && *p!='-') ++p; + if (p!=end && *(p+1)=='-' && + ((p>buf && *(p-1)=='\n') || (p==buf))) + { + p+=2; + break; + } else { + ++p; + } + } + + if (p==end) { + /* Unable to determine boundary. Maybe this is not a multipart + * message? + */ + PJ_LOG(4,(THIS_FILE, "Error: multipart boundary not specified and" + " unable to calculate from the body")); + return NULL; + } + + boundary.ptr = p; + while (p!=end && !pj_isspace(*p)) ++p; + boundary.slen = p - boundary.ptr; + + TRACE_((THIS_FILE, "Boundary is calculated: '%.*s'", + (int)boundary.slen, boundary.ptr)); + } + + /* Build the delimiter: + * delimiter = "--" boundary + */ + delim.slen = boundary.slen+2; + delim.ptr = (char*)pj_pool_alloc(pool, (int)delim.slen); + delim.ptr[0] = '-'; + delim.ptr[1] = '-'; + pj_memcpy(delim.ptr+2, boundary.ptr, boundary.slen); + + /* Start parsing the body, skip until the first delimiter. */ + curptr = buf; + endptr = buf + len; + { + pj_str_t body; + + body.ptr = buf; body.slen = len; + curptr = pj_strstr(&body, &delim); + if (!curptr) + return NULL; + } + + body = pjsip_multipart_create(pool, ctype, &boundary); + + for (;;) { + char *start_body, *end_body; + pjsip_multipart_part *part; + + /* Eat the boundary */ + curptr += delim.slen; + if (*curptr=='-' && curptr<endptr-1 && *(curptr+1)=='-') { + /* Found the closing delimiter */ + curptr += 2; + break; + } + /* Optional whitespace after delimiter */ + while (curptr!=endptr && IS_SPACE(*curptr)) ++curptr; + /* Mandatory CRLF */ + if (*curptr=='\r') ++curptr; + if (*curptr!='\n') { + /* Expecting a newline here */ + return NULL; + } + ++curptr; + + /* We now in the start of the body */ + start_body = curptr; + + /* Find the next delimiter */ + { + pj_str_t subbody; + + subbody.ptr = curptr; subbody.slen = endptr - curptr; + curptr = pj_strstr(&subbody, &delim); + if (!curptr) { + /* We're really expecting end delimiter to be found. */ + return NULL; + } + } + + end_body = curptr; + + /* The newline preceeding the delimiter is conceptually part of + * the delimiter, so trim it from the body. + */ + if (*(end_body-1) == '\n') + --end_body; + if (*(end_body-1) == '\r') + --end_body; + + /* Now that we have determined the part's boundary, parse it + * to get the header and body part of the part. + */ + part = parse_multipart_part(pool, start_body, end_body - start_body, + ctype); + if (part) { + pjsip_multipart_add_part(pool, body, part); + } + } + + return body; +} + diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c new file mode 100644 index 0000000..2d46fe9 --- /dev/null +++ b/pjsip/src/pjsip/sip_parser.c @@ -0,0 +1,2379 @@ +/* $Id: sip_parser.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_parser.h> +#include <pjsip/sip_uri.h> +#include <pjsip/sip_msg.h> +#include <pjsip/sip_multipart.h> +#include <pjsip/sip_auth_parser.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_transport.h> /* rdata structure */ +#include <pjlib-util/scanner.h> +#include <pjlib-util/string.h> +#include <pj/except.h> +#include <pj/log.h> +#include <pj/hash.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/ctype.h> +#include <pj/assert.h> + +#define THIS_FILE "sip_parser.c" + +#define ALNUM +#define RESERVED ";/?:@&=+$," +#define MARK "-_.!~*'()" +#define UNRESERVED ALNUM MARK +#define ESCAPED "%" +#define USER_UNRESERVED "&=+$,;?/" +#define PASS "&=+$," +#define TOKEN "-.!%*_`'~+" /* '=' was removed for parsing + * param */ +#define HOST "_-." +#define HEX_DIGIT "abcdefABCDEF" +#define PARAM_CHAR "[]/:&+$" UNRESERVED ESCAPED +#define HNV_UNRESERVED "[]/?:+$" +#define HDR_CHAR HNV_UNRESERVED UNRESERVED ESCAPED + +/* A generic URI can consist of (For a complete BNF see RFC 2396): + #?;:@&=+-_.!~*'()%$,/ + */ +#define GENERIC_URI_CHARS "#?;:@&=+-_.!~*'()%$,/" "%" + +#define UNREACHED(expr) + +#define IS_NEWLINE(c) ((c)=='\r' || (c)=='\n') +#define IS_SPACE(c) ((c)==' ' || (c)=='\t') + +/* + * Header parser records. + */ +typedef struct handler_rec +{ + char hname[PJSIP_MAX_HNAME_LEN+1]; + pj_size_t hname_len; + pj_uint32_t hname_hash; + pjsip_parse_hdr_func *handler; +} handler_rec; + +static handler_rec handler[PJSIP_MAX_HEADER_TYPES]; +static unsigned handler_count; +static int parser_is_initialized; + +/* + * URI parser records. + */ +typedef struct uri_parser_rec +{ + pj_str_t scheme; + pjsip_parse_uri_func *parse; +} uri_parser_rec; + +static uri_parser_rec uri_handler[PJSIP_MAX_URI_TYPES]; +static unsigned uri_handler_count; + +/* + * Global vars (also extern). + */ +int PJSIP_SYN_ERR_EXCEPTION = -1; + +/* Parser constants */ +static pjsip_parser_const_t pconst = +{ + { "user", 4}, /* pjsip_USER_STR */ + { "method", 6}, /* pjsip_METHOD_STR */ + { "transport", 9}, /* pjsip_TRANSPORT_STR */ + { "maddr", 5 }, /* pjsip_MADDR_STR */ + { "lr", 2 }, /* pjsip_LR_STR */ + { "sip", 3 }, /* pjsip_SIP_STR */ + { "sips", 4 }, /* pjsip_SIPS_STR */ + { "tel", 3 }, /* pjsip_TEL_STR */ + { "branch", 6 }, /* pjsip_BRANCH_STR */ + { "ttl", 3 }, /* pjsip_TTL_STR */ + { "received", 8 }, /* pjsip_RECEIVED_STR */ + { "q", 1 }, /* pjsip_Q_STR */ + { "expires", 7 }, /* pjsip_EXPIRES_STR */ + { "tag", 3 }, /* pjsip_TAG_STR */ + { "rport", 5} /* pjsip_RPORT_STR */ +}; + +/* Character Input Specification buffer. */ +static pj_cis_buf_t cis_buf; + + +/* + * Forward decl. + */ +static pjsip_msg * int_parse_msg( pjsip_parse_ctx *ctx, + pjsip_parser_err_report *err_list); +static void int_parse_param( pj_scanner *scanner, + pj_pool_t *pool, + pj_str_t *pname, + pj_str_t *pvalue, + unsigned option); +static void int_parse_uri_param( pj_scanner *scanner, + pj_pool_t *pool, + pj_str_t *pname, + pj_str_t *pvalue, + unsigned option); +static void int_parse_hparam( pj_scanner *scanner, + pj_pool_t *pool, + pj_str_t *hname, + pj_str_t *hvalue ); +static void int_parse_req_line( pj_scanner *scanner, + pj_pool_t *pool, + pjsip_request_line *req_line); +static int int_is_next_user( pj_scanner *scanner); +static void int_parse_status_line( pj_scanner *scanner, + pjsip_status_line *line); +static void int_parse_user_pass( pj_scanner *scanner, + pj_pool_t *pool, + pj_str_t *user, + pj_str_t *pass); +static void int_parse_uri_host_port( pj_scanner *scanner, + pj_str_t *p_host, + int *p_port); +static pjsip_uri * int_parse_uri_or_name_addr( pj_scanner *scanner, + pj_pool_t *pool, + unsigned option); +static void* int_parse_sip_url( pj_scanner *scanner, + pj_pool_t *pool, + pj_bool_t parse_params); +static pjsip_name_addr * + int_parse_name_addr( pj_scanner *scanner, + pj_pool_t *pool ); +static void* int_parse_other_uri(pj_scanner *scanner, + pj_pool_t *pool, + pj_bool_t parse_params); +static void parse_hdr_end( pj_scanner *scanner ); + +static pjsip_hdr* parse_hdr_accept( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_allow( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_call_id( pjsip_parse_ctx *ctx); +static pjsip_hdr* parse_hdr_contact( pjsip_parse_ctx *ctx); +static pjsip_hdr* parse_hdr_content_len( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_content_type( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_cseq( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_expires( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_from( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_max_forwards( pjsip_parse_ctx *ctx); +static pjsip_hdr* parse_hdr_min_expires( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_rr( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_route( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_require( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_retry_after( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_supported( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_to( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_unsupported( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_via( pjsip_parse_ctx *ctx ); +static pjsip_hdr* parse_hdr_generic_string( pjsip_parse_ctx *ctx); + +/* Convert non NULL terminated string to integer. */ +static unsigned long pj_strtoul_mindigit(const pj_str_t *str, + unsigned mindig) +{ + unsigned long value; + unsigned i; + + value = 0; + for (i=0; i<(unsigned)str->slen; ++i) { + value = value * 10 + (str->ptr[i] - '0'); + } + for (; i<mindig; ++i) { + value = value * 10; + } + return value; +} + +/* Case insensitive comparison */ +#define parser_stricmp(s1, s2) (s1.slen!=s2.slen || pj_stricmp_alnum(&s1, &s2)) + + +/* Get a token and unescape */ +PJ_INLINE(void) parser_get_and_unescape(pj_scanner *scanner, pj_pool_t *pool, + const pj_cis_t *spec, + const pj_cis_t *unesc_spec, + pj_str_t *token) +{ +#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(spec); + pj_scan_get_unescape(scanner, unesc_spec, token); +#else + PJ_UNUSED_ARG(unesc_spec); + pj_scan_get(scanner, spec, token); + *token = pj_str_unescape(pool, token); +#endif +} + + + +/* Syntax error handler for parser. */ +static void on_syntax_error(pj_scanner *scanner) +{ + PJ_UNUSED_ARG(scanner); + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); +} + +/* Get parser constants. */ +PJ_DEF(const pjsip_parser_const_t*) pjsip_parser_const(void) +{ + return &pconst; +} + +/* Concatenate unrecognized params into single string. */ +PJ_DEF(void) pjsip_concat_param_imp(pj_str_t *param, pj_pool_t *pool, + const pj_str_t *pname, + const pj_str_t *pvalue, + int sepchar) +{ + char *new_param, *p; + int len; + + len = param->slen + pname->slen + pvalue->slen + 3; + p = new_param = (char*) pj_pool_alloc(pool, len); + + if (param->slen) { + int old_len = param->slen; + pj_memcpy(p, param->ptr, old_len); + p += old_len; + } + *p++ = (char)sepchar; + pj_memcpy(p, pname->ptr, pname->slen); + p += pname->slen; + + if (pvalue->slen) { + *p++ = '='; + pj_memcpy(p, pvalue->ptr, pvalue->slen); + p += pvalue->slen; + } + + *p = '\0'; + + param->ptr = new_param; + param->slen = p - new_param; +} + +/* Initialize static properties of the parser. */ +static pj_status_t init_parser() +{ + pj_status_t status; + + /* + * Syntax error exception number. + */ + pj_assert (PJSIP_SYN_ERR_EXCEPTION == -1); + status = pj_exception_id_alloc("PJSIP syntax error", + &PJSIP_SYN_ERR_EXCEPTION); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* + * Init character input spec (cis) + */ + + pj_cis_buf_init(&cis_buf); + + status = pj_cis_init(&cis_buf, &pconst.pjsip_DIGIT_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_num(&pconst.pjsip_DIGIT_SPEC); + + status = pj_cis_init(&cis_buf, &pconst.pjsip_ALPHA_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_alpha( &pconst.pjsip_ALPHA_SPEC ); + + status = pj_cis_init(&cis_buf, &pconst.pjsip_ALNUM_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_alpha( &pconst.pjsip_ALNUM_SPEC ); + pj_cis_add_num( &pconst.pjsip_ALNUM_SPEC ); + + status = pj_cis_init(&cis_buf, &pconst.pjsip_NOT_NEWLINE); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str(&pconst.pjsip_NOT_NEWLINE, "\r\n"); + pj_cis_invert(&pconst.pjsip_NOT_NEWLINE); + + status = pj_cis_init(&cis_buf, &pconst.pjsip_NOT_COMMA_OR_NEWLINE); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str( &pconst.pjsip_NOT_COMMA_OR_NEWLINE, ",\r\n"); + pj_cis_invert(&pconst.pjsip_NOT_COMMA_OR_NEWLINE); + + status = pj_cis_dup(&pconst.pjsip_TOKEN_SPEC, &pconst.pjsip_ALNUM_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str( &pconst.pjsip_TOKEN_SPEC, TOKEN); + + status = pj_cis_dup(&pconst.pjsip_TOKEN_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_del_str(&pconst.pjsip_TOKEN_SPEC_ESC, "%"); + + status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC, &pconst.pjsip_TOKEN_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC, ":"); + + status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC_ESC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC, ":"); + + status = pj_cis_dup(&pconst.pjsip_HOST_SPEC, &pconst.pjsip_ALNUM_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str( &pconst.pjsip_HOST_SPEC, HOST); + + status = pj_cis_dup(&pconst.pjsip_HEX_SPEC, &pconst.pjsip_DIGIT_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str( &pconst.pjsip_HEX_SPEC, HEX_DIGIT); + + status = pj_cis_dup(&pconst.pjsip_PARAM_CHAR_SPEC, &pconst.pjsip_ALNUM_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str(&pconst.pjsip_PARAM_CHAR_SPEC, PARAM_CHAR); + + status = pj_cis_dup(&pconst.pjsip_PARAM_CHAR_SPEC_ESC, &pconst.pjsip_PARAM_CHAR_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_del_str(&pconst.pjsip_PARAM_CHAR_SPEC_ESC, ESCAPED); + + status = pj_cis_dup(&pconst.pjsip_HDR_CHAR_SPEC, &pconst.pjsip_ALNUM_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str(&pconst.pjsip_HDR_CHAR_SPEC, HDR_CHAR); + + status = pj_cis_dup(&pconst.pjsip_HDR_CHAR_SPEC_ESC, &pconst.pjsip_HDR_CHAR_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_del_str(&pconst.pjsip_HDR_CHAR_SPEC_ESC, ESCAPED); + + status = pj_cis_dup(&pconst.pjsip_USER_SPEC, &pconst.pjsip_ALNUM_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str( &pconst.pjsip_USER_SPEC, UNRESERVED ESCAPED USER_UNRESERVED ); + + status = pj_cis_dup(&pconst.pjsip_USER_SPEC_ESC, &pconst.pjsip_USER_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_del_str( &pconst.pjsip_USER_SPEC_ESC, ESCAPED); + + status = pj_cis_dup(&pconst.pjsip_USER_SPEC_LENIENT, &pconst.pjsip_USER_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str(&pconst.pjsip_USER_SPEC_LENIENT, "#"); + + status = pj_cis_dup(&pconst.pjsip_USER_SPEC_LENIENT_ESC, &pconst.pjsip_USER_SPEC_ESC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str(&pconst.pjsip_USER_SPEC_LENIENT_ESC, "#"); + + status = pj_cis_dup(&pconst.pjsip_PASSWD_SPEC, &pconst.pjsip_ALNUM_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str( &pconst.pjsip_PASSWD_SPEC, UNRESERVED ESCAPED PASS); + + status = pj_cis_dup(&pconst.pjsip_PASSWD_SPEC_ESC, &pconst.pjsip_PASSWD_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_del_str( &pconst.pjsip_PASSWD_SPEC_ESC, ESCAPED); + + status = pj_cis_init(&cis_buf, &pconst.pjsip_PROBE_USER_HOST_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str( &pconst.pjsip_PROBE_USER_HOST_SPEC, "@ \n>"); + pj_cis_invert( &pconst.pjsip_PROBE_USER_HOST_SPEC ); + + status = pj_cis_init(&cis_buf, &pconst.pjsip_DISPLAY_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str( &pconst.pjsip_DISPLAY_SPEC, ":\r\n<"); + pj_cis_invert(&pconst.pjsip_DISPLAY_SPEC); + + status = pj_cis_dup(&pconst.pjsip_OTHER_URI_CONTENT, &pconst.pjsip_ALNUM_SPEC); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + pj_cis_add_str( &pconst.pjsip_OTHER_URI_CONTENT, GENERIC_URI_CHARS); + + /* + * Register URI parsers. + */ + + status = pjsip_register_uri_parser("sip", &int_parse_sip_url); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_uri_parser("sips", &int_parse_sip_url); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* + * Register header parsers. + */ + + status = pjsip_register_hdr_parser( "Accept", NULL, &parse_hdr_accept); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Allow", NULL, &parse_hdr_allow); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Call-ID", "i", &parse_hdr_call_id); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Contact", "m", &parse_hdr_contact); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Content-Length", "l", + &parse_hdr_content_len); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Content-Type", "c", + &parse_hdr_content_type); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "CSeq", NULL, &parse_hdr_cseq); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Expires", NULL, &parse_hdr_expires); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "From", "f", &parse_hdr_from); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Max-Forwards", NULL, + &parse_hdr_max_forwards); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Min-Expires", NULL, + &parse_hdr_min_expires); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Record-Route", NULL, &parse_hdr_rr); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Route", NULL, &parse_hdr_route); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Require", NULL, &parse_hdr_require); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Retry-After", NULL, + &parse_hdr_retry_after); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Supported", "k", + &parse_hdr_supported); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "To", "t", &parse_hdr_to); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Unsupported", NULL, + &parse_hdr_unsupported); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_register_hdr_parser( "Via", "v", &parse_hdr_via); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* + * Register auth parser. + */ + + status = pjsip_auth_init_parser(); + + return status; +} + +void init_sip_parser(void) +{ + pj_enter_critical_section(); + if (++parser_is_initialized == 1) { + init_parser(); + } + pj_leave_critical_section(); +} + +void deinit_sip_parser(void) +{ + pj_enter_critical_section(); + if (--parser_is_initialized == 0) { + /* Clear header handlers */ + pj_bzero(handler, sizeof(handler)); + handler_count = 0; + + /* Clear URI handlers */ + pj_bzero(uri_handler, sizeof(uri_handler)); + uri_handler_count = 0; + + /* Deregister exception ID */ + pj_exception_id_free(PJSIP_SYN_ERR_EXCEPTION); + PJSIP_SYN_ERR_EXCEPTION = -1; + } + pj_leave_critical_section(); +} + +/* Compare the handler record with header name, and return: + * - 0 if handler match. + * - <0 if handler is 'less' than the header name. + * - >0 if handler is 'greater' than header name. + */ +PJ_INLINE(int) compare_handler( const handler_rec *r1, + const char *name, + pj_size_t name_len, + pj_uint32_t hash ) +{ + PJ_UNUSED_ARG(name_len); + + /* Compare hashed value. */ + if (r1->hname_hash < hash) + return -1; + if (r1->hname_hash > hash) + return 1; + + /* Compare length. */ + /* + if (r1->hname_len < name_len) + return -1; + if (r1->hname_len > name_len) + return 1; + */ + + /* Equal length and equal hash. compare the strings. */ + return pj_memcmp(r1->hname, name, name_len); +} + +/* Register one handler for one header name. */ +static pj_status_t int_register_parser( const char *name, + pjsip_parse_hdr_func *fptr ) +{ + unsigned pos; + handler_rec rec; + + if (handler_count >= PJ_ARRAY_SIZE(handler)) { + pj_assert(!"Too many handlers!"); + return PJ_ETOOMANY; + } + + /* Initialize temporary handler. */ + rec.handler = fptr; + rec.hname_len = strlen(name); + if (rec.hname_len >= sizeof(rec.hname)) { + pj_assert(!"Header name is too long!"); + return PJ_ENAMETOOLONG; + } + /* Copy name. */ + pj_memcpy(rec.hname, name, rec.hname_len); + rec.hname[rec.hname_len] = '\0'; + + /* Calculate hash value. */ + rec.hname_hash = pj_hash_calc(0, rec.hname, rec.hname_len); + + /* Get the pos to insert the new handler. */ + for (pos=0; pos < handler_count; ++pos) { + int d; + d = compare_handler(&handler[pos], rec.hname, rec.hname_len, + rec.hname_hash); + if (d == 0) { + pj_assert(0); + return PJ_EEXISTS; + } + if (d > 0) { + break; + } + } + + /* Shift handlers. */ + if (pos != handler_count) { + pj_memmove( &handler[pos+1], &handler[pos], + (handler_count-pos)*sizeof(handler_rec)); + } + /* Add new handler. */ + pj_memcpy( &handler[pos], &rec, sizeof(handler_rec)); + ++handler_count; + + return PJ_SUCCESS; +} + +/* Register parser handler. If both header name and short name are valid, + * then two instances of handler will be registered. + */ +PJ_DEF(pj_status_t) pjsip_register_hdr_parser( const char *hname, + const char *hshortname, + pjsip_parse_hdr_func *fptr) +{ + unsigned i, len; + char hname_lcase[PJSIP_MAX_HNAME_LEN+1]; + pj_status_t status; + + /* Check that name is not too long */ + len = pj_ansi_strlen(hname); + if (len > PJSIP_MAX_HNAME_LEN) { + pj_assert(!"Header name is too long!"); + return PJ_ENAMETOOLONG; + } + + /* Register the normal Mixed-Case name */ + status = int_register_parser(hname, fptr); + if (status != PJ_SUCCESS) { + return status; + } + + /* Get the lower-case name */ + for (i=0; i<len; ++i) { + hname_lcase[i] = (char)pj_tolower(hname[i]); + } + hname_lcase[len] = '\0'; + + /* Register the lower-case version of the name */ + status = int_register_parser(hname_lcase, fptr); + if (status != PJ_SUCCESS) { + return status; + } + + + /* Register the shortname version of the name */ + if (hshortname) { + status = int_register_parser(hshortname, fptr); + if (status != PJ_SUCCESS) + return status; + } + return PJ_SUCCESS; +} + + +/* Find handler to parse the header name. */ +static pjsip_parse_hdr_func * find_handler_imp(pj_uint32_t hash, + const pj_str_t *hname) +{ + handler_rec *first; + int comp; + unsigned n; + + /* Binary search for the handler. */ + comp = -1; + first = &handler[0]; + n = handler_count; + for (; n > 0; ) { + unsigned half = n / 2; + handler_rec *mid = first + half; + + comp = compare_handler(mid, hname->ptr, hname->slen, hash); + if (comp < 0) { + first = ++mid; + n -= half + 1; + } else if (comp==0) { + first = mid; + break; + } else { + n = half; + } + } + + return comp==0 ? first->handler : NULL; +} + + +/* Find handler to parse the header name. */ +static pjsip_parse_hdr_func* find_handler(const pj_str_t *hname) +{ + pj_uint32_t hash; + char hname_copy[PJSIP_MAX_HNAME_LEN]; + pj_str_t tmp; + pjsip_parse_hdr_func *handler; + + if (hname->slen >= PJSIP_MAX_HNAME_LEN) { + /* Guaranteed not to be able to find handler. */ + return NULL; + } + + /* First, common case, try to find handler with exact name */ + hash = pj_hash_calc(0, hname->ptr, hname->slen); + handler = find_handler_imp(hash, hname); + if (handler) + return handler; + + + /* If not found, try converting the header name to lowercase and + * search again. + */ + hash = pj_hash_calc_tolower(0, hname_copy, hname); + tmp.ptr = hname_copy; + tmp.slen = hname->slen; + return find_handler_imp(hash, &tmp); +} + + +/* Find URI handler. */ +static pjsip_parse_uri_func* find_uri_handler(const pj_str_t *scheme) +{ + unsigned i; + for (i=0; i<uri_handler_count; ++i) { + if (parser_stricmp(uri_handler[i].scheme, (*scheme))==0) + return uri_handler[i].parse; + } + return &int_parse_other_uri; +} + +/* Register URI parser. */ +PJ_DEF(pj_status_t) pjsip_register_uri_parser( char *scheme, + pjsip_parse_uri_func *func) +{ + if (uri_handler_count >= PJ_ARRAY_SIZE(uri_handler)) + return PJ_ETOOMANY; + + uri_handler[uri_handler_count].scheme = pj_str((char*)scheme); + uri_handler[uri_handler_count].parse = func; + ++uri_handler_count; + + return PJ_SUCCESS; +} + +/* Public function to parse SIP message. */ +PJ_DEF(pjsip_msg*) pjsip_parse_msg( pj_pool_t *pool, + char *buf, pj_size_t size, + pjsip_parser_err_report *err_list) +{ + pjsip_msg *msg = NULL; + pj_scanner scanner; + pjsip_parse_ctx context; + + pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER, + &on_syntax_error); + + context.scanner = &scanner; + context.pool = pool; + context.rdata = NULL; + + msg = int_parse_msg(&context, err_list); + + pj_scan_fini(&scanner); + return msg; +} + +/* Public function to parse as rdata.*/ +PJ_DEF(pjsip_msg *) pjsip_parse_rdata( char *buf, pj_size_t size, + pjsip_rx_data *rdata ) +{ + pj_scanner scanner; + pjsip_parse_ctx context; + + pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER, + &on_syntax_error); + + context.scanner = &scanner; + context.pool = rdata->tp_info.pool; + context.rdata = rdata; + + rdata->msg_info.msg = int_parse_msg(&context, &rdata->msg_info.parse_err); + + pj_scan_fini(&scanner); + return rdata->msg_info.msg; +} + +/* Determine if a message has been received. */ +PJ_DEF(pj_bool_t) pjsip_find_msg( const char *buf, pj_size_t size, + pj_bool_t is_datagram, pj_size_t *msg_size) +{ +#if PJ_HAS_TCP + const char *hdr_end; + const char *body_start; + const char *pos; + const char *line; + int content_length = -1; + pj_str_t cur_msg; + const pj_str_t end_hdr = { "\n\r\n", 3}; + + *msg_size = size; + + /* For datagram, the whole datagram IS the message. */ + if (is_datagram) { + return PJ_SUCCESS; + } + + + /* Find the end of header area by finding an empty line. + * Don't use plain strstr() since we want to be able to handle + * NULL character in the message + */ + cur_msg.ptr = (char*)buf; cur_msg.slen = size; + pos = pj_strstr(&cur_msg, &end_hdr); + if (pos == NULL) { + return PJSIP_EPARTIALMSG; + } + + hdr_end = pos+1; + body_start = pos+3; + + /* Find "Content-Length" header the hard way. */ + line = pj_strchr(&cur_msg, '\n'); + while (line && line < hdr_end) { + ++line; + if ( ((*line=='C' || *line=='c') && + strnicmp_alnum(line, "Content-Length", 14) == 0) || + ((*line=='l' || *line=='L') && + (*(line+1)==' ' || *(line+1)=='\t' || *(line+1)==':'))) + { + /* Try to parse the header. */ + pj_scanner scanner; + PJ_USE_EXCEPTION; + + pj_scan_init(&scanner, (char*)line, hdr_end-line, + PJ_SCAN_AUTOSKIP_WS_HEADER, &on_syntax_error); + + PJ_TRY { + pj_str_t str_clen; + + /* Get "Content-Length" or "L" name */ + if (*line=='C' || *line=='c') + pj_scan_advance_n(&scanner, 14, PJ_TRUE); + else if (*line=='l' || *line=='L') + pj_scan_advance_n(&scanner, 1, PJ_TRUE); + + /* Get colon */ + if (pj_scan_get_char(&scanner) != ':') { + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + } + + /* Get number */ + pj_scan_get(&scanner, &pconst.pjsip_DIGIT_SPEC, &str_clen); + + /* Get newline. */ + pj_scan_get_newline(&scanner); + + /* Found a valid Content-Length header. */ + content_length = pj_strtoul(&str_clen); + } + PJ_CATCH_ANY { + content_length = -1; + } + PJ_END + + pj_scan_fini(&scanner); + } + + /* Found valid Content-Length? */ + if (content_length != -1) + break; + + /* Go to next line. */ + cur_msg.slen -= (line - cur_msg.ptr); + cur_msg.ptr = (char*)line; + line = pj_strchr(&cur_msg, '\n'); + } + + /* Found Content-Length? */ + if (content_length == -1) { + return PJSIP_EMISSINGHDR; + } + + /* Enough packet received? */ + *msg_size = (body_start - buf) + content_length; + return (*msg_size) <= size ? PJ_SUCCESS : PJSIP_EPARTIALMSG; +#else + PJ_UNUSED_ARG(buf); + PJ_UNUSED_ARG(is_datagram); + *msg_size = size; + return PJ_SUCCESS; +#endif +} + +/* Public function to parse URI */ +PJ_DEF(pjsip_uri*) pjsip_parse_uri( pj_pool_t *pool, + char *buf, pj_size_t size, + unsigned option) +{ + pj_scanner scanner; + pjsip_uri *uri = NULL; + PJ_USE_EXCEPTION; + + pj_scan_init(&scanner, buf, size, 0, &on_syntax_error); + + + PJ_TRY { + uri = int_parse_uri_or_name_addr(&scanner, pool, option); + } + PJ_CATCH_ANY { + uri = NULL; + } + PJ_END; + + /* Must have exhausted all inputs. */ + if (pj_scan_is_eof(&scanner) || IS_NEWLINE(*scanner.curptr)) { + /* Success. */ + pj_scan_fini(&scanner); + return uri; + } + + /* Still have some characters unparsed. */ + pj_scan_fini(&scanner); + return NULL; +} + +/* SIP version */ +static void parse_sip_version(pj_scanner *scanner) +{ + pj_str_t SIP = { "SIP", 3 }; + pj_str_t V2 = { "2.0", 3 }; + pj_str_t sip, version; + + pj_scan_get( scanner, &pconst.pjsip_ALPHA_SPEC, &sip); + if (pj_scan_get_char(scanner) != '/') + on_syntax_error(scanner); + pj_scan_get_n( scanner, 3, &version); + if (pj_stricmp(&sip, &SIP) || pj_stricmp(&version, &V2)) + on_syntax_error(scanner); +} + +static pj_bool_t is_next_sip_version(pj_scanner *scanner) +{ + pj_str_t SIP = { "SIP", 3 }; + pj_str_t sip; + int c; + + c = pj_scan_peek(scanner, &pconst.pjsip_ALPHA_SPEC, &sip); + /* return TRUE if it is "SIP" followed by "/" or space. + * we include space since the "/" may be separated by space, + * although this would mean it would return TRUE if it is a + * request and the method is "SIP"! + */ + return c && (c=='/' || c==' ' || c=='\t') && pj_stricmp(&sip, &SIP)==0; +} + +/* Internal function to parse SIP message */ +static pjsip_msg *int_parse_msg( pjsip_parse_ctx *ctx, + pjsip_parser_err_report *err_list) +{ + pj_bool_t parsing_headers; + pjsip_msg *msg = NULL; + pj_str_t hname; + pjsip_ctype_hdr *ctype_hdr = NULL; + pj_scanner *scanner = ctx->scanner; + pj_pool_t *pool = ctx->pool; + PJ_USE_EXCEPTION; + + parsing_headers = PJ_FALSE; + +retry_parse: + PJ_TRY + { + if (parsing_headers) + goto parse_headers; + + /* Skip leading newlines. */ + while (IS_NEWLINE(*scanner->curptr)) { + pj_scan_get_newline(scanner); + } + + /* Check if we still have valid packet. + * Sometimes endpoints just send blank (CRLF) packets just to keep + * NAT bindings open. + */ + if (pj_scan_is_eof(scanner)) + return NULL; + + /* Parse request or status line */ + if (is_next_sip_version(scanner)) { + msg = pjsip_msg_create(pool, PJSIP_RESPONSE_MSG); + int_parse_status_line( scanner, &msg->line.status ); + } else { + msg = pjsip_msg_create(pool, PJSIP_REQUEST_MSG); + int_parse_req_line(scanner, pool, &msg->line.req ); + } + + parsing_headers = PJ_TRUE; + +parse_headers: + /* Parse headers. */ + do { + pjsip_parse_hdr_func * handler; + pjsip_hdr *hdr = NULL; + + /* Init hname just in case parsing fails. + * Ref: PROTOS #2412 + */ + hname.slen = 0; + + /* Get hname. */ + pj_scan_get( scanner, &pconst.pjsip_TOKEN_SPEC, &hname); + if (pj_scan_get_char( scanner ) != ':') { + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + } + + /* Find handler. */ + handler = find_handler(&hname); + + /* Call the handler if found. + * If no handler is found, then treat the header as generic + * hname/hvalue pair. + */ + if (handler) { + hdr = (*handler)(ctx); + + /* Note: + * hdr MAY BE NULL, if parsing does not yield a new header + * instance, e.g. the values have been added to existing + * header. See http://trac.pjsip.org/repos/ticket/940 + */ + + /* Check if we've just parsed a Content-Type header. + * We will check for a message body if we've got Content-Type + * header. + */ + if (hdr && hdr->type == PJSIP_H_CONTENT_TYPE) { + ctype_hdr = (pjsip_ctype_hdr*)hdr; + } + + } else { + hdr = parse_hdr_generic_string(ctx); + hdr->name = hdr->sname = hname; + } + + + /* Single parse of header line can produce multiple headers. + * For example, if one Contact: header contains Contact list + * separated by comma, then these Contacts will be split into + * different Contact headers. + * So here we must insert list instead of just insert one header. + */ + if (hdr) + pj_list_insert_nodes_before(&msg->hdr, hdr); + + /* Parse until EOF or an empty line is found. */ + } while (!pj_scan_is_eof(scanner) && !IS_NEWLINE(*scanner->curptr)); + + parsing_headers = PJ_FALSE; + + /* If empty line is found, eat it. */ + if (!pj_scan_is_eof(scanner)) { + if (IS_NEWLINE(*scanner->curptr)) { + pj_scan_get_newline(scanner); + } + } + + /* If we have Content-Type header, treat the rest of the message + * as body. + */ + if (ctype_hdr && scanner->curptr!=scanner->end) { + /* New: if Content-Type indicates that this is a multipart + * message body, parse it. + */ + const pj_str_t STR_MULTIPART = { "multipart", 9 }; + pjsip_msg_body *body; + + if (pj_stricmp(&ctype_hdr->media.type, &STR_MULTIPART)==0) { + body = pjsip_multipart_parse(pool, scanner->curptr, + scanner->end - scanner->curptr, + &ctype_hdr->media, 0); + } else { + body = PJ_POOL_ALLOC_T(pool, pjsip_msg_body); + pjsip_media_type_cp(pool, &body->content_type, + &ctype_hdr->media); + + body->data = scanner->curptr; + body->len = scanner->end - scanner->curptr; + body->print_body = &pjsip_print_text_body; + body->clone_data = &pjsip_clone_text_data; + } + + msg->body = body; + } + } + PJ_CATCH_ANY + { + /* Exception was thrown during parsing. + * Skip until newline, and parse next header. + */ + if (err_list) { + pjsip_parser_err_report *err_info; + + err_info = PJ_POOL_ALLOC_T(pool, pjsip_parser_err_report); + err_info->except_code = PJ_GET_EXCEPTION(); + err_info->line = scanner->line; + /* Scanner's column is zero based, so add 1 */ + err_info->col = pj_scan_get_col(scanner) + 1; + if (parsing_headers) + err_info->hname = hname; + else if (msg && msg->type == PJSIP_REQUEST_MSG) + err_info->hname = pj_str("Request Line"); + else if (msg && msg->type == PJSIP_RESPONSE_MSG) + err_info->hname = pj_str("Status Line"); + else + err_info->hname.slen = 0; + + pj_list_insert_before(err_list, err_info); + } + + if (parsing_headers) { + if (!pj_scan_is_eof(scanner)) { + /* Skip until next line. + * Watch for header continuation. + */ + do { + pj_scan_skip_line(scanner); + } while (IS_SPACE(*scanner->curptr)); + } + + /* Restore flag. Flag may be set in int_parse_sip_url() */ + scanner->skip_ws = PJ_SCAN_AUTOSKIP_WS_HEADER; + + /* Continue parse next header, if any. */ + if (!pj_scan_is_eof(scanner) && !IS_NEWLINE(*scanner->curptr)) { + goto retry_parse; + } + } + + msg = NULL; + } + PJ_END; + + return msg; +} + + +/* Parse parameter (pname ["=" pvalue]). */ +static void parse_param_imp( pj_scanner *scanner, pj_pool_t *pool, + pj_str_t *pname, pj_str_t *pvalue, + const pj_cis_t *spec, const pj_cis_t *esc_spec, + unsigned option) +{ + /* pname */ + parser_get_and_unescape(scanner, pool, spec, esc_spec, pname); + + /* init pvalue */ + pvalue->ptr = NULL; + pvalue->slen = 0; + + /* pvalue, if any */ + if (*scanner->curptr == '=') { + pj_scan_get_char(scanner); + if (!pj_scan_is_eof(scanner)) { + /* pvalue can be a quoted string. */ + if (*scanner->curptr == '"') { + pj_scan_get_quote( scanner, '"', '"', pvalue); + if (option & PJSIP_PARSE_REMOVE_QUOTE) { + pvalue->ptr++; + pvalue->slen -= 2; + } + } else if (*scanner->curptr == '[') { + /* pvalue can be a quoted IPv6; in this case, the + * '[' and ']' quote characters are to be removed + * from the pvalue. + */ + pj_scan_get_char(scanner); + pj_scan_get_until_ch(scanner, ']', pvalue); + pj_scan_get_char(scanner); + } else if(pj_cis_match(spec, *scanner->curptr)) { + parser_get_and_unescape(scanner, pool, spec, esc_spec, pvalue); + } + } + } +} + +/* Parse parameter (pname ["=" pvalue]) using token. */ +PJ_DEF(void) pjsip_parse_param_imp(pj_scanner *scanner, pj_pool_t *pool, + pj_str_t *pname, pj_str_t *pvalue, + unsigned option) +{ + parse_param_imp(scanner, pool, pname, pvalue, &pconst.pjsip_TOKEN_SPEC, + &pconst.pjsip_TOKEN_SPEC_ESC, option); +} + + +/* Parse parameter (pname ["=" pvalue]) using paramchar. */ +PJ_DEF(void) pjsip_parse_uri_param_imp( pj_scanner *scanner, pj_pool_t *pool, + pj_str_t *pname, pj_str_t *pvalue, + unsigned option) +{ + parse_param_imp(scanner,pool, pname, pvalue, &pconst.pjsip_PARAM_CHAR_SPEC, + &pconst.pjsip_PARAM_CHAR_SPEC_ESC, option); +} + + +/* Parse parameter (";" pname ["=" pvalue]) in SIP header. */ +static void int_parse_param( pj_scanner *scanner, pj_pool_t *pool, + pj_str_t *pname, pj_str_t *pvalue, + unsigned option) +{ + /* Get ';' character */ + pj_scan_get_char(scanner); + + /* Get pname and optionally pvalue */ + pjsip_parse_param_imp(scanner, pool, pname, pvalue, option); +} + +/* Parse parameter (";" pname ["=" pvalue]) in URI. */ +static void int_parse_uri_param( pj_scanner *scanner, pj_pool_t *pool, + pj_str_t *pname, pj_str_t *pvalue, + unsigned option) +{ + /* Get ';' character */ + pj_scan_get_char(scanner); + + /* Get pname and optionally pvalue */ + pjsip_parse_uri_param_imp(scanner, pool, pname, pvalue, + option); +} + + +/* Parse header parameter. */ +static void int_parse_hparam( pj_scanner *scanner, pj_pool_t *pool, + pj_str_t *hname, pj_str_t *hvalue ) +{ + /* Get '?' or '&' character. */ + pj_scan_get_char(scanner); + + /* hname */ + parser_get_and_unescape(scanner, pool, &pconst.pjsip_HDR_CHAR_SPEC, + &pconst.pjsip_HDR_CHAR_SPEC_ESC, hname); + + /* Init hvalue */ + hvalue->ptr = NULL; + hvalue->slen = 0; + + /* pvalue, if any */ + if (*scanner->curptr == '=') { + pj_scan_get_char(scanner); + if (!pj_scan_is_eof(scanner) && + pj_cis_match(&pconst.pjsip_HDR_CHAR_SPEC, *scanner->curptr)) + { + parser_get_and_unescape(scanner, pool, &pconst.pjsip_HDR_CHAR_SPEC, + &pconst.pjsip_HDR_CHAR_SPEC_ESC, hvalue); + } + } +} + +/* Parse host part: + * host = hostname / IPv4address / IPv6reference + */ +static void int_parse_host(pj_scanner *scanner, pj_str_t *host) +{ + if (*scanner->curptr == '[') { + /* Note: the '[' and ']' characters are removed from the host */ + pj_scan_get_char(scanner); + pj_scan_get_until_ch(scanner, ']', host); + pj_scan_get_char(scanner); + } else { + pj_scan_get( scanner, &pconst.pjsip_HOST_SPEC, host); + } +} + +/* Parse host:port in URI. */ +static void int_parse_uri_host_port( pj_scanner *scanner, + pj_str_t *host, int *p_port) +{ + int_parse_host(scanner, host); + + /* RFC3261 section 19.1.2: host don't need to be unescaped */ + if (*scanner->curptr == ':') { + pj_str_t port; + pj_scan_get_char(scanner); + pj_scan_get(scanner, &pconst.pjsip_DIGIT_SPEC, &port); + *p_port = pj_strtoul(&port); + } else { + *p_port = 0; + } +} + +/* Determine if the next token in an URI is a user specification. */ +static int int_is_next_user(pj_scanner *scanner) +{ + pj_str_t dummy; + int is_user; + + /* Find character '@'. If this character exist, then the token + * must be a username. + */ + if (pj_scan_peek( scanner, &pconst.pjsip_PROBE_USER_HOST_SPEC, &dummy) == '@') + is_user = 1; + else + is_user = 0; + + return is_user; +} + +/* Parse user:pass tokens in an URI. */ +static void int_parse_user_pass( pj_scanner *scanner, pj_pool_t *pool, + pj_str_t *user, pj_str_t *pass) +{ + parser_get_and_unescape(scanner, pool, &pconst.pjsip_USER_SPEC_LENIENT, + &pconst.pjsip_USER_SPEC_LENIENT_ESC, user); + + if ( *scanner->curptr == ':') { + pj_scan_get_char( scanner ); + parser_get_and_unescape(scanner, pool, &pconst.pjsip_PASSWD_SPEC, + &pconst.pjsip_PASSWD_SPEC_ESC, pass); + } else { + pass->ptr = NULL; + pass->slen = 0; + } + + /* Get the '@' */ + pj_scan_get_char( scanner ); +} + +/* Parse all types of URI. */ +static pjsip_uri *int_parse_uri_or_name_addr( pj_scanner *scanner, pj_pool_t *pool, + unsigned opt) +{ + pjsip_uri *uri; + int is_name_addr = 0; + + /* Exhaust any whitespaces. */ + pj_scan_skip_whitespace(scanner); + + if (*scanner->curptr=='"' || *scanner->curptr=='<') { + uri = (pjsip_uri*)int_parse_name_addr( scanner, pool ); + is_name_addr = 1; + } else { + pj_str_t scheme; + int next_ch; + + next_ch = pj_scan_peek( scanner, &pconst.pjsip_DISPLAY_SPEC, &scheme); + + if (next_ch==':') { + pjsip_parse_uri_func *func = find_uri_handler(&scheme); + + if (func == NULL) { + /* Unsupported URI scheme */ + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + } + + uri = (pjsip_uri*) + (*func)(scanner, pool, + (opt & PJSIP_PARSE_URI_IN_FROM_TO_HDR)==0); + + + } else { + uri = (pjsip_uri*)int_parse_name_addr( scanner, pool ); + is_name_addr = 1; + } + } + + /* Should we return the URI object as name address? */ + if (opt & PJSIP_PARSE_URI_AS_NAMEADDR) { + if (is_name_addr == 0) { + pjsip_name_addr *name_addr; + + name_addr = pjsip_name_addr_create(pool); + name_addr->uri = uri; + + uri = (pjsip_uri*)name_addr; + } + } + + return uri; +} + +/* Parse URI. */ +static pjsip_uri *int_parse_uri(pj_scanner *scanner, pj_pool_t *pool, + pj_bool_t parse_params) +{ + /* Bug: + * This function should not call back int_parse_name_addr() because + * it is called by that function. This would cause stack overflow + * with PROTOS test #1223. + if (*scanner->curptr=='"' || *scanner->curptr=='<') { + return (pjsip_uri*)int_parse_name_addr( scanner, pool ); + } else { + */ + pj_str_t scheme; + int colon; + pjsip_parse_uri_func *func; + + /* Get scheme. */ + colon = pj_scan_peek(scanner, &pconst.pjsip_TOKEN_SPEC, &scheme); + if (colon != ':') { + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + } + + func = find_uri_handler(&scheme); + if (func) { + return (pjsip_uri*)(*func)(scanner, pool, parse_params); + + } else { + /* Unsupported URI scheme */ + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + UNREACHED({ return NULL; /* Not reached. */ }) + } + + /* + } + */ +} + +/* Parse "sip:" and "sips:" URI. + * This actually returns (pjsip_sip_uri*) type, + */ +static void* int_parse_sip_url( pj_scanner *scanner, + pj_pool_t *pool, + pj_bool_t parse_params) +{ + pj_str_t scheme; + pjsip_sip_uri *url = NULL; + int colon; + int skip_ws = scanner->skip_ws; + scanner->skip_ws = 0; + + pj_scan_get(scanner, &pconst.pjsip_TOKEN_SPEC, &scheme); + colon = pj_scan_get_char(scanner); + if (colon != ':') { + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + } + + if (parser_stricmp(scheme, pconst.pjsip_SIP_STR)==0) { + url = pjsip_sip_uri_create(pool, 0); + + } else if (parser_stricmp(scheme, pconst.pjsip_SIPS_STR)==0) { + url = pjsip_sip_uri_create(pool, 1); + + } else { + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + /* should not reach here */ + UNREACHED({ + pj_assert(0); + return 0; + }) + } + + if (int_is_next_user(scanner)) { + int_parse_user_pass(scanner, pool, &url->user, &url->passwd); + } + + /* Get host:port */ + int_parse_uri_host_port(scanner, &url->host, &url->port); + + /* Get URL parameters. */ + if (parse_params) { + while (*scanner->curptr == ';' ) { + pj_str_t pname, pvalue; + + int_parse_uri_param( scanner, pool, &pname, &pvalue, 0); + + if (!parser_stricmp(pname, pconst.pjsip_USER_STR) && pvalue.slen) { + url->user_param = pvalue; + + } else if (!parser_stricmp(pname, pconst.pjsip_METHOD_STR) && pvalue.slen) { + url->method_param = pvalue; + + } else if (!parser_stricmp(pname, pconst.pjsip_TRANSPORT_STR) && pvalue.slen) { + url->transport_param = pvalue; + + } else if (!parser_stricmp(pname, pconst.pjsip_TTL_STR) && pvalue.slen) { + url->ttl_param = pj_strtoul(&pvalue); + + } else if (!parser_stricmp(pname, pconst.pjsip_MADDR_STR) && pvalue.slen) { + url->maddr_param = pvalue; + + } else if (!parser_stricmp(pname, pconst.pjsip_LR_STR)) { + url->lr_param = 1; + + } else { + pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param); + p->name = pname; + p->value = pvalue; + pj_list_insert_before(&url->other_param, p); + } + } + } + + /* Get header params. */ + if (parse_params && *scanner->curptr == '?') { + do { + pjsip_param *param; + param = PJ_POOL_ALLOC_T(pool, pjsip_param); + int_parse_hparam(scanner, pool, ¶m->name, ¶m->value); + pj_list_insert_before(&url->header_param, param); + } while (*scanner->curptr == '&'); + } + + scanner->skip_ws = skip_ws; + pj_scan_skip_whitespace(scanner); + return url; +} + +/* Parse nameaddr. */ +static pjsip_name_addr *int_parse_name_addr( pj_scanner *scanner, + pj_pool_t *pool ) +{ + int has_bracket; + pjsip_name_addr *name_addr; + + name_addr = pjsip_name_addr_create(pool); + + if (*scanner->curptr == '"') { + pj_scan_get_quote( scanner, '"', '"', &name_addr->display); + /* Trim the leading and ending quote */ + name_addr->display.ptr++; + name_addr->display.slen -= 2; + + } else if (*scanner->curptr != '<') { + int next; + pj_str_t dummy; + + /* This can be either the start of display name, + * the start of URL ("sip:", "sips:", "tel:", etc.), or '<' char. + * We're only interested in display name, because SIP URL + * will be parser later. + */ + next = pj_scan_peek(scanner, &pconst.pjsip_DISPLAY_SPEC, &dummy); + if (next == '<') { + /* Ok, this is what we're looking for, a display name. */ + pj_scan_get_until_ch( scanner, '<', &name_addr->display); + pj_strtrim(&name_addr->display); + } + } + + /* Manually skip whitespace. */ + pj_scan_skip_whitespace(scanner); + + /* Get the SIP-URL */ + has_bracket = (*scanner->curptr == '<'); + if (has_bracket) + pj_scan_get_char(scanner); + name_addr->uri = int_parse_uri( scanner, pool, PJ_TRUE ); + if (has_bracket) { + if (pj_scan_get_char(scanner) != '>') + PJ_THROW( PJSIP_SYN_ERR_EXCEPTION); + } + + return name_addr; +} + + +/* Parse other URI */ +static void* int_parse_other_uri(pj_scanner *scanner, + pj_pool_t *pool, + pj_bool_t parse_params) +{ + pjsip_other_uri *uri = 0; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + int skip_ws = scanner->skip_ws; + + PJ_UNUSED_ARG(parse_params); + + scanner->skip_ws = 0; + + uri = pjsip_other_uri_create(pool); + + pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &uri->scheme); + if (pj_scan_get_char(scanner) != ':') { + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + } + + pj_scan_get(scanner, &pc->pjsip_OTHER_URI_CONTENT, &uri->content); + scanner->skip_ws = skip_ws; + + return uri; +} + + +/* Parse SIP request line. */ +static void int_parse_req_line( pj_scanner *scanner, pj_pool_t *pool, + pjsip_request_line *req_line) +{ + pj_str_t token; + + pj_scan_get( scanner, &pconst.pjsip_TOKEN_SPEC, &token); + pjsip_method_init_np( &req_line->method, &token); + + req_line->uri = int_parse_uri(scanner, pool, PJ_TRUE); + parse_sip_version(scanner); + pj_scan_get_newline( scanner ); +} + +/* Parse status line. */ +static void int_parse_status_line( pj_scanner *scanner, + pjsip_status_line *status_line) +{ + pj_str_t token; + + parse_sip_version(scanner); + pj_scan_get( scanner, &pconst.pjsip_DIGIT_SPEC, &token); + status_line->code = pj_strtoul(&token); + if (*scanner->curptr != '\r' && *scanner->curptr != '\n') + pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &status_line->reason); + else + status_line->reason.slen=0, status_line->reason.ptr=NULL; + pj_scan_get_newline( scanner ); +} + + +/* + * Public API to parse SIP status line. + */ +PJ_DEF(pj_status_t) pjsip_parse_status_line( char *buf, pj_size_t size, + pjsip_status_line *status_line) +{ + pj_scanner scanner; + PJ_USE_EXCEPTION; + + pj_bzero(status_line, sizeof(*status_line)); + pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER, + &on_syntax_error); + + PJ_TRY { + int_parse_status_line(&scanner, status_line); + } + PJ_CATCH_ANY { + /* Tolerate the error if it is caused only by missing newline */ + if (status_line->code == 0 && status_line->reason.slen == 0) { + pj_scan_fini(&scanner); + return PJSIP_EINVALIDMSG; + } + } + PJ_END; + + pj_scan_fini(&scanner); + return PJ_SUCCESS; +} + + +/* Parse ending of header. */ +static void parse_hdr_end( pj_scanner *scanner ) +{ + if (pj_scan_is_eof(scanner)) { + ; /* Do nothing. */ + } else if (*scanner->curptr == '&') { + pj_scan_get_char(scanner); + } else { + pj_scan_get_newline(scanner); + } +} + +/* Parse ending of header. */ +PJ_DEF(void) pjsip_parse_end_hdr_imp( pj_scanner *scanner ) +{ + parse_hdr_end(scanner); +} + +/* Parse generic array header. */ +static void parse_generic_array_hdr( pjsip_generic_array_hdr *hdr, + pj_scanner *scanner) +{ + /* Some header fields allow empty elements in the value: + * Accept, Allow, Supported + */ + if (pj_scan_is_eof(scanner) || + *scanner->curptr == '\r' || *scanner->curptr == '\n') + { + goto end; + } + + if (hdr->count >= PJ_ARRAY_SIZE(hdr->values)) { + /* Too many elements */ + on_syntax_error(scanner); + return; + } + + pj_scan_get( scanner, &pconst.pjsip_NOT_COMMA_OR_NEWLINE, + &hdr->values[hdr->count]); + hdr->count++; + + while (*scanner->curptr == ',') { + pj_scan_get_char(scanner); + pj_scan_get( scanner, &pconst.pjsip_NOT_COMMA_OR_NEWLINE, + &hdr->values[hdr->count]); + hdr->count++; + + if (hdr->count >= PJSIP_GENERIC_ARRAY_MAX_COUNT) + break; + } + +end: + parse_hdr_end(scanner); +} + +/* Parse generic string header. */ +static void parse_generic_string_hdr( pjsip_generic_string_hdr *hdr, + pjsip_parse_ctx *ctx) +{ + pj_scanner *scanner = ctx->scanner; + + hdr->hvalue.slen = 0; + + /* header may be mangled hence the loop */ + while (pj_cis_match(&pconst.pjsip_NOT_NEWLINE, *scanner->curptr)) { + pj_str_t next, tmp; + + pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &hdr->hvalue); + if (pj_scan_is_eof(scanner) || IS_NEWLINE(*scanner->curptr)) + break; + /* mangled, get next fraction */ + pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &next); + /* concatenate */ + tmp.ptr = (char*)pj_pool_alloc(ctx->pool, + hdr->hvalue.slen + next.slen + 2); + tmp.slen = 0; + pj_strcpy(&tmp, &hdr->hvalue); + pj_strcat2(&tmp, " "); + pj_strcat(&tmp, &next); + tmp.ptr[tmp.slen] = '\0'; + + hdr->hvalue = tmp; + } + + parse_hdr_end(scanner); +} + +/* Parse generic integer header. */ +static void parse_generic_int_hdr( pjsip_generic_int_hdr *hdr, + pj_scanner *scanner ) +{ + pj_str_t tmp; + pj_scan_get( scanner, &pconst.pjsip_DIGIT_SPEC, &tmp); + hdr->ivalue = pj_strtoul(&tmp); + parse_hdr_end(scanner); +} + + +/* Parse Accept header. */ +static pjsip_hdr* parse_hdr_accept(pjsip_parse_ctx *ctx) +{ + pjsip_accept_hdr *accept = pjsip_accept_hdr_create(ctx->pool); + parse_generic_array_hdr(accept, ctx->scanner); + return (pjsip_hdr*)accept; +} + +/* Parse Allow header. */ +static pjsip_hdr* parse_hdr_allow(pjsip_parse_ctx *ctx) +{ + pjsip_allow_hdr *allow = pjsip_allow_hdr_create(ctx->pool); + parse_generic_array_hdr(allow, ctx->scanner); + return (pjsip_hdr*)allow; +} + +/* Parse Call-ID header. */ +static pjsip_hdr* parse_hdr_call_id(pjsip_parse_ctx *ctx) +{ + pjsip_cid_hdr *hdr = pjsip_cid_hdr_create(ctx->pool); + pj_scan_get( ctx->scanner, &pconst.pjsip_NOT_NEWLINE, &hdr->id); + parse_hdr_end(ctx->scanner); + + if (ctx->rdata) + ctx->rdata->msg_info.cid = hdr; + + return (pjsip_hdr*)hdr; +} + +/* Parse and interpret Contact param. */ +static void int_parse_contact_param( pjsip_contact_hdr *hdr, + pj_scanner *scanner, + pj_pool_t *pool) +{ + while ( *scanner->curptr == ';' ) { + pj_str_t pname, pvalue; + + int_parse_param( scanner, pool, &pname, &pvalue, 0); + if (!parser_stricmp(pname, pconst.pjsip_Q_STR) && pvalue.slen) { + char *dot_pos = (char*) pj_memchr(pvalue.ptr, '.', pvalue.slen); + if (!dot_pos) { + hdr->q1000 = pj_strtoul(&pvalue) * 1000; + } else { + pj_str_t tmp = pvalue; + + tmp.slen = dot_pos - pvalue.ptr; + hdr->q1000 = pj_strtoul(&tmp) * 1000; + + pvalue.slen = (pvalue.ptr+pvalue.slen) - (dot_pos+1); + pvalue.ptr = dot_pos + 1; + hdr->q1000 += pj_strtoul_mindigit(&pvalue, 3); + } + } else if (!parser_stricmp(pname, pconst.pjsip_EXPIRES_STR) && pvalue.slen) { + hdr->expires = pj_strtoul(&pvalue); + + } else { + pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param); + p->name = pname; + p->value = pvalue; + pj_list_insert_before(&hdr->other_param, p); + } + } +} + +/* Parse Contact header. */ +static pjsip_hdr* parse_hdr_contact( pjsip_parse_ctx *ctx ) +{ + pjsip_contact_hdr *first = NULL; + pj_scanner *scanner = ctx->scanner; + + do { + pjsip_contact_hdr *hdr = pjsip_contact_hdr_create(ctx->pool); + if (first == NULL) + first = hdr; + else + pj_list_insert_before(first, hdr); + + if (*scanner->curptr == '*') { + pj_scan_get_char(scanner); + hdr->star = 1; + + } else { + hdr->star = 0; + hdr->uri = int_parse_uri_or_name_addr(scanner, ctx->pool, + PJSIP_PARSE_URI_AS_NAMEADDR | + PJSIP_PARSE_URI_IN_FROM_TO_HDR); + + int_parse_contact_param(hdr, scanner, ctx->pool); + } + + if (*scanner->curptr != ',') + break; + + pj_scan_get_char(scanner); + + } while (1); + + parse_hdr_end(scanner); + + return (pjsip_hdr*)first; +} + +/* Parse Content-Length header. */ +static pjsip_hdr* parse_hdr_content_len( pjsip_parse_ctx *ctx ) +{ + pj_str_t digit; + pjsip_clen_hdr *hdr; + + hdr = pjsip_clen_hdr_create(ctx->pool); + pj_scan_get(ctx->scanner, &pconst.pjsip_DIGIT_SPEC, &digit); + hdr->len = pj_strtoul(&digit); + parse_hdr_end(ctx->scanner); + + if (ctx->rdata) + ctx->rdata->msg_info.clen = hdr; + + return (pjsip_hdr*)hdr; +} + +/* Parse Content-Type header. */ +static pjsip_hdr* parse_hdr_content_type( pjsip_parse_ctx *ctx ) +{ + pjsip_ctype_hdr *hdr; + pj_scanner *scanner = ctx->scanner; + + hdr = pjsip_ctype_hdr_create(ctx->pool); + + /* Parse media type and subtype. */ + pj_scan_get(scanner, &pconst.pjsip_TOKEN_SPEC, &hdr->media.type); + pj_scan_get_char(scanner); + pj_scan_get(scanner, &pconst.pjsip_TOKEN_SPEC, &hdr->media.subtype); + + /* Parse media parameters */ + while (*scanner->curptr == ';') { + pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param); + int_parse_param(scanner, ctx->pool, ¶m->name, ¶m->value, 0); + pj_list_push_back(&hdr->media.param, param); + } + + parse_hdr_end(ctx->scanner); + + if (ctx->rdata) + ctx->rdata->msg_info.ctype = hdr; + + return (pjsip_hdr*)hdr; +} + +/* Parse CSeq header. */ +static pjsip_hdr* parse_hdr_cseq( pjsip_parse_ctx *ctx ) +{ + pj_str_t cseq, method; + pjsip_cseq_hdr *hdr; + + hdr = pjsip_cseq_hdr_create(ctx->pool); + pj_scan_get( ctx->scanner, &pconst.pjsip_DIGIT_SPEC, &cseq); + hdr->cseq = pj_strtoul(&cseq); + + pj_scan_get( ctx->scanner, &pconst.pjsip_TOKEN_SPEC, &method); + pjsip_method_init_np(&hdr->method, &method); + + parse_hdr_end( ctx->scanner ); + + if (ctx->rdata) + ctx->rdata->msg_info.cseq = hdr; + + return (pjsip_hdr*)hdr; +} + +/* Parse Expires header. */ +static pjsip_hdr* parse_hdr_expires(pjsip_parse_ctx *ctx) +{ + pjsip_expires_hdr *hdr = pjsip_expires_hdr_create(ctx->pool, 0); + parse_generic_int_hdr(hdr, ctx->scanner); + return (pjsip_hdr*)hdr; +} + +/* Parse From: or To: header. */ +static void parse_hdr_fromto( pj_scanner *scanner, + pj_pool_t *pool, + pjsip_from_hdr *hdr) +{ + hdr->uri = int_parse_uri_or_name_addr(scanner, pool, + PJSIP_PARSE_URI_AS_NAMEADDR | + PJSIP_PARSE_URI_IN_FROM_TO_HDR); + + while ( *scanner->curptr == ';' ) { + pj_str_t pname, pvalue; + + int_parse_param( scanner, pool, &pname, &pvalue, 0); + + if (!parser_stricmp(pname, pconst.pjsip_TAG_STR)) { + hdr->tag = pvalue; + + } else { + pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param); + p->name = pname; + p->value = pvalue; + pj_list_insert_before(&hdr->other_param, p); + } + } + + parse_hdr_end(scanner); +} + +/* Parse From: header. */ +static pjsip_hdr* parse_hdr_from( pjsip_parse_ctx *ctx ) +{ + pjsip_from_hdr *hdr = pjsip_from_hdr_create(ctx->pool); + parse_hdr_fromto(ctx->scanner, ctx->pool, hdr); + if (ctx->rdata) + ctx->rdata->msg_info.from = hdr; + + return (pjsip_hdr*)hdr; +} + +/* Parse Require: header. */ +static pjsip_hdr* parse_hdr_require( pjsip_parse_ctx *ctx ) +{ + pjsip_require_hdr *hdr; + pj_bool_t new_hdr = (ctx->rdata==NULL || + ctx->rdata->msg_info.require == NULL); + + if (ctx->rdata && ctx->rdata->msg_info.require) { + hdr = ctx->rdata->msg_info.require; + } else { + hdr = pjsip_require_hdr_create(ctx->pool); + if (ctx->rdata) + ctx->rdata->msg_info.require = hdr; + } + + parse_generic_array_hdr(hdr, ctx->scanner); + + return new_hdr ? (pjsip_hdr*)hdr : NULL; +} + +/* Parse Retry-After: header. */ +static pjsip_hdr* parse_hdr_retry_after(pjsip_parse_ctx *ctx) +{ + pjsip_retry_after_hdr *hdr; + pj_scanner *scanner = ctx->scanner; + pj_str_t tmp; + + hdr = pjsip_retry_after_hdr_create(ctx->pool, 0); + + pj_scan_get(scanner, &pconst.pjsip_DIGIT_SPEC, &tmp); + hdr->ivalue = pj_strtoul(&tmp); + + while (!pj_scan_is_eof(scanner) && *scanner->curptr!='\r' && + *scanner->curptr!='\n') + { + if (*scanner->curptr=='(') { + pj_scan_get_quote(scanner, '(', ')', &hdr->comment); + /* Trim the leading and ending parens */ + hdr->comment.ptr++; + hdr->comment.slen -= 2; + } else if (*scanner->curptr==';') { + pjsip_param *prm = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param); + int_parse_param(scanner, ctx->pool, &prm->name, &prm->value, 0); + pj_list_push_back(&hdr->param, prm); + } + } + + parse_hdr_end(scanner); + return (pjsip_hdr*)hdr; +} + +/* Parse Supported: header. */ +static pjsip_hdr* parse_hdr_supported(pjsip_parse_ctx *ctx) +{ + pjsip_supported_hdr *hdr; + pj_bool_t new_hdr = (ctx->rdata==NULL || + ctx->rdata->msg_info.supported == NULL); + + if (ctx->rdata && ctx->rdata->msg_info.supported) { + hdr = ctx->rdata->msg_info.supported; + } else { + hdr = pjsip_supported_hdr_create(ctx->pool); + if (ctx->rdata) + ctx->rdata->msg_info.supported = hdr; + } + + parse_generic_array_hdr(hdr, ctx->scanner); + return new_hdr ? (pjsip_hdr*)hdr : NULL; +} + +/* Parse To: header. */ +static pjsip_hdr* parse_hdr_to( pjsip_parse_ctx *ctx ) +{ + pjsip_to_hdr *hdr = pjsip_to_hdr_create(ctx->pool); + parse_hdr_fromto(ctx->scanner, ctx->pool, hdr); + + if (ctx->rdata) + ctx->rdata->msg_info.to = hdr; + + return (pjsip_hdr*)hdr; +} + +/* Parse Unsupported: header. */ +static pjsip_hdr* parse_hdr_unsupported(pjsip_parse_ctx *ctx) +{ + pjsip_unsupported_hdr *hdr = pjsip_unsupported_hdr_create(ctx->pool); + parse_generic_array_hdr(hdr, ctx->scanner); + return (pjsip_hdr*)hdr; +} + +/* Parse and interpret Via parameters. */ +static void int_parse_via_param( pjsip_via_hdr *hdr, pj_scanner *scanner, + pj_pool_t *pool) +{ + while ( *scanner->curptr == ';' ) { + pj_str_t pname, pvalue; + + //Parse with PARAM_CHAR instead, to allow IPv6 + //No, back to using int_parse_param() for the "`" character! + //int_parse_param( scanner, pool, &pname, &pvalue, 0); + //parse_param_imp(scanner, pool, &pname, &pvalue, + // &pconst.pjsip_TOKEN_SPEC, + // &pconst.pjsip_TOKEN_SPEC_ESC, 0); + //int_parse_param(scanner, pool, &pname, &pvalue, 0); + // This should be the correct one: + // added special spec for Via parameter, basically token plus + // ":" to allow IPv6 address in the received param. + pj_scan_get_char(scanner); + parse_param_imp(scanner, pool, &pname, &pvalue, + &pconst.pjsip_VIA_PARAM_SPEC, + &pconst.pjsip_VIA_PARAM_SPEC_ESC, + 0); + + if (!parser_stricmp(pname, pconst.pjsip_BRANCH_STR) && pvalue.slen) { + hdr->branch_param = pvalue; + + } else if (!parser_stricmp(pname, pconst.pjsip_TTL_STR) && pvalue.slen) { + hdr->ttl_param = pj_strtoul(&pvalue); + + } else if (!parser_stricmp(pname, pconst.pjsip_MADDR_STR) && pvalue.slen) { + hdr->maddr_param = pvalue; + + } else if (!parser_stricmp(pname, pconst.pjsip_RECEIVED_STR) && pvalue.slen) { + hdr->recvd_param = pvalue; + + } else if (!parser_stricmp(pname, pconst.pjsip_RPORT_STR)) { + if (pvalue.slen) + hdr->rport_param = pj_strtoul(&pvalue); + else + hdr->rport_param = 0; + } else { + pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param); + p->name = pname; + p->value = pvalue; + pj_list_insert_before(&hdr->other_param, p); + } + } +} + +/* Parse Max-Forwards header. */ +static pjsip_hdr* parse_hdr_max_forwards( pjsip_parse_ctx *ctx ) +{ + pjsip_max_fwd_hdr *hdr; + hdr = pjsip_max_fwd_hdr_create(ctx->pool, 0); + parse_generic_int_hdr(hdr, ctx->scanner); + + if (ctx->rdata) + ctx->rdata->msg_info.max_fwd = hdr; + + return (pjsip_hdr*)hdr; +} + +/* Parse Min-Expires header. */ +static pjsip_hdr* parse_hdr_min_expires(pjsip_parse_ctx *ctx) +{ + pjsip_min_expires_hdr *hdr; + hdr = pjsip_min_expires_hdr_create(ctx->pool, 0); + parse_generic_int_hdr(hdr, ctx->scanner); + return (pjsip_hdr*)hdr; +} + + +/* Parse Route: or Record-Route: header. */ +static void parse_hdr_rr_route( pj_scanner *scanner, pj_pool_t *pool, + pjsip_routing_hdr *hdr ) +{ + pjsip_name_addr *temp=int_parse_name_addr(scanner, pool); + + pj_memcpy(&hdr->name_addr, temp, sizeof(*temp)); + + while (*scanner->curptr == ';') { + pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param); + int_parse_param(scanner, pool, &p->name, &p->value, 0); + pj_list_insert_before(&hdr->other_param, p); + } +} + +/* Parse Record-Route header. */ +static pjsip_hdr* parse_hdr_rr( pjsip_parse_ctx *ctx) +{ + pjsip_rr_hdr *first = NULL; + pj_scanner *scanner = ctx->scanner; + + do { + pjsip_rr_hdr *hdr = pjsip_rr_hdr_create(ctx->pool); + if (!first) { + first = hdr; + } else { + pj_list_insert_before(first, hdr); + } + parse_hdr_rr_route(scanner, ctx->pool, hdr); + if (*scanner->curptr == ',') { + pj_scan_get_char(scanner); + } else { + break; + } + } while (1); + parse_hdr_end(scanner); + + if (ctx->rdata && ctx->rdata->msg_info.record_route==NULL) + ctx->rdata->msg_info.record_route = first; + + return (pjsip_hdr*)first; +} + +/* Parse Route: header. */ +static pjsip_hdr* parse_hdr_route( pjsip_parse_ctx *ctx ) +{ + pjsip_route_hdr *first = NULL; + pj_scanner *scanner = ctx->scanner; + + do { + pjsip_route_hdr *hdr = pjsip_route_hdr_create(ctx->pool); + if (!first) { + first = hdr; + } else { + pj_list_insert_before(first, hdr); + } + parse_hdr_rr_route(scanner, ctx->pool, hdr); + if (*scanner->curptr == ',') { + pj_scan_get_char(scanner); + } else { + break; + } + } while (1); + parse_hdr_end(scanner); + + if (ctx->rdata && ctx->rdata->msg_info.route==NULL) + ctx->rdata->msg_info.route = first; + + return (pjsip_hdr*)first; +} + +/* Parse Via: header. */ +static pjsip_hdr* parse_hdr_via( pjsip_parse_ctx *ctx ) +{ + pjsip_via_hdr *first = NULL; + pj_scanner *scanner = ctx->scanner; + + do { + pjsip_via_hdr *hdr = pjsip_via_hdr_create(ctx->pool); + if (!first) + first = hdr; + else + pj_list_insert_before(first, hdr); + + parse_sip_version(scanner); + if (pj_scan_get_char(scanner) != '/') + on_syntax_error(scanner); + + pj_scan_get( scanner, &pconst.pjsip_TOKEN_SPEC, &hdr->transport); + int_parse_host(scanner, &hdr->sent_by.host); + + if (*scanner->curptr==':') { + pj_str_t digit; + pj_scan_get_char(scanner); + pj_scan_get(scanner, &pconst.pjsip_DIGIT_SPEC, &digit); + hdr->sent_by.port = pj_strtoul(&digit); + } + + int_parse_via_param(hdr, scanner, ctx->pool); + + if (*scanner->curptr == '(') { + pj_scan_get_char(scanner); + pj_scan_get_until_ch( scanner, ')', &hdr->comment); + pj_scan_get_char( scanner ); + } + + if (*scanner->curptr != ',') + break; + + pj_scan_get_char(scanner); + + } while (1); + + parse_hdr_end(scanner); + + if (ctx->rdata && ctx->rdata->msg_info.via == NULL) + ctx->rdata->msg_info.via = first; + + return (pjsip_hdr*)first; +} + +/* Parse generic header. */ +static pjsip_hdr* parse_hdr_generic_string( pjsip_parse_ctx *ctx ) +{ + pjsip_generic_string_hdr *hdr; + + hdr = pjsip_generic_string_hdr_create(ctx->pool, NULL, NULL); + parse_generic_string_hdr(hdr, ctx); + return (pjsip_hdr*)hdr; + +} + +/* Public function to parse a header value. */ +PJ_DEF(void*) pjsip_parse_hdr( pj_pool_t *pool, const pj_str_t *hname, + char *buf, pj_size_t size, int *parsed_len ) +{ + pj_scanner scanner; + pjsip_hdr *hdr = NULL; + pjsip_parse_ctx context; + PJ_USE_EXCEPTION; + + pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER, + &on_syntax_error); + + context.scanner = &scanner; + context.pool = pool; + context.rdata = NULL; + + PJ_TRY { + pjsip_parse_hdr_func *handler = find_handler(hname); + if (handler) { + hdr = (*handler)(&context); + } else { + hdr = parse_hdr_generic_string(&context); + hdr->type = PJSIP_H_OTHER; + pj_strdup(pool, &hdr->name, hname); + hdr->sname = hdr->name; + } + + } + PJ_CATCH_ANY { + hdr = NULL; + } + PJ_END + + if (parsed_len) { + *parsed_len = (scanner.curptr - scanner.begin); + } + + pj_scan_fini(&scanner); + + return hdr; +} + +/* Parse multiple header lines */ +PJ_DEF(pj_status_t) pjsip_parse_headers( pj_pool_t *pool, char *input, + pj_size_t size, pjsip_hdr *hlist, + unsigned options) +{ + enum { STOP_ON_ERROR = 1 }; + pj_scanner scanner; + pjsip_parse_ctx ctx; + pj_str_t hname; + PJ_USE_EXCEPTION; + + pj_scan_init(&scanner, input, size, PJ_SCAN_AUTOSKIP_WS_HEADER, + &on_syntax_error); + + pj_bzero(&ctx, sizeof(ctx)); + ctx.scanner = &scanner; + ctx.pool = pool; + +retry_parse: + PJ_TRY + { + /* Parse headers. */ + do { + pjsip_parse_hdr_func * handler; + pjsip_hdr *hdr = NULL; + + /* Init hname just in case parsing fails. + * Ref: PROTOS #2412 + */ + hname.slen = 0; + + /* Get hname. */ + pj_scan_get( &scanner, &pconst.pjsip_TOKEN_SPEC, &hname); + if (pj_scan_get_char( &scanner ) != ':') { + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + } + + /* Find handler. */ + handler = find_handler(&hname); + + /* Call the handler if found. + * If no handler is found, then treat the header as generic + * hname/hvalue pair. + */ + if (handler) { + hdr = (*handler)(&ctx); + } else { + hdr = parse_hdr_generic_string(&ctx); + hdr->name = hdr->sname = hname; + } + + /* Single parse of header line can produce multiple headers. + * For example, if one Contact: header contains Contact list + * separated by comma, then these Contacts will be split into + * different Contact headers. + * So here we must insert list instead of just insert one header. + */ + if (hdr) + pj_list_insert_nodes_before(hlist, hdr); + + /* Parse until EOF or an empty line is found. */ + } while (!pj_scan_is_eof(&scanner) && !IS_NEWLINE(*scanner.curptr)); + + /* If empty line is found, eat it. */ + if (!pj_scan_is_eof(&scanner)) { + if (IS_NEWLINE(*scanner.curptr)) { + pj_scan_get_newline(&scanner); + } + } + } + PJ_CATCH_ANY + { + PJ_LOG(4,(THIS_FILE, "Error parsing header: '%.*s' line %d col %d", + (int)hname.slen, hname.ptr, scanner.line, + pj_scan_get_col(&scanner))); + + /* Exception was thrown during parsing. */ + if ((options & STOP_ON_ERROR) == STOP_ON_ERROR) { + pj_scan_fini(&scanner); + return PJSIP_EINVALIDHDR; + } + + /* Skip until newline, and parse next header. */ + if (!pj_scan_is_eof(&scanner)) { + /* Skip until next line. + * Watch for header continuation. + */ + do { + pj_scan_skip_line(&scanner); + } while (IS_SPACE(*scanner.curptr)); + } + + /* Restore flag. Flag may be set in int_parse_sip_url() */ + scanner.skip_ws = PJ_SCAN_AUTOSKIP_WS_HEADER; + + /* Continue parse next header, if any. */ + if (!pj_scan_is_eof(&scanner) && !IS_NEWLINE(*scanner.curptr)) { + goto retry_parse; + } + + } + PJ_END; + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsip/sip_parser_wrap.cpp b/pjsip/src/pjsip/sip_parser_wrap.cpp new file mode 100644 index 0000000..21f9718 --- /dev/null +++ b/pjsip/src/pjsip/sip_parser_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: sip_parser_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * 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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "sip_parser.c" diff --git a/pjsip/src/pjsip/sip_resolve.c b/pjsip/src/pjsip/sip_resolve.c new file mode 100644 index 0000000..f5efc1e --- /dev/null +++ b/pjsip/src/pjsip/sip_resolve.c @@ -0,0 +1,520 @@ +/* $Id: sip_resolve.c 4108 2012-04-27 01:32:12Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_resolve.h> +#include <pjsip/sip_transport.h> +#include <pjsip/sip_errno.h> +#include <pjlib-util/errno.h> +#include <pjlib-util/srv_resolver.h> +#include <pj/addr_resolv.h> +#include <pj/array.h> +#include <pj/assert.h> +#include <pj/ctype.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/rand.h> +#include <pj/string.h> + + +#define THIS_FILE "sip_resolve.c" + +#define ADDR_MAX_COUNT 8 + +struct naptr_target +{ + pj_str_t res_type; /**< e.g. "_sip._udp" */ + pj_str_t name; /**< Domain name. */ + pjsip_transport_type_e type; /**< Transport type. */ + unsigned order; /**< Order */ + unsigned pref; /**< Preference. */ +}; + +struct query +{ + char *objname; + + pj_dns_type query_type; + void *token; + pjsip_resolver_callback *cb; + pj_dns_async_query *object; + pj_status_t last_error; + + /* Original request: */ + struct { + pjsip_host_info target; + unsigned def_port; + } req; + + /* NAPTR records: */ + unsigned naptr_cnt; + struct naptr_target naptr[8]; +}; + + +struct pjsip_resolver_t +{ + pj_dns_resolver *res; +}; + + +static void srv_resolver_cb(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec); +static void dns_a_callback(void *user_data, + pj_status_t status, + pj_dns_parsed_packet *response); + + +/* + * Public API to create the resolver. + */ +PJ_DEF(pj_status_t) pjsip_resolver_create( pj_pool_t *pool, + pjsip_resolver_t **p_res) +{ + pjsip_resolver_t *resolver; + + PJ_ASSERT_RETURN(pool && p_res, PJ_EINVAL); + resolver = PJ_POOL_ZALLOC_T(pool, pjsip_resolver_t); + *p_res = resolver; + + return PJ_SUCCESS; +} + + +/* + * Public API to set the DNS resolver instance for the SIP resolver. + */ +PJ_DEF(pj_status_t) pjsip_resolver_set_resolver(pjsip_resolver_t *res, + pj_dns_resolver *dns_res) +{ +#if PJSIP_HAS_RESOLVER + res->res = dns_res; + return PJ_SUCCESS; +#else + PJ_UNUSED_ARG(res); + PJ_UNUSED_ARG(dns_res); + pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)"); + return PJ_EINVALIDOP; +#endif +} + + +/* + * Public API to get the internal DNS resolver. + */ +PJ_DEF(pj_dns_resolver*) pjsip_resolver_get_resolver(pjsip_resolver_t *res) +{ + return res->res; +} + + +/* + * Public API to create destroy the resolver + */ +PJ_DEF(void) pjsip_resolver_destroy(pjsip_resolver_t *resolver) +{ + if (resolver->res) { +#if PJSIP_HAS_RESOLVER + pj_dns_resolver_destroy(resolver->res, PJ_FALSE); +#endif + resolver->res = NULL; + } +} + +/* + * Internal: + * determine if an address is a valid IP address, and if it is, + * return the IP version (4 or 6). + */ +static int get_ip_addr_ver(const pj_str_t *host) +{ + pj_in_addr dummy; + pj_in6_addr dummy6; + + /* First check with inet_aton() */ + if (pj_inet_aton(host, &dummy) > 0) + return 4; + + /* Then check if this is an IPv6 address */ + if (pj_inet_pton(pj_AF_INET6(), host, &dummy6) == PJ_SUCCESS) + return 6; + + /* Not an IP address */ + return 0; +} + + +/* + * This is the main function for performing server resolution. + */ +PJ_DEF(void) pjsip_resolve( pjsip_resolver_t *resolver, + pj_pool_t *pool, + const pjsip_host_info *target, + void *token, + pjsip_resolver_callback *cb) +{ + pjsip_server_addresses svr_addr; + pj_status_t status = PJ_SUCCESS; + int ip_addr_ver; + struct query *query; + pjsip_transport_type_e type = target->type; + + /* Is it IP address or hostname? And if it's an IP, which version? */ + ip_addr_ver = get_ip_addr_ver(&target->addr.host); + + /* Set the transport type if not explicitly specified. + * RFC 3263 section 4.1 specify rules to set up this. + */ + if (type == PJSIP_TRANSPORT_UNSPECIFIED) { + if (ip_addr_ver || (target->addr.port != 0)) { +#if PJ_HAS_TCP + if (target->flag & PJSIP_TRANSPORT_SECURE) + { + type = PJSIP_TRANSPORT_TLS; + } else if (target->flag & PJSIP_TRANSPORT_RELIABLE) + { + type = PJSIP_TRANSPORT_TCP; + } else +#endif + { + type = PJSIP_TRANSPORT_UDP; + } + } else { + /* No type or explicit port is specified, and the address is + * not IP address. + * In this case, full NAPTR resolution must be performed. + * But we don't support it (yet). + */ +#if PJ_HAS_TCP + if (target->flag & PJSIP_TRANSPORT_SECURE) + { + type = PJSIP_TRANSPORT_TLS; + } else if (target->flag & PJSIP_TRANSPORT_RELIABLE) + { + type = PJSIP_TRANSPORT_TCP; + } else +#endif + { + type = PJSIP_TRANSPORT_UDP; + } + } + + /* Add IPv6 flag for IPv6 address */ + if (ip_addr_ver == 6) + type = (pjsip_transport_type_e)((int)type + PJSIP_TRANSPORT_IPV6); + } + + + /* If target is an IP address, or if resolver is not configured, + * we can just finish the resolution now using pj_gethostbyname() + */ + if (ip_addr_ver || resolver->res == NULL) { + char addr_str[PJ_INET6_ADDRSTRLEN+10]; + pj_uint16_t srv_port; + + if (ip_addr_ver != 0) { + /* Target is an IP address, no need to resolve */ + if (ip_addr_ver == 4) { + pj_sockaddr_init(pj_AF_INET(), &svr_addr.entry[0].addr, + NULL, 0); + pj_inet_aton(&target->addr.host, + &svr_addr.entry[0].addr.ipv4.sin_addr); + } else { + pj_sockaddr_init(pj_AF_INET6(), &svr_addr.entry[0].addr, + NULL, 0); + pj_inet_pton(pj_AF_INET6(), &target->addr.host, + &svr_addr.entry[0].addr.ipv6.sin6_addr); + } + } else { + pj_addrinfo ai; + unsigned count; + int af; + + PJ_LOG(5,(THIS_FILE, + "DNS resolver not available, target '%.*s:%d' type=%s " + "will be resolved with getaddrinfo()", + target->addr.host.slen, + target->addr.host.ptr, + target->addr.port, + pjsip_transport_get_type_name(target->type))); + + if (type & PJSIP_TRANSPORT_IPV6) { + af = pj_AF_INET6(); + } else { + af = pj_AF_INET(); + } + + /* Resolve */ + count = 1; + status = pj_getaddrinfo(af, &target->addr.host, &count, &ai); + if (status != PJ_SUCCESS) { + /* "Normalize" error to PJ_ERESOLVE. This is a special error + * because it will be translated to SIP status 502 by + * sip_transaction.c + */ + status = PJ_ERESOLVE; + goto on_error; + } + + svr_addr.entry[0].addr.addr.sa_family = (pj_uint16_t)af; + pj_memcpy(&svr_addr.entry[0].addr, &ai.ai_addr, + sizeof(pj_sockaddr)); + } + + /* Set the port number */ + if (target->addr.port == 0) { + srv_port = (pj_uint16_t) + pjsip_transport_get_default_port_for_type(type); + } else { + srv_port = (pj_uint16_t)target->addr.port; + } + pj_sockaddr_set_port(&svr_addr.entry[0].addr, srv_port); + + /* Call the callback. */ + PJ_LOG(5,(THIS_FILE, + "Target '%.*s:%d' type=%s resolved to " + "'%s' type=%s (%s)", + (int)target->addr.host.slen, + target->addr.host.ptr, + target->addr.port, + pjsip_transport_get_type_name(target->type), + pj_sockaddr_print(&svr_addr.entry[0].addr, addr_str, + sizeof(addr_str), 3), + pjsip_transport_get_type_name(type), + pjsip_transport_get_type_desc(type))); + svr_addr.count = 1; + svr_addr.entry[0].priority = 0; + svr_addr.entry[0].weight = 0; + svr_addr.entry[0].type = type; + svr_addr.entry[0].addr_len = pj_sockaddr_get_len(&svr_addr.entry[0].addr); + (*cb)(status, token, &svr_addr); + + /* Done. */ + return; + } + + /* Target is not an IP address so we need to resolve it. */ +#if PJSIP_HAS_RESOLVER + + /* Build the query state */ + query = PJ_POOL_ZALLOC_T(pool, struct query); + query->objname = THIS_FILE; + query->token = token; + query->cb = cb; + query->req.target = *target; + pj_strdup(pool, &query->req.target.addr.host, &target->addr.host); + + /* If port is not specified, start with SRV resolution + * (should be with NAPTR, but we'll do that later) + */ + PJ_TODO(SUPPORT_DNS_NAPTR); + + /* Build dummy NAPTR entry */ + query->naptr_cnt = 1; + pj_bzero(&query->naptr[0], sizeof(query->naptr[0])); + query->naptr[0].order = 0; + query->naptr[0].pref = 0; + query->naptr[0].type = type; + pj_strdup(pool, &query->naptr[0].name, &target->addr.host); + + + /* Start DNS SRV or A resolution, depending on whether port is specified */ + if (target->addr.port == 0) { + query->query_type = PJ_DNS_TYPE_SRV; + + query->req.def_port = 5060; + + if (type == PJSIP_TRANSPORT_TLS) { + query->naptr[0].res_type = pj_str("_sips._tcp."); + query->req.def_port = 5061; + } else if (type == PJSIP_TRANSPORT_TCP) + query->naptr[0].res_type = pj_str("_sip._tcp."); + else if (type == PJSIP_TRANSPORT_UDP) + query->naptr[0].res_type = pj_str("_sip._udp."); + else { + pj_assert(!"Unknown transport type"); + query->naptr[0].res_type = pj_str("_sip._udp."); + + } + + } else { + /* Otherwise if port is specified, start with A (or AAAA) host + * resolution + */ + query->query_type = PJ_DNS_TYPE_A; + query->naptr[0].res_type.slen = 0; + query->req.def_port = target->addr.port; + } + + /* Start the asynchronous query */ + PJ_LOG(5, (query->objname, + "Starting async DNS %s query: target=%.*s%.*s, transport=%s, " + "port=%d", + pj_dns_get_type_name(query->query_type), + (int)query->naptr[0].res_type.slen, + query->naptr[0].res_type.ptr, + (int)query->naptr[0].name.slen, query->naptr[0].name.ptr, + pjsip_transport_get_type_name(target->type), + target->addr.port)); + + if (query->query_type == PJ_DNS_TYPE_SRV) { + + status = pj_dns_srv_resolve(&query->naptr[0].name, + &query->naptr[0].res_type, + query->req.def_port, pool, resolver->res, + PJ_TRUE, query, &srv_resolver_cb, NULL); + + } else if (query->query_type == PJ_DNS_TYPE_A) { + + status = pj_dns_resolver_start_query(resolver->res, + &query->naptr[0].name, + PJ_DNS_TYPE_A, 0, + &dns_a_callback, + query, &query->object); + + } else { + pj_assert(!"Unexpected"); + status = PJ_EBUG; + } + + if (status != PJ_SUCCESS) + goto on_error; + + return; + +#else /* PJSIP_HAS_RESOLVER */ + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(query); + PJ_UNUSED_ARG(srv_name); +#endif /* PJSIP_HAS_RESOLVER */ + +on_error: + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + PJ_LOG(4,(THIS_FILE, "Failed to resolve '%.*s'. Err=%d (%s)", + (int)target->addr.host.slen, + target->addr.host.ptr, + status, + pj_strerror(status,errmsg,sizeof(errmsg)).ptr)); + (*cb)(status, token, NULL); + return; + } +} + +#if PJSIP_HAS_RESOLVER + +/* + * This callback is called when target is resolved with DNS A query. + */ +static void dns_a_callback(void *user_data, + pj_status_t status, + pj_dns_parsed_packet *pkt) +{ + struct query *query = (struct query*) user_data; + pjsip_server_addresses srv; + pj_dns_a_record rec; + unsigned i; + + rec.addr_count = 0; + + /* Parse the response */ + if (status == PJ_SUCCESS) { + status = pj_dns_parse_a_response(pkt, &rec); + } + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + /* Log error */ + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(query->objname, "DNS A record resolution failed: %s", + errmsg)); + + /* Call the callback */ + (*query->cb)(status, query->token, NULL); + return; + } + + /* Build server addresses and call callback */ + srv.count = 0; + for (i=0; i<rec.addr_count; ++i) { + srv.entry[srv.count].type = query->naptr[0].type; + srv.entry[srv.count].priority = 0; + srv.entry[srv.count].weight = 0; + srv.entry[srv.count].addr_len = sizeof(pj_sockaddr_in); + pj_sockaddr_in_init(&srv.entry[srv.count].addr.ipv4, + 0, (pj_uint16_t)query->req.def_port); + srv.entry[srv.count].addr.ipv4.sin_addr.s_addr = + rec.addr[i].s_addr; + + ++srv.count; + } + + /* Call the callback */ + (*query->cb)(PJ_SUCCESS, query->token, &srv); +} + + +/* Callback to be called by DNS SRV resolution */ +static void srv_resolver_cb(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + struct query *query = (struct query*) user_data; + pjsip_server_addresses srv; + unsigned i; + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + /* Log error */ + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(query->objname, "DNS A record resolution failed: %s", + errmsg)); + + /* Call the callback */ + (*query->cb)(status, query->token, NULL); + return; + } + + /* Build server addresses and call callback */ + srv.count = 0; + for (i=0; i<rec->count; ++i) { + unsigned j; + + for (j=0; j<rec->entry[i].server.addr_count; ++j) { + srv.entry[srv.count].type = query->naptr[0].type; + srv.entry[srv.count].priority = rec->entry[i].priority; + srv.entry[srv.count].weight = rec->entry[i].weight; + srv.entry[srv.count].addr_len = sizeof(pj_sockaddr_in); + pj_sockaddr_in_init(&srv.entry[srv.count].addr.ipv4, + 0, (pj_uint16_t)rec->entry[i].port); + srv.entry[srv.count].addr.ipv4.sin_addr.s_addr = + rec->entry[i].server.addr[j].s_addr; + + ++srv.count; + } + } + + /* Call the callback */ + (*query->cb)(PJ_SUCCESS, query->token, &srv); +} + +#endif /* PJSIP_HAS_RESOLVER */ + diff --git a/pjsip/src/pjsip/sip_tel_uri.c b/pjsip/src/pjsip/sip_tel_uri.c new file mode 100644 index 0000000..4120ae0 --- /dev/null +++ b/pjsip/src/pjsip/sip_tel_uri.c @@ -0,0 +1,448 @@ +/* $Id: sip_tel_uri.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_tel_uri.h> +#include <pjsip/sip_msg.h> +#include <pjsip/sip_parser.h> +#include <pjsip/print_util.h> +#include <pj/pool.h> +#include <pj/assert.h> +#include <pj/string.h> +#include <pj/ctype.h> +#include <pj/except.h> +#include <pjlib-util/string.h> +#include <pjlib-util/scanner.h> + +#define ALPHA +#define DIGITS "0123456789" +#define HEX "aAbBcCdDeEfF" +#define HEX_DIGITS DIGITS HEX +#define VISUAL_SEP "-.()" +#define PHONE_DIGITS DIGITS VISUAL_SEP +#define GLOBAL_DIGITS "+" PHONE_DIGITS +#define LOCAL_DIGITS HEX_DIGITS "*#" VISUAL_SEP +#define NUMBER_SPEC LOCAL_DIGITS GLOBAL_DIGITS +#define PHONE_CONTEXT ALPHA GLOBAL_DIGITS +//#define RESERVED ";/?:@&=+$," +#define RESERVED "/:@&$,+" +#define MARK "-_.!~*'()" +#define UNRESERVED ALPHA DIGITS MARK +#define ESCAPED "%" +#define URIC RESERVED UNRESERVED ESCAPED "[]+" +#define PARAM_UNRESERVED "[]/:&+$" +#define PARAM_CHAR PARAM_UNRESERVED UNRESERVED ESCAPED + +static pj_cis_buf_t cis_buf; +static pj_cis_t pjsip_TEL_NUMBER_SPEC; +static pj_cis_t pjsip_TEL_EXT_VALUE_SPEC; +static pj_cis_t pjsip_TEL_PHONE_CONTEXT_SPEC; +static pj_cis_t pjsip_TEL_URIC_SPEC; +static pj_cis_t pjsip_TEL_VISUAL_SEP_SPEC; +static pj_cis_t pjsip_TEL_PNAME_SPEC; +static pj_cis_t pjsip_TEL_PVALUE_SPEC; +static pj_cis_t pjsip_TEL_PVALUE_SPEC_ESC; +static pj_cis_t pjsip_TEL_PARSING_PVALUE_SPEC; +static pj_cis_t pjsip_TEL_PARSING_PVALUE_SPEC_ESC; + +static pj_str_t pjsip_ISUB_STR = { "isub", 4 }; +static pj_str_t pjsip_EXT_STR = { "ext", 3 }; +static pj_str_t pjsip_PH_CTX_STR = { "phone-context", 13 }; + + +static const pj_str_t *tel_uri_get_scheme( const pjsip_tel_uri* ); +static void *tel_uri_get_uri( pjsip_tel_uri* ); +static pj_ssize_t tel_uri_print( pjsip_uri_context_e context, + const pjsip_tel_uri *url, + char *buf, pj_size_t size); +static int tel_uri_cmp( pjsip_uri_context_e context, + const pjsip_tel_uri *url1, const pjsip_tel_uri *url2); +static pjsip_tel_uri* tel_uri_clone(pj_pool_t *pool, const pjsip_tel_uri *rhs); +static void* tel_uri_parse( pj_scanner *scanner, pj_pool_t *pool, + pj_bool_t parse_params); + +typedef const pj_str_t* (*P_GET_SCHEME)(const void*); +typedef void* (*P_GET_URI)(void*); +typedef pj_ssize_t (*P_PRINT_URI)(pjsip_uri_context_e,const void *, + char*,pj_size_t); +typedef int (*P_CMP_URI)(pjsip_uri_context_e, const void*, + const void*); +typedef void* (*P_CLONE)(pj_pool_t*, const void*); + +static pjsip_uri_vptr tel_uri_vptr = +{ + (P_GET_SCHEME) &tel_uri_get_scheme, + (P_GET_URI) &tel_uri_get_uri, + (P_PRINT_URI) &tel_uri_print, + (P_CMP_URI) &tel_uri_cmp, + (P_CLONE) &tel_uri_clone +}; + + +PJ_DEF(pjsip_tel_uri*) pjsip_tel_uri_create(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = PJ_POOL_ZALLOC_T(pool, pjsip_tel_uri); + uri->vptr = &tel_uri_vptr; + pj_list_init(&uri->other_param); + return uri; +} + + +static const pj_str_t *tel_uri_get_scheme( const pjsip_tel_uri *uri ) +{ + PJ_UNUSED_ARG(uri); + return &pjsip_parser_const()->pjsip_TEL_STR; +} + +static void *tel_uri_get_uri( pjsip_tel_uri *uri ) +{ + return uri; +} + + +pj_status_t pjsip_tel_uri_subsys_init(void) +{ + pj_status_t status; + + pj_cis_buf_init(&cis_buf); + + status = pj_cis_init(&cis_buf, &pjsip_TEL_EXT_VALUE_SPEC); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + pj_cis_add_str(&pjsip_TEL_EXT_VALUE_SPEC, PHONE_DIGITS); + + status = pj_cis_init(&cis_buf, &pjsip_TEL_NUMBER_SPEC); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + pj_cis_add_str(&pjsip_TEL_NUMBER_SPEC, NUMBER_SPEC); + + status = pj_cis_init(&cis_buf, &pjsip_TEL_VISUAL_SEP_SPEC); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + pj_cis_add_str(&pjsip_TEL_VISUAL_SEP_SPEC, VISUAL_SEP); + + status = pj_cis_init(&cis_buf, &pjsip_TEL_PHONE_CONTEXT_SPEC); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + pj_cis_add_alpha(&pjsip_TEL_PHONE_CONTEXT_SPEC); + pj_cis_add_num(&pjsip_TEL_PHONE_CONTEXT_SPEC); + pj_cis_add_str(&pjsip_TEL_PHONE_CONTEXT_SPEC, PHONE_CONTEXT); + + status = pj_cis_init(&cis_buf, &pjsip_TEL_URIC_SPEC); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + pj_cis_add_alpha(&pjsip_TEL_URIC_SPEC); + pj_cis_add_num(&pjsip_TEL_URIC_SPEC); + pj_cis_add_str(&pjsip_TEL_URIC_SPEC, URIC); + + status = pj_cis_init(&cis_buf, &pjsip_TEL_PNAME_SPEC); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + pj_cis_add_alpha(&pjsip_TEL_PNAME_SPEC); + pj_cis_add_num(&pjsip_TEL_PNAME_SPEC); + pj_cis_add_str(&pjsip_TEL_PNAME_SPEC, "-"); + + status = pj_cis_init(&cis_buf, &pjsip_TEL_PVALUE_SPEC); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + pj_cis_add_alpha(&pjsip_TEL_PVALUE_SPEC); + pj_cis_add_num(&pjsip_TEL_PVALUE_SPEC); + pj_cis_add_str(&pjsip_TEL_PVALUE_SPEC, PARAM_CHAR); + + status = pj_cis_dup(&pjsip_TEL_PVALUE_SPEC_ESC, &pjsip_TEL_PVALUE_SPEC); + pj_cis_del_str(&pjsip_TEL_PVALUE_SPEC_ESC, "%"); + + status = pj_cis_dup(&pjsip_TEL_PARSING_PVALUE_SPEC, &pjsip_TEL_URIC_SPEC); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + pj_cis_add_cis(&pjsip_TEL_PARSING_PVALUE_SPEC, &pjsip_TEL_PVALUE_SPEC); + pj_cis_add_str(&pjsip_TEL_PARSING_PVALUE_SPEC, "="); + + status = pj_cis_dup(&pjsip_TEL_PARSING_PVALUE_SPEC_ESC, + &pjsip_TEL_PARSING_PVALUE_SPEC); + pj_cis_del_str(&pjsip_TEL_PARSING_PVALUE_SPEC_ESC, "%"); + + status = pjsip_register_uri_parser("tel", &tel_uri_parse); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, status); + + return PJ_SUCCESS; +} + +/* Print tel: URI */ +static pj_ssize_t tel_uri_print( pjsip_uri_context_e context, + const pjsip_tel_uri *uri, + char *buf, pj_size_t size) +{ + int printed; + char *startbuf = buf; + char *endbuf = buf+size; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + PJ_UNUSED_ARG(context); + + /* Print scheme. */ + copy_advance(buf, pc->pjsip_TEL_STR); + *buf++ = ':'; + + /* Print number. */ + copy_advance_escape(buf, uri->number, pjsip_TEL_NUMBER_SPEC); + + /* ISDN sub-address or extension must appear first. */ + + /* Extension param. */ + copy_advance_pair_escape(buf, ";ext=", 5, uri->ext_param, + pjsip_TEL_EXT_VALUE_SPEC); + + /* ISDN sub-address. */ + copy_advance_pair_escape(buf, ";isub=", 6, uri->isub_param, + pjsip_TEL_URIC_SPEC); + + /* Followed by phone context, if present. */ + copy_advance_pair_escape(buf, ";phone-context=", 15, uri->context, + pjsip_TEL_PHONE_CONTEXT_SPEC); + + + /* Print other parameters. */ + printed = pjsip_param_print_on(&uri->other_param, buf, (endbuf-buf), + &pjsip_TEL_PNAME_SPEC, + &pjsip_TEL_PVALUE_SPEC, ';'); + if (printed < 0) + return -1; + buf += printed; + + return (buf-startbuf); +} + +/* Compare two numbers, according to RFC 3966: + * - both must be either local or global numbers. + * - The 'global-number-digits' and the 'local-number-digits' must be + * equal, after removing all visual separators. + */ +PJ_DEF(int) pjsip_tel_nb_cmp(const pj_str_t *number1, const pj_str_t *number2) +{ + const char *s1 = number1->ptr, + *e1 = number1->ptr + number1->slen, + *s2 = number2->ptr, + *e2 = number2->ptr + number2->slen; + + /* Compare each number, ignoreing visual separators. */ + while (s1!=e1 && s2!=e2) { + int diff; + + if (pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s1)) { + ++s1; + continue; + } + if (pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s2)) { + ++s2; + continue; + } + + diff = pj_tolower(*s1) - pj_tolower(*s2); + if (!diff) { + ++s1, ++s2; + continue; + } else + return diff; + } + + /* Exhaust remaining visual separators. */ + while (s1!=e1 && pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s1)) + ++s1; + while (s2!=e2 && pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s2)) + ++s2; + + if (s1==e1 && s2==e2) + return 0; + else if (s1==e1) + return -1; + else + return 1; +} + +/* Compare two tel: URI */ +static int tel_uri_cmp( pjsip_uri_context_e context, + const pjsip_tel_uri *url1, const pjsip_tel_uri *url2) +{ + int result; + + PJ_UNUSED_ARG(context); + + /* Scheme must match. */ + if (url1->vptr != url2->vptr) + return -1; + + /* Compare number. */ + result = pjsip_tel_nb_cmp(&url1->number, &url2->number); + if (result != 0) + return result; + + /* Compare phone-context as hostname or as as global nb. */ + if (url1->context.slen) { + if (*url1->context.ptr != '+') + result = pj_stricmp(&url1->context, &url2->context); + else + result = pjsip_tel_nb_cmp(&url1->context, &url2->context); + + if (result != 0) + return result; + + } else if (url2->context.slen) + return -1; + + /* Compare extension. */ + if (url1->ext_param.slen) { + result = pjsip_tel_nb_cmp(&url1->ext_param, &url2->ext_param); + if (result != 0) + return result; + } + + /* Compare isub bytes by bytes. */ + if (url1->isub_param.slen) { + result = pj_stricmp(&url1->isub_param, &url2->isub_param); + if (result != 0) + return result; + } + + /* Other parameters are compared regardless of the order. + * If one URI has parameter not found in the other URI, the URIs are + * not equal. + */ + if (url1->other_param.next != &url1->other_param) { + const pjsip_param *p1, *p2; + int cnt1 = 0, cnt2 = 0; + + p1 = url1->other_param.next; + while (p1 != &url1->other_param) { + p2 = pjsip_param_cfind(&url2->other_param, &p1->name); + if (!p2 ) + return 1; + + result = pj_stricmp(&p1->value, &p2->value); + if (result != 0) + return result; + + p1 = p1->next; + ++cnt1; + } + + p2 = url2->other_param.next; + while (p2 != &url2->other_param) + ++cnt2, p2 = p2->next; + + if (cnt1 < cnt2) + return -1; + else if (cnt1 > cnt2) + return 1; + + } else if (url2->other_param.next != &url2->other_param) + return -1; + + /* Equal. */ + return 0; +} + +/* Clone tel: URI */ +static pjsip_tel_uri* tel_uri_clone(pj_pool_t *pool, const pjsip_tel_uri *rhs) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + pj_strdup(pool, &uri->number, &rhs->number); + pj_strdup(pool, &uri->context, &rhs->context); + pj_strdup(pool, &uri->ext_param, &rhs->ext_param); + pj_strdup(pool, &uri->isub_param, &rhs->isub_param); + pjsip_param_clone(pool, &uri->other_param, &rhs->other_param); + + return uri; +} + +/* Parse tel: URI + * THis actually returns (pjsip_tel_uri *) type. + */ +static void* tel_uri_parse( pj_scanner *scanner, pj_pool_t *pool, + pj_bool_t parse_params) +{ + pjsip_tel_uri *uri; + pj_str_t token; + int skip_ws = scanner->skip_ws; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + scanner->skip_ws = 0; + + /* Parse scheme. */ + pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &token); + if (pj_scan_get_char(scanner) != ':') + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + if (pj_stricmp_alnum(&token, &pc->pjsip_TEL_STR) != 0) + PJ_THROW(PJSIP_SYN_ERR_EXCEPTION); + + /* Create URI */ + uri = pjsip_tel_uri_create(pool); + + /* Get the phone number. */ +#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + pj_scan_get_unescape(scanner, &pjsip_TEL_NUMBER_SPEC, &uri->number); +#else + pj_scan_get(scanner, &pjsip_TEL_NUMBER_SPEC, &uri->number); + uri->number = pj_str_unescape(pool, &uri->number); +#endif + + /* Get all parameters. */ + if (parse_params && *scanner->curptr==';') { + pj_str_t pname, pvalue; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + do { + /* Eat the ';' separator. */ + pj_scan_get_char(scanner); + + /* Get pname. */ + pj_scan_get(scanner, &pc->pjsip_PARAM_CHAR_SPEC, &pname); + + if (*scanner->curptr == '=') { + pj_scan_get_char(scanner); + +# if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + pj_scan_get_unescape(scanner, + &pjsip_TEL_PARSING_PVALUE_SPEC_ESC, + &pvalue); +# else + pj_scan_get(scanner, &pjsip_TEL_PARSING_PVALUE_SPEC, + &pvalue); + pvalue = pj_str_unescape(pool, &pvalue); +# endif + + } else { + pvalue.slen = 0; + pvalue.ptr = NULL; + } + + /* Save the parameters. */ + if (pj_stricmp_alnum(&pname, &pjsip_ISUB_STR)==0) { + uri->isub_param = pvalue; + } else if (pj_stricmp_alnum(&pname, &pjsip_EXT_STR)==0) { + uri->ext_param = pvalue; + } else if (pj_stricmp_alnum(&pname, &pjsip_PH_CTX_STR)==0) { + uri->context = pvalue; + } else { + pjsip_param *param = PJ_POOL_ALLOC_T(pool, pjsip_param); + param->name = pname; + param->value = pvalue; + pj_list_insert_before(&uri->other_param, param); + } + + } while (*scanner->curptr==';'); + } + + scanner->skip_ws = skip_ws; + pj_scan_skip_whitespace(scanner); + return uri; +} + diff --git a/pjsip/src/pjsip/sip_tel_uri_wrap.cpp b/pjsip/src/pjsip/sip_tel_uri_wrap.cpp new file mode 100644 index 0000000..d13fb52 --- /dev/null +++ b/pjsip/src/pjsip/sip_tel_uri_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: sip_tel_uri_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * 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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "sip_tel_uri.c" diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c new file mode 100644 index 0000000..4b3dd12 --- /dev/null +++ b/pjsip/src/pjsip/sip_transaction.c @@ -0,0 +1,3277 @@ +/* $Id: sip_transaction.c 4165 2012-06-14 09:04:20Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_util.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_event.h> +#include <pjlib-util/errno.h> +#include <pj/hash.h> +#include <pj/pool.h> +#include <pj/os.h> +#include <pj/string.h> +#include <pj/assert.h> +#include <pj/guid.h> +#include <pj/log.h> + +#define THIS_FILE "sip_transaction.c" + +#if 0 +#define TSX_TRACE_(expr) PJ_LOG(3,expr) +#else +#define TSX_TRACE_(expr) +#endif + +/* When this macro is set, transaction will keep the hashed value + * so that future lookup (to unregister transaction) does not need + * to recalculate the hash again. It should gains a little bit of + * performance, so generally we'd want this. + */ +#define PRECALC_HASH + + +/* Defined in sip_util_statefull.c */ +extern pjsip_module mod_stateful_util; + + +/***************************************************************************** + ** + ** Declarations and static variable definitions section. + ** + ***************************************************************************** + **/ +/* Prototypes. */ +static pj_status_t mod_tsx_layer_load(pjsip_endpoint *endpt); +static pj_status_t mod_tsx_layer_start(void); +static pj_status_t mod_tsx_layer_stop(void); +static pj_status_t mod_tsx_layer_unload(void); +static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata); + +/* Transaction layer module definition. */ +static struct mod_tsx_layer +{ + struct pjsip_module mod; + pj_pool_t *pool; + pjsip_endpoint *endpt; + pj_mutex_t *mutex; + pj_hash_table_t *htable; +} mod_tsx_layer = +{ { + NULL, NULL, /* List's prev and next. */ + { "mod-tsx-layer", 13 }, /* Module name. */ + -1, /* Module ID */ + PJSIP_MOD_PRIORITY_TSX_LAYER, /* Priority. */ + mod_tsx_layer_load, /* load(). */ + mod_tsx_layer_start, /* start() */ + mod_tsx_layer_stop, /* stop() */ + mod_tsx_layer_unload, /* unload() */ + mod_tsx_layer_on_rx_request, /* on_rx_request() */ + mod_tsx_layer_on_rx_response, /* on_rx_response() */ + NULL + } +}; + +/* Thread Local Storage ID for transaction lock */ +static long pjsip_tsx_lock_tls_id; + +/* Transaction state names */ +static const char *state_str[] = +{ + "Null", + "Calling", + "Trying", + "Proceeding", + "Completed", + "Confirmed", + "Terminated", + "Destroyed", +}; + +/* Role names */ +static const char *role_name[] = +{ + "UAC", + "UAS" +}; + +/* Transport flag. */ +enum +{ + TSX_HAS_PENDING_TRANSPORT = 1, + TSX_HAS_PENDING_RESCHED = 2, + TSX_HAS_PENDING_SEND = 4, + TSX_HAS_PENDING_DESTROY = 8, + TSX_HAS_RESOLVED_SERVER = 16, +}; + +/* Transaction lock. */ +typedef struct tsx_lock_data { + struct tsx_lock_data *prev; + pjsip_transaction *tsx; + int is_alive; +} tsx_lock_data; + + +/* Timer timeout value constants */ +static pj_time_val t1_timer_val = { PJSIP_T1_TIMEOUT/1000, + PJSIP_T1_TIMEOUT%1000 }; +static pj_time_val t2_timer_val = { PJSIP_T2_TIMEOUT/1000, + PJSIP_T2_TIMEOUT%1000 }; +static pj_time_val t4_timer_val = { PJSIP_T4_TIMEOUT/1000, + PJSIP_T4_TIMEOUT%1000 }; +static pj_time_val td_timer_val = { PJSIP_TD_TIMEOUT/1000, + PJSIP_TD_TIMEOUT%1000 }; +static pj_time_val timeout_timer_val = { (64*PJSIP_T1_TIMEOUT)/1000, + (64*PJSIP_T1_TIMEOUT)%1000 }; + +#define TIMER_INACTIVE 0 +#define TIMER_ACTIVE 1 + + +/* Prototypes. */ +static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck); +static pj_status_t unlock_tsx( pjsip_transaction *tsx, + struct tsx_lock_data *lck); +static pj_status_t tsx_on_state_null( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_calling( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_trying( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_proceeding_uac( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_completed_uac( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_confirmed( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_terminated( pjsip_transaction *tsx, + pjsip_event *event); +static pj_status_t tsx_on_state_destroyed( pjsip_transaction *tsx, + pjsip_event *event); +static void tsx_timer_callback( pj_timer_heap_t *theap, + pj_timer_entry *entry); +static void tsx_tp_state_callback( + pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info); +static pj_status_t tsx_create( pjsip_module *tsx_user, + pjsip_transaction **p_tsx); +static pj_status_t tsx_destroy( pjsip_transaction *tsx ); +static void tsx_resched_retransmission( pjsip_transaction *tsx ); +static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched); +static int tsx_send_msg( pjsip_transaction *tsx, + pjsip_tx_data *tdata); +static void tsx_update_transport( pjsip_transaction *tsx, + pjsip_transport *tp); + + +/* State handlers for UAC, indexed by state */ +static int (*tsx_state_handler_uac[PJSIP_TSX_STATE_MAX])(pjsip_transaction *, + pjsip_event *) = +{ + &tsx_on_state_null, + &tsx_on_state_calling, + NULL, + &tsx_on_state_proceeding_uac, + &tsx_on_state_completed_uac, + &tsx_on_state_confirmed, + &tsx_on_state_terminated, + &tsx_on_state_destroyed, +}; + +/* State handlers for UAS */ +static int (*tsx_state_handler_uas[PJSIP_TSX_STATE_MAX])(pjsip_transaction *, + pjsip_event *) = +{ + &tsx_on_state_null, + NULL, + &tsx_on_state_trying, + &tsx_on_state_proceeding_uas, + &tsx_on_state_completed_uas, + &tsx_on_state_confirmed, + &tsx_on_state_terminated, + &tsx_on_state_destroyed, +}; + +/***************************************************************************** + ** + ** Utilities + ** + ***************************************************************************** + */ +/* + * Get transaction state name. + */ +PJ_DEF(const char *) pjsip_tsx_state_str(pjsip_tsx_state_e state) +{ + return state_str[state]; +} + +/* + * Get the role name. + */ +PJ_DEF(const char *) pjsip_role_name(pjsip_role_e role) +{ + return role_name[role]; +} + + +/* + * Create transaction key for RFC2543 compliant messages, which don't have + * unique branch parameter in the top most Via header. + * + * INVITE requests matches a transaction if the following attributes + * match the original request: + * - Request-URI + * - To tag + * - From tag + * - Call-ID + * - CSeq + * - top Via header + * + * CANCEL matching is done similarly as INVITE, except: + * - CSeq method will differ + * - To tag is not matched. + * + * ACK matching is done similarly, except that: + * - method of the CSeq will differ, + * - To tag is matched to the response sent by the server transaction. + * + * The transaction key is constructed from the common components of above + * components. Additional comparison is needed to fully match a transaction. + */ +static pj_status_t create_tsx_key_2543( pj_pool_t *pool, + pj_str_t *str, + pjsip_role_e role, + const pjsip_method *method, + const pjsip_rx_data *rdata ) +{ +#define SEPARATOR '$' + char *key, *p, *end; + int len; + pj_size_t len_required; + pjsip_uri *req_uri; + pj_str_t *host; + + PJ_ASSERT_RETURN(pool && str && method && rdata, PJ_EINVAL); + PJ_ASSERT_RETURN(rdata->msg_info.msg, PJ_EINVAL); + PJ_ASSERT_RETURN(rdata->msg_info.via, PJSIP_EMISSINGHDR); + PJ_ASSERT_RETURN(rdata->msg_info.cseq, PJSIP_EMISSINGHDR); + PJ_ASSERT_RETURN(rdata->msg_info.from, PJSIP_EMISSINGHDR); + + host = &rdata->msg_info.via->sent_by.host; + req_uri = (pjsip_uri*)rdata->msg_info.msg->line.req.uri; + + /* Calculate length required. */ + len_required = 9 + /* CSeq number */ + rdata->msg_info.from->tag.slen + /* From tag. */ + rdata->msg_info.cid->id.slen + /* Call-ID */ + host->slen + /* Via host. */ + 9 + /* Via port. */ + 16; /* Separator+Allowance. */ + key = p = (char*) pj_pool_alloc(pool, len_required); + end = p + len_required; + + /* Add role. */ + *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's'); + *p++ = SEPARATOR; + + /* Add method, except when method is INVITE or ACK. */ + if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) { + pj_memcpy(p, method->name.ptr, method->name.slen); + p += method->name.slen; + *p++ = '$'; + } + + /* Add CSeq (only the number). */ + len = pj_utoa(rdata->msg_info.cseq->cseq, p); + p += len; + *p++ = SEPARATOR; + + /* Add From tag. */ + len = rdata->msg_info.from->tag.slen; + pj_memcpy( p, rdata->msg_info.from->tag.ptr, len); + p += len; + *p++ = SEPARATOR; + + /* Add Call-ID. */ + len = rdata->msg_info.cid->id.slen; + pj_memcpy( p, rdata->msg_info.cid->id.ptr, len ); + p += len; + *p++ = SEPARATOR; + + /* Add top Via header. + * We don't really care whether the port contains the real port (because + * it can be omited if default port is used). Anyway this function is + * only used to match request retransmission, and we expect that the + * request retransmissions will contain the same port. + */ + pj_memcpy(p, host->ptr, host->slen); + p += host->slen; + *p++ = ':'; + + len = pj_utoa(rdata->msg_info.via->sent_by.port, p); + p += len; + *p++ = SEPARATOR; + + *p++ = '\0'; + + /* Done. */ + str->ptr = key; + str->slen = p-key; + + return PJ_SUCCESS; +} + +/* + * Create transaction key for RFC3161 compliant system. + */ +static pj_status_t create_tsx_key_3261( pj_pool_t *pool, + pj_str_t *key, + pjsip_role_e role, + const pjsip_method *method, + const pj_str_t *branch) +{ + char *p; + + PJ_ASSERT_RETURN(pool && key && method && branch, PJ_EINVAL); + + p = key->ptr = (char*) + pj_pool_alloc(pool, branch->slen + method->name.slen + 4 ); + + /* Add role. */ + *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's'); + *p++ = SEPARATOR; + + /* Add method, except when method is INVITE or ACK. */ + if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) { + pj_memcpy(p, method->name.ptr, method->name.slen); + p += method->name.slen; + *p++ = '$'; + } + + /* Add branch ID. */ + pj_memcpy(p, branch->ptr, branch->slen); + p += branch->slen; + + /* Set length */ + key->slen = p - key->ptr; + + return PJ_SUCCESS; +} + +/* + * Create key from the incoming data, to be used to search the transaction + * in the transaction hash table. + */ +PJ_DEF(pj_status_t) pjsip_tsx_create_key( pj_pool_t *pool, pj_str_t *key, + pjsip_role_e role, + const pjsip_method *method, + const pjsip_rx_data *rdata) +{ + pj_str_t rfc3261_branch = {PJSIP_RFC3261_BRANCH_ID, + PJSIP_RFC3261_BRANCH_LEN}; + + + /* Get the branch parameter in the top-most Via. + * If branch parameter is started with "z9hG4bK", then the message was + * generated by agent compliant with RFC3261. Otherwise, it will be + * handled as RFC2543. + */ + const pj_str_t *branch = &rdata->msg_info.via->branch_param; + + if (pj_strncmp(branch,&rfc3261_branch,PJSIP_RFC3261_BRANCH_LEN)==0) { + + /* Create transaction key. */ + return create_tsx_key_3261(pool, key, role, method, branch); + + } else { + /* Create the key for the message. This key will be matched up + * with the transaction key. For RFC2563 transactions, the + * transaction key was created by the same function, so it will + * match the message. + */ + return create_tsx_key_2543( pool, key, role, method, rdata ); + } +} + +/***************************************************************************** + ** + ** Transaction layer module + ** + ***************************************************************************** + **/ +/* + * Create transaction layer module and registers it to the endpoint. + */ +PJ_DEF(pj_status_t) pjsip_tsx_layer_init_module(pjsip_endpoint *endpt) +{ + pj_pool_t *pool; + pj_status_t status; + + + PJ_ASSERT_RETURN(mod_tsx_layer.endpt==NULL, PJ_EINVALIDOP); + + /* Initialize timer values */ + t1_timer_val.sec = pjsip_cfg()->tsx.t1 / 1000; + t1_timer_val.msec = pjsip_cfg()->tsx.t1 % 1000; + t2_timer_val.sec = pjsip_cfg()->tsx.t2 / 1000; + t2_timer_val.msec = pjsip_cfg()->tsx.t2 % 1000; + t4_timer_val.sec = pjsip_cfg()->tsx.t4 / 1000; + t4_timer_val.msec = pjsip_cfg()->tsx.t4 % 1000; + td_timer_val.sec = pjsip_cfg()->tsx.td / 1000; + td_timer_val.msec = pjsip_cfg()->tsx.td % 1000; + /* Changed the initialization below to use td_timer_val instead, to enable + * customization to the timeout value. + */ + //timeout_timer_val.sec = (64 * pjsip_cfg()->tsx.t1) / 1000; + //timeout_timer_val.msec = (64 * pjsip_cfg()->tsx.t1) % 1000; + timeout_timer_val = td_timer_val; + + /* Initialize TLS ID for transaction lock. */ + status = pj_thread_local_alloc(&pjsip_tsx_lock_tls_id); + if (status != PJ_SUCCESS) + return status; + + pj_thread_local_set(pjsip_tsx_lock_tls_id, NULL); + + /* + * Initialize transaction layer structure. + */ + + /* Create pool for the module. */ + pool = pjsip_endpt_create_pool(endpt, "tsxlayer", + PJSIP_POOL_TSX_LAYER_LEN, + PJSIP_POOL_TSX_LAYER_INC ); + if (!pool) + return PJ_ENOMEM; + + + /* Initialize some attributes. */ + mod_tsx_layer.pool = pool; + mod_tsx_layer.endpt = endpt; + + + /* Create hash table. */ + mod_tsx_layer.htable = pj_hash_create( pool, pjsip_cfg()->tsx.max_count ); + if (!mod_tsx_layer.htable) { + pjsip_endpt_release_pool(endpt, pool); + return PJ_ENOMEM; + } + + /* Create mutex. */ + status = pj_mutex_create_recursive(pool, "tsxlayer", &mod_tsx_layer.mutex); + if (status != PJ_SUCCESS) { + pjsip_endpt_release_pool(endpt, pool); + return status; + } + + /* + * Register transaction layer module to endpoint. + */ + status = pjsip_endpt_register_module( endpt, &mod_tsx_layer.mod ); + if (status != PJ_SUCCESS) { + pj_mutex_destroy(mod_tsx_layer.mutex); + pjsip_endpt_release_pool(endpt, pool); + return status; + } + + /* Register mod_stateful_util module (sip_util_statefull.c) */ + status = pjsip_endpt_register_module(endpt, &mod_stateful_util); + if (status != PJ_SUCCESS) { + return status; + } + + return PJ_SUCCESS; +} + + +/* + * Get the instance of transaction layer module. + */ +PJ_DEF(pjsip_module*) pjsip_tsx_layer_instance(void) +{ + return &mod_tsx_layer.mod; +} + + +/* + * Unregister and destroy transaction layer module. + */ +PJ_DEF(pj_status_t) pjsip_tsx_layer_destroy(void) +{ + /* Are we registered? */ + PJ_ASSERT_RETURN(mod_tsx_layer.endpt!=NULL, PJ_EINVALIDOP); + + /* Unregister from endpoint. + * Clean-ups will be done in the unload() module callback. + */ + return pjsip_endpt_unregister_module( mod_tsx_layer.endpt, + &mod_tsx_layer.mod); +} + + +/* + * Register the transaction to the hash table. + */ +static pj_status_t mod_tsx_layer_register_tsx( pjsip_transaction *tsx) +{ + pj_assert(tsx->transaction_key.slen != 0); + + /* Lock hash table mutex. */ + pj_mutex_lock(mod_tsx_layer.mutex); + + /* Check if no transaction with the same key exists. + * Do not use PJ_ASSERT_RETURN since it evaluates the expression + * twice! + */ + if(pj_hash_get(mod_tsx_layer.htable, + tsx->transaction_key.ptr, + tsx->transaction_key.slen, + NULL)) + { + pj_mutex_unlock(mod_tsx_layer.mutex); + PJ_LOG(2,(THIS_FILE, + "Unable to register %.*s transaction (key exists)", + (int)tsx->method.name.slen, + tsx->method.name.ptr)); + return PJ_EEXISTS; + } + + TSX_TRACE_((THIS_FILE, + "Transaction %p registered with hkey=0x%p and key=%.*s", + tsx, tsx->hashed_key, tsx->transaction_key.slen, + tsx->transaction_key.ptr)); + + /* Register the transaction to the hash table. */ +#ifdef PRECALC_HASH + pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr, + tsx->transaction_key.slen, tsx->hashed_key, tsx); +#else + pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr, + tsx->transaction_key.slen, 0, tsx); +#endif + + /* Unlock mutex. */ + pj_mutex_unlock(mod_tsx_layer.mutex); + + return PJ_SUCCESS; +} + + +/* + * Unregister the transaction from the hash table. + */ +static void mod_tsx_layer_unregister_tsx( pjsip_transaction *tsx) +{ + if (mod_tsx_layer.mod.id == -1) { + /* The transaction layer has been unregistered. This could happen + * if the transaction was pending on transport and the application + * is shutdown. See http://trac.pjsip.org/repos/ticket/1033. In + * this case just do nothing. + */ + return; + } + + pj_assert(tsx->transaction_key.slen != 0); + //pj_assert(tsx->state != PJSIP_TSX_STATE_NULL); + + /* Lock hash table mutex. */ + pj_mutex_lock(mod_tsx_layer.mutex); + + /* Register the transaction to the hash table. */ +#ifdef PRECALC_HASH + pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr, + tsx->transaction_key.slen, tsx->hashed_key, NULL); +#else + pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr, + tsx->transaction_key.slen, 0, NULL); +#endif + + TSX_TRACE_((THIS_FILE, + "Transaction %p unregistered, hkey=0x%p and key=%.*s", + tsx, tsx->hashed_key, tsx->transaction_key.slen, + tsx->transaction_key.ptr)); + + /* Unlock mutex. */ + pj_mutex_unlock(mod_tsx_layer.mutex); +} + + +/* + * Retrieve the current number of transactions currently registered in + * the hash table. + */ +PJ_DEF(unsigned) pjsip_tsx_layer_get_tsx_count(void) +{ + unsigned count; + + /* Are we registered? */ + PJ_ASSERT_RETURN(mod_tsx_layer.endpt!=NULL, 0); + + pj_mutex_lock(mod_tsx_layer.mutex); + count = pj_hash_count(mod_tsx_layer.htable); + pj_mutex_unlock(mod_tsx_layer.mutex); + + return count; +} + + +/* + * Find a transaction. + */ +PJ_DEF(pjsip_transaction*) pjsip_tsx_layer_find_tsx( const pj_str_t *key, + pj_bool_t lock ) +{ + pjsip_transaction *tsx; + pj_uint32_t hval = 0; + + pj_mutex_lock(mod_tsx_layer.mutex); + tsx = (pjsip_transaction*) + pj_hash_get( mod_tsx_layer.htable, key->ptr, key->slen, &hval ); + pj_mutex_unlock(mod_tsx_layer.mutex); + + TSX_TRACE_((THIS_FILE, + "Finding tsx with hkey=0x%p and key=%.*s: found %p", + hval, key->slen, key->ptr, tsx)); + + /* Race condition! + * Transaction may gets deleted before we have chance to lock it. + */ + PJ_TODO(FIX_RACE_CONDITION_HERE); + if (tsx && lock) + pj_mutex_lock(tsx->mutex); + + return tsx; +} + + +/* This module callback is called when module is being loaded by + * endpoint. It does nothing for this module. + */ +static pj_status_t mod_tsx_layer_load(pjsip_endpoint *endpt) +{ + PJ_UNUSED_ARG(endpt); + return PJ_SUCCESS; +} + + +/* This module callback is called when module is being started by + * endpoint. It does nothing for this module. + */ +static pj_status_t mod_tsx_layer_start(void) +{ + return PJ_SUCCESS; +} + + +/* This module callback is called when module is being stopped by + * endpoint. + */ +static pj_status_t mod_tsx_layer_stop(void) +{ + pj_hash_iterator_t it_buf, *it; + + PJ_LOG(4,(THIS_FILE, "Stopping transaction layer module")); + + pj_mutex_lock(mod_tsx_layer.mutex); + + /* Destroy all transactions. */ + it = pj_hash_first(mod_tsx_layer.htable, &it_buf); + while (it) { + pjsip_transaction *tsx = (pjsip_transaction*) + pj_hash_this(mod_tsx_layer.htable, it); + pj_hash_iterator_t *next = pj_hash_next(mod_tsx_layer.htable, it); + if (tsx) { + pjsip_tsx_terminate(tsx, PJSIP_SC_SERVICE_UNAVAILABLE); + mod_tsx_layer_unregister_tsx(tsx); + tsx_destroy(tsx); + } + it = next; + } + + pj_mutex_unlock(mod_tsx_layer.mutex); + + PJ_LOG(4,(THIS_FILE, "Stopped transaction layer module")); + + return PJ_SUCCESS; +} + + +/* Destroy this module */ +static void tsx_layer_destroy(pjsip_endpoint *endpt) +{ + PJ_UNUSED_ARG(endpt); + + /* Destroy mutex. */ + pj_mutex_destroy(mod_tsx_layer.mutex); + + /* Release pool. */ + pjsip_endpt_release_pool(mod_tsx_layer.endpt, mod_tsx_layer.pool); + + /* Free TLS */ + pj_thread_local_free(pjsip_tsx_lock_tls_id); + + /* Mark as unregistered. */ + mod_tsx_layer.endpt = NULL; + + PJ_LOG(4,(THIS_FILE, "Transaction layer module destroyed")); +} + + +/* This module callback is called when module is being unloaded by + * endpoint. + */ +static pj_status_t mod_tsx_layer_unload(void) +{ + /* Only self destroy when there's no transaction in the table. + * Transaction may refuse to destroy when it has pending + * transmission. If we destroy the module now, application will + * crash when the pending transaction finally got error response + * from transport and when it tries to unregister itself. + */ + if (pj_hash_count(mod_tsx_layer.htable) != 0) { + if (pjsip_endpt_atexit(mod_tsx_layer.endpt, &tsx_layer_destroy) != + PJ_SUCCESS) + { + PJ_LOG(3,(THIS_FILE, "Failed to register transaction layer " + "module destroy.")); + } + return PJ_EBUSY; + } + + tsx_layer_destroy(mod_tsx_layer.endpt); + + return PJ_SUCCESS; +} + + +/* This module callback is called when endpoint has received an + * incoming request message. + */ +static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata) +{ + pj_str_t key; + pj_uint32_t hval = 0; + pjsip_transaction *tsx; + + pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAS, + &rdata->msg_info.cseq->method, rdata); + + /* Find transaction. */ + pj_mutex_lock( mod_tsx_layer.mutex ); + + tsx = (pjsip_transaction*) + pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval ); + + + TSX_TRACE_((THIS_FILE, + "Finding tsx for request, hkey=0x%p and key=%.*s, found %p", + hval, key.slen, key.ptr, tsx)); + + + if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) { + /* Transaction not found. + * Reject the request so that endpoint passes the request to + * upper layer modules. + */ + pj_mutex_unlock( mod_tsx_layer.mutex); + return PJ_FALSE; + } + + /* Unlock hash table. */ + pj_mutex_unlock( mod_tsx_layer.mutex ); + + /* Race condition! + * Transaction may gets deleted before we have chance to lock it + * in pjsip_tsx_recv_msg(). + */ + PJ_TODO(FIX_RACE_CONDITION_HERE); + + /* Pass the message to the transaction. */ + pjsip_tsx_recv_msg(tsx, rdata ); + + return PJ_TRUE; +} + + +/* This module callback is called when endpoint has received an + * incoming response message. + */ +static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata) +{ + pj_str_t key; + pj_uint32_t hval = 0; + pjsip_transaction *tsx; + + pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, + &rdata->msg_info.cseq->method, rdata); + + /* Find transaction. */ + pj_mutex_lock( mod_tsx_layer.mutex ); + + tsx = (pjsip_transaction*) + pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval ); + + + TSX_TRACE_((THIS_FILE, + "Finding tsx for response, hkey=0x%p and key=%.*s, found %p", + hval, key.slen, key.ptr, tsx)); + + + if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) { + /* Transaction not found. + * Reject the request so that endpoint passes the request to + * upper layer modules. + */ + pj_mutex_unlock( mod_tsx_layer.mutex); + return PJ_FALSE; + } + + /* Unlock hash table. */ + pj_mutex_unlock( mod_tsx_layer.mutex ); + + /* Race condition! + * Transaction may gets deleted before we have chance to lock it + * in pjsip_tsx_recv_msg(). + */ + PJ_TODO(FIX_RACE_CONDITION_HERE); + + /* Pass the message to the transaction. */ + pjsip_tsx_recv_msg(tsx, rdata ); + + return PJ_TRUE; +} + + +/* + * Get transaction instance in the rdata. + */ +PJ_DEF(pjsip_transaction*) pjsip_rdata_get_tsx( pjsip_rx_data *rdata ) +{ + return (pjsip_transaction*) + rdata->endpt_info.mod_data[mod_tsx_layer.mod.id]; +} + + +/* + * Dump transaction layer. + */ +PJ_DEF(void) pjsip_tsx_layer_dump(pj_bool_t detail) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + pj_hash_iterator_t itbuf, *it; + + /* Lock mutex. */ + pj_mutex_lock(mod_tsx_layer.mutex); + + PJ_LOG(3, (THIS_FILE, "Dumping transaction table:")); + PJ_LOG(3, (THIS_FILE, " Total %d transactions", + pj_hash_count(mod_tsx_layer.htable))); + + if (detail) { + it = pj_hash_first(mod_tsx_layer.htable, &itbuf); + if (it == NULL) { + PJ_LOG(3, (THIS_FILE, " - none - ")); + } else { + while (it != NULL) { + pjsip_transaction *tsx = (pjsip_transaction*) + pj_hash_this(mod_tsx_layer.htable,it); + + PJ_LOG(3, (THIS_FILE, " %s %s|%d|%s", + tsx->obj_name, + (tsx->last_tx? + pjsip_tx_data_get_info(tsx->last_tx): + "none"), + tsx->status_code, + pjsip_tsx_state_str(tsx->state))); + + it = pj_hash_next(mod_tsx_layer.htable, it); + } + } + } + + /* Unlock mutex. */ + pj_mutex_unlock(mod_tsx_layer.mutex); +#endif +} + +/***************************************************************************** + ** + ** Transaction + ** + ***************************************************************************** + **/ +/* + * Lock transaction and set the value of Thread Local Storage. + */ +static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck) +{ + struct tsx_lock_data *prev_data; + + pj_mutex_lock(tsx->mutex); + prev_data = (struct tsx_lock_data *) + pj_thread_local_get(pjsip_tsx_lock_tls_id); + lck->prev = prev_data; + lck->tsx = tsx; + lck->is_alive = 1; + pj_thread_local_set(pjsip_tsx_lock_tls_id, lck); +} + + +/* + * Unlock transaction. + * This will selectively unlock the mutex ONLY IF the transaction has not been + * destroyed. The function knows whether the transaction has been destroyed + * because when transaction is destroyed the is_alive flag for the transaction + * will be set to zero. + */ +static pj_status_t unlock_tsx( pjsip_transaction *tsx, + struct tsx_lock_data *lck) +{ + pj_assert( (void*)pj_thread_local_get(pjsip_tsx_lock_tls_id) == lck); + pj_assert( lck->tsx == tsx ); + pj_thread_local_set(pjsip_tsx_lock_tls_id, lck->prev); + if (lck->is_alive) + pj_mutex_unlock(tsx->mutex); + + return lck->is_alive ? PJ_SUCCESS : PJSIP_ETSXDESTROYED; +} + + +/* Lock transaction for accessing the timeout timer only. */ +static void lock_timer(pjsip_transaction *tsx) +{ + pj_mutex_lock(tsx->mutex_b); +} + +/* Unlock timer */ +static void unlock_timer(pjsip_transaction *tsx) +{ + pj_mutex_unlock(tsx->mutex_b); +} + +/* Create and initialize basic transaction structure. + * This function is called by both UAC and UAS creation. + */ +static pj_status_t tsx_create( pjsip_module *tsx_user, + pjsip_transaction **p_tsx) +{ + pj_pool_t *pool; + pjsip_transaction *tsx; + pj_status_t status; + + pool = pjsip_endpt_create_pool( mod_tsx_layer.endpt, "tsx", + PJSIP_POOL_TSX_LEN, PJSIP_POOL_TSX_INC ); + if (!pool) + return PJ_ENOMEM; + + tsx = PJ_POOL_ZALLOC_T(pool, pjsip_transaction); + tsx->pool = pool; + tsx->tsx_user = tsx_user; + tsx->endpt = mod_tsx_layer.endpt; + + pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name), + "tsx%p", tsx); + pj_memcpy(pool->obj_name, tsx->obj_name, sizeof(pool->obj_name)); + + tsx->handle_200resp = 1; + tsx->retransmit_timer.id = 0; + tsx->retransmit_timer.user_data = tsx; + tsx->retransmit_timer.cb = &tsx_timer_callback; + tsx->timeout_timer.id = 0; + tsx->timeout_timer.user_data = tsx; + tsx->timeout_timer.cb = &tsx_timer_callback; + + status = pj_mutex_create_recursive(pool, tsx->obj_name, &tsx->mutex); + if (status != PJ_SUCCESS) { + pjsip_endpt_release_pool(mod_tsx_layer.endpt, pool); + return status; + } + + status = pj_mutex_create_simple(pool, tsx->obj_name, &tsx->mutex_b); + if (status != PJ_SUCCESS) { + pj_mutex_destroy(tsx->mutex); + pjsip_endpt_release_pool(mod_tsx_layer.endpt, pool); + return status; + } + + *p_tsx = tsx; + return PJ_SUCCESS; +} + + +/* Destroy transaction. */ +static pj_status_t tsx_destroy( pjsip_transaction *tsx ) +{ + struct tsx_lock_data *lck; + + /* Release the transport */ + tsx_update_transport(tsx, NULL); + + /* Decrement reference counter in transport selector */ + pjsip_tpselector_dec_ref(&tsx->tp_sel); + + /* Free last transmitted message. */ + if (tsx->last_tx) { + pjsip_tx_data_dec_ref( tsx->last_tx ); + tsx->last_tx = NULL; + } + /* Cancel timeout timer. */ + if (tsx->timeout_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = 0; + } + /* Cancel retransmission timer. */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* Clear some pending flags. */ + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED | TSX_HAS_PENDING_SEND); + + /* Refuse to destroy transaction if it has pending resolving. */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_DESTROY; + tsx->tsx_user = NULL; + PJ_LOG(4,(tsx->obj_name, "Will destroy later because transport is " + "in progress")); + return PJ_EBUSY; + } + + /* Clear TLS, so that mutex will not be unlocked */ + lck = (struct tsx_lock_data*) pj_thread_local_get(pjsip_tsx_lock_tls_id); + while (lck) { + if (lck->tsx == tsx) { + lck->is_alive = 0; + } + lck = lck->prev; + } + + pj_mutex_destroy(tsx->mutex_b); + pj_mutex_destroy(tsx->mutex); + + PJ_LOG(5,(tsx->obj_name, "Transaction destroyed!")); + + pjsip_endpt_release_pool(tsx->endpt, tsx->pool); + + return PJ_SUCCESS; +} + + +/* + * Callback when timer expires. + */ +static void tsx_timer_callback( pj_timer_heap_t *theap, pj_timer_entry *entry) +{ + pjsip_event event; + pjsip_transaction *tsx = (pjsip_transaction*) entry->user_data; + struct tsx_lock_data lck; + + PJ_UNUSED_ARG(theap); + + entry->id = 0; + + PJ_LOG(5,(tsx->obj_name, "%s timer event", + (entry==&tsx->retransmit_timer ? "Retransmit":"Timeout"))); + pj_log_push_indent(); + + + PJSIP_EVENT_INIT_TIMER(event, entry); + + /* Dispatch event to transaction. */ + lock_tsx(tsx, &lck); + (*tsx->state_handler)(tsx, &event); + unlock_tsx(tsx, &lck); + + pj_log_pop_indent(); +} + + +/* + * Set transaction state, and inform TU about the transaction state change. + */ +static void tsx_set_state( pjsip_transaction *tsx, + pjsip_tsx_state_e state, + pjsip_event_id_e event_src_type, + void *event_src ) +{ + pjsip_tsx_state_e prev_state = tsx->state; + + /* New state must be greater than previous state */ + pj_assert(state >= tsx->state); + + PJ_LOG(5, (tsx->obj_name, "State changed from %s to %s, event=%s", + state_str[tsx->state], state_str[state], + pjsip_event_str(event_src_type))); + pj_log_push_indent(); + + /* Change state. */ + tsx->state = state; + + /* Update the state handlers. */ + if (tsx->role == PJSIP_ROLE_UAC) { + tsx->state_handler = tsx_state_handler_uac[state]; + } else { + tsx->state_handler = tsx_state_handler_uas[state]; + } + + /* Before informing TU about state changed, inform TU about + * rx event. + */ + if (event_src_type==PJSIP_EVENT_RX_MSG && tsx->tsx_user) { + pjsip_rx_data *rdata = (pjsip_rx_data*) event_src; + + pj_assert(rdata != NULL); + + if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG && + tsx->tsx_user->on_rx_response) + { + (*tsx->tsx_user->on_rx_response)(rdata); + } + + } + + /* Inform TU about state changed. */ + if (tsx->tsx_user && tsx->tsx_user->on_tsx_state) { + pjsip_event e; + PJSIP_EVENT_INIT_TSX_STATE(e, tsx, event_src_type, event_src, + prev_state); + (*tsx->tsx_user->on_tsx_state)(tsx, &e); + } + + + /* When the transaction is terminated, release transport, and free the + * saved last transmitted message. + */ + if (state == PJSIP_TSX_STATE_TERMINATED) { + pj_time_val timeout = {0, 0}; + + /* If we're still waiting for a message to be sent.. */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + /* Disassociate ourselves from the outstanding transmit data + * so that when the send callback is called we will be able + * to ignore that (otherwise we'll get assertion, see + * http://trac.pjsip.org/repos/ticket/1033) + */ + if (tsx->pending_tx) { + tsx->pending_tx->mod_data[mod_tsx_layer.mod.id] = NULL; + tsx->pending_tx = NULL; + } + tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT); + } + + lock_timer(tsx); + + /* Cancel timeout timer. */ + if (tsx->timeout_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = 0; + } + + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout); + + unlock_timer(tsx); + + } else if (state == PJSIP_TSX_STATE_DESTROYED) { + + /* Unregister transaction. */ + mod_tsx_layer_unregister_tsx(tsx); + + /* Destroy transaction. */ + tsx_destroy(tsx); + } + + pj_log_pop_indent(); +} + + +/* + * Create, initialize, and register UAC transaction. + */ +PJ_DEF(pj_status_t) pjsip_tsx_create_uac( pjsip_module *tsx_user, + pjsip_tx_data *tdata, + pjsip_transaction **p_tsx) +{ + pjsip_transaction *tsx; + pjsip_msg *msg; + pjsip_cseq_hdr *cseq; + pjsip_via_hdr *via; + pjsip_host_info dst_info; + struct tsx_lock_data lck; + pj_status_t status; + + /* Validate arguments. */ + PJ_ASSERT_RETURN(tdata && tdata->msg && p_tsx, PJ_EINVAL); + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Method MUST NOT be ACK! */ + PJ_ASSERT_RETURN(tdata->msg->line.req.method.id != PJSIP_ACK_METHOD, + PJ_EINVALIDOP); + + /* Keep shortcut */ + msg = tdata->msg; + + /* Make sure CSeq header is present. */ + cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL); + if (!cseq) { + pj_assert(!"CSeq header not present in outgoing message!"); + return PJSIP_EMISSINGHDR; + } + + + /* Create transaction instance. */ + status = tsx_create( tsx_user, &tsx); + if (status != PJ_SUCCESS) + return status; + + + /* Lock transaction. */ + lock_tsx(tsx, &lck); + + /* Role is UAC. */ + tsx->role = PJSIP_ROLE_UAC; + + /* Save method. */ + pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method); + + /* Save CSeq. */ + tsx->cseq = cseq->cseq; + + /* Generate Via header if it doesn't exist. */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_VIA, NULL); + if (via == NULL) { + via = pjsip_via_hdr_create(tdata->pool); + pjsip_msg_insert_first_hdr(msg, (pjsip_hdr*) via); + } + + /* Generate branch parameter if it doesn't exist. */ + if (via->branch_param.slen == 0) { + pj_str_t tmp; + via->branch_param.ptr = (char*) + pj_pool_alloc(tsx->pool, PJSIP_MAX_BRANCH_LEN); + via->branch_param.slen = PJSIP_MAX_BRANCH_LEN; + pj_memcpy(via->branch_param.ptr, PJSIP_RFC3261_BRANCH_ID, + PJSIP_RFC3261_BRANCH_LEN); + tmp.ptr = via->branch_param.ptr + PJSIP_RFC3261_BRANCH_LEN + 2; + *(tmp.ptr-2) = 80; *(tmp.ptr-1) = 106; + pj_generate_unique_string( &tmp ); + + /* Save branch parameter. */ + tsx->branch = via->branch_param; + + } else { + /* Copy branch parameter. */ + pj_strdup(tsx->pool, &tsx->branch, &via->branch_param); + } + + /* Generate transaction key. */ + create_tsx_key_3261( tsx->pool, &tsx->transaction_key, + PJSIP_ROLE_UAC, &tsx->method, + &via->branch_param); + + /* Calculate hashed key value. */ +#ifdef PRECALC_HASH + tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr, + tsx->transaction_key.slen); +#endif + + PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen, + tsx->transaction_key.ptr)); + + /* Begin with State_Null. + * Manually set-up the state becase we don't want to call the callback. + */ + tsx->state = PJSIP_TSX_STATE_NULL; + tsx->state_handler = &tsx_on_state_null; + + /* Save the message. */ + tsx->last_tx = tdata; + pjsip_tx_data_add_ref(tsx->last_tx); + + /* Determine whether reliable transport should be used initially. + * This will be updated whenever transport has changed. + */ + status = pjsip_get_request_dest(tdata, &dst_info); + if (status != PJ_SUCCESS) { + unlock_tsx(tsx, &lck); + tsx_destroy(tsx); + return status; + } + tsx->is_reliable = (dst_info.flag & PJSIP_TRANSPORT_RELIABLE); + + /* Register transaction to hash table. */ + status = mod_tsx_layer_register_tsx(tsx); + if (status != PJ_SUCCESS) { + /* The assertion is removed by #1090: + pj_assert(!"Bug in branch_param generator (i.e. not unique)"); + */ + unlock_tsx(tsx, &lck); + tsx_destroy(tsx); + return status; + } + + + /* Unlock transaction and return. */ + unlock_tsx(tsx, &lck); + + pj_log_push_indent(); + PJ_LOG(5,(tsx->obj_name, "Transaction created for %s", + pjsip_tx_data_get_info(tdata))); + pj_log_pop_indent(); + + *p_tsx = tsx; + return PJ_SUCCESS; +} + + +/* + * Create, initialize, and register UAS transaction. + */ +PJ_DEF(pj_status_t) pjsip_tsx_create_uas( pjsip_module *tsx_user, + pjsip_rx_data *rdata, + pjsip_transaction **p_tsx) +{ + pjsip_transaction *tsx; + pjsip_msg *msg; + pj_str_t *branch; + pjsip_cseq_hdr *cseq; + pj_status_t status; + struct tsx_lock_data lck; + + /* Validate arguments. */ + PJ_ASSERT_RETURN(rdata && rdata->msg_info.msg && p_tsx, PJ_EINVAL); + + /* Keep shortcut to message */ + msg = rdata->msg_info.msg; + + /* Make sure this is a request message. */ + PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG); + + /* Make sure method is not ACK */ + PJ_ASSERT_RETURN(msg->line.req.method.id != PJSIP_ACK_METHOD, + PJ_EINVALIDOP); + + /* Make sure CSeq header is present. */ + cseq = rdata->msg_info.cseq; + if (!cseq) + return PJSIP_EMISSINGHDR; + + /* Make sure Via header is present. */ + if (rdata->msg_info.via == NULL) + return PJSIP_EMISSINGHDR; + + /* Check that method in CSeq header match request method. + * Reference: PROTOS #1922 + */ + if (pjsip_method_cmp(&msg->line.req.method, + &rdata->msg_info.cseq->method) != 0) + { + PJ_LOG(4,(THIS_FILE, "Error: CSeq header contains different " + "method than the request line")); + return PJSIP_EINVALIDHDR; + } + + /* + * Create transaction instance. + */ + status = tsx_create( tsx_user, &tsx); + if (status != PJ_SUCCESS) + return status; + + + /* Lock transaction. */ + lock_tsx(tsx, &lck); + + /* Role is UAS */ + tsx->role = PJSIP_ROLE_UAS; + + /* Save method. */ + pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method); + + /* Save CSeq */ + tsx->cseq = cseq->cseq; + + /* Get transaction key either from branch for RFC3261 message, or + * create transaction key. + */ + status = pjsip_tsx_create_key(tsx->pool, &tsx->transaction_key, + PJSIP_ROLE_UAS, &tsx->method, rdata); + if (status != PJ_SUCCESS) { + unlock_tsx(tsx, &lck); + tsx_destroy(tsx); + return status; + } + + /* Calculate hashed key value. */ +#ifdef PRECALC_HASH + tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr, + tsx->transaction_key.slen); +#endif + + /* Duplicate branch parameter for transaction. */ + branch = &rdata->msg_info.via->branch_param; + pj_strdup(tsx->pool, &tsx->branch, branch); + + PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen, + tsx->transaction_key.ptr)); + + + /* Begin with state NULL. + * Manually set-up the state becase we don't want to call the callback. + */ + tsx->state = PJSIP_TSX_STATE_NULL; + tsx->state_handler = &tsx_on_state_null; + + /* Get response address. */ + status = pjsip_get_response_addr( tsx->pool, rdata, &tsx->res_addr ); + if (status != PJ_SUCCESS) { + unlock_tsx(tsx, &lck); + tsx_destroy(tsx); + return status; + } + + /* If it's decided that we should use current transport, keep the + * transport. + */ + if (tsx->res_addr.transport) { + tsx_update_transport(tsx, tsx->res_addr.transport); + pj_memcpy(&tsx->addr, &tsx->res_addr.addr, tsx->res_addr.addr_len); + tsx->addr_len = tsx->res_addr.addr_len; + tsx->is_reliable = PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport); + } else { + tsx->is_reliable = + (tsx->res_addr.dst_host.flag & PJSIP_TRANSPORT_RELIABLE); + } + + + /* Register the transaction. */ + status = mod_tsx_layer_register_tsx(tsx); + if (status != PJ_SUCCESS) { + unlock_tsx(tsx, &lck); + tsx_destroy(tsx); + return status; + } + + /* Put this transaction in rdata's mod_data. */ + rdata->endpt_info.mod_data[mod_tsx_layer.mod.id] = tsx; + + /* Unlock transaction and return. */ + unlock_tsx(tsx, &lck); + + pj_log_push_indent(); + PJ_LOG(5,(tsx->obj_name, "Transaction created for %s", + pjsip_rx_data_get_info(rdata))); + pj_log_pop_indent(); + + + *p_tsx = tsx; + return PJ_SUCCESS; +} + + +/* + * Bind transaction to a specific transport/listener. + */ +PJ_DEF(pj_status_t) pjsip_tsx_set_transport(pjsip_transaction *tsx, + const pjsip_tpselector *sel) +{ + struct tsx_lock_data lck; + + /* Must be UAC transaction */ + PJ_ASSERT_RETURN(tsx && sel, PJ_EINVAL); + + /* Start locking the transaction. */ + lock_tsx(tsx, &lck); + + /* Decrement reference counter of previous transport selector */ + pjsip_tpselector_dec_ref(&tsx->tp_sel); + + /* Copy transport selector structure .*/ + pj_memcpy(&tsx->tp_sel, sel, sizeof(*sel)); + + /* Increment reference counter */ + pjsip_tpselector_add_ref(&tsx->tp_sel); + + /* Unlock transaction. */ + unlock_tsx(tsx, &lck); + + return PJ_SUCCESS; +} + + +/* + * Set transaction status code and reason. + */ +static void tsx_set_status_code(pjsip_transaction *tsx, + int code, const pj_str_t *reason) +{ + tsx->status_code = code; + if (reason) + pj_strdup(tsx->pool, &tsx->status_text, reason); + else + tsx->status_text = *pjsip_get_status_text(code); +} + + +/* + * Forcely terminate transaction. + */ +PJ_DEF(pj_status_t) pjsip_tsx_terminate( pjsip_transaction *tsx, int code ) +{ + struct tsx_lock_data lck; + + PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL); + + PJ_LOG(5,(tsx->obj_name, "Request to terminate transaction")); + + PJ_ASSERT_RETURN(code >= 200, PJ_EINVAL); + + if (tsx->state >= PJSIP_TSX_STATE_TERMINATED) + return PJ_SUCCESS; + + pj_log_push_indent(); + + lock_tsx(tsx, &lck); + tsx_set_status_code(tsx, code, NULL); + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, PJSIP_EVENT_USER, NULL); + unlock_tsx(tsx, &lck); + + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + + +/* + * Cease retransmission on the UAC transaction. The UAC transaction is + * still considered running, and it will complete when either final + * response is received or the transaction times out. + */ +PJ_DEF(pj_status_t) pjsip_tsx_stop_retransmit(pjsip_transaction *tsx) +{ + struct tsx_lock_data lck; + + PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(tsx->role == PJSIP_ROLE_UAC && + tsx->method.id == PJSIP_INVITE_METHOD, + PJ_EINVALIDOP); + + PJ_LOG(5,(tsx->obj_name, "Request to stop retransmission")); + + pj_log_push_indent(); + + lock_tsx(tsx, &lck); + /* Cancel retransmission timer. */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + unlock_tsx(tsx, &lck); + + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + + +/* + * Start a timer to terminate transaction after the specified time + * has elapsed. + */ +PJ_DEF(pj_status_t) pjsip_tsx_set_timeout( pjsip_transaction *tsx, + unsigned millisec) +{ + pj_time_val timeout; + + PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(tsx->role == PJSIP_ROLE_UAC && + tsx->method.id == PJSIP_INVITE_METHOD, + PJ_EINVALIDOP); + + /* Note: must not call lock_tsx() as that would introduce deadlock. + * See #1121. + */ + lock_timer(tsx); + + /* Transaction should normally not have final response, but as + * #1121 says there is a (tolerable) window of race condition + * where this might happen. + */ + if (tsx->status_code >= 200 && tsx->timeout_timer.id != 0) { + /* Timeout is already set */ + unlock_timer(tsx); + return PJ_EEXISTS; + } + + if (tsx->timeout_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = 0; + } + + timeout.sec = 0; + timeout.msec = millisec; + pj_time_val_normalize(&timeout); + + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt, &tsx->timeout_timer, + &timeout); + + + unlock_timer(tsx); + + return PJ_SUCCESS; +} + + +/* + * This function is called by TU to send a message. + */ +PJ_DEF(pj_status_t) pjsip_tsx_send_msg( pjsip_transaction *tsx, + pjsip_tx_data *tdata ) +{ + pjsip_event event; + struct tsx_lock_data lck; + pj_status_t status; + + if (tdata == NULL) + tdata = tsx->last_tx; + + PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP); + + PJ_LOG(5,(tsx->obj_name, "Sending %s in state %s", + pjsip_tx_data_get_info(tdata), + state_str[tsx->state])); + pj_log_push_indent(); + + PJSIP_EVENT_INIT_TX_MSG(event, tdata); + + /* Dispatch to transaction. */ + lock_tsx(tsx, &lck); + + /* Set transport selector to tdata */ + pjsip_tx_data_set_transport(tdata, &tsx->tp_sel); + + /* Dispatch to state handler */ + status = (*tsx->state_handler)(tsx, &event); + + unlock_tsx(tsx, &lck); + + /* Only decrement reference counter when it returns success. + * (This is the specification from the .PDF design document). + */ + if (status == PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + } + + pj_log_pop_indent(); + + return status; +} + + +/* + * This function is called by endpoint when incoming message for the + * transaction is received. + */ +PJ_DEF(void) pjsip_tsx_recv_msg( pjsip_transaction *tsx, + pjsip_rx_data *rdata) +{ + pjsip_event event; + struct tsx_lock_data lck; + pj_status_t status; + + PJ_LOG(5,(tsx->obj_name, "Incoming %s in state %s", + pjsip_rx_data_get_info(rdata), state_str[tsx->state])); + pj_log_push_indent(); + + /* Put the transaction in the rdata's mod_data. */ + rdata->endpt_info.mod_data[mod_tsx_layer.mod.id] = tsx; + + /* Init event. */ + PJSIP_EVENT_INIT_RX_MSG(event, rdata); + + /* Dispatch to transaction. */ + lock_tsx(tsx, &lck); + status = (*tsx->state_handler)(tsx, &event); + unlock_tsx(tsx, &lck); + + pj_log_pop_indent(); +} + + +/* Callback called by send message framework */ +static void send_msg_callback( pjsip_send_state *send_state, + pj_ssize_t sent, pj_bool_t *cont ) +{ + pjsip_transaction *tsx = (pjsip_transaction*) send_state->token; + pjsip_tx_data *tdata = send_state->tdata; + struct tsx_lock_data lck; + + /* Check if transaction has cancelled itself from this transmit + * notification (https://trac.pjsip.org/repos/ticket/1033). + * Also check if the transaction layer itself may have been shutdown + * (https://trac.pjsip.org/repos/ticket/1535) + */ + if (mod_tsx_layer.mod.id < 0 || + tdata->mod_data[mod_tsx_layer.mod.id] == NULL) + { + *cont = PJ_FALSE; + return; + } + + /* Reset */ + tdata->mod_data[mod_tsx_layer.mod.id] = NULL; + tsx->pending_tx = NULL; + + lock_tsx(tsx, &lck); + + if (sent > 0) { + /* Successfully sent! */ + pj_assert(send_state->cur_transport != NULL); + + if (tsx->transport != send_state->cur_transport) { + /* Update transport. */ + tsx_update_transport(tsx, send_state->cur_transport); + + /* Update remote address. */ + tsx->addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len; + pj_memcpy(&tsx->addr, + &tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr, + tsx->addr_len); + + /* Update is_reliable flag. */ + tsx->is_reliable = PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport); + } + + /* Clear pending transport flag. */ + tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT); + + /* Mark that we have resolved the addresses. */ + tsx->transport_flag |= TSX_HAS_RESOLVED_SERVER; + + /* Pending destroy? */ + if (tsx->transport_flag & TSX_HAS_PENDING_DESTROY) { + tsx_set_state( tsx, PJSIP_TSX_STATE_DESTROYED, + PJSIP_EVENT_UNKNOWN, NULL ); + unlock_tsx(tsx, &lck); + return; + } + + /* Need to transmit a message? */ + if (tsx->transport_flag & TSX_HAS_PENDING_SEND) { + tsx->transport_flag &= ~(TSX_HAS_PENDING_SEND); + tsx_send_msg(tsx, tsx->last_tx); + } + + /* Need to reschedule retransmission? */ + if (tsx->transport_flag & TSX_HAS_PENDING_RESCHED) { + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED); + + /* Only update when transport turns out to be unreliable. */ + if (!tsx->is_reliable) { + tsx_resched_retransmission(tsx); + } + } + + } else { + /* Failed to send! */ + pj_assert(sent != 0); + + /* If transaction is using the same transport as the failed one, + * release the transport. + */ + if (send_state->cur_transport==tsx->transport) + tsx_update_transport(tsx, NULL); + + /* Also stop processing if transaction has been flagged with + * pending destroy (http://trac.pjsip.org/repos/ticket/906) + */ + if ((!*cont) || (tsx->transport_flag & TSX_HAS_PENDING_DESTROY)) { + char errmsg[PJ_ERR_MSG_SIZE]; + pjsip_status_code sc; + pj_str_t err; + + tsx->transport_err = -sent; + + err =pj_strerror(-sent, errmsg, sizeof(errmsg)); + + PJ_LOG(2,(tsx->obj_name, + "Failed to send %s! err=%d (%s)", + pjsip_tx_data_get_info(send_state->tdata), -sent, + errmsg)); + + /* Clear pending transport flag. */ + tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT); + + /* Mark that we have resolved the addresses. */ + tsx->transport_flag |= TSX_HAS_RESOLVED_SERVER; + + /* Server resolution error is now mapped to 502 instead of 503, + * since with 503 normally client should try again. + * See http://trac.pjsip.org/repos/ticket/870 + */ + if (-sent==PJ_ERESOLVE || -sent==PJLIB_UTIL_EDNS_NXDOMAIN) + sc = PJSIP_SC_BAD_GATEWAY; + else + sc = PJSIP_SC_TSX_TRANSPORT_ERROR; + + /* Terminate transaction, if it's not already terminated. */ + tsx_set_status_code(tsx, sc, &err); + if (tsx->state != PJSIP_TSX_STATE_TERMINATED && + tsx->state != PJSIP_TSX_STATE_DESTROYED) + { + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TRANSPORT_ERROR, send_state->tdata); + } + /* Don't forget to destroy if we have pending destroy flag + * (http://trac.pjsip.org/repos/ticket/906) + */ + else if (tsx->transport_flag & TSX_HAS_PENDING_DESTROY) + { + tsx_set_state( tsx, PJSIP_TSX_STATE_DESTROYED, + PJSIP_EVENT_TRANSPORT_ERROR, send_state->tdata); + } + + } else { + char errmsg[PJ_ERR_MSG_SIZE]; + + PJ_LOG(2,(tsx->obj_name, + "Temporary failure in sending %s, " + "will try next server. Err=%d (%s)", + pjsip_tx_data_get_info(send_state->tdata), -sent, + pj_strerror(-sent, errmsg, sizeof(errmsg)).ptr)); + + /* Reset retransmission count */ + tsx->retransmit_count = 0; + + /* And reset timeout timer */ + if (tsx->timeout_timer.id) { + lock_timer(tsx); + + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = TIMER_INACTIVE; + + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout_timer_val); + + unlock_timer(tsx); + } + + /* Put again pending tdata */ + tdata->mod_data[mod_tsx_layer.mod.id] = tsx; + tsx->pending_tx = tdata; + } + } + + unlock_tsx(tsx, &lck); +} + + +/* Transport callback. */ +static void transport_callback(void *token, pjsip_tx_data *tdata, + pj_ssize_t sent) +{ + if (sent < 0) { + pjsip_transaction *tsx = (pjsip_transaction*) token; + struct tsx_lock_data lck; + char errmsg[PJ_ERR_MSG_SIZE]; + pj_str_t err; + + tsx->transport_err = -sent; + + err = pj_strerror(-sent, errmsg, sizeof(errmsg)); + + PJ_LOG(2,(tsx->obj_name, "Transport failed to send %s! Err=%d (%s)", + pjsip_tx_data_get_info(tdata), -sent, errmsg)); + + lock_tsx(tsx, &lck); + + /* Release transport. */ + tsx_update_transport(tsx, NULL); + + /* Terminate transaction. */ + tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err); + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TRANSPORT_ERROR, tdata ); + + unlock_tsx(tsx, &lck); + } +} + + +/* + * Callback when transport state changes. + */ +static void tsx_tp_state_callback( pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info) +{ + PJ_UNUSED_ARG(tp); + + if (state == PJSIP_TP_STATE_DISCONNECTED) { + pjsip_transaction *tsx; + struct tsx_lock_data lck; + + pj_assert(tp && info && info->user_data); + + tsx = (pjsip_transaction*)info->user_data; + + lock_tsx(tsx, &lck); + + /* Terminate transaction when transport disconnected */ + if (tsx->state < PJSIP_TSX_STATE_TERMINATED) { + pj_str_t err; + char errmsg[PJ_ERR_MSG_SIZE]; + + err = pj_strerror(info->status, errmsg, sizeof(errmsg)); + tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err); + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TRANSPORT_ERROR, NULL); + } + + unlock_tsx(tsx, &lck); + } +} + + +/* + * Send message to the transport. + */ +static pj_status_t tsx_send_msg( pjsip_transaction *tsx, + pjsip_tx_data *tdata) +{ + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(tsx && tdata, PJ_EINVAL); + + /* Send later if transport is still pending. */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_SEND; + return PJ_SUCCESS; + } + + /* If we have the transport, send the message using that transport. + * Otherwise perform full transport resolution. + */ + if (tsx->transport) { + status = pjsip_transport_send( tsx->transport, tdata, &tsx->addr, + tsx->addr_len, tsx, + &transport_callback); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + PJ_LOG(2,(tsx->obj_name, + "Error sending %s: Err=%d (%s)", + pjsip_tx_data_get_info(tdata), status, + pj_strerror(status, errmsg, sizeof(errmsg)).ptr)); + + /* On error, release transport to force using full transport + * resolution procedure. + */ + tsx_update_transport(tsx, NULL); + + tsx->addr_len = 0; + tsx->res_addr.transport = NULL; + tsx->res_addr.addr_len = 0; + } else { + return PJ_SUCCESS; + } + } + + /* We are here because we don't have transport, or we failed to send + * the message using existing transport. If we haven't resolved the + * server before, then begin the long process of resolving the server + * and send the message with possibly new server. + */ + pj_assert(status != PJ_SUCCESS || tsx->transport == NULL); + + /* If we have resolved the server, we treat the error as permanent error. + * Terminate transaction with transport error failure. + */ + if (tsx->transport_flag & TSX_HAS_RESOLVED_SERVER) { + + char errmsg[PJ_ERR_MSG_SIZE]; + pj_str_t err; + + if (status == PJ_SUCCESS) { + pj_assert(!"Unexpected status!"); + status = PJ_EUNKNOWN; + } + + /* We have resolved the server!. + * Treat this as permanent transport error. + */ + err = pj_strerror(status, errmsg, sizeof(errmsg)); + + PJ_LOG(2,(tsx->obj_name, + "Transport error, terminating transaction. " + "Err=%d (%s)", + status, errmsg)); + + tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err); + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TRANSPORT_ERROR, NULL ); + + return status; + } + + /* Must add reference counter because the send request functions + * decrement the reference counter. + */ + pjsip_tx_data_add_ref(tdata); + + /* Also attach ourselves to the transmit data so that we'll be able + * to unregister ourselves from the send notification of this + * transmit data. + */ + tdata->mod_data[mod_tsx_layer.mod.id] = tsx; + tsx->pending_tx = tdata; + + /* Begin resolving destination etc to send the message. */ + if (tdata->msg->type == PJSIP_REQUEST_MSG) { + + tsx->transport_flag |= TSX_HAS_PENDING_TRANSPORT; + status = pjsip_endpt_send_request_stateless(tsx->endpt, tdata, tsx, + &send_msg_callback); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + tdata->mod_data[mod_tsx_layer.mod.id] = NULL; + tsx->pending_tx = NULL; + } + + /* Check if transaction is terminated. */ + if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TERMINATED) + status = tsx->transport_err; + + } else { + + tsx->transport_flag |= TSX_HAS_PENDING_TRANSPORT; + status = pjsip_endpt_send_response( tsx->endpt, &tsx->res_addr, + tdata, tsx, + &send_msg_callback); + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + tdata->mod_data[mod_tsx_layer.mod.id] = NULL; + tsx->pending_tx = NULL; + } + + /* Check if transaction is terminated. */ + if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TERMINATED) + status = tsx->transport_err; + + } + + + return status; +} + + +/* + * Manually retransmit the last messagewithout updating the transaction state. + */ +PJ_DEF(pj_status_t) pjsip_tsx_retransmit_no_state(pjsip_transaction *tsx, + pjsip_tx_data *tdata) +{ + struct tsx_lock_data lck; + pj_status_t status; + + lock_tsx(tsx, &lck); + if (tdata == NULL) { + tdata = tsx->last_tx; + } + status = tsx_send_msg(tsx, tdata); + unlock_tsx(tsx, &lck); + + /* Only decrement reference counter when it returns success. + * (This is the specification from the .PDF design document). + */ + if (status == PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + } + + return status; +} + + +/* + * Retransmit last message sent. + */ +static void tsx_resched_retransmission( pjsip_transaction *tsx ) +{ + pj_uint32_t msec_time; + + pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0); + + if (tsx->role==PJSIP_ROLE_UAC && tsx->status_code >= 100) + msec_time = pjsip_cfg()->tsx.t2; + else + msec_time = (1 << (tsx->retransmit_count)) * pjsip_cfg()->tsx.t1; + + if (tsx->role == PJSIP_ROLE_UAC) { + pj_assert(tsx->status_code < 200); + /* Retransmission for non-INVITE transaction caps-off at T2 */ + if (msec_time > pjsip_cfg()->tsx.t2 && + tsx->method.id != PJSIP_INVITE_METHOD) + { + msec_time = pjsip_cfg()->tsx.t2; + } + } else { + /* For UAS, this can be retransmission of 2xx response for INVITE + * or non-100 1xx response. + */ + if (tsx->status_code < 200) { + /* non-100 1xx retransmission is at 60 seconds */ + msec_time = PJSIP_TSX_1XX_RETRANS_DELAY * 1000; + } else { + /* Retransmission of INVITE final response also caps-off at T2 */ + pj_assert(tsx->status_code >= 200); + if (msec_time > pjsip_cfg()->tsx.t2) + msec_time = pjsip_cfg()->tsx.t2; + } + } + + if (msec_time != 0) { + pj_time_val timeout; + + timeout.sec = msec_time / 1000; + timeout.msec = msec_time % 1000; + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->retransmit_timer, + &timeout); + } +} + +/* + * Retransmit last message sent. + */ +static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(tsx->last_tx!=NULL, PJ_EBUG); + + PJ_LOG(5,(tsx->obj_name, "Retransmiting %s, count=%d, restart?=%d", + pjsip_tx_data_get_info(tsx->last_tx), + tsx->retransmit_count, resched)); + + ++tsx->retransmit_count; + + /* Restart timer T1 first before sending the message to ensure that + * retransmission timer is not engaged when loop transport is used. + */ + if (resched) { + pj_assert(tsx->state != PJSIP_TSX_STATE_CONFIRMED); + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + tsx_resched_retransmission(tsx); + } + } + + status = tsx_send_msg( tsx, tsx->last_tx); + if (status != PJ_SUCCESS) { + return status; + } + + return PJ_SUCCESS; +} + +static void tsx_update_transport( pjsip_transaction *tsx, + pjsip_transport *tp) +{ + pj_assert(tsx); + + if (tsx->transport) { + pjsip_transport_remove_state_listener(tsx->transport, + tsx->tp_st_key, tsx); + pjsip_transport_dec_ref( tsx->transport ); + tsx->transport = NULL; + } + + if (tp) { + tsx->transport = tp; + pjsip_transport_add_ref(tp); + pjsip_transport_add_state_listener(tp, &tsx_tp_state_callback, tsx, + &tsx->tp_st_key); + } +} + + +/* + * Handler for events in state Null. + */ +static pj_status_t tsx_on_state_null( pjsip_transaction *tsx, + pjsip_event *event ) +{ + pj_status_t status; + + pj_assert(tsx->state == PJSIP_TSX_STATE_NULL); + + if (tsx->role == PJSIP_ROLE_UAS) { + + /* Set state to Trying. */ + pj_assert(event->type == PJSIP_EVENT_RX_MSG && + event->body.rx_msg.rdata->msg_info.msg->type == + PJSIP_REQUEST_MSG); + tsx_set_state( tsx, PJSIP_TSX_STATE_TRYING, PJSIP_EVENT_RX_MSG, + event->body.rx_msg.rdata); + + } else { + pjsip_tx_data *tdata; + + /* Must be transmit event. + * You may got this assertion when using loop transport with delay + * set to zero. That would cause on_rx_response() callback to be + * called before tsx_send_msg() has completed. + */ + PJ_ASSERT_RETURN(event->type == PJSIP_EVENT_TX_MSG, PJ_EBUG); + + /* Get the txdata */ + tdata = event->body.tx_msg.tdata; + + /* Save the message for retransmission. */ + if (tsx->last_tx && tsx->last_tx != tdata) { + pjsip_tx_data_dec_ref(tsx->last_tx); + tsx->last_tx = NULL; + } + if (tsx->last_tx != tdata) { + tsx->last_tx = tdata; + pjsip_tx_data_add_ref(tdata); + } + + /* Send the message. */ + status = tsx_send_msg( tsx, tdata); + if (status != PJ_SUCCESS) { + return status; + } + + /* Start Timer B (or called timer F for non-INVITE) for transaction + * timeout. + */ + lock_timer(tsx); + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout_timer_val); + unlock_timer(tsx); + + /* Start Timer A (or timer E) for retransmission only if unreliable + * transport is being used. + */ + if (!tsx->is_reliable) { + tsx->retransmit_count = 0; + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt, &tsx->retransmit_timer, + &t1_timer_val); + } + } + + /* Move state. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_CALLING, + PJSIP_EVENT_TX_MSG, tdata); + } + + return PJ_SUCCESS; +} + + +/* + * State Calling is for UAC after it sends request but before any responses + * is received. + */ +static pj_status_t tsx_on_state_calling( pjsip_transaction *tsx, + pjsip_event *event ) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_CALLING); + pj_assert(tsx->role == PJSIP_ROLE_UAC); + + if (event->type == PJSIP_EVENT_TIMER && + event->body.timer.entry == &tsx->retransmit_timer) + { + pj_status_t status; + + /* Retransmit the request. */ + status = tsx_retransmit( tsx, 1 ); + if (status != PJ_SUCCESS) { + return status; + } + + } else if (event->type == PJSIP_EVENT_TIMER && + event->body.timer.entry == &tsx->timeout_timer) + { + + /* Cancel retransmission timer. */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED); + + /* Set status code */ + tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL); + + /* Inform TU. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer); + + /* Transaction is destroyed */ + //return PJSIP_ETSXDESTROYED; + + } else if (event->type == PJSIP_EVENT_RX_MSG) { + pjsip_msg *msg; + int code; + + /* Get message instance */ + msg = event->body.rx_msg.rdata->msg_info.msg; + + /* Better be a response message. */ + if (msg->type != PJSIP_RESPONSE_MSG) + return PJSIP_ENOTRESPONSEMSG; + + code = msg->line.status.code; + + /* If the response is final, cancel both retransmission and timeout + * timer. + */ + if (code >= 200) { + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + if (tsx->timeout_timer.id != 0) { + lock_timer(tsx); + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + tsx->timeout_timer.id = 0; + unlock_timer(tsx); + } + + } else { + /* Cancel retransmit timer (for non-INVITE transaction, the + * retransmit timer will be rescheduled at T2. + */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* For provisional response, only cancel retransmit when this + * is an INVITE transaction. For non-INVITE, section 17.1.2.1 + * of RFC 3261 says that: + * - retransmit timer is set to T2 + * - timeout timer F is not deleted. + */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + /* Cancel timeout timer */ + lock_timer(tsx); + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer); + unlock_timer(tsx); + + } else { + if (!tsx->is_reliable) { + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt, + &tsx->retransmit_timer, + &t2_timer_val); + } + } + } + + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED); + + + /* Discard retransmission message if it is not INVITE. + * The INVITE tdata is needed in case we have to generate ACK for + * the final response. + */ + /* Keep last_tx for authorization. */ + //blp: always keep last_tx until transaction is destroyed + //code = msg->line.status.code; + //if (tsx->method.id != PJSIP_INVITE_METHOD && code!=401 && code!=407) { + // pjsip_tx_data_dec_ref(tsx->last_tx); + // tsx->last_tx = NULL; + //} + + /* Processing is similar to state Proceeding. */ + tsx_on_state_proceeding_uac( tsx, event); + + } else { + pj_assert(!"Unexpected event"); + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + + +/* + * State Trying is for UAS after it received request but before any responses + * is sent. + * Note: this is different than RFC3261, which can use Trying state for + * non-INVITE client transaction (bug in RFC?). + */ +static pj_status_t tsx_on_state_trying( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_status_t status; + + pj_assert(tsx->state == PJSIP_TSX_STATE_TRYING); + + /* This state is only for UAS */ + pj_assert(tsx->role == PJSIP_ROLE_UAS); + + /* Better be transmission of response message. + * If we've got request retransmission, this means that the TU hasn't + * transmitted any responses within 500 ms, which is not allowed. If + * this happens, just ignore the event (we couldn't retransmit last + * response because we haven't sent any!). + */ + if (event->type != PJSIP_EVENT_TX_MSG) { + return PJ_SUCCESS; + } + + /* The rest of the processing of the event is exactly the same as in + * "Proceeding" state. + */ + status = tsx_on_state_proceeding_uas( tsx, event); + + /* Inform the TU of the state transision if state is still State_Trying */ + if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TRYING) { + + tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING, + PJSIP_EVENT_TX_MSG, event->body.tx_msg.tdata); + + } + + return status; +} + + +/* + * Handler for events in Proceeding for UAS + * This state happens after the TU sends provisional response. + */ +static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_PROCEEDING || + tsx->state == PJSIP_TSX_STATE_TRYING); + + /* This state is only for UAS. */ + pj_assert(tsx->role == PJSIP_ROLE_UAS); + + /* Receive request retransmission. */ + if (event->type == PJSIP_EVENT_RX_MSG) { + + pj_status_t status; + + /* Must have last response sent. */ + PJ_ASSERT_RETURN(tsx->last_tx != NULL, PJ_EBUG); + + /* Send last response */ + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_SEND; + } else { + status = tsx_send_msg(tsx, tsx->last_tx); + if (status != PJ_SUCCESS) + return status; + } + + } else if (event->type == PJSIP_EVENT_TX_MSG ) { + pjsip_tx_data *tdata = event->body.tx_msg.tdata; + pj_status_t status; + + /* The TU sends response message to the request. Save this message so + * that we can retransmit the last response in case we receive request + * retransmission. + */ + pjsip_msg *msg = tdata->msg; + + /* This can only be a response message. */ + PJ_ASSERT_RETURN(msg->type==PJSIP_RESPONSE_MSG, PJSIP_ENOTRESPONSEMSG); + + /* Update last status */ + tsx_set_status_code(tsx, msg->line.status.code, + &msg->line.status.reason); + + /* Discard the saved last response (it will be updated later as + * necessary). + */ + if (tsx->last_tx && tsx->last_tx != tdata) { + pjsip_tx_data_dec_ref( tsx->last_tx ); + tsx->last_tx = NULL; + } + + /* Send the message. */ + status = tsx_send_msg(tsx, tdata); + if (status != PJ_SUCCESS) { + return status; + } + + // Update To tag header for RFC2543 transaction. + // TODO: + + /* Update transaction state */ + if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 100)) { + + if (tsx->last_tx != tdata) { + tsx->last_tx = tdata; + pjsip_tx_data_add_ref( tdata ); + } + + tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING, + PJSIP_EVENT_TX_MSG, tdata ); + + /* Retransmit provisional response every 1 minute if this is + * an INVITE provisional response greater than 100. + */ + if (PJSIP_TSX_1XX_RETRANS_DELAY > 0 && + tsx->method.id==PJSIP_INVITE_METHOD && tsx->status_code>100) + { + + /* Stop 1xx retransmission timer, if any */ + if (tsx->retransmit_timer.id) { + pjsip_endpt_cancel_timer(tsx->endpt, + &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* Schedule retransmission */ + tsx->retransmit_count = 0; + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + pj_time_val delay = {PJSIP_TSX_1XX_RETRANS_DELAY, 0}; + + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, + &tsx->retransmit_timer, + &delay); + } + } + + } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) { + + /* Stop 1xx retransmission timer, if any */ + if (tsx->retransmit_timer.id) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->handle_200resp==0) { + + /* 2xx class message is not saved, because retransmission + * is handled by TU. + */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TX_MSG, tdata ); + + /* Transaction is destroyed. */ + //return PJSIP_ETSXDESTROYED; + + } else { + pj_time_val timeout; + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + tsx->retransmit_count = 0; + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, + &tsx->retransmit_timer, + &t1_timer_val); + } + } + + /* Save last response sent for retransmission when request + * retransmission is received. + */ + if (tsx->last_tx != tdata) { + tsx->last_tx = tdata; + pjsip_tx_data_add_ref(tdata); + } + + /* Setup timeout timer: */ + + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + /* Start Timer H at 64*T1 for INVITE server transaction, + * regardless of transport. + */ + timeout = timeout_timer_val; + + } else if (!tsx->is_reliable) { + + /* For non-INVITE, start timer J at 64*T1 for unreliable + * transport. + */ + timeout = timeout_timer_val; + + } else { + + /* Transaction terminates immediately for non-INVITE when + * reliable transport is used. + */ + timeout.sec = timeout.msec = 0; + } + + lock_timer(tsx); + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout); + unlock_timer(tsx); + + /* Set state to "Completed" */ + tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, + PJSIP_EVENT_TX_MSG, tdata ); + } + + } else if (tsx->status_code >= 300) { + + /* Stop 1xx retransmission timer, if any */ + if (tsx->retransmit_timer.id) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* 3xx-6xx class message causes transaction to move to + * "Completed" state. + */ + if (tsx->last_tx != tdata) { + tsx->last_tx = tdata; + pjsip_tx_data_add_ref( tdata ); + } + + /* For INVITE, start timer H for transaction termination + * regardless whether transport is reliable or not. + * For non-INVITE, start timer J with the value of 64*T1 for + * non-reliable transports, and zero for reliable transports. + */ + lock_timer(tsx); + if (tsx->method.id == PJSIP_INVITE_METHOD) { + /* Start timer H for INVITE */ + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer, + &timeout_timer_val); + } else if (!tsx->is_reliable) { + /* Start timer J on 64*T1 seconds for non-INVITE */ + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer, + &timeout_timer_val); + } else { + /* Start timer J on zero seconds for non-INVITE */ + pj_time_val zero_time = { 0, 0 }; + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer, + &zero_time); + } + unlock_timer(tsx); + + /* For INVITE, if unreliable transport is used, retransmission + * timer G will be scheduled (retransmission). + */ + if (!tsx->is_reliable) { + pjsip_cseq_hdr *cseq = (pjsip_cseq_hdr*) + pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ, + NULL); + if (cseq->method.id == PJSIP_INVITE_METHOD) { + tsx->retransmit_count = 0; + if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) { + tsx->transport_flag |= TSX_HAS_PENDING_RESCHED; + } else { + tsx->retransmit_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer(tsx->endpt, + &tsx->retransmit_timer, + &t1_timer_val); + } + } + } + + /* Inform TU */ + tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, + PJSIP_EVENT_TX_MSG, tdata ); + + } else { + pj_assert(0); + } + + + } else if (event->type == PJSIP_EVENT_TIMER && + event->body.timer.entry == &tsx->retransmit_timer) { + + /* Retransmission timer elapsed. */ + pj_status_t status; + + /* Must not be triggered while transport is pending. */ + pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0); + + /* Must have last response to retransmit. */ + pj_assert(tsx->last_tx != NULL); + + /* Retransmit the last response. */ + status = tsx_retransmit( tsx, 1 ); + if (status != PJ_SUCCESS) { + return status; + } + + } else if (event->type == PJSIP_EVENT_TIMER && + event->body.timer.entry == &tsx->timeout_timer) { + + /* Timeout timer. should not happen? */ + pj_assert(!"Should not happen(?)"); + + tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL); + + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer); + + return PJ_EBUG; + + } else { + pj_assert(!"Unexpected event"); + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + + +/* + * Handler for events in Proceeding for UAC + * This state happens after provisional response(s) has been received from + * UAS. + */ +static pj_status_t tsx_on_state_proceeding_uac(pjsip_transaction *tsx, + pjsip_event *event) +{ + + pj_assert(tsx->state == PJSIP_TSX_STATE_PROCEEDING || + tsx->state == PJSIP_TSX_STATE_CALLING); + + if (event->type != PJSIP_EVENT_TIMER) { + pjsip_msg *msg; + + /* Must be incoming response, because we should not retransmit + * request once response has been received. + */ + pj_assert(event->type == PJSIP_EVENT_RX_MSG); + if (event->type != PJSIP_EVENT_RX_MSG) { + return PJ_EINVALIDOP; + } + + msg = event->body.rx_msg.rdata->msg_info.msg; + + /* Must be a response message. */ + if (msg->type != PJSIP_RESPONSE_MSG) { + pj_assert(!"Expecting response message!"); + return PJSIP_ENOTRESPONSEMSG; + } + + tsx_set_status_code(tsx, msg->line.status.code, + &msg->line.status.reason); + + } else { + if (event->body.timer.entry == &tsx->retransmit_timer) { + /* Retransmit message. */ + pj_status_t status; + + status = tsx_retransmit( tsx, 1 ); + + return status; + + } else { + tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL); + } + } + + if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 100)) { + + /* Inform the message to TU. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + + } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code,200)) { + + /* Stop timeout timer B/F. */ + lock_timer(tsx); + pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer ); + unlock_timer(tsx); + + /* For INVITE, the state moves to Terminated state (because ACK is + * handled in TU). For non-INVITE, state moves to Completed. + */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + //return PJSIP_ETSXDESTROYED; + + } else { + pj_time_val timeout; + + /* For unreliable transport, start timer D (for INVITE) or + * timer K for non-INVITE. */ + if (!tsx->is_reliable) { + if (tsx->method.id == PJSIP_INVITE_METHOD) { + timeout = td_timer_val; + } else { + timeout = t4_timer_val; + } + } else { + timeout.sec = timeout.msec = 0; + } + lock_timer(tsx); + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout); + unlock_timer(tsx); + + /* Cancel retransmission timer */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* Move state to Completed, inform TU. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + } + + } else if (event->type == PJSIP_EVENT_TIMER && + event->body.timer.entry == &tsx->timeout_timer) { + + /* Inform TU. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer); + + + } else if (tsx->status_code >= 300 && tsx->status_code <= 699) { + + +#if 0 + /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ + /* + * This is the old code; it's broken for authentication. + */ + pj_time_val timeout; + pj_status_t status; + + /* Stop timer B. */ + pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer ); + + /* Generate and send ACK for INVITE. */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + pjsip_tx_data *ack; + + status = pjsip_endpt_create_ack( tsx->endpt, tsx->last_tx, + event->body.rx_msg.rdata, + &ack); + if (status != PJ_SUCCESS) + return status; + + if (ack != tsx->last_tx) { + pjsip_tx_data_dec_ref(tsx->last_tx); + tsx->last_tx = ack; + } + + status = tsx_send_msg( tsx, tsx->last_tx); + if (status != PJ_SUCCESS) { + return status; + } + } + + /* Start Timer D with TD/T4 timer if unreliable transport is used. */ + if (!tsx->is_reliable) { + if (tsx->method.id == PJSIP_INVITE_METHOD) { + timeout = td_timer_val; + } else { + timeout = t4_timer_val; + } + } else { + timeout.sec = timeout.msec = 0; + } + tsx->timeout->timer.id = TSX_TIMER_TIMEOUT; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout); + + /* Inform TU. + * blp: You might be tempted to move this notification before + * sending ACK, but I think you shouldn't. Better set-up + * everything before calling tsx_user's callback to avoid + * mess up. + */ + tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + + /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ +#endif + + /* New code, taken from 0.2.9.x branch */ + pj_time_val timeout; + pjsip_tx_data *ack_tdata = NULL; + + /* Cancel retransmission timer */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + + /* Stop timer B. */ + lock_timer(tsx); + tsx->timeout_timer.id = 0; + pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer ); + unlock_timer(tsx); + + /* Generate and send ACK (for INVITE) */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + pj_status_t status; + + status = pjsip_endpt_create_ack( tsx->endpt, tsx->last_tx, + event->body.rx_msg.rdata, + &ack_tdata); + if (status != PJ_SUCCESS) + return status; + + status = tsx_send_msg( tsx, ack_tdata); + if (status != PJ_SUCCESS) + return status; + } + + /* Inform TU. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata); + + /* Generate and send ACK for INVITE. */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + if (ack_tdata != tsx->last_tx) { + pjsip_tx_data_dec_ref(tsx->last_tx); + tsx->last_tx = ack_tdata; + + /* This is a bug. + tsx_send_msg() does NOT decrement tdata's reference counter, + so if we add the reference counter here, tdata will have + reference counter 2, causing it to leak. + pjsip_tx_data_add_ref(ack_tdata); + */ + } + } + + /* Start Timer D with TD/T4 timer if unreliable transport is used. */ + /* Note: tsx->transport may be NULL! */ + if (!tsx->is_reliable) { + if (tsx->method.id == PJSIP_INVITE_METHOD) { + timeout = td_timer_val; + } else { + timeout = t4_timer_val; + } + } else { + timeout.sec = timeout.msec = 0; + } + lock_timer(tsx); + /* In the short period above timer may have been inserted + * by set_timeout() (by CANCEL). Cancel it if necessary. See: + * https://trac.pjsip.org/repos/ticket/1374 + */ + if (tsx->timeout_timer.id) + pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer ); + tsx->timeout_timer.id = TIMER_ACTIVE; + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout); + unlock_timer(tsx); + + } else { + // Shouldn't happen because there's no timer for this state. + pj_assert(!"Unexpected event"); + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + + +/* + * Handler for events in Completed state for UAS + */ +static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_COMPLETED); + + if (event->type == PJSIP_EVENT_RX_MSG) { + pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg; + + /* This must be a request message retransmission. */ + if (msg->type != PJSIP_REQUEST_MSG) + return PJSIP_ENOTREQUESTMSG; + + /* On receive request retransmission, retransmit last response. */ + if (msg->line.req.method.id != PJSIP_ACK_METHOD) { + pj_status_t status; + + status = tsx_retransmit( tsx, 0 ); + if (status != PJ_SUCCESS) { + return status; + } + + } else { + pj_time_val timeout; + + /* Process incoming ACK request. */ + + /* Verify that this is an INVITE transaction */ + if (tsx->method.id != PJSIP_INVITE_METHOD) { + PJ_LOG(2, (tsx->obj_name, + "Received illegal ACK for %.*s transaction", + (int)tsx->method.name.slen, + tsx->method.name.ptr)); + return PJSIP_EINVALIDMETHOD; + } + + /* Cease retransmission. */ + if (tsx->retransmit_timer.id != 0) { + pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer); + tsx->retransmit_timer.id = 0; + } + tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED); + + /* Reschedule timeout timer. */ + lock_timer(tsx); + pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer ); + tsx->timeout_timer.id = TIMER_ACTIVE; + + /* Timer I is T4 timer for unreliable transports, and + * zero seconds for reliable transports. + */ + if (!tsx->is_reliable) { + timeout.sec = 0; + timeout.msec = 0; + } else { + timeout.sec = t4_timer_val.sec; + timeout.msec = t4_timer_val.msec; + } + pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, + &timeout); + unlock_timer(tsx); + + /* Move state to "Confirmed" */ + tsx_set_state( tsx, PJSIP_TSX_STATE_CONFIRMED, + PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata ); + } + + } else if (event->type == PJSIP_EVENT_TIMER) { + + if (event->body.timer.entry == &tsx->retransmit_timer) { + /* Retransmit message. */ + pj_status_t status; + + status = tsx_retransmit( tsx, 1 ); + if (status != PJ_SUCCESS) { + return status; + } + + } else { + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + /* For INVITE, this means that ACK was never received. + * Set state to Terminated, and inform TU. + */ + + tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL); + + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer ); + + //return PJSIP_ETSXDESTROYED; + + } else { + /* Transaction terminated, it can now be deleted. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer ); + //return PJSIP_ETSXDESTROYED; + } + } + + } else { + /* Ignore request to transmit. */ + PJ_ASSERT_RETURN(event->type == PJSIP_EVENT_TX_MSG && + event->body.tx_msg.tdata == tsx->last_tx, + PJ_EINVALIDOP); + } + + return PJ_SUCCESS; +} + + +/* + * Handler for events in Completed state for UAC transaction. + */ +static pj_status_t tsx_on_state_completed_uac( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_COMPLETED); + + if (event->type == PJSIP_EVENT_TIMER) { + /* Must be the timeout timer. */ + pj_assert(event->body.timer.entry == &tsx->timeout_timer); + + /* Move to Terminated state. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, event->body.timer.entry ); + + /* Transaction has been destroyed. */ + //return PJSIP_ETSXDESTROYED; + + } else if (event->type == PJSIP_EVENT_RX_MSG) { + if (tsx->method.id == PJSIP_INVITE_METHOD) { + /* On received of final response retransmission, retransmit the ACK. + * TU doesn't need to be informed. + */ + pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg; + pj_assert(msg->type == PJSIP_RESPONSE_MSG); + if (msg->type==PJSIP_RESPONSE_MSG && + msg->line.status.code >= 200) + { + pj_status_t status; + + status = tsx_retransmit( tsx, 0 ); + if (status != PJ_SUCCESS) { + return status; + } + } else { + /* Very late retransmission of privisional response. */ + pj_assert( msg->type == PJSIP_RESPONSE_MSG ); + } + } else { + /* Just drop the response. */ + } + + } else { + pj_assert(!"Unexpected event"); + return PJ_EINVALIDOP; + } + + return PJ_SUCCESS; +} + + +/* + * Handler for events in state Confirmed. + */ +static pj_status_t tsx_on_state_confirmed( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_CONFIRMED); + + /* This state is only for UAS for INVITE. */ + pj_assert(tsx->role == PJSIP_ROLE_UAS); + pj_assert(tsx->method.id == PJSIP_INVITE_METHOD); + + /* Absorb any ACK received. */ + if (event->type == PJSIP_EVENT_RX_MSG) { + + pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg; + + /* Only expecting request message. */ + if (msg->type != PJSIP_REQUEST_MSG) + return PJSIP_ENOTREQUESTMSG; + + /* Must be an ACK request or a late INVITE retransmission. */ + pj_assert(msg->line.req.method.id == PJSIP_ACK_METHOD || + msg->line.req.method.id == PJSIP_INVITE_METHOD); + + } else if (event->type == PJSIP_EVENT_TIMER) { + /* Must be from timeout_timer_. */ + pj_assert(event->body.timer.entry == &tsx->timeout_timer); + + /* Move to Terminated state. */ + tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, + PJSIP_EVENT_TIMER, &tsx->timeout_timer ); + + /* Transaction has been destroyed. */ + //return PJSIP_ETSXDESTROYED; + + } else { + pj_assert(!"Unexpected event"); + return PJ_EBUG; + } + + return PJ_SUCCESS; +} + + +/* + * Handler for events in state Terminated. + */ +static pj_status_t tsx_on_state_terminated( pjsip_transaction *tsx, + pjsip_event *event) +{ + pj_assert(tsx->state == PJSIP_TSX_STATE_TERMINATED); + + /* Ignore events other than timer. This used to be an assertion but + * events may genuinely arrive at this state. + */ + if (event->type != PJSIP_EVENT_TIMER) { + return PJ_EIGNORED; + } + + /* Destroy this transaction */ + tsx_set_state(tsx, PJSIP_TSX_STATE_DESTROYED, + event->type, event->body.user.user1 ); + + return PJ_SUCCESS; +} + + +/* + * Handler for events in state Destroyed. + * Shouldn't happen! + */ +static pj_status_t tsx_on_state_destroyed(pjsip_transaction *tsx, + pjsip_event *event) +{ + PJ_UNUSED_ARG(tsx); + PJ_UNUSED_ARG(event); + + // See https://trac.pjsip.org/repos/ticket/1432 + //pj_assert(!"Not expecting any events!!"); + + return PJ_EIGNORED; +} + diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c new file mode 100644 index 0000000..4d8b77e --- /dev/null +++ b/pjsip/src/pjsip/sip_transport.c @@ -0,0 +1,1942 @@ +/* $Id: sip_transport.c 4094 2012-04-26 09:31:00Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_transport.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_parser.h> +#include <pjsip/sip_msg.h> +#include <pjsip/sip_private.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_module.h> +#include <pj/except.h> +#include <pj/os.h> +#include <pj/log.h> +#include <pj/ioqueue.h> +#include <pj/hash.h> +#include <pj/string.h> +#include <pj/pool.h> +#include <pj/assert.h> +#include <pj/lock.h> +#include <pj/list.h> + + +#define THIS_FILE "sip_transport.c" + +#if 0 +# define TRACE_(x) PJ_LOG(5,x) + +static const char *addr_string(const pj_sockaddr_t *addr) +{ + static char str[PJ_INET6_ADDRSTRLEN]; + pj_inet_ntop(((const pj_sockaddr*)addr)->addr.sa_family, + pj_sockaddr_get_addr(addr), + str, sizeof(str)); + return str; +} +#else +# define TRACE_(x) +#endif + +/* Prototype. */ +static pj_status_t mod_on_tx_msg(pjsip_tx_data *tdata); + +/* This module has sole purpose to print transmit data to contigous buffer + * before actually transmitted to the wire. + */ +static pjsip_module mod_msg_print = +{ + NULL, NULL, /* prev and next */ + { "mod-msg-print", 13}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + &mod_on_tx_msg, /* on_tx_request() */ + &mod_on_tx_msg, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +/* + * Transport manager. + */ +struct pjsip_tpmgr +{ + pj_hash_table_t *table; + pj_lock_t *lock; + pjsip_endpoint *endpt; + pjsip_tpfactory factory_list; +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + pj_atomic_t *tdata_counter; +#endif + void (*on_rx_msg)(pjsip_endpoint*, pj_status_t, pjsip_rx_data*); + pj_status_t (*on_tx_msg)(pjsip_endpoint*, pjsip_tx_data*); + pjsip_tp_state_callback tp_state_cb; +}; + + +/* Transport state listener list type */ +typedef struct tp_state_listener +{ + PJ_DECL_LIST_MEMBER(struct tp_state_listener); + + pjsip_tp_state_callback cb; + void *user_data; +} tp_state_listener; + + +/* + * Transport data. + */ +typedef struct transport_data +{ + /* Transport listeners */ + tp_state_listener st_listeners; + tp_state_listener st_listeners_empty; +} transport_data; + + +/***************************************************************************** + * + * GENERAL TRANSPORT (NAMES, TYPES, ETC.) + * + *****************************************************************************/ + +/* + * Transport names. + */ +struct transport_names_t +{ + pjsip_transport_type_e type; /* Transport type */ + pj_uint16_t port; /* Default port number */ + pj_str_t name; /* Id tag */ + const char *description; /* Longer description */ + unsigned flag; /* Flags */ + char name_buf[16]; /* For user's transport */ +} transport_names[16] = +{ + { + PJSIP_TRANSPORT_UNSPECIFIED, + 0, + {"Unspecified", 11}, + "Unspecified", + 0 + }, + { + PJSIP_TRANSPORT_UDP, + 5060, + {"UDP", 3}, + "UDP transport", + PJSIP_TRANSPORT_DATAGRAM + }, + { + PJSIP_TRANSPORT_TCP, + 5060, + {"TCP", 3}, + "TCP transport", + PJSIP_TRANSPORT_RELIABLE + }, + { + PJSIP_TRANSPORT_TLS, + 5061, + {"TLS", 3}, + "TLS transport", + PJSIP_TRANSPORT_RELIABLE | PJSIP_TRANSPORT_SECURE + }, + { + PJSIP_TRANSPORT_SCTP, + 5060, + {"SCTP", 4}, + "SCTP transport", + PJSIP_TRANSPORT_RELIABLE + }, + { + PJSIP_TRANSPORT_LOOP, + 15060, + {"LOOP", 4}, + "Loopback transport", + PJSIP_TRANSPORT_RELIABLE + }, + { + PJSIP_TRANSPORT_LOOP_DGRAM, + 15060, + {"LOOP-DGRAM", 10}, + "Loopback datagram transport", + PJSIP_TRANSPORT_DATAGRAM + }, + { + PJSIP_TRANSPORT_UDP6, + 5060, + {"UDP", 3}, + "UDP IPv6 transport", + PJSIP_TRANSPORT_DATAGRAM + }, + { + PJSIP_TRANSPORT_TCP6, + 5060, + {"TCP", 3}, + "TCP IPv6 transport", + PJSIP_TRANSPORT_RELIABLE + }, +}; + +static void tp_state_callback(pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info); + + +struct transport_names_t *get_tpname(pjsip_transport_type_e type) +{ + unsigned i; + for (i=0; i<PJ_ARRAY_SIZE(transport_names); ++i) { + if (transport_names[i].type == type) + return &transport_names[i]; + } + pj_assert(!"Invalid transport type!"); + return NULL; +} + + + +/* + * Register new transport type to PJSIP. + */ +PJ_DEF(pj_status_t) pjsip_transport_register_type( unsigned tp_flag, + const char *tp_name, + int def_port, + int *p_tp_type) +{ + unsigned i; + + PJ_ASSERT_RETURN(tp_flag && tp_name && def_port, PJ_EINVAL); + PJ_ASSERT_RETURN(pj_ansi_strlen(tp_name) < + PJ_ARRAY_SIZE(transport_names[0].name_buf), + PJ_ENAMETOOLONG); + + for (i=1; i<PJ_ARRAY_SIZE(transport_names); ++i) { + if (transport_names[i].type == 0) + break; + } + + if (i == PJ_ARRAY_SIZE(transport_names)) + return PJ_ETOOMANY; + + transport_names[i].type = (pjsip_transport_type_e)i; + transport_names[i].port = (pj_uint16_t)def_port; + pj_ansi_strcpy(transport_names[i].name_buf, tp_name); + transport_names[i].name = pj_str(transport_names[i].name_buf); + transport_names[i].flag = tp_flag; + + if (p_tp_type) + *p_tp_type = i; + + return PJ_SUCCESS; +} + + +/* + * Get transport type from name. + */ +PJ_DEF(pjsip_transport_type_e) pjsip_transport_get_type_from_name(const pj_str_t *name) +{ + unsigned i; + + if (name->slen == 0) + return PJSIP_TRANSPORT_UNSPECIFIED; + + /* Get transport type from name. */ + for (i=0; i<PJ_ARRAY_SIZE(transport_names); ++i) { + if (pj_stricmp(name, &transport_names[i].name) == 0) { + return transport_names[i].type; + } + } + + pj_assert(!"Invalid transport name"); + return PJSIP_TRANSPORT_UNSPECIFIED; +} + + +/* + * Get the transport type for the specified flags. + */ +PJ_DEF(pjsip_transport_type_e) pjsip_transport_get_type_from_flag(unsigned flag) +{ + unsigned i; + + /* Get the transport type for the specified flags. */ + for (i=0; i<PJ_ARRAY_SIZE(transport_names); ++i) { + if (transport_names[i].flag == flag) { + return transport_names[i].type; + } + } + + pj_assert(!"Invalid transport type"); + return PJSIP_TRANSPORT_UNSPECIFIED; +} + +/* + * Get the socket address family of a given transport type. + */ +PJ_DEF(int) pjsip_transport_type_get_af(pjsip_transport_type_e type) +{ + if (type & PJSIP_TRANSPORT_IPV6) + return pj_AF_INET6(); + else + return pj_AF_INET(); +} + +PJ_DEF(unsigned) pjsip_transport_get_flag_from_type(pjsip_transport_type_e type) +{ + /* Return transport flag. */ + return get_tpname(type)->flag; +} + +/* + * Get the default SIP port number for the specified type. + */ +PJ_DEF(int) pjsip_transport_get_default_port_for_type(pjsip_transport_type_e type) +{ + /* Return the port. */ + return get_tpname(type)->port; +} + +/* + * Get transport name. + */ +PJ_DEF(const char*) pjsip_transport_get_type_name(pjsip_transport_type_e type) +{ + /* Return the name. */ + return get_tpname(type)->name.ptr; +} + +/* + * Get transport description. + */ +PJ_DEF(const char*) pjsip_transport_get_type_desc(pjsip_transport_type_e type) +{ + /* Return the description. */ + return get_tpname(type)->description; +} + + +/***************************************************************************** + * + * TRANSPORT SELECTOR + * + *****************************************************************************/ + +/* + * Add transport/listener reference in the selector. + */ +PJ_DEF(void) pjsip_tpselector_add_ref(pjsip_tpselector *sel) +{ + if (sel->type == PJSIP_TPSELECTOR_TRANSPORT && sel->u.transport != NULL) + pjsip_transport_add_ref(sel->u.transport); + else if (sel->type == PJSIP_TPSELECTOR_LISTENER && sel->u.listener != NULL) + ; /* Hmm.. looks like we don't have reference counter for listener */ +} + + +/* + * Decrement transport/listener reference in the selector. + */ +PJ_DEF(void) pjsip_tpselector_dec_ref(pjsip_tpselector *sel) +{ + if (sel->type == PJSIP_TPSELECTOR_TRANSPORT && sel->u.transport != NULL) + pjsip_transport_dec_ref(sel->u.transport); + else if (sel->type == PJSIP_TPSELECTOR_LISTENER && sel->u.listener != NULL) + ; /* Hmm.. looks like we don't have reference counter for listener */ +} + + +/***************************************************************************** + * + * TRANSMIT DATA BUFFER MANIPULATION. + * + *****************************************************************************/ + +/* + * Create new transmit buffer. + */ +PJ_DEF(pj_status_t) pjsip_tx_data_create( pjsip_tpmgr *mgr, + pjsip_tx_data **p_tdata ) +{ + pj_pool_t *pool; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(mgr && p_tdata, PJ_EINVAL); + + pool = pjsip_endpt_create_pool( mgr->endpt, "tdta%p", + PJSIP_POOL_LEN_TDATA, + PJSIP_POOL_INC_TDATA ); + if (!pool) + return PJ_ENOMEM; + + tdata = PJ_POOL_ZALLOC_T(pool, pjsip_tx_data); + tdata->pool = pool; + tdata->mgr = mgr; + pj_memcpy(tdata->obj_name, pool->obj_name, PJ_MAX_OBJ_NAME); + + status = pj_atomic_create(tdata->pool, 0, &tdata->ref_cnt); + if (status != PJ_SUCCESS) { + pjsip_endpt_release_pool( mgr->endpt, tdata->pool ); + return status; + } + + //status = pj_lock_create_simple_mutex(pool, "tdta%p", &tdata->lock); + status = pj_lock_create_null_mutex(pool, "tdta%p", &tdata->lock); + if (status != PJ_SUCCESS) { + pjsip_endpt_release_pool( mgr->endpt, tdata->pool ); + return status; + } + + pj_ioqueue_op_key_init(&tdata->op_key.key, sizeof(tdata->op_key.key)); + +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + pj_atomic_inc( tdata->mgr->tdata_counter ); +#endif + + *p_tdata = tdata; + return PJ_SUCCESS; +} + + +/* + * Add reference to tx buffer. + */ +PJ_DEF(void) pjsip_tx_data_add_ref( pjsip_tx_data *tdata ) +{ + pj_atomic_inc(tdata->ref_cnt); +} + +/* + * Decrease transport data reference, destroy it when the reference count + * reaches zero. + */ +PJ_DEF(pj_status_t) pjsip_tx_data_dec_ref( pjsip_tx_data *tdata ) +{ + pj_assert( pj_atomic_get(tdata->ref_cnt) > 0); + if (pj_atomic_dec_and_get(tdata->ref_cnt) <= 0) { + PJ_LOG(5,(tdata->obj_name, "Destroying txdata %s", + pjsip_tx_data_get_info(tdata))); + pjsip_tpselector_dec_ref(&tdata->tp_sel); +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + pj_atomic_dec( tdata->mgr->tdata_counter ); +#endif + pj_atomic_destroy( tdata->ref_cnt ); + pj_lock_destroy( tdata->lock ); + pjsip_endpt_release_pool( tdata->mgr->endpt, tdata->pool ); + return PJSIP_EBUFDESTROYED; + } else { + return PJ_SUCCESS; + } +} + +/* + * Invalidate the content of the print buffer to force the message to be + * re-printed when sent. + */ +PJ_DEF(void) pjsip_tx_data_invalidate_msg( pjsip_tx_data *tdata ) +{ + tdata->buf.cur = tdata->buf.start; + tdata->info = NULL; +} + +/* + * Print the SIP message to transmit data buffer's internal buffer. + */ +PJ_DEF(pj_status_t) pjsip_tx_data_encode(pjsip_tx_data *tdata) +{ + /* Allocate buffer if necessary. */ + if (tdata->buf.start == NULL) { + PJ_USE_EXCEPTION; + + PJ_TRY { + tdata->buf.start = (char*) + pj_pool_alloc(tdata->pool, PJSIP_MAX_PKT_LEN); + } + PJ_CATCH_ANY { + return PJ_ENOMEM; + } + PJ_END + + tdata->buf.cur = tdata->buf.start; + tdata->buf.end = tdata->buf.start + PJSIP_MAX_PKT_LEN; + } + + /* Do we need to reprint? */ + if (!pjsip_tx_data_is_valid(tdata)) { + pj_ssize_t size; + + size = pjsip_msg_print( tdata->msg, tdata->buf.start, + tdata->buf.end - tdata->buf.start); + if (size < 0) { + return PJSIP_EMSGTOOLONG; + } + pj_assert(size != 0); + tdata->buf.cur[size] = '\0'; + tdata->buf.cur += size; + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_bool_t) pjsip_tx_data_is_valid( pjsip_tx_data *tdata ) +{ + return tdata->buf.cur != tdata->buf.start; +} + +static char *get_msg_info(pj_pool_t *pool, const char *obj_name, + const pjsip_msg *msg) +{ + char info_buf[128], *info; + const pjsip_cseq_hdr *cseq; + int len; + + cseq = (const pjsip_cseq_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL); + PJ_ASSERT_RETURN(cseq != NULL, "INVALID MSG"); + + if (msg->type == PJSIP_REQUEST_MSG) { + len = pj_ansi_snprintf(info_buf, sizeof(info_buf), + "Request msg %.*s/cseq=%d (%s)", + (int)msg->line.req.method.name.slen, + msg->line.req.method.name.ptr, + cseq->cseq, obj_name); + } else { + len = pj_ansi_snprintf(info_buf, sizeof(info_buf), + "Response msg %d/%.*s/cseq=%d (%s)", + msg->line.status.code, + (int)cseq->method.name.slen, + cseq->method.name.ptr, + cseq->cseq, obj_name); + } + + if (len < 1 || len >= (int)sizeof(info_buf)) { + return (char*)obj_name; + } + + info = (char*) pj_pool_alloc(pool, len+1); + pj_memcpy(info, info_buf, len+1); + + return info; +} + +PJ_DEF(char*) pjsip_tx_data_get_info( pjsip_tx_data *tdata ) +{ + /* tdata->info may be assigned by application so if it exists + * just return it. + */ + if (tdata->info) + return tdata->info; + + if (tdata==NULL || tdata->msg==NULL) + return "NULL"; + + pj_lock_acquire(tdata->lock); + tdata->info = get_msg_info(tdata->pool, tdata->obj_name, tdata->msg); + pj_lock_release(tdata->lock); + + return tdata->info; +} + +PJ_DEF(pj_status_t) pjsip_tx_data_set_transport(pjsip_tx_data *tdata, + const pjsip_tpselector *sel) +{ + PJ_ASSERT_RETURN(tdata && sel, PJ_EINVAL); + + pj_lock_acquire(tdata->lock); + + pjsip_tpselector_dec_ref(&tdata->tp_sel); + + pj_memcpy(&tdata->tp_sel, sel, sizeof(*sel)); + pjsip_tpselector_add_ref(&tdata->tp_sel); + + pj_lock_release(tdata->lock); + + return PJ_SUCCESS; +} + + +PJ_DEF(char*) pjsip_rx_data_get_info(pjsip_rx_data *rdata) +{ + char obj_name[PJ_MAX_OBJ_NAME]; + + PJ_ASSERT_RETURN(rdata->msg_info.msg, "INVALID MSG"); + + if (rdata->msg_info.info) + return rdata->msg_info.info; + + pj_ansi_strcpy(obj_name, "rdata"); + pj_ansi_snprintf(obj_name+5, sizeof(obj_name)-5, "%p", rdata); + + rdata->msg_info.info = get_msg_info(rdata->tp_info.pool, obj_name, + rdata->msg_info.msg); + return rdata->msg_info.info; +} + +/***************************************************************************** + * + * TRANSPORT KEY + * + *****************************************************************************/ + + +/***************************************************************************** + * + * TRANSPORT + * + *****************************************************************************/ + +static void transport_send_callback(pjsip_transport *transport, + void *token, + pj_ssize_t size) +{ + pjsip_tx_data *tdata = (pjsip_tx_data*) token; + + PJ_UNUSED_ARG(transport); + + /* Mark pending off so that app can resend/reuse txdata from inside + * the callback. + */ + tdata->is_pending = 0; + + /* Call callback, if any. */ + if (tdata->cb) { + (*tdata->cb)(tdata->token, tdata, size); + } + + /* Decrement reference count. */ + pjsip_tx_data_dec_ref(tdata); +} + +/* This function is called by endpoint for on_tx_request() and on_tx_response() + * notification. + */ +static pj_status_t mod_on_tx_msg(pjsip_tx_data *tdata) +{ + return pjsip_tx_data_encode(tdata); +} + +/* + * Send a SIP message using the specified transport. + */ +PJ_DEF(pj_status_t) pjsip_transport_send( pjsip_transport *tr, + pjsip_tx_data *tdata, + const pj_sockaddr_t *addr, + int addr_len, + void *token, + pjsip_tp_send_callback cb) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(tr && tdata && addr, PJ_EINVAL); + + /* Is it currently being sent? */ + if (tdata->is_pending) { + pj_assert(!"Invalid operation step!"); + PJ_LOG(2,(THIS_FILE, "Unable to send %s: message is pending", + pjsip_tx_data_get_info(tdata))); + return PJSIP_EPENDINGTX; + } + + /* Add reference to prevent deletion, and to cancel idle timer if + * it's running. + */ + pjsip_transport_add_ref(tr); + + /* Fill in tp_info. */ + tdata->tp_info.transport = tr; + pj_memcpy(&tdata->tp_info.dst_addr, addr, addr_len); + tdata->tp_info.dst_addr_len = addr_len; + + pj_inet_ntop(((pj_sockaddr*)addr)->addr.sa_family, + pj_sockaddr_get_addr(addr), + tdata->tp_info.dst_name, + sizeof(tdata->tp_info.dst_name)); + tdata->tp_info.dst_port = pj_sockaddr_get_port(addr); + + /* Distribute to modules. + * When the message reach mod_msg_print, the contents of the message will + * be "printed" to contiguous buffer. + */ + if (tr->tpmgr->on_tx_msg) { + status = (*tr->tpmgr->on_tx_msg)(tr->endpt, tdata); + if (status != PJ_SUCCESS) { + pjsip_transport_dec_ref(tr); + return status; + } + } + + /* Save callback data. */ + tdata->token = token; + tdata->cb = cb; + + /* Add reference counter. */ + pjsip_tx_data_add_ref(tdata); + + /* Mark as pending. */ + tdata->is_pending = 1; + + /* Send to transport. */ + status = (*tr->send_msg)(tr, tdata, addr, addr_len, (void*)tdata, + &transport_send_callback); + + if (status != PJ_EPENDING) { + tdata->is_pending = 0; + pjsip_tx_data_dec_ref(tdata); + } + + pjsip_transport_dec_ref(tr); + return status; +} + + +/* send_raw() callback */ +static void send_raw_callback(pjsip_transport *transport, + void *token, + pj_ssize_t size) +{ + pjsip_tx_data *tdata = (pjsip_tx_data*) token; + + /* Mark pending off so that app can resend/reuse txdata from inside + * the callback. + */ + tdata->is_pending = 0; + + /* Call callback, if any. */ + if (tdata->cb) { + (*tdata->cb)(tdata->token, tdata, size); + } + + /* Decrement tdata reference count. */ + pjsip_tx_data_dec_ref(tdata); + + /* Decrement transport reference count */ + pjsip_transport_dec_ref(transport); +} + + +/* Send raw data */ +PJ_DEF(pj_status_t) pjsip_tpmgr_send_raw(pjsip_tpmgr *mgr, + pjsip_transport_type_e tp_type, + const pjsip_tpselector *sel, + pjsip_tx_data *tdata, + const void *raw_data, + pj_size_t data_len, + const pj_sockaddr_t *addr, + int addr_len, + void *token, + pjsip_tp_send_callback cb) +{ + pjsip_transport *tr; + pj_status_t status; + + /* Acquire the transport */ + status = pjsip_tpmgr_acquire_transport(mgr, tp_type, addr, addr_len, + sel, &tr); + if (status != PJ_SUCCESS) + return status; + + /* Create transmit data buffer if one is not specified */ + if (tdata == NULL) { + status = pjsip_endpt_create_tdata(tr->endpt, &tdata); + if (status != PJ_SUCCESS) { + pjsip_transport_dec_ref(tr); + return status; + } + + tdata->info = "raw"; + + /* Add reference counter. */ + pjsip_tx_data_add_ref(tdata); + } + + /* Allocate buffer */ + if (tdata->buf.start == NULL || + (tdata->buf.end - tdata->buf.start) < (int)data_len) + { + /* Note: data_len may be zero, so allocate +1 */ + tdata->buf.start = (char*) pj_pool_alloc(tdata->pool, data_len+1); + tdata->buf.end = tdata->buf.start + data_len + 1; + } + + /* Copy data, if any! (application may send zero len packet) */ + if (data_len) { + pj_memcpy(tdata->buf.start, raw_data, data_len); + } + tdata->buf.cur = tdata->buf.start + data_len; + + /* Save callback data. */ + tdata->token = token; + tdata->cb = cb; + + /* Mark as pending. */ + tdata->is_pending = 1; + + /* Send to transport */ + status = tr->send_msg(tr, tdata, addr, addr_len, + tdata, &send_raw_callback); + + if (status != PJ_EPENDING) { + /* callback will not be called, so destroy tdata now. */ + pjsip_tx_data_dec_ref(tdata); + pjsip_transport_dec_ref(tr); + } + + return status; +} + + +static void transport_idle_callback(pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pjsip_transport *tp = (pjsip_transport*) entry->user_data; + pj_assert(tp != NULL); + + PJ_UNUSED_ARG(timer_heap); + + entry->id = PJ_FALSE; + pjsip_transport_destroy(tp); +} + +/* + * Add ref. + */ +PJ_DEF(pj_status_t) pjsip_transport_add_ref( pjsip_transport *tp ) +{ + PJ_ASSERT_RETURN(tp != NULL, PJ_EINVAL); + + if (pj_atomic_inc_and_get(tp->ref_cnt) == 1) { + pj_lock_acquire(tp->tpmgr->lock); + /* Verify again. */ + if (pj_atomic_get(tp->ref_cnt) == 1) { + if (tp->idle_timer.id != PJ_FALSE) { + pjsip_endpt_cancel_timer(tp->tpmgr->endpt, &tp->idle_timer); + tp->idle_timer.id = PJ_FALSE; + } + } + pj_lock_release(tp->tpmgr->lock); + } + + return PJ_SUCCESS; +} + +/* + * Dec ref. + */ +PJ_DEF(pj_status_t) pjsip_transport_dec_ref( pjsip_transport *tp ) +{ + PJ_ASSERT_RETURN(tp != NULL, PJ_EINVAL); + + pj_assert(pj_atomic_get(tp->ref_cnt) > 0); + + if (pj_atomic_dec_and_get(tp->ref_cnt) == 0) { + pj_lock_acquire(tp->tpmgr->lock); + /* Verify again. Do not register timer if the transport is + * being destroyed. + */ + if (pj_atomic_get(tp->ref_cnt) == 0 && !tp->is_destroying) { + pj_time_val delay; + + /* If transport is in graceful shutdown, then this is the + * last user who uses the transport. Schedule to destroy the + * transport immediately. Otherwise schedule idle timer. + */ + if (tp->is_shutdown) { + delay.sec = delay.msec = 0; + } else { + delay.sec = (tp->dir==PJSIP_TP_DIR_OUTGOING) ? + PJSIP_TRANSPORT_IDLE_TIME : + PJSIP_TRANSPORT_SERVER_IDLE_TIME; + delay.msec = 0; + } + + pj_assert(tp->idle_timer.id == 0); + tp->idle_timer.id = PJ_TRUE; + pjsip_endpt_schedule_timer(tp->tpmgr->endpt, &tp->idle_timer, + &delay); + } + pj_lock_release(tp->tpmgr->lock); + } + + return PJ_SUCCESS; +} + + +/** + * Register a transport. + */ +PJ_DEF(pj_status_t) pjsip_transport_register( pjsip_tpmgr *mgr, + pjsip_transport *tp ) +{ + int key_len; + pj_uint32_t hval; + void *entry; + + /* Init. */ + tp->tpmgr = mgr; + pj_bzero(&tp->idle_timer, sizeof(tp->idle_timer)); + tp->idle_timer.user_data = tp; + tp->idle_timer.cb = &transport_idle_callback; + + /* + * Register to hash table (see Trac ticket #42). + */ + key_len = sizeof(tp->key.type) + tp->addr_len; + pj_lock_acquire(mgr->lock); + + /* If entry already occupied, unregister previous entry */ + hval = 0; + entry = pj_hash_get(mgr->table, &tp->key, key_len, &hval); + if (entry != NULL) + pj_hash_set(NULL, mgr->table, &tp->key, key_len, hval, NULL); + + /* Register new entry */ + pj_hash_set(tp->pool, mgr->table, &tp->key, key_len, hval, tp); + + pj_lock_release(mgr->lock); + + TRACE_((THIS_FILE,"Transport %s registered: type=%s, remote=%s:%d", + tp->obj_name, + pjsip_transport_get_type_name(tp->key.type), + addr_string(&tp->key.rem_addr), + pj_sockaddr_get_port(&tp->key.rem_addr))); + + return PJ_SUCCESS; +} + +/* Force destroy transport (e.g. during transport manager shutdown. */ +static pj_status_t destroy_transport( pjsip_tpmgr *mgr, + pjsip_transport *tp ) +{ + int key_len; + pj_uint32_t hval; + void *entry; + + TRACE_((THIS_FILE, "Transport %s is being destroyed", tp->obj_name)); + + pj_lock_acquire(tp->lock); + pj_lock_acquire(mgr->lock); + + tp->is_destroying = PJ_TRUE; + + /* + * Unregister timer, if any. + */ + //pj_assert(tp->idle_timer.id == PJ_FALSE); + if (tp->idle_timer.id != PJ_FALSE) { + pjsip_endpt_cancel_timer(mgr->endpt, &tp->idle_timer); + tp->idle_timer.id = PJ_FALSE; + } + + /* + * Unregister from hash table (see Trac ticket #42). + */ + key_len = sizeof(tp->key.type) + tp->addr_len; + hval = 0; + entry = pj_hash_get(mgr->table, &tp->key, key_len, &hval); + if (entry == (void*)tp) + pj_hash_set(NULL, mgr->table, &tp->key, key_len, hval, NULL); + + pj_lock_release(mgr->lock); + + /* Destroy. */ + return tp->destroy(tp); +} + + +/* + * Start graceful shutdown procedure for this transport. + */ +PJ_DEF(pj_status_t) pjsip_transport_shutdown(pjsip_transport *tp) +{ + pjsip_tpmgr *mgr; + pj_status_t status; + + TRACE_((THIS_FILE, "Transport %s shutting down", tp->obj_name)); + + pj_lock_acquire(tp->lock); + + mgr = tp->tpmgr; + pj_lock_acquire(mgr->lock); + + /* Do nothing if transport is being shutdown already */ + if (tp->is_shutdown) { + pj_lock_release(tp->lock); + pj_lock_release(mgr->lock); + return PJ_SUCCESS; + } + + status = PJ_SUCCESS; + + /* Instruct transport to shutdown itself */ + if (tp->do_shutdown) + status = tp->do_shutdown(tp); + + if (status == PJ_SUCCESS) + tp->is_shutdown = PJ_TRUE; + + /* If transport reference count is zero, start timer count-down */ + if (pj_atomic_get(tp->ref_cnt) == 0) { + pjsip_transport_add_ref(tp); + pjsip_transport_dec_ref(tp); + } + + pj_lock_release(tp->lock); + pj_lock_release(mgr->lock); + + return status; +} + + +/** + * Unregister transport. + */ +PJ_DEF(pj_status_t) pjsip_transport_destroy( pjsip_transport *tp) +{ + /* Must have no user. */ + PJ_ASSERT_RETURN(pj_atomic_get(tp->ref_cnt) == 0, PJSIP_EBUSY); + + /* Destroy. */ + return destroy_transport(tp->tpmgr, tp); +} + + + +/***************************************************************************** + * + * TRANSPORT FACTORY + * + *****************************************************************************/ + + +PJ_DEF(pj_status_t) pjsip_tpmgr_register_tpfactory( pjsip_tpmgr *mgr, + pjsip_tpfactory *tpf) +{ + pjsip_tpfactory *p; + pj_status_t status; + + pj_lock_acquire(mgr->lock); + + /* Check that no factory with the same type has been registered. */ + status = PJ_SUCCESS; + for (p=mgr->factory_list.next; p!=&mgr->factory_list; p=p->next) { + if (p->type == tpf->type) { + status = PJSIP_ETYPEEXISTS; + break; + } + if (p == tpf) { + status = PJ_EEXISTS; + break; + } + } + + if (status != PJ_SUCCESS) { + pj_lock_release(mgr->lock); + return status; + } + + pj_list_insert_before(&mgr->factory_list, tpf); + + pj_lock_release(mgr->lock); + + return PJ_SUCCESS; +} + + +/** + * Unregister factory. + */ +PJ_DEF(pj_status_t) pjsip_tpmgr_unregister_tpfactory( pjsip_tpmgr *mgr, + pjsip_tpfactory *tpf) +{ + pj_lock_acquire(mgr->lock); + + pj_assert(pj_list_find_node(&mgr->factory_list, tpf) == tpf); + pj_list_erase(tpf); + + pj_lock_release(mgr->lock); + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * + * TRANSPORT MANAGER + * + *****************************************************************************/ + +/* + * Create a new transport manager. + */ +PJ_DEF(pj_status_t) pjsip_tpmgr_create( pj_pool_t *pool, + pjsip_endpoint *endpt, + pjsip_rx_callback rx_cb, + pjsip_tx_callback tx_cb, + pjsip_tpmgr **p_mgr) +{ + pjsip_tpmgr *mgr; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && endpt && rx_cb && p_mgr, PJ_EINVAL); + + /* Register mod_msg_print module. */ + status = pjsip_endpt_register_module(endpt, &mod_msg_print); + if (status != PJ_SUCCESS) + return status; + + /* Create and initialize transport manager. */ + mgr = PJ_POOL_ZALLOC_T(pool, pjsip_tpmgr); + mgr->endpt = endpt; + mgr->on_rx_msg = rx_cb; + mgr->on_tx_msg = tx_cb; + pj_list_init(&mgr->factory_list); + + mgr->table = pj_hash_create(pool, PJSIP_TPMGR_HTABLE_SIZE); + if (!mgr->table) + return PJ_ENOMEM; + + status = pj_lock_create_recursive_mutex(pool, "tmgr%p", &mgr->lock); + if (status != PJ_SUCCESS) + return status; + +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + status = pj_atomic_create(pool, 0, &mgr->tdata_counter); + if (status != PJ_SUCCESS) + return status; +#endif + + /* Set transport state callback */ + status = pjsip_tpmgr_set_state_cb(mgr, &tp_state_callback); + if (status != PJ_SUCCESS) + return status; + + PJ_LOG(5, (THIS_FILE, "Transport manager created.")); + + *p_mgr = mgr; + return PJ_SUCCESS; +} + + + +/* + * Find out the appropriate local address info (IP address and port) to + * advertise in Contact header based on the remote address to be + * contacted. The local address info would be the address name of the + * transport or listener which will be used to send the request. + * + * In this implementation, it will only select the transport based on + * the transport type in the request. + */ +PJ_DEF(pj_status_t) pjsip_tpmgr_find_local_addr( pjsip_tpmgr *tpmgr, + pj_pool_t *pool, + pjsip_transport_type_e type, + const pjsip_tpselector *sel, + pj_str_t *ip_addr, + int *port) +{ + pj_status_t status = PJSIP_EUNSUPTRANSPORT; + unsigned flag; + + /* Sanity checks */ + PJ_ASSERT_RETURN(tpmgr && pool && ip_addr && port, PJ_EINVAL); + + ip_addr->slen = 0; + *port = 0; + + flag = pjsip_transport_get_flag_from_type(type); + + if (sel && sel->type == PJSIP_TPSELECTOR_TRANSPORT && + sel->u.transport) + { + pj_strdup(pool, ip_addr, &sel->u.transport->local_name.host); + *port = sel->u.transport->local_name.port; + status = PJ_SUCCESS; + + } else if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER && + sel->u.listener) + { + pj_strdup(pool, ip_addr, &sel->u.listener->addr_name.host); + *port = sel->u.listener->addr_name.port; + status = PJ_SUCCESS; + + } else if ((flag & PJSIP_TRANSPORT_DATAGRAM) != 0) { + + pj_sockaddr remote; + int addr_len; + pjsip_transport *tp; + + pj_bzero(&remote, sizeof(remote)); + if (type & PJSIP_TRANSPORT_IPV6) { + addr_len = sizeof(pj_sockaddr_in6); + remote.addr.sa_family = pj_AF_INET6(); + } else { + addr_len = sizeof(pj_sockaddr_in); + remote.addr.sa_family = pj_AF_INET(); + } + + status = pjsip_tpmgr_acquire_transport(tpmgr, type, &remote, + addr_len, NULL, &tp); + + if (status == PJ_SUCCESS) { + pj_strdup(pool, ip_addr, &tp->local_name.host); + *port = tp->local_name.port; + status = PJ_SUCCESS; + + pjsip_transport_dec_ref(tp); + } + + } else { + /* For connection oriented transport, enum the factories */ + pjsip_tpfactory *f; + + pj_lock_acquire(tpmgr->lock); + + f = tpmgr->factory_list.next; + while (f != &tpmgr->factory_list) { + if (f->type == type) + break; + f = f->next; + } + + if (f != &tpmgr->factory_list) { + pj_strdup(pool, ip_addr, &f->addr_name.host); + *port = f->addr_name.port; + status = PJ_SUCCESS; + } + pj_lock_release(tpmgr->lock); + } + + return status; +} + +/* + * Return number of transports currently registered to the transport + * manager. + */ +PJ_DEF(unsigned) pjsip_tpmgr_get_transport_count(pjsip_tpmgr *mgr) +{ + pj_hash_iterator_t itr_val; + pj_hash_iterator_t *itr; + int nr_of_transports = 0; + + pj_lock_acquire(mgr->lock); + + itr = pj_hash_first(mgr->table, &itr_val); + while (itr) { + nr_of_transports++; + itr = pj_hash_next(mgr->table, itr); + } + + pj_lock_release(mgr->lock); + + return nr_of_transports; +} + +/* + * pjsip_tpmgr_destroy() + * + * Destroy transport manager. + */ +PJ_DEF(pj_status_t) pjsip_tpmgr_destroy( pjsip_tpmgr *mgr ) +{ + pj_hash_iterator_t itr_val; + pj_hash_iterator_t *itr; + pjsip_tpfactory *factory; + pjsip_endpoint *endpt = mgr->endpt; + + PJ_LOG(5, (THIS_FILE, "Destroying transport manager")); + + pj_lock_acquire(mgr->lock); + + /* + * Destroy all transports. + */ + itr = pj_hash_first(mgr->table, &itr_val); + while (itr != NULL) { + pj_hash_iterator_t *next; + pjsip_transport *transport; + + transport = (pjsip_transport*) pj_hash_this(mgr->table, itr); + + next = pj_hash_next(mgr->table, itr); + + destroy_transport(mgr, transport); + + itr = next; + } + + /* + * Destroy all factories/listeners. + */ + factory = mgr->factory_list.next; + while (factory != &mgr->factory_list) { + pjsip_tpfactory *next = factory->next; + + factory->destroy(factory); + + factory = next; + } + + pj_lock_release(mgr->lock); + pj_lock_destroy(mgr->lock); + + /* Unregister mod_msg_print. */ + if (mod_msg_print.id != -1) { + pjsip_endpt_unregister_module(endpt, &mod_msg_print); + } + +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + /* If you encounter assert error on this line, it means there are + * leakings in transmit data (i.e. some transmit data have not been + * destroyed). + */ + //pj_assert(pj_atomic_get(mgr->tdata_counter) == 0); + if (pj_atomic_get(mgr->tdata_counter) != 0) { + PJ_LOG(3,(THIS_FILE, "Warning: %d transmit buffer(s) not freed!", + pj_atomic_get(mgr->tdata_counter))); + } +#endif + + return PJ_SUCCESS; +} + + +/* + * pjsip_tpmgr_receive_packet() + * + * Called by tranports when they receive a new packet. + */ +PJ_DEF(pj_ssize_t) pjsip_tpmgr_receive_packet( pjsip_tpmgr *mgr, + pjsip_rx_data *rdata) +{ + pjsip_transport *tr = rdata->tp_info.transport; + + char *current_pkt; + pj_size_t remaining_len; + pj_size_t total_processed = 0; + + /* Check size. */ + pj_assert(rdata->pkt_info.len > 0); + if (rdata->pkt_info.len <= 0) + return -1; + + current_pkt = rdata->pkt_info.packet; + remaining_len = rdata->pkt_info.len; + + /* Must NULL terminate buffer. This is the requirement of the + * parser etc. + */ + current_pkt[remaining_len] = '\0'; + + /* Process all message fragments. */ + while (remaining_len > 0) { + + pjsip_msg *msg; + char *p, *end; + char saved; + pj_size_t msg_fragment_size; + + /* Skip leading newlines as pjsip_find_msg() currently can't + * handle leading newlines. + */ + for (p=current_pkt, end=p+remaining_len; p!=end; ++p) { + if (*p != '\r' && *p != '\n') + break; + } + if (p!=current_pkt) { + remaining_len -= (p - current_pkt); + total_processed += (p - current_pkt); + current_pkt = p; + if (remaining_len == 0) { + return total_processed; + } + } + + /* Initialize default fragment size. */ + msg_fragment_size = remaining_len; + + /* Clear and init msg_info in rdata. + * Endpoint might inspect the values there when we call the callback + * to report some errors. + */ + pj_bzero(&rdata->msg_info, sizeof(rdata->msg_info)); + pj_list_init(&rdata->msg_info.parse_err); + rdata->msg_info.msg_buf = current_pkt; + rdata->msg_info.len = remaining_len; + + /* For TCP transport, check if the whole message has been received. */ + if ((tr->flag & PJSIP_TRANSPORT_DATAGRAM) == 0) { + pj_status_t msg_status; + msg_status = pjsip_find_msg(current_pkt, remaining_len, PJ_FALSE, + &msg_fragment_size); + if (msg_status != PJ_SUCCESS) { + if (remaining_len == PJSIP_MAX_PKT_LEN) { + mgr->on_rx_msg(mgr->endpt, PJSIP_ERXOVERFLOW, rdata); + /* Exhaust all data. */ + return rdata->pkt_info.len; + } else { + /* Not enough data in packet. */ + return total_processed; + } + } + } + + /* Update msg_info. */ + rdata->msg_info.len = msg_fragment_size; + + /* Null terminate packet */ + saved = current_pkt[msg_fragment_size]; + current_pkt[msg_fragment_size] = '\0'; + + /* Parse the message. */ + rdata->msg_info.msg = msg = + pjsip_parse_rdata( current_pkt, msg_fragment_size, rdata); + + /* Restore null termination */ + current_pkt[msg_fragment_size] = saved; + + /* Check for parsing syntax error */ + if (msg==NULL || !pj_list_empty(&rdata->msg_info.parse_err)) { + pjsip_parser_err_report *err; + char buf[128]; + pj_str_t tmp; + + /* Gather syntax error information */ + tmp.ptr = buf; tmp.slen = 0; + err = rdata->msg_info.parse_err.next; + while (err != &rdata->msg_info.parse_err) { + int len; + len = pj_ansi_snprintf(tmp.ptr+tmp.slen, sizeof(buf)-tmp.slen, + ": %s exception when parsing '%.*s' " + "header on line %d col %d", + pj_exception_id_name(err->except_code), + (int)err->hname.slen, err->hname.ptr, + err->line, err->col); + if (len > 0 && len < (int) (sizeof(buf)-tmp.slen)) { + tmp.slen += len; + } + err = err->next; + } + + /* Only print error message if there's error. + * Sometimes we receive blank packets (packets with only CRLF) + * which were sent to keep NAT bindings. + */ + if (tmp.slen) { + PJ_LOG(1, (THIS_FILE, + "Error processing %d bytes packet from %s %s:%d %.*s:\n" + "%.*s\n" + "-- end of packet.", + msg_fragment_size, + rdata->tp_info.transport->type_name, + rdata->pkt_info.src_name, + rdata->pkt_info.src_port, + (int)tmp.slen, tmp.ptr, + (int)msg_fragment_size, + rdata->msg_info.msg_buf)); + } + + goto finish_process_fragment; + } + + /* Perform basic header checking. */ + if (rdata->msg_info.cid == NULL || + rdata->msg_info.cid->id.slen == 0 || + rdata->msg_info.from == NULL || + rdata->msg_info.to == NULL || + rdata->msg_info.via == NULL || + rdata->msg_info.cseq == NULL) + { + mgr->on_rx_msg(mgr->endpt, PJSIP_EMISSINGHDR, rdata); + goto finish_process_fragment; + } + + /* For request: */ + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { + /* always add received parameter to the via. */ + pj_strdup2(rdata->tp_info.pool, + &rdata->msg_info.via->recvd_param, + rdata->pkt_info.src_name); + + /* RFC 3581: + * If message contains "rport" param, put the received port there. + */ + if (rdata->msg_info.via->rport_param == 0) { + rdata->msg_info.via->rport_param = rdata->pkt_info.src_port; + } + } else { + /* Drop malformed responses */ + if (rdata->msg_info.msg->line.status.code < 100 || + rdata->msg_info.msg->line.status.code >= 700) + { + mgr->on_rx_msg(mgr->endpt, PJSIP_EINVALIDSTATUS, rdata); + goto finish_process_fragment; + } + } + + /* Drop response message if it has more than one Via. + */ + /* This is wrong. Proxy DOES receive responses with multiple + * Via headers! Thanks Aldo <acampi at deis.unibo.it> for pointing + * this out. + + if (msg->type == PJSIP_RESPONSE_MSG) { + pjsip_hdr *hdr; + hdr = (pjsip_hdr*)rdata->msg_info.via->next; + if (hdr != &msg->hdr) { + hdr = pjsip_msg_find_hdr(msg, PJSIP_H_VIA, hdr); + if (hdr) { + mgr->on_rx_msg(mgr->endpt, PJSIP_EMULTIPLEVIA, rdata); + goto finish_process_fragment; + } + } + } + */ + + /* Call the transport manager's upstream message callback. + */ + mgr->on_rx_msg(mgr->endpt, PJ_SUCCESS, rdata); + + +finish_process_fragment: + total_processed += msg_fragment_size; + current_pkt += msg_fragment_size; + remaining_len -= msg_fragment_size; + + } /* while (rdata->pkt_info.len > 0) */ + + + return total_processed; +} + + +/* + * pjsip_tpmgr_acquire_transport() + * + * Get transport suitable to communicate to remote. Create a new one + * if necessary. + */ +PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + const pjsip_tpselector *sel, + pjsip_transport **tp) +{ + return pjsip_tpmgr_acquire_transport2(mgr, type, remote, addr_len, sel, + NULL, tp); +} + +/* + * pjsip_tpmgr_acquire_transport2() + * + * Get transport suitable to communicate to remote. Create a new one + * if necessary. + */ +PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr, + pjsip_transport_type_e type, + const pj_sockaddr_t *remote, + int addr_len, + const pjsip_tpselector *sel, + pjsip_tx_data *tdata, + pjsip_transport **tp) +{ + pjsip_tpfactory *factory; + pj_status_t status; + + TRACE_((THIS_FILE,"Acquiring transport type=%s, remote=%s:%d", + pjsip_transport_get_type_name(type), + addr_string(remote), + pj_sockaddr_get_port(remote))); + + pj_lock_acquire(mgr->lock); + + /* If transport is specified, then just use it if it is suitable + * for the destination. + */ + if (sel && sel->type == PJSIP_TPSELECTOR_TRANSPORT && + sel->u.transport) + { + pjsip_transport *seltp = sel->u.transport; + + /* See if the transport is (not) suitable */ + if (seltp->key.type != type) { + pj_lock_release(mgr->lock); + return PJSIP_ETPNOTSUITABLE; + } + + /* We could also verify that the destination address is reachable + * from this transport (i.e. both are equal), but if application + * has requested a specific transport to be used, assume that + * it knows what to do. + * + * In other words, I don't think destination verification is a good + * idea for now. + */ + + /* Transport looks to be suitable to use, so just use it. */ + pjsip_transport_add_ref(seltp); + pj_lock_release(mgr->lock); + *tp = seltp; + + TRACE_((THIS_FILE, "Transport %s acquired", seltp->obj_name)); + return PJ_SUCCESS; + + + } else if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER && + sel->u.listener) + { + /* Application has requested that a specific listener is to + * be used. In this case, skip transport hash table lookup. + */ + + /* Verify that the listener type matches the destination type */ + if (sel->u.listener->type != type) { + pj_lock_release(mgr->lock); + return PJSIP_ETPNOTSUITABLE; + } + + /* We'll use this listener to create transport */ + factory = sel->u.listener; + + } else { + + /* + * This is the "normal" flow, where application doesn't specify + * specific transport/listener to be used to send message to. + * In this case, lookup the transport from the hash table. + */ + pjsip_transport_key key; + int key_len; + pjsip_transport *transport; + + pj_bzero(&key, sizeof(key)); + key_len = sizeof(key.type) + addr_len; + + /* First try to get exact destination. */ + key.type = type; + pj_memcpy(&key.rem_addr, remote, addr_len); + + transport = (pjsip_transport*) + pj_hash_get(mgr->table, &key, key_len, NULL); + + if (transport == NULL) { + unsigned flag = pjsip_transport_get_flag_from_type(type); + const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote; + + + /* Ignore address for loop transports. */ + if (type == PJSIP_TRANSPORT_LOOP || + type == PJSIP_TRANSPORT_LOOP_DGRAM) + { + pj_sockaddr *addr = &key.rem_addr; + + pj_bzero(addr, addr_len); + key_len = sizeof(key.type) + addr_len; + transport = (pjsip_transport*) + pj_hash_get(mgr->table, &key, key_len, NULL); + } + /* For datagram transports, try lookup with zero address. + */ + else if (flag & PJSIP_TRANSPORT_DATAGRAM) + { + pj_sockaddr *addr = &key.rem_addr; + + pj_bzero(addr, addr_len); + addr->addr.sa_family = remote_addr->addr.sa_family; + + key_len = sizeof(key.type) + addr_len; + transport = (pjsip_transport*) + pj_hash_get(mgr->table, &key, key_len, NULL); + } + } + + if (transport!=NULL && !transport->is_shutdown) { + /* + * Transport found! + */ + pjsip_transport_add_ref(transport); + pj_lock_release(mgr->lock); + *tp = transport; + + TRACE_((THIS_FILE, "Transport %s acquired", transport->obj_name)); + return PJ_SUCCESS; + } + + /* + * Transport not found! + * Find factory that can create such transport. + */ + factory = mgr->factory_list.next; + while (factory != &mgr->factory_list) { + if (factory->type == type) + break; + factory = factory->next; + } + + if (factory == &mgr->factory_list) { + /* No factory can create the transport! */ + pj_lock_release(mgr->lock); + TRACE_((THIS_FILE, "No suitable factory was found either")); + return PJSIP_EUNSUPTRANSPORT; + } + } + + TRACE_((THIS_FILE, "Creating new transport from factory")); + + /* Request factory to create transport. */ + if (factory->create_transport2) { + status = factory->create_transport2(factory, mgr, mgr->endpt, + (const pj_sockaddr*) remote, + addr_len, tdata, tp); + } else { + status = factory->create_transport(factory, mgr, mgr->endpt, + (const pj_sockaddr*) remote, + addr_len, tp); + } + if (status == PJ_SUCCESS) { + PJ_ASSERT_ON_FAIL(tp!=NULL, + {pj_lock_release(mgr->lock); return PJ_EBUG;}); + pjsip_transport_add_ref(*tp); + } + pj_lock_release(mgr->lock); + return status; +} + +/** + * Dump transport info. + */ +PJ_DEF(void) pjsip_tpmgr_dump_transports(pjsip_tpmgr *mgr) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + pj_hash_iterator_t itr_val; + pj_hash_iterator_t *itr; + pjsip_tpfactory *factory; + + pj_lock_acquire(mgr->lock); + +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 + PJ_LOG(3,(THIS_FILE, " Outstanding transmit buffers: %d", + pj_atomic_get(mgr->tdata_counter))); +#endif + + PJ_LOG(3, (THIS_FILE, " Dumping listeners:")); + factory = mgr->factory_list.next; + while (factory != &mgr->factory_list) { + PJ_LOG(3, (THIS_FILE, " %s %s:%.*s:%d", + factory->obj_name, + factory->type_name, + (int)factory->addr_name.host.slen, + factory->addr_name.host.ptr, + (int)factory->addr_name.port)); + factory = factory->next; + } + + itr = pj_hash_first(mgr->table, &itr_val); + if (itr) { + PJ_LOG(3, (THIS_FILE, " Dumping transports:")); + + do { + pjsip_transport *t = (pjsip_transport*) + pj_hash_this(mgr->table, itr); + + PJ_LOG(3, (THIS_FILE, " %s %s (refcnt=%d%s)", + t->obj_name, + t->info, + pj_atomic_get(t->ref_cnt), + (t->idle_timer.id ? " [idle]" : ""))); + + itr = pj_hash_next(mgr->table, itr); + } while (itr); + } + + pj_lock_release(mgr->lock); +#else + PJ_UNUSED_ARG(mgr); +#endif +} + +/** + * Set callback of global transport state notification. + */ +PJ_DEF(pj_status_t) pjsip_tpmgr_set_state_cb(pjsip_tpmgr *mgr, + pjsip_tp_state_callback cb) +{ + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + mgr->tp_state_cb = cb; + + return PJ_SUCCESS; +} + +/** + * Get callback of global transport state notification. + */ +PJ_DEF(pjsip_tp_state_callback) pjsip_tpmgr_get_state_cb( + const pjsip_tpmgr *mgr) +{ + PJ_ASSERT_RETURN(mgr, NULL); + + return mgr->tp_state_cb; +} + + +/** + * Allocate and init transport data. + */ +static void init_tp_data(pjsip_transport *tp) +{ + transport_data *tp_data; + + pj_assert(tp && !tp->data); + + tp_data = PJ_POOL_ZALLOC_T(tp->pool, transport_data); + pj_list_init(&tp_data->st_listeners); + pj_list_init(&tp_data->st_listeners_empty); + tp->data = tp_data; +} + + +static void tp_state_callback(pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info) +{ + transport_data *tp_data; + + pj_lock_acquire(tp->lock); + + tp_data = (transport_data*)tp->data; + + /* Notify the transport state listeners, if any. */ + if (!tp_data || pj_list_empty(&tp_data->st_listeners)) { + goto on_return; + } else { + pjsip_transport_state_info st_info; + tp_state_listener *st_listener = tp_data->st_listeners.next; + + /* As we need to put the user data into the transport state info, + * let's use a copy of transport state info. + */ + pj_memcpy(&st_info, info, sizeof(st_info)); + while (st_listener != &tp_data->st_listeners) { + st_info.user_data = st_listener->user_data; + (*st_listener->cb)(tp, state, &st_info); + + st_listener = st_listener->next; + } + } + +on_return: + pj_lock_release(tp->lock); +} + + +/** + * Add a listener to the specified transport for transport state notification. + */ +PJ_DEF(pj_status_t) pjsip_transport_add_state_listener ( + pjsip_transport *tp, + pjsip_tp_state_callback cb, + void *user_data, + pjsip_tp_state_listener_key **key) +{ + transport_data *tp_data; + tp_state_listener *entry; + + PJ_ASSERT_RETURN(tp && cb && key, PJ_EINVAL); + + pj_lock_acquire(tp->lock); + + /* Init transport data, if it hasn't */ + if (!tp->data) + init_tp_data(tp); + + tp_data = (transport_data*)tp->data; + + /* Init the new listener entry. Use available empty slot, if any, + * otherwise allocate it using the transport pool. + */ + if (!pj_list_empty(&tp_data->st_listeners_empty)) { + entry = tp_data->st_listeners_empty.next; + pj_list_erase(entry); + } else { + entry = PJ_POOL_ZALLOC_T(tp->pool, tp_state_listener); + } + entry->cb = cb; + entry->user_data = user_data; + + /* Add the new listener entry to the listeners list */ + pj_list_push_back(&tp_data->st_listeners, entry); + + *key = entry; + + pj_lock_release(tp->lock); + + return PJ_SUCCESS; +} + +/** + * Remove a listener from the specified transport for transport state + * notification. + */ +PJ_DEF(pj_status_t) pjsip_transport_remove_state_listener ( + pjsip_transport *tp, + pjsip_tp_state_listener_key *key, + const void *user_data) +{ + transport_data *tp_data; + tp_state_listener *entry; + + PJ_ASSERT_RETURN(tp && key, PJ_EINVAL); + + pj_lock_acquire(tp->lock); + + tp_data = (transport_data*)tp->data; + + /* Transport data is NULL or no registered listener? */ + if (!tp_data || pj_list_empty(&tp_data->st_listeners)) { + pj_lock_release(tp->lock); + return PJ_ENOTFOUND; + } + + entry = (tp_state_listener*)key; + + /* Validate the user data */ + if (entry->user_data != user_data) { + pj_assert(!"Invalid transport state listener key"); + pj_lock_release(tp->lock); + return PJ_EBUG; + } + + /* Reset the entry and move it to the empty list */ + entry->cb = NULL; + entry->user_data = NULL; + pj_list_erase(entry); + pj_list_push_back(&tp_data->st_listeners_empty, entry); + + pj_lock_release(tp->lock); + + return PJ_SUCCESS; +} diff --git a/pjsip/src/pjsip/sip_transport_loop.c b/pjsip/src/pjsip/sip_transport_loop.c new file mode 100644 index 0000000..498b529 --- /dev/null +++ b/pjsip/src/pjsip/sip_transport_loop.c @@ -0,0 +1,509 @@ +/* $Id: sip_transport_loop.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_transport_loop.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pj/pool.h> +#include <pj/os.h> +#include <pj/string.h> +#include <pj/lock.h> +#include <pj/assert.h> +#include <pj/compat/socket.h> + + +#define ADDR_LOOP "128.0.0.1" +#define ADDR_LOOP_DGRAM "129.0.0.1" + + +/** This structure describes incoming packet. */ +struct recv_list +{ + PJ_DECL_LIST_MEMBER(struct recv_list); + pjsip_rx_data rdata; +}; + +/** This structure is used to keep delayed send failure. */ +struct send_list +{ + PJ_DECL_LIST_MEMBER(struct send_list); + pj_time_val sent_time; + pj_ssize_t sent; + pjsip_tx_data *tdata; + void *token; + void (*callback)(pjsip_transport*, void*, pj_ssize_t); +}; + +/** This structure describes the loop transport. */ +struct loop_transport +{ + pjsip_transport base; + pj_pool_t *pool; + pj_thread_t *thread; + pj_bool_t thread_quit_flag; + pj_bool_t discard; + int fail_mode; + unsigned recv_delay; + unsigned send_delay; + struct recv_list recv_list; + struct send_list send_list; +}; + + +/* Helper function to create "incoming" packet */ +struct recv_list *create_incoming_packet( struct loop_transport *loop, + pjsip_tx_data *tdata ) +{ + pj_pool_t *pool; + struct recv_list *pkt; + + pool = pjsip_endpt_create_pool(loop->base.endpt, "rdata", + PJSIP_POOL_RDATA_LEN, + PJSIP_POOL_RDATA_INC+5); + if (!pool) + return NULL; + + pkt = PJ_POOL_ZALLOC_T(pool, struct recv_list); + + /* Initialize rdata. */ + pkt->rdata.tp_info.pool = pool; + pkt->rdata.tp_info.transport = &loop->base; + + /* Copy the packet. */ + pj_memcpy(pkt->rdata.pkt_info.packet, tdata->buf.start, + tdata->buf.cur - tdata->buf.start); + pkt->rdata.pkt_info.len = tdata->buf.cur - tdata->buf.start; + + /* the source address */ + pkt->rdata.pkt_info.src_addr.addr.sa_family = pj_AF_INET(); + + /* "Source address" info. */ + pkt->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in); + if (loop->base.key.type == PJSIP_TRANSPORT_LOOP) { + pj_ansi_strcpy(pkt->rdata.pkt_info.src_name, ADDR_LOOP); + } else { + pj_ansi_strcpy(pkt->rdata.pkt_info.src_name, ADDR_LOOP_DGRAM); + } + pkt->rdata.pkt_info.src_port = loop->base.local_name.port; + + /* When do we need to "deliver" this packet. */ + pj_gettimeofday(&pkt->rdata.pkt_info.timestamp); + pkt->rdata.pkt_info.timestamp.msec += loop->recv_delay; + pj_time_val_normalize(&pkt->rdata.pkt_info.timestamp); + + /* Done. */ + + return pkt; +} + + +/* Helper function to add pending notification callback. */ +static pj_status_t add_notification( struct loop_transport *loop, + pjsip_tx_data *tdata, + pj_ssize_t sent, + void *token, + void (*callback)(pjsip_transport*, + void*, pj_ssize_t)) +{ + struct send_list *sent_status; + + pjsip_tx_data_add_ref(tdata); + pj_lock_acquire(tdata->lock); + sent_status = PJ_POOL_ALLOC_T(tdata->pool, struct send_list); + pj_lock_release(tdata->lock); + + sent_status->sent = sent; + sent_status->tdata = tdata; + sent_status->token = token; + sent_status->callback = callback; + + pj_gettimeofday(&sent_status->sent_time); + sent_status->sent_time.msec += loop->send_delay; + pj_time_val_normalize(&sent_status->sent_time); + + pj_lock_acquire(loop->base.lock); + pj_list_push_back(&loop->send_list, sent_status); + pj_lock_release(loop->base.lock); + + return PJ_SUCCESS; +} + +/* Handler for sending outgoing message; called by transport manager. */ +static pj_status_t loop_send_msg( pjsip_transport *tp, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, + int addr_len, + void *token, + pjsip_transport_callback cb) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + struct recv_list *recv_pkt; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + PJ_UNUSED_ARG(rem_addr); + PJ_UNUSED_ARG(addr_len); + + + /* Need to send failure? */ + if (loop->fail_mode) { + if (loop->send_delay == 0) { + return PJ_STATUS_FROM_OS(OSERR_ECONNRESET); + } else { + add_notification(loop, tdata, -PJ_STATUS_FROM_OS(OSERR_ECONNRESET), + token, cb); + + return PJ_EPENDING; + } + } + + /* Discard any packets? */ + if (loop->discard) + return PJ_SUCCESS; + + /* Create rdata for the "incoming" packet. */ + recv_pkt = create_incoming_packet(loop, tdata); + if (!recv_pkt) + return PJ_ENOMEM; + + /* If delay is not configured, deliver this packet now! */ + if (loop->recv_delay == 0) { + pj_ssize_t size_eaten; + + size_eaten = pjsip_tpmgr_receive_packet( loop->base.tpmgr, + &recv_pkt->rdata); + pj_assert(size_eaten == recv_pkt->rdata.pkt_info.len); + + pjsip_endpt_release_pool(loop->base.endpt, + recv_pkt->rdata.tp_info.pool); + + } else { + /* Otherwise if delay is configured, add the "packet" to the + * receive list to be processed by worker thread. + */ + pj_lock_acquire(loop->base.lock); + pj_list_push_back(&loop->recv_list, recv_pkt); + pj_lock_release(loop->base.lock); + } + + if (loop->send_delay != 0) { + add_notification(loop, tdata, tdata->buf.cur - tdata->buf.start, + token, cb); + return PJ_EPENDING; + } else { + return PJ_SUCCESS; + } +} + +/* Handler to destroy the transport; called by transport manager */ +static pj_status_t loop_destroy(pjsip_transport *tp) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + loop->thread_quit_flag = 1; + /* Unlock transport mutex before joining thread. */ + pj_lock_release(tp->lock); + pj_thread_join(loop->thread); + pj_thread_destroy(loop->thread); + + /* Clear pending send notifications. */ + while (!pj_list_empty(&loop->send_list)) { + struct send_list *node = loop->send_list.next; + /* Notify callback. */ + if (node->callback) { + (*node->callback)(&loop->base, node->token, -PJSIP_ESHUTDOWN); + } + pj_list_erase(node); + pjsip_tx_data_dec_ref(node->tdata); + } + + /* Clear "incoming" packets in the queue. */ + while (!pj_list_empty(&loop->recv_list)) { + struct recv_list *node = loop->recv_list.next; + pj_list_erase(node); + pjsip_endpt_release_pool(loop->base.endpt, + node->rdata.tp_info.pool); + } + + /* Self destruct.. heheh.. */ + pj_lock_destroy(loop->base.lock); + pj_atomic_destroy(loop->base.ref_cnt); + pjsip_endpt_release_pool(loop->base.endpt, loop->base.pool); + + return PJ_SUCCESS; +} + +/* Worker thread for loop transport. */ +static int loop_transport_worker_thread(void *arg) +{ + struct loop_transport *loop = (struct loop_transport*) arg; + struct recv_list r; + struct send_list s; + + pj_list_init(&r); + pj_list_init(&s); + + while (!loop->thread_quit_flag) { + pj_time_val now; + + pj_thread_sleep(1); + pj_gettimeofday(&now); + + pj_lock_acquire(loop->base.lock); + + /* Move expired send notification to local list. */ + while (!pj_list_empty(&loop->send_list)) { + struct send_list *node = loop->send_list.next; + + /* Break when next node time is greater than now. */ + if (PJ_TIME_VAL_GTE(node->sent_time, now)) + break; + + /* Delete this from the list. */ + pj_list_erase(node); + + /* Add to local list. */ + pj_list_push_back(&s, node); + } + + /* Move expired "incoming" packet to local list. */ + while (!pj_list_empty(&loop->recv_list)) { + struct recv_list *node = loop->recv_list.next; + + /* Break when next node time is greater than now. */ + if (PJ_TIME_VAL_GTE(node->rdata.pkt_info.timestamp, now)) + break; + + /* Delete this from the list. */ + pj_list_erase(node); + + /* Add to local list. */ + pj_list_push_back(&r, node); + + } + + pj_lock_release(loop->base.lock); + + /* Process send notification and incoming packet notification + * without holding down the loop's mutex. + */ + while (!pj_list_empty(&s)) { + struct send_list *node = s.next; + + pj_list_erase(node); + + /* Notify callback. */ + if (node->callback) { + (*node->callback)(&loop->base, node->token, node->sent); + } + + /* Decrement tdata reference counter. */ + pjsip_tx_data_dec_ref(node->tdata); + } + + /* Process "incoming" packet. */ + while (!pj_list_empty(&r)) { + struct recv_list *node = r.next; + pj_ssize_t size_eaten; + + pj_list_erase(node); + + /* Notify transport manager about the "incoming packet" */ + size_eaten = pjsip_tpmgr_receive_packet(loop->base.tpmgr, + &node->rdata); + + /* Must "eat" all the packets. */ + pj_assert(size_eaten == node->rdata.pkt_info.len); + + /* Done. */ + pjsip_endpt_release_pool(loop->base.endpt, + node->rdata.tp_info.pool); + } + } + + return 0; +} + + +/* Start loop transport. */ +PJ_DEF(pj_status_t) pjsip_loop_start( pjsip_endpoint *endpt, + pjsip_transport **transport) +{ + pj_pool_t *pool; + struct loop_transport *loop; + pj_status_t status; + + /* Create pool. */ + pool = pjsip_endpt_create_pool(endpt, "loop", 4000, 4000); + if (!pool) + return PJ_ENOMEM; + + /* Create the loop structure. */ + loop = PJ_POOL_ZALLOC_T(pool, struct loop_transport); + + /* Initialize transport properties. */ + pj_ansi_snprintf(loop->base.obj_name, sizeof(loop->base.obj_name), + "loop%p", loop); + loop->base.pool = pool; + status = pj_atomic_create(pool, 0, &loop->base.ref_cnt); + if (status != PJ_SUCCESS) + goto on_error; + status = pj_lock_create_recursive_mutex(pool, "loop", &loop->base.lock); + if (status != PJ_SUCCESS) + goto on_error; + loop->base.key.type = PJSIP_TRANSPORT_LOOP_DGRAM; + //loop->base.key.rem_addr.sa_family = pj_AF_INET(); + loop->base.type_name = "LOOP-DGRAM"; + loop->base.info = "LOOP-DGRAM"; + loop->base.flag = PJSIP_TRANSPORT_DATAGRAM; + loop->base.local_name.host = pj_str(ADDR_LOOP_DGRAM); + loop->base.local_name.port = + pjsip_transport_get_default_port_for_type((pjsip_transport_type_e) + loop->base.key.type); + loop->base.addr_len = sizeof(pj_sockaddr_in); + loop->base.dir = PJSIP_TP_DIR_NONE; + loop->base.endpt = endpt; + loop->base.tpmgr = pjsip_endpt_get_tpmgr(endpt); + loop->base.send_msg = &loop_send_msg; + loop->base.destroy = &loop_destroy; + + pj_list_init(&loop->recv_list); + pj_list_init(&loop->send_list); + + /* Create worker thread. */ + status = pj_thread_create(pool, "loop", + &loop_transport_worker_thread, loop, 0, + PJ_THREAD_SUSPENDED, &loop->thread); + if (status != PJ_SUCCESS) + goto on_error; + + /* Register to transport manager. */ + status = pjsip_transport_register( loop->base.tpmgr, &loop->base); + if (status != PJ_SUCCESS) + goto on_error; + + /* Start the thread. */ + status = pj_thread_resume(loop->thread); + if (status != PJ_SUCCESS) + goto on_error; + + /* + * Done. + */ + + if (transport) + *transport = &loop->base; + + return PJ_SUCCESS; + +on_error: + if (loop->base.lock) + pj_lock_destroy(loop->base.lock); + if (loop->thread) + pj_thread_destroy(loop->thread); + if (loop->base.ref_cnt) + pj_atomic_destroy(loop->base.ref_cnt); + pjsip_endpt_release_pool(endpt, loop->pool); + return status; +} + + +PJ_DEF(pj_status_t) pjsip_loop_set_discard( pjsip_transport *tp, + pj_bool_t discard, + pj_bool_t *prev_value ) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + if (prev_value) + *prev_value = loop->discard; + loop->discard = discard; + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_loop_set_failure( pjsip_transport *tp, + int fail_flag, + int *prev_value ) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + if (prev_value) + *prev_value = loop->fail_mode; + loop->fail_mode = fail_flag; + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_loop_set_recv_delay( pjsip_transport *tp, + unsigned delay, + unsigned *prev_value) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + if (prev_value) + *prev_value = loop->recv_delay; + loop->recv_delay = delay; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsip_loop_set_send_callback_delay( pjsip_transport *tp, + unsigned delay, + unsigned *prev_value) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + if (prev_value) + *prev_value = loop->send_delay; + loop->send_delay = delay; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjsip_loop_set_delay( pjsip_transport *tp, unsigned delay ) +{ + struct loop_transport *loop = (struct loop_transport*)tp; + + PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP || + tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL); + + loop->recv_delay = delay; + loop->send_delay = delay; + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsip/sip_transport_tcp.c b/pjsip/src/pjsip/sip_transport_tcp.c new file mode 100644 index 0000000..141434b --- /dev/null +++ b/pjsip/src/pjsip/sip_transport_tcp.c @@ -0,0 +1,1417 @@ +/* $Id: sip_transport_tcp.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_transport_tcp.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pj/compat/socket.h> +#include <pj/addr_resolv.h> +#include <pj/activesock.h> +#include <pj/assert.h> +#include <pj/lock.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/string.h> + +/* Only declare the API if PJ_HAS_TCP is true */ +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP!=0 + + +#define THIS_FILE "sip_transport_tcp.c" + +#define MAX_ASYNC_CNT 16 +#define POOL_LIS_INIT 512 +#define POOL_LIS_INC 512 +#define POOL_TP_INIT 512 +#define POOL_TP_INC 512 + +struct tcp_listener; +struct tcp_transport; + + +/* + * This is the TCP listener, which is a "descendant" of pjsip_tpfactory (the + * SIP transport factory). + */ +struct tcp_listener +{ + pjsip_tpfactory factory; + pj_bool_t is_registered; + pjsip_endpoint *endpt; + pjsip_tpmgr *tpmgr; + pj_activesock_t *asock; + pj_qos_type qos_type; + pj_qos_params qos_params; +}; + + +/* + * This structure is used to keep delayed transmit operation in a list. + * A delayed transmission occurs when application sends tx_data when + * the TCP connect/establishment is still in progress. These delayed + * transmission will be "flushed" once the socket is connected (either + * successfully or with errors). + */ +struct delayed_tdata +{ + PJ_DECL_LIST_MEMBER(struct delayed_tdata); + pjsip_tx_data_op_key *tdata_op_key; +}; + + +/* + * This structure describes the TCP transport, and it's descendant of + * pjsip_transport. + */ +struct tcp_transport +{ + pjsip_transport base; + pj_bool_t is_server; + + /* Do not save listener instance in the transport, because + * listener might be destroyed during transport's lifetime. + * See http://trac.pjsip.org/repos/ticket/491 + struct tcp_listener *listener; + */ + + pj_bool_t is_registered; + pj_bool_t is_closing; + pj_status_t close_reason; + pj_sock_t sock; + pj_activesock_t *asock; + pj_bool_t has_pending_connect; + + /* Keep-alive timer. */ + pj_timer_entry ka_timer; + pj_time_val last_activity; + pjsip_tx_data_op_key ka_op_key; + pj_str_t ka_pkt; + + /* TCP transport can only have one rdata! + * Otherwise chunks of incoming PDU may be received on different + * buffer. + */ + pjsip_rx_data rdata; + + /* Pending transmission list. */ + struct delayed_tdata delayed_list; +}; + + +/**************************************************************************** + * PROTOTYPES + */ + +/* This callback is called when pending accept() operation completes. */ +static pj_bool_t on_accept_complete(pj_activesock_t *asock, + pj_sock_t newsock, + const pj_sockaddr_t *src_addr, + int src_addr_len); + +/* This callback is called by transport manager to destroy listener */ +static pj_status_t lis_destroy(pjsip_tpfactory *factory); + +/* This callback is called by transport manager to create transport */ +static pj_status_t lis_create_transport(pjsip_tpfactory *factory, + pjsip_tpmgr *mgr, + pjsip_endpoint *endpt, + const pj_sockaddr *rem_addr, + int addr_len, + pjsip_transport **transport); + +/* Common function to create and initialize transport */ +static pj_status_t tcp_create(struct tcp_listener *listener, + pj_pool_t *pool, + pj_sock_t sock, pj_bool_t is_server, + const pj_sockaddr_in *local, + const pj_sockaddr_in *remote, + struct tcp_transport **p_tcp); + + +static void tcp_perror(const char *sender, const char *title, + pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + + PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status)); +} + + +static void sockaddr_to_host_port( pj_pool_t *pool, + pjsip_host_port *host_port, + const pj_sockaddr_in *addr ) +{ + host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4); + pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 2); + host_port->host.slen = pj_ansi_strlen(host_port->host.ptr); + host_port->port = pj_sockaddr_get_port(addr); +} + + +static void tcp_init_shutdown(struct tcp_transport *tcp, pj_status_t status) +{ + pjsip_tp_state_callback state_cb; + + if (tcp->close_reason == PJ_SUCCESS) + tcp->close_reason = status; + + if (tcp->base.is_shutdown) + return; + + /* Prevent immediate transport destroy by application, as transport + * state notification callback may be stacked and transport instance + * must remain valid at any point in the callback. + */ + pjsip_transport_add_ref(&tcp->base); + + /* Notify application of transport disconnected state */ + state_cb = pjsip_tpmgr_get_state_cb(tcp->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + + pj_bzero(&state_info, sizeof(state_info)); + state_info.status = tcp->close_reason; + (*state_cb)(&tcp->base, PJSIP_TP_STATE_DISCONNECTED, &state_info); + } + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + pjsip_transport_shutdown(&tcp->base); + + /* Now, it is ok to destroy the transport. */ + pjsip_transport_dec_ref(&tcp->base); +} + + +/* + * Initialize pjsip_tcp_transport_cfg structure with default values. + */ +PJ_DEF(void) pjsip_tcp_transport_cfg_default(pjsip_tcp_transport_cfg *cfg, + int af) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->af = af; + pj_sockaddr_init(cfg->af, &cfg->bind_addr, NULL, 0); + cfg->async_cnt = 1; +} + + +/**************************************************************************** + * The TCP listener/transport factory. + */ + +/* + * This is the public API to create, initialize, register, and start the + * TCP listener. + */ +PJ_DEF(pj_status_t) pjsip_tcp_transport_start3( + pjsip_endpoint *endpt, + const pjsip_tcp_transport_cfg *cfg, + pjsip_tpfactory **p_factory + ) +{ + pj_pool_t *pool; + pj_sock_t sock = PJ_INVALID_SOCKET; + struct tcp_listener *listener; + pj_activesock_cfg asock_cfg; + pj_activesock_cb listener_cb; + pj_sockaddr *listener_addr; + int addr_len; + pj_status_t status; + + /* Sanity check */ + PJ_ASSERT_RETURN(endpt && cfg->async_cnt, PJ_EINVAL); + + /* Verify that address given in a_name (if any) is valid */ + if (cfg->addr_name.host.slen) { + pj_sockaddr tmp; + + status = pj_sockaddr_init(cfg->af, &tmp, &cfg->addr_name.host, + (pj_uint16_t)cfg->addr_name.port); + if (status != PJ_SUCCESS || !pj_sockaddr_has_addr(&tmp) || + (cfg->af==pj_AF_INET() && + tmp.ipv4.sin_addr.s_addr==PJ_INADDR_NONE)) + { + /* Invalid address */ + return PJ_EINVAL; + } + } + + pool = pjsip_endpt_create_pool(endpt, "tcplis", POOL_LIS_INIT, + POOL_LIS_INC); + PJ_ASSERT_RETURN(pool, PJ_ENOMEM); + + + listener = PJ_POOL_ZALLOC_T(pool, struct tcp_listener); + listener->factory.pool = pool; + listener->factory.type = PJSIP_TRANSPORT_TCP; + listener->factory.type_name = "tcp"; + listener->factory.flag = + pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TCP); + listener->qos_type = cfg->qos_type; + pj_memcpy(&listener->qos_params, &cfg->qos_params, + sizeof(cfg->qos_params)); + + pj_ansi_strcpy(listener->factory.obj_name, "tcplis"); + + status = pj_lock_create_recursive_mutex(pool, "tcplis", + &listener->factory.lock); + if (status != PJ_SUCCESS) + goto on_error; + + + /* Create socket */ + status = pj_sock_socket(cfg->af, pj_SOCK_STREAM(), 0, &sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(sock, cfg->qos_type, &cfg->qos_params, + 2, listener->factory.obj_name, + "SIP TCP listener socket"); + + /* Bind socket */ + listener_addr = &listener->factory.local_addr; + pj_sockaddr_cp(listener_addr, &cfg->bind_addr); + + status = pj_sock_bind(sock, listener_addr, + pj_sockaddr_get_len(listener_addr)); + if (status != PJ_SUCCESS) + goto on_error; + + /* Retrieve the bound address */ + addr_len = pj_sockaddr_get_len(listener_addr); + status = pj_sock_getsockname(sock, listener_addr, &addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + /* If published host/IP is specified, then use that address as the + * listener advertised address. + */ + if (cfg->addr_name.host.slen) { + /* Copy the address */ + listener->factory.addr_name = cfg->addr_name; + pj_strdup(listener->factory.pool, &listener->factory.addr_name.host, + &cfg->addr_name.host); + listener->factory.addr_name.port = cfg->addr_name.port; + + } else { + /* No published address is given, use the bound address */ + + /* If the address returns 0.0.0.0, use the default + * interface address as the transport's address. + */ + if (!pj_sockaddr_has_addr(listener_addr)) { + pj_sockaddr hostip; + + status = pj_gethostip(pj_AF_INET(), &hostip); + if (status != PJ_SUCCESS) + goto on_error; + + pj_memcpy(pj_sockaddr_get_addr(listener_addr), + pj_sockaddr_get_addr(&hostip), + pj_sockaddr_get_addr_len(&hostip)); + } + + /* Save the address name */ + sockaddr_to_host_port(listener->factory.pool, + &listener->factory.addr_name, + (pj_sockaddr_in*)listener_addr); + } + + /* If port is zero, get the bound port */ + if (listener->factory.addr_name.port == 0) { + listener->factory.addr_name.port = pj_sockaddr_get_port(listener_addr); + } + + pj_ansi_snprintf(listener->factory.obj_name, + sizeof(listener->factory.obj_name), + "tcplis:%d", listener->factory.addr_name.port); + + + /* Start listening to the address */ + status = pj_sock_listen(sock, PJSIP_TCP_TRANSPORT_BACKLOG); + if (status != PJ_SUCCESS) + goto on_error; + + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + if (cfg->async_cnt > MAX_ASYNC_CNT) + asock_cfg.async_cnt = MAX_ASYNC_CNT; + else + asock_cfg.async_cnt = cfg->async_cnt; + + pj_bzero(&listener_cb, sizeof(listener_cb)); + listener_cb.on_accept_complete = &on_accept_complete; + status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), &asock_cfg, + pjsip_endpt_get_ioqueue(endpt), + &listener_cb, listener, + &listener->asock); + + /* Register to transport manager */ + listener->endpt = endpt; + listener->tpmgr = pjsip_endpt_get_tpmgr(endpt); + listener->factory.create_transport = lis_create_transport; + listener->factory.destroy = lis_destroy; + listener->is_registered = PJ_TRUE; + status = pjsip_tpmgr_register_tpfactory(listener->tpmgr, + &listener->factory); + if (status != PJ_SUCCESS) { + listener->is_registered = PJ_FALSE; + goto on_error; + } + + /* Start pending accept() operations */ + status = pj_activesock_start_accept(listener->asock, pool); + if (status != PJ_SUCCESS) + goto on_error; + + PJ_LOG(4,(listener->factory.obj_name, + "SIP TCP listener ready for incoming connections at %.*s:%d", + (int)listener->factory.addr_name.host.slen, + listener->factory.addr_name.host.ptr, + listener->factory.addr_name.port)); + + /* Return the pointer to user */ + if (p_factory) *p_factory = &listener->factory; + + return PJ_SUCCESS; + +on_error: + if (listener->asock==NULL && sock!=PJ_INVALID_SOCKET) + pj_sock_close(sock); + lis_destroy(&listener->factory); + return status; +} + + +/* + * This is the public API to create, initialize, register, and start the + * TCP listener. + */ +PJ_DEF(pj_status_t) pjsip_tcp_transport_start2(pjsip_endpoint *endpt, + const pj_sockaddr_in *local, + const pjsip_host_port *a_name, + unsigned async_cnt, + pjsip_tpfactory **p_factory) +{ + pjsip_tcp_transport_cfg cfg; + + pjsip_tcp_transport_cfg_default(&cfg, pj_AF_INET()); + + if (local) + pj_sockaddr_cp(&cfg.bind_addr, local); + else + pj_sockaddr_init(cfg.af, &cfg.bind_addr, NULL, 0); + + if (a_name) + pj_memcpy(&cfg.addr_name, a_name, sizeof(*a_name)); + + if (async_cnt) + cfg.async_cnt = async_cnt; + + return pjsip_tcp_transport_start3(endpt, &cfg, p_factory); +} + + +/* + * This is the public API to create, initialize, register, and start the + * TCP listener. + */ +PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt, + const pj_sockaddr_in *local, + unsigned async_cnt, + pjsip_tpfactory **p_factory) +{ + return pjsip_tcp_transport_start2(endpt, local, NULL, async_cnt, p_factory); +} + + +/* This callback is called by transport manager to destroy listener */ +static pj_status_t lis_destroy(pjsip_tpfactory *factory) +{ + struct tcp_listener *listener = (struct tcp_listener *)factory; + + if (listener->is_registered) { + pjsip_tpmgr_unregister_tpfactory(listener->tpmgr, &listener->factory); + listener->is_registered = PJ_FALSE; + } + + if (listener->asock) { + pj_activesock_close(listener->asock); + listener->asock = NULL; + } + + if (listener->factory.lock) { + pj_lock_destroy(listener->factory.lock); + listener->factory.lock = NULL; + } + + if (listener->factory.pool) { + pj_pool_t *pool = listener->factory.pool; + + PJ_LOG(4,(listener->factory.obj_name, "SIP TCP listener destroyed")); + + listener->factory.pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + + +/***************************************************************************/ +/* + * TCP Transport + */ + +/* + * Prototypes. + */ +/* Called by transport manager to send message */ +static pj_status_t tcp_send_msg(pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, + int addr_len, + void *token, + pjsip_transport_callback callback); + +/* Called by transport manager to shutdown */ +static pj_status_t tcp_shutdown(pjsip_transport *transport); + +/* Called by transport manager to destroy transport */ +static pj_status_t tcp_destroy_transport(pjsip_transport *transport); + +/* Utility to destroy transport */ +static pj_status_t tcp_destroy(pjsip_transport *transport, + pj_status_t reason); + +/* Callback on incoming data */ +static pj_bool_t on_data_read(pj_activesock_t *asock, + void *data, + pj_size_t size, + pj_status_t status, + pj_size_t *remainder); + +/* Callback when packet is sent */ +static pj_bool_t on_data_sent(pj_activesock_t *asock, + pj_ioqueue_op_key_t *send_key, + pj_ssize_t sent); + +/* Callback when connect completes */ +static pj_bool_t on_connect_complete(pj_activesock_t *asock, + pj_status_t status); + +/* TCP keep-alive timer callback */ +static void tcp_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e); + +/* + * Common function to create TCP transport, called when pending accept() and + * pending connect() complete. + */ +static pj_status_t tcp_create( struct tcp_listener *listener, + pj_pool_t *pool, + pj_sock_t sock, pj_bool_t is_server, + const pj_sockaddr_in *local, + const pj_sockaddr_in *remote, + struct tcp_transport **p_tcp) +{ + struct tcp_transport *tcp; + pj_ioqueue_t *ioqueue; + pj_activesock_cfg asock_cfg; + pj_activesock_cb tcp_callback; + const pj_str_t ka_pkt = PJSIP_TCP_KEEP_ALIVE_DATA; + pj_status_t status; + + + PJ_ASSERT_RETURN(sock != PJ_INVALID_SOCKET, PJ_EINVAL); + + + if (pool == NULL) { + pool = pjsip_endpt_create_pool(listener->endpt, "tcp", + POOL_TP_INIT, POOL_TP_INC); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + } + + /* + * Create and initialize basic transport structure. + */ + tcp = PJ_POOL_ZALLOC_T(pool, struct tcp_transport); + tcp->is_server = is_server; + tcp->sock = sock; + /*tcp->listener = listener;*/ + pj_list_init(&tcp->delayed_list); + tcp->base.pool = pool; + + pj_ansi_snprintf(tcp->base.obj_name, PJ_MAX_OBJ_NAME, + (is_server ? "tcps%p" :"tcpc%p"), tcp); + + status = pj_atomic_create(pool, 0, &tcp->base.ref_cnt); + if (status != PJ_SUCCESS) { + goto on_error; + } + + status = pj_lock_create_recursive_mutex(pool, "tcp", &tcp->base.lock); + if (status != PJ_SUCCESS) { + goto on_error; + } + + tcp->base.key.type = PJSIP_TRANSPORT_TCP; + pj_memcpy(&tcp->base.key.rem_addr, remote, sizeof(pj_sockaddr_in)); + tcp->base.type_name = "tcp"; + tcp->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TCP); + + tcp->base.info = (char*) pj_pool_alloc(pool, 64); + pj_ansi_snprintf(tcp->base.info, 64, "TCP to %s:%d", + pj_inet_ntoa(remote->sin_addr), + (int)pj_ntohs(remote->sin_port)); + + tcp->base.addr_len = sizeof(pj_sockaddr_in); + pj_memcpy(&tcp->base.local_addr, local, sizeof(pj_sockaddr_in)); + sockaddr_to_host_port(pool, &tcp->base.local_name, local); + sockaddr_to_host_port(pool, &tcp->base.remote_name, remote); + tcp->base.dir = is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING; + + tcp->base.endpt = listener->endpt; + tcp->base.tpmgr = listener->tpmgr; + tcp->base.send_msg = &tcp_send_msg; + tcp->base.do_shutdown = &tcp_shutdown; + tcp->base.destroy = &tcp_destroy_transport; + + + /* Create active socket */ + pj_activesock_cfg_default(&asock_cfg); + asock_cfg.async_cnt = 1; + + pj_bzero(&tcp_callback, sizeof(tcp_callback)); + tcp_callback.on_data_read = &on_data_read; + tcp_callback.on_data_sent = &on_data_sent; + tcp_callback.on_connect_complete = &on_connect_complete; + + ioqueue = pjsip_endpt_get_ioqueue(listener->endpt); + status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), &asock_cfg, + ioqueue, &tcp_callback, tcp, &tcp->asock); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Register transport to transport manager */ + status = pjsip_transport_register(listener->tpmgr, &tcp->base); + if (status != PJ_SUCCESS) { + goto on_error; + } + + tcp->is_registered = PJ_TRUE; + + /* Initialize keep-alive timer */ + tcp->ka_timer.user_data = (void*)tcp; + tcp->ka_timer.cb = &tcp_keep_alive_timer; + pj_ioqueue_op_key_init(&tcp->ka_op_key.key, sizeof(pj_ioqueue_op_key_t)); + pj_strdup(tcp->base.pool, &tcp->ka_pkt, &ka_pkt); + + /* Done setting up basic transport. */ + *p_tcp = tcp; + + PJ_LOG(4,(tcp->base.obj_name, "TCP %s transport created", + (tcp->is_server ? "server" : "client"))); + + return PJ_SUCCESS; + +on_error: + tcp_destroy(&tcp->base, status); + return status; +} + + +/* Flush all delayed transmision once the socket is connected. */ +static void tcp_flush_pending_tx(struct tcp_transport *tcp) +{ + pj_lock_acquire(tcp->base.lock); + while (!pj_list_empty(&tcp->delayed_list)) { + struct delayed_tdata *pending_tx; + pjsip_tx_data *tdata; + pj_ioqueue_op_key_t *op_key; + pj_ssize_t size; + pj_status_t status; + + pending_tx = tcp->delayed_list.next; + pj_list_erase(pending_tx); + + tdata = pending_tx->tdata_op_key->tdata; + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + /* send! */ + size = tdata->buf.cur - tdata->buf.start; + status = pj_activesock_send(tcp->asock, op_key, tdata->buf.start, + &size, 0); + if (status != PJ_EPENDING) { + on_data_sent(tcp->asock, op_key, size); + } + + } + pj_lock_release(tcp->base.lock); +} + + +/* Called by transport manager to destroy transport */ +static pj_status_t tcp_destroy_transport(pjsip_transport *transport) +{ + struct tcp_transport *tcp = (struct tcp_transport*)transport; + + /* Transport would have been unregistered by now since this callback + * is called by transport manager. + */ + tcp->is_registered = PJ_FALSE; + + return tcp_destroy(transport, tcp->close_reason); +} + + +/* Destroy TCP transport */ +static pj_status_t tcp_destroy(pjsip_transport *transport, + pj_status_t reason) +{ + struct tcp_transport *tcp = (struct tcp_transport*)transport; + + if (tcp->close_reason == 0) + tcp->close_reason = reason; + + if (tcp->is_registered) { + tcp->is_registered = PJ_FALSE; + pjsip_transport_destroy(transport); + + /* pjsip_transport_destroy will recursively call this function + * again. + */ + return PJ_SUCCESS; + } + + /* Mark transport as closing */ + tcp->is_closing = PJ_TRUE; + + /* Stop keep-alive timer. */ + if (tcp->ka_timer.id) { + pjsip_endpt_cancel_timer(tcp->base.endpt, &tcp->ka_timer); + tcp->ka_timer.id = PJ_FALSE; + } + + /* Cancel all delayed transmits */ + while (!pj_list_empty(&tcp->delayed_list)) { + struct delayed_tdata *pending_tx; + pj_ioqueue_op_key_t *op_key; + + pending_tx = tcp->delayed_list.next; + pj_list_erase(pending_tx); + + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + on_data_sent(tcp->asock, op_key, -reason); + } + + if (tcp->rdata.tp_info.pool) { + pj_pool_release(tcp->rdata.tp_info.pool); + tcp->rdata.tp_info.pool = NULL; + } + + if (tcp->asock) { + pj_activesock_close(tcp->asock); + tcp->asock = NULL; + tcp->sock = PJ_INVALID_SOCKET; + } else if (tcp->sock != PJ_INVALID_SOCKET) { + pj_sock_close(tcp->sock); + tcp->sock = PJ_INVALID_SOCKET; + } + + if (tcp->base.lock) { + pj_lock_destroy(tcp->base.lock); + tcp->base.lock = NULL; + } + + if (tcp->base.ref_cnt) { + pj_atomic_destroy(tcp->base.ref_cnt); + tcp->base.ref_cnt = NULL; + } + + if (tcp->base.pool) { + pj_pool_t *pool; + + if (reason != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(reason, errmsg, sizeof(errmsg)); + PJ_LOG(4,(tcp->base.obj_name, + "TCP transport destroyed with reason %d: %s", + reason, errmsg)); + + } else { + + PJ_LOG(4,(tcp->base.obj_name, + "TCP transport destroyed normally")); + + } + + pool = tcp->base.pool; + tcp->base.pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + + +/* + * This utility function creates receive data buffers and start + * asynchronous recv() operations from the socket. It is called after + * accept() or connect() operation complete. + */ +static pj_status_t tcp_start_read(struct tcp_transport *tcp) +{ + pj_pool_t *pool; + pj_ssize_t size; + pj_sockaddr_in *rem_addr; + void *readbuf[1]; + pj_status_t status; + + /* Init rdata */ + pool = pjsip_endpt_create_pool(tcp->base.endpt, + "rtd%p", + PJSIP_POOL_RDATA_LEN, + PJSIP_POOL_RDATA_INC); + if (!pool) { + tcp_perror(tcp->base.obj_name, "Unable to create pool", PJ_ENOMEM); + return PJ_ENOMEM; + } + + tcp->rdata.tp_info.pool = pool; + + tcp->rdata.tp_info.transport = &tcp->base; + tcp->rdata.tp_info.tp_data = tcp; + tcp->rdata.tp_info.op_key.rdata = &tcp->rdata; + pj_ioqueue_op_key_init(&tcp->rdata.tp_info.op_key.op_key, + sizeof(pj_ioqueue_op_key_t)); + + tcp->rdata.pkt_info.src_addr = tcp->base.key.rem_addr; + tcp->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in); + rem_addr = (pj_sockaddr_in*) &tcp->base.key.rem_addr; + pj_ansi_strcpy(tcp->rdata.pkt_info.src_name, + pj_inet_ntoa(rem_addr->sin_addr)); + tcp->rdata.pkt_info.src_port = pj_ntohs(rem_addr->sin_port); + + size = sizeof(tcp->rdata.pkt_info.packet); + readbuf[0] = tcp->rdata.pkt_info.packet; + status = pj_activesock_start_read2(tcp->asock, tcp->base.pool, size, + readbuf, 0); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + PJ_LOG(4, (tcp->base.obj_name, + "pj_activesock_start_read() error, status=%d", + status)); + return status; + } + + return PJ_SUCCESS; +} + + +/* This callback is called by transport manager for the TCP factory + * to create outgoing transport to the specified destination. + */ +static pj_status_t lis_create_transport(pjsip_tpfactory *factory, + pjsip_tpmgr *mgr, + pjsip_endpoint *endpt, + const pj_sockaddr *rem_addr, + int addr_len, + pjsip_transport **p_transport) +{ + struct tcp_listener *listener; + struct tcp_transport *tcp; + pj_sock_t sock; + pj_sockaddr_in local_addr; + pj_status_t status; + + /* Sanity checks */ + PJ_ASSERT_RETURN(factory && mgr && endpt && rem_addr && + addr_len && p_transport, PJ_EINVAL); + + /* Check that address is a sockaddr_in */ + PJ_ASSERT_RETURN(rem_addr->addr.sa_family == pj_AF_INET() && + addr_len == sizeof(pj_sockaddr_in), PJ_EINVAL); + + + listener = (struct tcp_listener*)factory; + + + /* Create socket */ + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock); + if (status != PJ_SUCCESS) + return status; + + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(sock, listener->qos_type, + &listener->qos_params, + 2, listener->factory.obj_name, + "outgoing SIP TCP socket"); + + /* Bind to any port */ + status = pj_sock_bind_in(sock, 0, 0); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + /* Get the local port */ + addr_len = sizeof(pj_sockaddr_in); + status = pj_sock_getsockname(sock, &local_addr, &addr_len); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + /* Initially set the address from the listener's address */ + local_addr.sin_addr.s_addr = + ((pj_sockaddr_in*)&listener->factory.local_addr)->sin_addr.s_addr; + + /* Create the transport descriptor */ + status = tcp_create(listener, NULL, sock, PJ_FALSE, &local_addr, + (pj_sockaddr_in*)rem_addr, &tcp); + if (status != PJ_SUCCESS) + return status; + + + /* Start asynchronous connect() operation */ + tcp->has_pending_connect = PJ_TRUE; + status = pj_activesock_start_connect(tcp->asock, tcp->base.pool, rem_addr, + sizeof(pj_sockaddr_in)); + if (status == PJ_SUCCESS) { + on_connect_complete(tcp->asock, PJ_SUCCESS); + } else if (status != PJ_EPENDING) { + tcp_destroy(&tcp->base, status); + return status; + } + + if (tcp->has_pending_connect) { + /* Update (again) local address, just in case local address currently + * set is different now that asynchronous connect() is started. + */ + addr_len = sizeof(pj_sockaddr_in); + if (pj_sock_getsockname(sock, &local_addr, &addr_len)==PJ_SUCCESS) { + pj_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tcp->base.local_addr; + + /* Some systems (like old Win32 perhaps) may not set local address + * properly before socket is fully connected. + */ + if (tp_addr->sin_addr.s_addr != local_addr.sin_addr.s_addr && + local_addr.sin_addr.s_addr != 0) + { + tp_addr->sin_addr.s_addr = local_addr.sin_addr.s_addr; + tp_addr->sin_port = local_addr.sin_port; + sockaddr_to_host_port(tcp->base.pool, &tcp->base.local_name, + &local_addr); + } + } + + PJ_LOG(4,(tcp->base.obj_name, + "TCP transport %.*s:%d is connecting to %.*s:%d...", + (int)tcp->base.local_name.host.slen, + tcp->base.local_name.host.ptr, + tcp->base.local_name.port, + (int)tcp->base.remote_name.host.slen, + tcp->base.remote_name.host.ptr, + tcp->base.remote_name.port)); + } + + /* Done */ + *p_transport = &tcp->base; + + return PJ_SUCCESS; +} + + +/* + * This callback is called by active socket when pending accept() operation + * has completed. + */ +static pj_bool_t on_accept_complete(pj_activesock_t *asock, + pj_sock_t sock, + const pj_sockaddr_t *src_addr, + int src_addr_len) +{ + struct tcp_listener *listener; + struct tcp_transport *tcp; + char addr[PJ_INET6_ADDRSTRLEN+10]; + pjsip_tp_state_callback state_cb; + pj_status_t status; + + PJ_UNUSED_ARG(src_addr_len); + + listener = (struct tcp_listener*) pj_activesock_get_user_data(asock); + + PJ_ASSERT_RETURN(sock != PJ_INVALID_SOCKET, PJ_TRUE); + + PJ_LOG(4,(listener->factory.obj_name, + "TCP listener %.*s:%d: got incoming TCP connection " + "from %s, sock=%d", + (int)listener->factory.addr_name.host.slen, + listener->factory.addr_name.host.ptr, + listener->factory.addr_name.port, + pj_sockaddr_print(src_addr, addr, sizeof(addr), 3), + sock)); + + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(sock, listener->qos_type, + &listener->qos_params, + 2, listener->factory.obj_name, + "incoming SIP TCP socket"); + + /* + * Incoming connection! + * Create TCP transport for the new socket. + */ + status = tcp_create( listener, NULL, sock, PJ_TRUE, + (const pj_sockaddr_in*)&listener->factory.local_addr, + (const pj_sockaddr_in*)src_addr, &tcp); + if (status == PJ_SUCCESS) { + status = tcp_start_read(tcp); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(tcp->base.obj_name, "New transport cancelled")); + tcp_destroy(&tcp->base, status); + } else { + /* Start keep-alive timer */ + if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) { + pj_time_val delay = {PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0}; + pjsip_endpt_schedule_timer(listener->endpt, + &tcp->ka_timer, + &delay); + tcp->ka_timer.id = PJ_TRUE; + pj_gettimeofday(&tcp->last_activity); + } + + /* Notify application of transport state accepted */ + state_cb = pjsip_tpmgr_get_state_cb(tcp->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + + pj_bzero(&state_info, sizeof(state_info)); + (*state_cb)(&tcp->base, PJSIP_TP_STATE_CONNECTED, &state_info); + } + } + } + + return PJ_TRUE; +} + + +/* + * Callback from ioqueue when packet is sent. + */ +static pj_bool_t on_data_sent(pj_activesock_t *asock, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_sent) +{ + struct tcp_transport *tcp = (struct tcp_transport*) + pj_activesock_get_user_data(asock); + pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key; + + /* Note that op_key may be the op_key from keep-alive, thus + * it will not have tdata etc. + */ + + tdata_op_key->tdata = NULL; + + if (tdata_op_key->callback) { + /* + * Notify sip_transport.c that packet has been sent. + */ + if (bytes_sent == 0) + bytes_sent = -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN); + + tdata_op_key->callback(&tcp->base, tdata_op_key->token, bytes_sent); + + /* Mark last activity time */ + pj_gettimeofday(&tcp->last_activity); + + } + + /* Check for error/closure */ + if (bytes_sent <= 0) { + pj_status_t status; + + PJ_LOG(5,(tcp->base.obj_name, "TCP send() error, sent=%d", + bytes_sent)); + + status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) : + -bytes_sent; + + tcp_init_shutdown(tcp, status); + + return PJ_FALSE; + } + + return PJ_TRUE; +} + + +/* + * This callback is called by transport manager to send SIP message + */ +static pj_status_t tcp_send_msg(pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, + int addr_len, + void *token, + pjsip_transport_callback callback) +{ + struct tcp_transport *tcp = (struct tcp_transport*)transport; + pj_ssize_t size; + pj_bool_t delayed = PJ_FALSE; + pj_status_t status = PJ_SUCCESS; + + /* Sanity check */ + PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL); + + /* Check that there's no pending operation associated with the tdata */ + PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX); + + /* Check the address is supported */ + PJ_ASSERT_RETURN(rem_addr && addr_len==sizeof(pj_sockaddr_in), PJ_EINVAL); + + + + /* Init op key. */ + tdata->op_key.tdata = tdata; + tdata->op_key.token = token; + tdata->op_key.callback = callback; + + /* If asynchronous connect() has not completed yet, just put the + * transmit data in the pending transmission list since we can not + * use the socket yet. + */ + if (tcp->has_pending_connect) { + + /* + * Looks like connect() is still in progress. Check again (this time + * with holding the lock) to be sure. + */ + pj_lock_acquire(tcp->base.lock); + + if (tcp->has_pending_connect) { + struct delayed_tdata *delayed_tdata; + + /* + * connect() is still in progress. Put the transmit data to + * the delayed list. + */ + delayed_tdata = PJ_POOL_ALLOC_T(tdata->pool, + struct delayed_tdata); + delayed_tdata->tdata_op_key = &tdata->op_key; + + pj_list_push_back(&tcp->delayed_list, delayed_tdata); + status = PJ_EPENDING; + + /* Prevent pj_ioqueue_send() to be called below */ + delayed = PJ_TRUE; + } + + pj_lock_release(tcp->base.lock); + } + + if (!delayed) { + /* + * Transport is ready to go. Send the packet to ioqueue to be + * sent asynchronously. + */ + size = tdata->buf.cur - tdata->buf.start; + status = pj_activesock_send(tcp->asock, + (pj_ioqueue_op_key_t*)&tdata->op_key, + tdata->buf.start, &size, 0); + + if (status != PJ_EPENDING) { + /* Not pending (could be immediate success or error) */ + tdata->op_key.tdata = NULL; + + /* Shutdown transport on closure/errors */ + if (size <= 0) { + + PJ_LOG(5,(tcp->base.obj_name, "TCP send() error, sent=%d", + size)); + + if (status == PJ_SUCCESS) + status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN); + + tcp_init_shutdown(tcp, status); + } + } + } + + return status; +} + + +/* + * This callback is called by transport manager to shutdown transport. + */ +static pj_status_t tcp_shutdown(pjsip_transport *transport) +{ + struct tcp_transport *tcp = (struct tcp_transport*)transport; + + /* Stop keep-alive timer. */ + if (tcp->ka_timer.id) { + pjsip_endpt_cancel_timer(tcp->base.endpt, &tcp->ka_timer); + tcp->ka_timer.id = PJ_FALSE; + } + + return PJ_SUCCESS; +} + + +/* + * Callback from ioqueue that an incoming data is received from the socket. + */ +static pj_bool_t on_data_read(pj_activesock_t *asock, + void *data, + pj_size_t size, + pj_status_t status, + pj_size_t *remainder) +{ + enum { MAX_IMMEDIATE_PACKET = 10 }; + struct tcp_transport *tcp; + pjsip_rx_data *rdata; + + PJ_UNUSED_ARG(data); + + tcp = (struct tcp_transport*) pj_activesock_get_user_data(asock); + rdata = &tcp->rdata; + + /* Don't do anything if transport is closing. */ + if (tcp->is_closing) { + tcp->is_closing++; + return PJ_FALSE; + } + + /* Houston, we have packet! Report the packet to transport manager + * to be parsed. + */ + if (status == PJ_SUCCESS) { + pj_size_t size_eaten; + + /* Mark this as an activity */ + pj_gettimeofday(&tcp->last_activity); + + pj_assert((void*)rdata->pkt_info.packet == data); + + /* Init pkt_info part. */ + rdata->pkt_info.len = size; + rdata->pkt_info.zero = 0; + pj_gettimeofday(&rdata->pkt_info.timestamp); + + /* Report to transport manager. + * The transport manager will tell us how many bytes of the packet + * have been processed (as valid SIP message). + */ + size_eaten = + pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr, + rdata); + + pj_assert(size_eaten <= (pj_size_t)rdata->pkt_info.len); + + /* Move unprocessed data to the front of the buffer */ + *remainder = size - size_eaten; + if (*remainder > 0 && *remainder != size) { + pj_memmove(rdata->pkt_info.packet, + rdata->pkt_info.packet + size_eaten, + *remainder); + } + + } else { + + /* Transport is closed */ + PJ_LOG(4,(tcp->base.obj_name, "TCP connection closed")); + + tcp_init_shutdown(tcp, status); + + return PJ_FALSE; + + } + + /* Reset pool. */ + pj_pool_reset(rdata->tp_info.pool); + + return PJ_TRUE; +} + + +/* + * Callback from ioqueue when asynchronous connect() operation completes. + */ +static pj_bool_t on_connect_complete(pj_activesock_t *asock, + pj_status_t status) +{ + struct tcp_transport *tcp; + pj_sockaddr_in addr; + int addrlen; + pjsip_tp_state_callback state_cb; + + tcp = (struct tcp_transport*) pj_activesock_get_user_data(asock); + + /* Mark that pending connect() operation has completed. */ + tcp->has_pending_connect = PJ_FALSE; + + /* Check connect() status */ + if (status != PJ_SUCCESS) { + + tcp_perror(tcp->base.obj_name, "TCP connect() error", status); + + /* Cancel all delayed transmits */ + while (!pj_list_empty(&tcp->delayed_list)) { + struct delayed_tdata *pending_tx; + pj_ioqueue_op_key_t *op_key; + + pending_tx = tcp->delayed_list.next; + pj_list_erase(pending_tx); + + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + on_data_sent(tcp->asock, op_key, -status); + } + + tcp_init_shutdown(tcp, status); + return PJ_FALSE; + } + + PJ_LOG(4,(tcp->base.obj_name, + "TCP transport %.*s:%d is connected to %.*s:%d", + (int)tcp->base.local_name.host.slen, + tcp->base.local_name.host.ptr, + tcp->base.local_name.port, + (int)tcp->base.remote_name.host.slen, + tcp->base.remote_name.host.ptr, + tcp->base.remote_name.port)); + + + /* Update (again) local address, just in case local address currently + * set is different now that the socket is connected (could happen + * on some systems, like old Win32 probably?). + */ + addrlen = sizeof(pj_sockaddr_in); + if (pj_sock_getsockname(tcp->sock, &addr, &addrlen)==PJ_SUCCESS) { + pj_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tcp->base.local_addr; + + if (pj_sockaddr_has_addr(&addr) && + tp_addr->sin_addr.s_addr != addr.sin_addr.s_addr) + { + tp_addr->sin_addr.s_addr = addr.sin_addr.s_addr; + tp_addr->sin_port = addr.sin_port; + sockaddr_to_host_port(tcp->base.pool, &tcp->base.local_name, + tp_addr); + } + } + + /* Start pending read */ + status = tcp_start_read(tcp); + if (status != PJ_SUCCESS) { + tcp_init_shutdown(tcp, status); + return PJ_FALSE; + } + + /* Notify application of transport state connected */ + state_cb = pjsip_tpmgr_get_state_cb(tcp->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + + pj_bzero(&state_info, sizeof(state_info)); + (*state_cb)(&tcp->base, PJSIP_TP_STATE_CONNECTED, &state_info); + } + + /* Flush all pending send operations */ + tcp_flush_pending_tx(tcp); + + /* Start keep-alive timer */ + if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) { + pj_time_val delay = { PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0 }; + pjsip_endpt_schedule_timer(tcp->base.endpt, &tcp->ka_timer, + &delay); + tcp->ka_timer.id = PJ_TRUE; + pj_gettimeofday(&tcp->last_activity); + } + + return PJ_TRUE; +} + +/* Transport keep-alive timer callback */ +static void tcp_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e) +{ + struct tcp_transport *tcp = (struct tcp_transport*) e->user_data; + pj_time_val delay; + pj_time_val now; + pj_ssize_t size; + pj_status_t status; + + PJ_UNUSED_ARG(th); + + tcp->ka_timer.id = PJ_TRUE; + + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, tcp->last_activity); + + if (now.sec > 0 && now.sec < PJSIP_TCP_KEEP_ALIVE_INTERVAL) { + /* There has been activity, so don't send keep-alive */ + delay.sec = PJSIP_TCP_KEEP_ALIVE_INTERVAL - now.sec; + delay.msec = 0; + + pjsip_endpt_schedule_timer(tcp->base.endpt, &tcp->ka_timer, + &delay); + tcp->ka_timer.id = PJ_TRUE; + return; + } + + PJ_LOG(5,(tcp->base.obj_name, "Sending %d byte(s) keep-alive to %.*s:%d", + (int)tcp->ka_pkt.slen, (int)tcp->base.remote_name.host.slen, + tcp->base.remote_name.host.ptr, + tcp->base.remote_name.port)); + + /* Send the data */ + size = tcp->ka_pkt.slen; + status = pj_activesock_send(tcp->asock, &tcp->ka_op_key.key, + tcp->ka_pkt.ptr, &size, 0); + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + tcp_perror(tcp->base.obj_name, + "Error sending keep-alive packet", status); + tcp_init_shutdown(tcp, status); + return; + } + + /* Register next keep-alive */ + delay.sec = PJSIP_TCP_KEEP_ALIVE_INTERVAL; + delay.msec = 0; + + pjsip_endpt_schedule_timer(tcp->base.endpt, &tcp->ka_timer, + &delay); + tcp->ka_timer.id = PJ_TRUE; +} + + +#endif /* PJ_HAS_TCP */ + diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c new file mode 100644 index 0000000..878b6db --- /dev/null +++ b/pjsip/src/pjsip/sip_transport_tls.c @@ -0,0 +1,1610 @@ +/* $Id: sip_transport_tls.c 4146 2012-05-30 06:35:59Z nanang $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <pjsip/sip_transport_tls.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pj/compat/socket.h> +#include <pj/addr_resolv.h> +#include <pj/ssl_sock.h> +#include <pj/assert.h> +#include <pj/hash.h> +#include <pj/lock.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/string.h> + +#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0 + +#define THIS_FILE "sip_transport_tls.c" + +#define MAX_ASYNC_CNT 16 +#define POOL_LIS_INIT 512 +#define POOL_LIS_INC 512 +#define POOL_TP_INIT 512 +#define POOL_TP_INC 512 + +struct tls_listener; +struct tls_transport; + +/* + * Definition of TLS/SSL transport listener, and it's descendant of + * pjsip_tpfactory. + */ +struct tls_listener +{ + pjsip_tpfactory factory; + pj_bool_t is_registered; + pjsip_endpoint *endpt; + pjsip_tpmgr *tpmgr; + pj_ssl_sock_t *ssock; + pj_ssl_cert_t *cert; + pjsip_tls_setting tls_setting; +}; + + +/* + * This structure is used to keep delayed transmit operation in a list. + * A delayed transmission occurs when application sends tx_data when + * the TLS connect/establishment is still in progress. These delayed + * transmission will be "flushed" once the socket is connected (either + * successfully or with errors). + */ +struct delayed_tdata +{ + PJ_DECL_LIST_MEMBER(struct delayed_tdata); + pjsip_tx_data_op_key *tdata_op_key; +}; + + +/* + * TLS/SSL transport, and it's descendant of pjsip_transport. + */ +struct tls_transport +{ + pjsip_transport base; + pj_bool_t is_server; + pj_str_t remote_name; + + pj_bool_t is_registered; + pj_bool_t is_closing; + pj_status_t close_reason; + pj_ssl_sock_t *ssock; + pj_bool_t has_pending_connect; + pj_bool_t verify_server; + + /* Keep-alive timer. */ + pj_timer_entry ka_timer; + pj_time_val last_activity; + pjsip_tx_data_op_key ka_op_key; + pj_str_t ka_pkt; + + /* TLS transport can only have one rdata! + * Otherwise chunks of incoming PDU may be received on different + * buffer. + */ + pjsip_rx_data rdata; + + /* Pending transmission list. */ + struct delayed_tdata delayed_list; +}; + + +/**************************************************************************** + * PROTOTYPES + */ + +/* This callback is called when pending accept() operation completes. */ +static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock, + pj_ssl_sock_t *new_ssock, + const pj_sockaddr_t *src_addr, + int src_addr_len); + +/* Callback on incoming data */ +static pj_bool_t on_data_read(pj_ssl_sock_t *ssock, + void *data, + pj_size_t size, + pj_status_t status, + pj_size_t *remainder); + +/* Callback when packet is sent */ +static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock, + pj_ioqueue_op_key_t *send_key, + pj_ssize_t sent); + +/* This callback is called by transport manager to destroy listener */ +static pj_status_t lis_destroy(pjsip_tpfactory *factory); + +/* This callback is called by transport manager to create transport */ +static pj_status_t lis_create_transport(pjsip_tpfactory *factory, + pjsip_tpmgr *mgr, + pjsip_endpoint *endpt, + const pj_sockaddr *rem_addr, + int addr_len, + pjsip_tx_data *tdata, + pjsip_transport **transport); + +/* Common function to create and initialize transport */ +static pj_status_t tls_create(struct tls_listener *listener, + pj_pool_t *pool, + pj_ssl_sock_t *ssock, + pj_bool_t is_server, + const pj_sockaddr_in *local, + const pj_sockaddr_in *remote, + const pj_str_t *remote_name, + struct tls_transport **p_tls); + + +static void tls_perror(const char *sender, const char *title, + pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + + PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status)); +} + + +static void sockaddr_to_host_port( pj_pool_t *pool, + pjsip_host_port *host_port, + const pj_sockaddr_in *addr ) +{ + host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4); + pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 2); + host_port->host.slen = pj_ansi_strlen(host_port->host.ptr); + host_port->port = pj_sockaddr_get_port(addr); +} + + +static void tls_init_shutdown(struct tls_transport *tls, pj_status_t status) +{ + pjsip_tp_state_callback state_cb; + + if (tls->close_reason == PJ_SUCCESS) + tls->close_reason = status; + + if (tls->base.is_shutdown) + return; + + /* Prevent immediate transport destroy by application, as transport + * state notification callback may be stacked and transport instance + * must remain valid at any point in the callback. + */ + pjsip_transport_add_ref(&tls->base); + + /* Notify application of transport disconnected state */ + state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + pjsip_tls_state_info tls_info; + pj_ssl_sock_info ssl_info; + + /* Init transport state info */ + pj_bzero(&state_info, sizeof(state_info)); + state_info.status = tls->close_reason; + + if (tls->ssock && + pj_ssl_sock_get_info(tls->ssock, &ssl_info) == PJ_SUCCESS) + { + pj_bzero(&tls_info, sizeof(tls_info)); + tls_info.ssl_sock_info = &ssl_info; + state_info.ext_info = &tls_info; + } + + (*state_cb)(&tls->base, PJSIP_TP_STATE_DISCONNECTED, &state_info); + } + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + pjsip_transport_shutdown(&tls->base); + + /* Now, it is ok to destroy the transport. */ + pjsip_transport_dec_ref(&tls->base); +} + + +/**************************************************************************** + * The TLS listener/transport factory. + */ + +/* + * This is the public API to create, initialize, register, and start the + * TLS listener. + */ +PJ_DEF(pj_status_t) pjsip_tls_transport_start (pjsip_endpoint *endpt, + const pjsip_tls_setting *opt, + const pj_sockaddr_in *local, + const pjsip_host_port *a_name, + unsigned async_cnt, + pjsip_tpfactory **p_factory) +{ + pj_pool_t *pool; + struct tls_listener *listener; + pj_ssl_sock_param ssock_param; + pj_sockaddr_in *listener_addr; + pj_bool_t has_listener; + pj_status_t status; + + /* Sanity check */ + PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL); + + /* Verify that address given in a_name (if any) is valid */ + if (a_name && a_name->host.slen) { + pj_sockaddr_in tmp; + + status = pj_sockaddr_in_init(&tmp, &a_name->host, + (pj_uint16_t)a_name->port); + if (status != PJ_SUCCESS || tmp.sin_addr.s_addr == PJ_INADDR_ANY || + tmp.sin_addr.s_addr == PJ_INADDR_NONE) + { + /* Invalid address */ + return PJ_EINVAL; + } + } + + pool = pjsip_endpt_create_pool(endpt, "tlslis", POOL_LIS_INIT, + POOL_LIS_INC); + PJ_ASSERT_RETURN(pool, PJ_ENOMEM); + + listener = PJ_POOL_ZALLOC_T(pool, struct tls_listener); + listener->factory.pool = pool; + listener->factory.type = PJSIP_TRANSPORT_TLS; + listener->factory.type_name = "tls"; + listener->factory.flag = + pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS); + + pj_ansi_strcpy(listener->factory.obj_name, "tlslis"); + + if (opt) + pjsip_tls_setting_copy(pool, &listener->tls_setting, opt); + else + pjsip_tls_setting_default(&listener->tls_setting); + + status = pj_lock_create_recursive_mutex(pool, "tlslis", + &listener->factory.lock); + if (status != PJ_SUCCESS) + goto on_error; + + if (async_cnt > MAX_ASYNC_CNT) + async_cnt = MAX_ASYNC_CNT; + + /* Build SSL socket param */ + pj_ssl_sock_param_default(&ssock_param); + ssock_param.cb.on_accept_complete = &on_accept_complete; + ssock_param.cb.on_data_read = &on_data_read; + ssock_param.cb.on_data_sent = &on_data_sent; + ssock_param.async_cnt = async_cnt; + ssock_param.ioqueue = pjsip_endpt_get_ioqueue(endpt); + ssock_param.require_client_cert = listener->tls_setting.require_client_cert; + ssock_param.timeout = listener->tls_setting.timeout; + ssock_param.user_data = listener; + ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket + * due to verification error */ + if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN) + ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN; + if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN) + ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN; + ssock_param.ciphers_num = listener->tls_setting.ciphers_num; + ssock_param.ciphers = listener->tls_setting.ciphers; + ssock_param.qos_type = listener->tls_setting.qos_type; + ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error; + pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params, + sizeof(ssock_param.qos_params)); + + has_listener = PJ_FALSE; + + switch(listener->tls_setting.method) { + case PJSIP_TLSV1_METHOD: + ssock_param.proto = PJ_SSL_SOCK_PROTO_TLS1; + break; + case PJSIP_SSLV2_METHOD: + ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL2; + break; + case PJSIP_SSLV3_METHOD: + ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL3; + break; + case PJSIP_SSLV23_METHOD: + ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL23; + break; + default: + ssock_param.proto = PJ_SSL_SOCK_PROTO_DEFAULT; + break; + } + + /* Create SSL socket */ + status = pj_ssl_sock_create(pool, &ssock_param, &listener->ssock); + if (status != PJ_SUCCESS) + goto on_error; + + listener_addr = (pj_sockaddr_in*)&listener->factory.local_addr; + if (local) { + pj_sockaddr_cp((pj_sockaddr_t*)listener_addr, + (const pj_sockaddr_t*)local); + } else { + pj_sockaddr_in_init(listener_addr, NULL, 0); + } + + /* Check if certificate/CA list for SSL socket is set */ + if (listener->tls_setting.cert_file.slen || + listener->tls_setting.ca_list_file.slen) + { + status = pj_ssl_cert_load_from_files(pool, + &listener->tls_setting.ca_list_file, + &listener->tls_setting.cert_file, + &listener->tls_setting.privkey_file, + &listener->tls_setting.password, + &listener->cert); + if (status != PJ_SUCCESS) + goto on_error; + + status = pj_ssl_sock_set_certificate(listener->ssock, pool, + listener->cert); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Start accepting incoming connections. Note that some TLS/SSL backends + * may not support for SSL socket server. + */ + has_listener = PJ_FALSE; + + status = pj_ssl_sock_start_accept(listener->ssock, pool, + (pj_sockaddr_t*)listener_addr, + pj_sockaddr_get_len((pj_sockaddr_t*)listener_addr)); + if (status == PJ_SUCCESS || status == PJ_EPENDING) { + pj_ssl_sock_info info; + has_listener = PJ_TRUE; + + /* Retrieve the bound address */ + status = pj_ssl_sock_get_info(listener->ssock, &info); + if (status == PJ_SUCCESS) + pj_sockaddr_cp(listener_addr, (pj_sockaddr_t*)&info.local_addr); + } else if (status != PJ_ENOTSUP) { + goto on_error; + } + + /* If published host/IP is specified, then use that address as the + * listener advertised address. + */ + if (a_name && a_name->host.slen) { + /* Copy the address */ + listener->factory.addr_name = *a_name; + pj_strdup(listener->factory.pool, &listener->factory.addr_name.host, + &a_name->host); + listener->factory.addr_name.port = a_name->port; + + } else { + /* No published address is given, use the bound address */ + + /* If the address returns 0.0.0.0, use the default + * interface address as the transport's address. + */ + if (listener_addr->sin_addr.s_addr == 0) { + pj_sockaddr hostip; + + status = pj_gethostip(pj_AF_INET(), &hostip); + if (status != PJ_SUCCESS) + goto on_error; + + listener_addr->sin_addr.s_addr = hostip.ipv4.sin_addr.s_addr; + } + + /* Save the address name */ + sockaddr_to_host_port(listener->factory.pool, + &listener->factory.addr_name, listener_addr); + } + + /* If port is zero, get the bound port */ + if (listener->factory.addr_name.port == 0) { + listener->factory.addr_name.port = pj_ntohs(listener_addr->sin_port); + } + + pj_ansi_snprintf(listener->factory.obj_name, + sizeof(listener->factory.obj_name), + "tlslis:%d", listener->factory.addr_name.port); + + /* Register to transport manager */ + listener->endpt = endpt; + listener->tpmgr = pjsip_endpt_get_tpmgr(endpt); + listener->factory.create_transport2 = lis_create_transport; + listener->factory.destroy = lis_destroy; + listener->is_registered = PJ_TRUE; + status = pjsip_tpmgr_register_tpfactory(listener->tpmgr, + &listener->factory); + if (status != PJ_SUCCESS) { + listener->is_registered = PJ_FALSE; + goto on_error; + } + + if (has_listener) { + PJ_LOG(4,(listener->factory.obj_name, + "SIP TLS listener is ready for incoming connections " + "at %.*s:%d", + (int)listener->factory.addr_name.host.slen, + listener->factory.addr_name.host.ptr, + listener->factory.addr_name.port)); + } else { + PJ_LOG(4,(listener->factory.obj_name, "SIP TLS is ready " + "(client only)")); + } + + /* Return the pointer to user */ + if (p_factory) *p_factory = &listener->factory; + + return PJ_SUCCESS; + +on_error: + lis_destroy(&listener->factory); + return status; +} + + +/* This callback is called by transport manager to destroy listener */ +static pj_status_t lis_destroy(pjsip_tpfactory *factory) +{ + struct tls_listener *listener = (struct tls_listener *)factory; + + if (listener->is_registered) { + pjsip_tpmgr_unregister_tpfactory(listener->tpmgr, &listener->factory); + listener->is_registered = PJ_FALSE; + } + + if (listener->ssock) { + pj_ssl_sock_close(listener->ssock); + listener->ssock = NULL; + } + + if (listener->factory.lock) { + pj_lock_destroy(listener->factory.lock); + listener->factory.lock = NULL; + } + + if (listener->factory.pool) { + pj_pool_t *pool = listener->factory.pool; + + PJ_LOG(4,(listener->factory.obj_name, "SIP TLS listener destroyed")); + + listener->factory.pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + + +/***************************************************************************/ +/* + * TLS Transport + */ + +/* + * Prototypes. + */ +/* Called by transport manager to send message */ +static pj_status_t tls_send_msg(pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, + int addr_len, + void *token, + pjsip_transport_callback callback); + +/* Called by transport manager to shutdown */ +static pj_status_t tls_shutdown(pjsip_transport *transport); + +/* Called by transport manager to destroy transport */ +static pj_status_t tls_destroy_transport(pjsip_transport *transport); + +/* Utility to destroy transport */ +static pj_status_t tls_destroy(pjsip_transport *transport, + pj_status_t reason); + +/* Callback when connect completes */ +static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock, + pj_status_t status); + +/* TLS keep-alive timer callback */ +static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e); + +/* + * Common function to create TLS transport, called when pending accept() and + * pending connect() complete. + */ +static pj_status_t tls_create( struct tls_listener *listener, + pj_pool_t *pool, + pj_ssl_sock_t *ssock, + pj_bool_t is_server, + const pj_sockaddr_in *local, + const pj_sockaddr_in *remote, + const pj_str_t *remote_name, + struct tls_transport **p_tls) +{ + struct tls_transport *tls; + const pj_str_t ka_pkt = PJSIP_TLS_KEEP_ALIVE_DATA; + pj_status_t status; + + + PJ_ASSERT_RETURN(listener && ssock && local && remote && p_tls, PJ_EINVAL); + + + if (pool == NULL) { + pool = pjsip_endpt_create_pool(listener->endpt, "tls", + POOL_TP_INIT, POOL_TP_INC); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + } + + /* + * Create and initialize basic transport structure. + */ + tls = PJ_POOL_ZALLOC_T(pool, struct tls_transport); + tls->is_server = is_server; + tls->verify_server = listener->tls_setting.verify_server; + pj_list_init(&tls->delayed_list); + tls->base.pool = pool; + + pj_ansi_snprintf(tls->base.obj_name, PJ_MAX_OBJ_NAME, + (is_server ? "tlss%p" :"tlsc%p"), tls); + + status = pj_atomic_create(pool, 0, &tls->base.ref_cnt); + if (status != PJ_SUCCESS) { + goto on_error; + } + + status = pj_lock_create_recursive_mutex(pool, "tls", &tls->base.lock); + if (status != PJ_SUCCESS) { + goto on_error; + } + + if (remote_name) + pj_strdup(pool, &tls->remote_name, remote_name); + + tls->base.key.type = PJSIP_TRANSPORT_TLS; + pj_memcpy(&tls->base.key.rem_addr, remote, sizeof(pj_sockaddr_in)); + tls->base.type_name = "tls"; + tls->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS); + + tls->base.info = (char*) pj_pool_alloc(pool, 64); + pj_ansi_snprintf(tls->base.info, 64, "TLS to %s:%d", + pj_inet_ntoa(remote->sin_addr), + (int)pj_ntohs(remote->sin_port)); + + tls->base.addr_len = sizeof(pj_sockaddr_in); + tls->base.dir = is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING; + + /* Set initial local address */ + if (!pj_sockaddr_has_addr(local)) { + pj_sockaddr_cp(&tls->base.local_addr, + &listener->factory.local_addr); + } else { + pj_sockaddr_cp(&tls->base.local_addr, local); + } + + sockaddr_to_host_port(pool, &tls->base.local_name, + (pj_sockaddr_in*)&tls->base.local_addr); + if (tls->remote_name.slen) { + tls->base.remote_name.host = tls->remote_name; + tls->base.remote_name.port = pj_sockaddr_in_get_port(remote); + } else { + sockaddr_to_host_port(pool, &tls->base.remote_name, remote); + } + + tls->base.endpt = listener->endpt; + tls->base.tpmgr = listener->tpmgr; + tls->base.send_msg = &tls_send_msg; + tls->base.do_shutdown = &tls_shutdown; + tls->base.destroy = &tls_destroy_transport; + + tls->ssock = ssock; + + /* Register transport to transport manager */ + status = pjsip_transport_register(listener->tpmgr, &tls->base); + if (status != PJ_SUCCESS) { + goto on_error; + } + + tls->is_registered = PJ_TRUE; + + /* Initialize keep-alive timer */ + tls->ka_timer.user_data = (void*)tls; + tls->ka_timer.cb = &tls_keep_alive_timer; + pj_ioqueue_op_key_init(&tls->ka_op_key.key, sizeof(pj_ioqueue_op_key_t)); + pj_strdup(tls->base.pool, &tls->ka_pkt, &ka_pkt); + + /* Done setting up basic transport. */ + *p_tls = tls; + + PJ_LOG(4,(tls->base.obj_name, "TLS %s transport created", + (tls->is_server ? "server" : "client"))); + + return PJ_SUCCESS; + +on_error: + tls_destroy(&tls->base, status); + return status; +} + + +/* Flush all delayed transmision once the socket is connected. */ +static void tls_flush_pending_tx(struct tls_transport *tls) +{ + pj_lock_acquire(tls->base.lock); + while (!pj_list_empty(&tls->delayed_list)) { + struct delayed_tdata *pending_tx; + pjsip_tx_data *tdata; + pj_ioqueue_op_key_t *op_key; + pj_ssize_t size; + pj_status_t status; + + pending_tx = tls->delayed_list.next; + pj_list_erase(pending_tx); + + tdata = pending_tx->tdata_op_key->tdata; + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + /* send! */ + size = tdata->buf.cur - tdata->buf.start; + status = pj_ssl_sock_send(tls->ssock, op_key, tdata->buf.start, + &size, 0); + + if (status != PJ_EPENDING) { + on_data_sent(tls->ssock, op_key, size); + } + } + pj_lock_release(tls->base.lock); +} + + +/* Called by transport manager to destroy transport */ +static pj_status_t tls_destroy_transport(pjsip_transport *transport) +{ + struct tls_transport *tls = (struct tls_transport*)transport; + + /* Transport would have been unregistered by now since this callback + * is called by transport manager. + */ + tls->is_registered = PJ_FALSE; + + return tls_destroy(transport, tls->close_reason); +} + + +/* Destroy TLS transport */ +static pj_status_t tls_destroy(pjsip_transport *transport, + pj_status_t reason) +{ + struct tls_transport *tls = (struct tls_transport*)transport; + + if (tls->close_reason == 0) + tls->close_reason = reason; + + if (tls->is_registered) { + tls->is_registered = PJ_FALSE; + pjsip_transport_destroy(transport); + + /* pjsip_transport_destroy will recursively call this function + * again. + */ + return PJ_SUCCESS; + } + + /* Mark transport as closing */ + tls->is_closing = PJ_TRUE; + + /* Stop keep-alive timer. */ + if (tls->ka_timer.id) { + pjsip_endpt_cancel_timer(tls->base.endpt, &tls->ka_timer); + tls->ka_timer.id = PJ_FALSE; + } + + /* Cancel all delayed transmits */ + while (!pj_list_empty(&tls->delayed_list)) { + struct delayed_tdata *pending_tx; + pj_ioqueue_op_key_t *op_key; + + pending_tx = tls->delayed_list.next; + pj_list_erase(pending_tx); + + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + on_data_sent(tls->ssock, op_key, -reason); + } + + if (tls->rdata.tp_info.pool) { + pj_pool_release(tls->rdata.tp_info.pool); + tls->rdata.tp_info.pool = NULL; + } + + if (tls->ssock) { + pj_ssl_sock_close(tls->ssock); + tls->ssock = NULL; + } + if (tls->base.lock) { + pj_lock_destroy(tls->base.lock); + tls->base.lock = NULL; + } + + if (tls->base.ref_cnt) { + pj_atomic_destroy(tls->base.ref_cnt); + tls->base.ref_cnt = NULL; + } + + if (tls->base.pool) { + pj_pool_t *pool; + + if (reason != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(reason, errmsg, sizeof(errmsg)); + PJ_LOG(4,(tls->base.obj_name, + "TLS transport destroyed with reason %d: %s", + reason, errmsg)); + + } else { + + PJ_LOG(4,(tls->base.obj_name, + "TLS transport destroyed normally")); + + } + + pool = tls->base.pool; + tls->base.pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + + +/* + * This utility function creates receive data buffers and start + * asynchronous recv() operations from the socket. It is called after + * accept() or connect() operation complete. + */ +static pj_status_t tls_start_read(struct tls_transport *tls) +{ + pj_pool_t *pool; + pj_ssize_t size; + pj_sockaddr_in *rem_addr; + void *readbuf[1]; + pj_status_t status; + + /* Init rdata */ + pool = pjsip_endpt_create_pool(tls->base.endpt, + "rtd%p", + PJSIP_POOL_RDATA_LEN, + PJSIP_POOL_RDATA_INC); + if (!pool) { + tls_perror(tls->base.obj_name, "Unable to create pool", PJ_ENOMEM); + return PJ_ENOMEM; + } + + tls->rdata.tp_info.pool = pool; + + tls->rdata.tp_info.transport = &tls->base; + tls->rdata.tp_info.tp_data = tls; + tls->rdata.tp_info.op_key.rdata = &tls->rdata; + pj_ioqueue_op_key_init(&tls->rdata.tp_info.op_key.op_key, + sizeof(pj_ioqueue_op_key_t)); + + tls->rdata.pkt_info.src_addr = tls->base.key.rem_addr; + tls->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in); + rem_addr = (pj_sockaddr_in*) &tls->base.key.rem_addr; + pj_ansi_strcpy(tls->rdata.pkt_info.src_name, + pj_inet_ntoa(rem_addr->sin_addr)); + tls->rdata.pkt_info.src_port = pj_ntohs(rem_addr->sin_port); + + size = sizeof(tls->rdata.pkt_info.packet); + readbuf[0] = tls->rdata.pkt_info.packet; + status = pj_ssl_sock_start_read2(tls->ssock, tls->base.pool, size, + readbuf, 0); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + PJ_LOG(4, (tls->base.obj_name, + "pj_ssl_sock_start_read() error, status=%d", + status)); + return status; + } + + return PJ_SUCCESS; +} + + +/* This callback is called by transport manager for the TLS factory + * to create outgoing transport to the specified destination. + */ +static pj_status_t lis_create_transport(pjsip_tpfactory *factory, + pjsip_tpmgr *mgr, + pjsip_endpoint *endpt, + const pj_sockaddr *rem_addr, + int addr_len, + pjsip_tx_data *tdata, + pjsip_transport **p_transport) +{ + struct tls_listener *listener; + struct tls_transport *tls; + pj_pool_t *pool; + pj_ssl_sock_t *ssock; + pj_ssl_sock_param ssock_param; + pj_sockaddr_in local_addr; + pj_str_t remote_name; + pj_status_t status; + + /* Sanity checks */ + PJ_ASSERT_RETURN(factory && mgr && endpt && rem_addr && + addr_len && p_transport, PJ_EINVAL); + + /* Check that address is a sockaddr_in */ + PJ_ASSERT_RETURN(rem_addr->addr.sa_family == pj_AF_INET() && + addr_len == sizeof(pj_sockaddr_in), PJ_EINVAL); + + + listener = (struct tls_listener*)factory; + + pool = pjsip_endpt_create_pool(listener->endpt, "tls", + POOL_TP_INIT, POOL_TP_INC); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + /* Get remote host name from tdata */ + if (tdata) + remote_name = tdata->dest_info.name; + else + pj_bzero(&remote_name, sizeof(remote_name)); + + /* Build SSL socket param */ + pj_ssl_sock_param_default(&ssock_param); + ssock_param.cb.on_connect_complete = &on_connect_complete; + ssock_param.cb.on_data_read = &on_data_read; + ssock_param.cb.on_data_sent = &on_data_sent; + ssock_param.async_cnt = 1; + ssock_param.ioqueue = pjsip_endpt_get_ioqueue(listener->endpt); + ssock_param.server_name = remote_name; + ssock_param.timeout = listener->tls_setting.timeout; + ssock_param.user_data = NULL; /* pending, must be set later */ + ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket + * due to verification error */ + if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN) + ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN; + if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN) + ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN; + ssock_param.ciphers_num = listener->tls_setting.ciphers_num; + ssock_param.ciphers = listener->tls_setting.ciphers; + ssock_param.qos_type = listener->tls_setting.qos_type; + ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error; + pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params, + sizeof(ssock_param.qos_params)); + + switch(listener->tls_setting.method) { + case PJSIP_TLSV1_METHOD: + ssock_param.proto = PJ_SSL_SOCK_PROTO_TLS1; + break; + case PJSIP_SSLV2_METHOD: + ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL2; + break; + case PJSIP_SSLV3_METHOD: + ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL3; + break; + case PJSIP_SSLV23_METHOD: + ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL23; + break; + default: + ssock_param.proto = PJ_SSL_SOCK_PROTO_DEFAULT; + break; + } + + status = pj_ssl_sock_create(pool, &ssock_param, &ssock); + if (status != PJ_SUCCESS) + return status; + + /* Apply SSL certificate */ + if (listener->cert) { + status = pj_ssl_sock_set_certificate(ssock, pool, listener->cert); + if (status != PJ_SUCCESS) + return status; + } + + /* Initially set bind address to PJ_INADDR_ANY port 0 */ + pj_sockaddr_in_init(&local_addr, NULL, 0); + + /* Create the transport descriptor */ + status = tls_create(listener, pool, ssock, PJ_FALSE, &local_addr, + (pj_sockaddr_in*)rem_addr, &remote_name, &tls); + if (status != PJ_SUCCESS) + return status; + + /* Set the "pending" SSL socket user data */ + pj_ssl_sock_set_user_data(tls->ssock, tls); + + /* Start asynchronous connect() operation */ + tls->has_pending_connect = PJ_TRUE; + status = pj_ssl_sock_start_connect(tls->ssock, tls->base.pool, + (pj_sockaddr_t*)&local_addr, + (pj_sockaddr_t*)rem_addr, + addr_len); + if (status == PJ_SUCCESS) { + on_connect_complete(tls->ssock, PJ_SUCCESS); + } else if (status != PJ_EPENDING) { + tls_destroy(&tls->base, status); + return status; + } + + if (tls->has_pending_connect) { + pj_ssl_sock_info info; + + /* Update local address, just in case local address currently set is + * different now that asynchronous connect() is started. + */ + + /* Retrieve the bound address */ + status = pj_ssl_sock_get_info(tls->ssock, &info); + if (status == PJ_SUCCESS) { + pj_uint16_t new_port; + + new_port = pj_sockaddr_get_port((pj_sockaddr_t*)&info.local_addr); + + if (pj_sockaddr_has_addr((pj_sockaddr_t*)&info.local_addr)) { + /* Update sockaddr */ + pj_sockaddr_cp((pj_sockaddr_t*)&tls->base.local_addr, + (pj_sockaddr_t*)&info.local_addr); + } else if (new_port && new_port != pj_sockaddr_get_port( + (pj_sockaddr_t*)&tls->base.local_addr)) + { + /* Update port only */ + pj_sockaddr_set_port(&tls->base.local_addr, + new_port); + } + + sockaddr_to_host_port(tls->base.pool, &tls->base.local_name, + (pj_sockaddr_in*)&tls->base.local_addr); + } + + PJ_LOG(4,(tls->base.obj_name, + "TLS transport %.*s:%d is connecting to %.*s:%d...", + (int)tls->base.local_name.host.slen, + tls->base.local_name.host.ptr, + tls->base.local_name.port, + (int)tls->base.remote_name.host.slen, + tls->base.remote_name.host.ptr, + tls->base.remote_name.port)); + } + + /* Done */ + *p_transport = &tls->base; + + return PJ_SUCCESS; +} + + +/* + * This callback is called by SSL socket when pending accept() operation + * has completed. + */ +static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock, + pj_ssl_sock_t *new_ssock, + const pj_sockaddr_t *src_addr, + int src_addr_len) +{ + struct tls_listener *listener; + struct tls_transport *tls; + pj_ssl_sock_info ssl_info; + char addr[PJ_INET6_ADDRSTRLEN+10]; + pjsip_tp_state_callback state_cb; + pj_bool_t is_shutdown; + pj_status_t status; + + PJ_UNUSED_ARG(src_addr_len); + + listener = (struct tls_listener*) pj_ssl_sock_get_user_data(ssock); + + PJ_ASSERT_RETURN(new_ssock, PJ_TRUE); + + PJ_LOG(4,(listener->factory.obj_name, + "TLS listener %.*s:%d: got incoming TLS connection " + "from %s, sock=%d", + (int)listener->factory.addr_name.host.slen, + listener->factory.addr_name.host.ptr, + listener->factory.addr_name.port, + pj_sockaddr_print(src_addr, addr, sizeof(addr), 3), + new_ssock)); + + /* Retrieve SSL socket info, close the socket if this is failed + * as the SSL socket info availability is rather critical here. + */ + status = pj_ssl_sock_get_info(new_ssock, &ssl_info); + if (status != PJ_SUCCESS) { + pj_ssl_sock_close(new_ssock); + return PJ_TRUE; + } + + /* + * Incoming connection! + * Create TLS transport for the new socket. + */ + status = tls_create( listener, NULL, new_ssock, PJ_TRUE, + (const pj_sockaddr_in*)&listener->factory.local_addr, + (const pj_sockaddr_in*)src_addr, NULL, &tls); + + if (status != PJ_SUCCESS) + return PJ_TRUE; + + /* Set the "pending" SSL socket user data */ + pj_ssl_sock_set_user_data(new_ssock, tls); + + /* Prevent immediate transport destroy as application may access it + * (getting info, etc) in transport state notification callback. + */ + pjsip_transport_add_ref(&tls->base); + + /* If there is verification error and verification is mandatory, shutdown + * and destroy the transport. + */ + if (ssl_info.verify_status && listener->tls_setting.verify_client) { + if (tls->close_reason == PJ_SUCCESS) + tls->close_reason = PJSIP_TLS_ECERTVERIF; + pjsip_transport_shutdown(&tls->base); + } + + /* Notify transport state to application */ + state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + pjsip_tls_state_info tls_info; + pjsip_transport_state tp_state; + + /* Init transport state info */ + pj_bzero(&tls_info, sizeof(tls_info)); + pj_bzero(&state_info, sizeof(state_info)); + tls_info.ssl_sock_info = &ssl_info; + state_info.ext_info = &tls_info; + + /* Set transport state based on verification status */ + if (ssl_info.verify_status && listener->tls_setting.verify_client) + { + tp_state = PJSIP_TP_STATE_DISCONNECTED; + state_info.status = PJSIP_TLS_ECERTVERIF; + } else { + tp_state = PJSIP_TP_STATE_CONNECTED; + state_info.status = PJ_SUCCESS; + } + + (*state_cb)(&tls->base, tp_state, &state_info); + } + + /* Release transport reference. If transport is shutting down, it may + * get destroyed here. + */ + is_shutdown = tls->base.is_shutdown; + pjsip_transport_dec_ref(&tls->base); + if (is_shutdown) + return PJ_TRUE; + + + status = tls_start_read(tls); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(tls->base.obj_name, "New transport cancelled")); + tls_init_shutdown(tls, status); + tls_destroy(&tls->base, status); + } else { + /* Start keep-alive timer */ + if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) { + pj_time_val delay = {PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0}; + pjsip_endpt_schedule_timer(listener->endpt, + &tls->ka_timer, + &delay); + tls->ka_timer.id = PJ_TRUE; + pj_gettimeofday(&tls->last_activity); + } + } + + return PJ_TRUE; +} + + +/* + * Callback from ioqueue when packet is sent. + */ +static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_sent) +{ + struct tls_transport *tls = (struct tls_transport*) + pj_ssl_sock_get_user_data(ssock); + pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key; + + /* Note that op_key may be the op_key from keep-alive, thus + * it will not have tdata etc. + */ + + tdata_op_key->tdata = NULL; + + if (tdata_op_key->callback) { + /* + * Notify sip_transport.c that packet has been sent. + */ + if (bytes_sent == 0) + bytes_sent = -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN); + + tdata_op_key->callback(&tls->base, tdata_op_key->token, bytes_sent); + + /* Mark last activity time */ + pj_gettimeofday(&tls->last_activity); + + } + + /* Check for error/closure */ + if (bytes_sent <= 0) { + pj_status_t status; + + PJ_LOG(5,(tls->base.obj_name, "TLS send() error, sent=%d", + bytes_sent)); + + status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) : + -bytes_sent; + + tls_init_shutdown(tls, status); + + return PJ_FALSE; + } + + return PJ_TRUE; +} + + +/* + * This callback is called by transport manager to send SIP message + */ +static pj_status_t tls_send_msg(pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, + int addr_len, + void *token, + pjsip_transport_callback callback) +{ + struct tls_transport *tls = (struct tls_transport*)transport; + pj_ssize_t size; + pj_bool_t delayed = PJ_FALSE; + pj_status_t status = PJ_SUCCESS; + + /* Sanity check */ + PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL); + + /* Check that there's no pending operation associated with the tdata */ + PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX); + + /* Check the address is supported */ + PJ_ASSERT_RETURN(rem_addr && addr_len==sizeof(pj_sockaddr_in), PJ_EINVAL); + + + + /* Init op key. */ + tdata->op_key.tdata = tdata; + tdata->op_key.token = token; + tdata->op_key.callback = callback; + + /* If asynchronous connect() has not completed yet, just put the + * transmit data in the pending transmission list since we can not + * use the socket yet. + */ + if (tls->has_pending_connect) { + + /* + * Looks like connect() is still in progress. Check again (this time + * with holding the lock) to be sure. + */ + pj_lock_acquire(tls->base.lock); + + if (tls->has_pending_connect) { + struct delayed_tdata *delayed_tdata; + + /* + * connect() is still in progress. Put the transmit data to + * the delayed list. + */ + delayed_tdata = PJ_POOL_ALLOC_T(tdata->pool, + struct delayed_tdata); + delayed_tdata->tdata_op_key = &tdata->op_key; + + pj_list_push_back(&tls->delayed_list, delayed_tdata); + status = PJ_EPENDING; + + /* Prevent pj_ioqueue_send() to be called below */ + delayed = PJ_TRUE; + } + + pj_lock_release(tls->base.lock); + } + + if (!delayed) { + /* + * Transport is ready to go. Send the packet to ioqueue to be + * sent asynchronously. + */ + size = tdata->buf.cur - tdata->buf.start; + status = pj_ssl_sock_send(tls->ssock, + (pj_ioqueue_op_key_t*)&tdata->op_key, + tdata->buf.start, &size, 0); + + if (status != PJ_EPENDING) { + /* Not pending (could be immediate success or error) */ + tdata->op_key.tdata = NULL; + + /* Shutdown transport on closure/errors */ + if (size <= 0) { + + PJ_LOG(5,(tls->base.obj_name, "TLS send() error, sent=%d", + size)); + + if (status == PJ_SUCCESS) + status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN); + + tls_init_shutdown(tls, status); + } + } + } + + return status; +} + + +/* + * This callback is called by transport manager to shutdown transport. + */ +static pj_status_t tls_shutdown(pjsip_transport *transport) +{ + struct tls_transport *tls = (struct tls_transport*)transport; + + /* Stop keep-alive timer. */ + if (tls->ka_timer.id) { + pjsip_endpt_cancel_timer(tls->base.endpt, &tls->ka_timer); + tls->ka_timer.id = PJ_FALSE; + } + + return PJ_SUCCESS; +} + + +/* + * Callback from ioqueue that an incoming data is received from the socket. + */ +static pj_bool_t on_data_read(pj_ssl_sock_t *ssock, + void *data, + pj_size_t size, + pj_status_t status, + pj_size_t *remainder) +{ + enum { MAX_IMMEDIATE_PACKET = 10 }; + struct tls_transport *tls; + pjsip_rx_data *rdata; + + PJ_UNUSED_ARG(data); + + tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock); + rdata = &tls->rdata; + + /* Don't do anything if transport is closing. */ + if (tls->is_closing) { + tls->is_closing++; + return PJ_FALSE; + } + + /* Houston, we have packet! Report the packet to transport manager + * to be parsed. + */ + if (status == PJ_SUCCESS) { + pj_size_t size_eaten; + + /* Mark this as an activity */ + pj_gettimeofday(&tls->last_activity); + + pj_assert((void*)rdata->pkt_info.packet == data); + + /* Init pkt_info part. */ + rdata->pkt_info.len = size; + rdata->pkt_info.zero = 0; + pj_gettimeofday(&rdata->pkt_info.timestamp); + + /* Report to transport manager. + * The transport manager will tell us how many bytes of the packet + * have been processed (as valid SIP message). + */ + size_eaten = + pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr, + rdata); + + pj_assert(size_eaten <= (pj_size_t)rdata->pkt_info.len); + + /* Move unprocessed data to the front of the buffer */ + *remainder = size - size_eaten; + if (*remainder > 0 && *remainder != size) { + pj_memmove(rdata->pkt_info.packet, + rdata->pkt_info.packet + size_eaten, + *remainder); + } + + } else { + + /* Transport is closed */ + PJ_LOG(4,(tls->base.obj_name, "TLS connection closed")); + + tls_init_shutdown(tls, status); + + return PJ_FALSE; + + } + + /* Reset pool. */ + pj_pool_reset(rdata->tp_info.pool); + + return PJ_TRUE; +} + + +/* + * Callback from ioqueue when asynchronous connect() operation completes. + */ +static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock, + pj_status_t status) +{ + struct tls_transport *tls; + pj_ssl_sock_info ssl_info; + pj_sockaddr_in addr, *tp_addr; + pjsip_tp_state_callback state_cb; + pj_bool_t is_shutdown; + + tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock); + + /* Check connect() status */ + if (status != PJ_SUCCESS) { + + tls_perror(tls->base.obj_name, "TLS connect() error", status); + + /* Cancel all delayed transmits */ + while (!pj_list_empty(&tls->delayed_list)) { + struct delayed_tdata *pending_tx; + pj_ioqueue_op_key_t *op_key; + + pending_tx = tls->delayed_list.next; + pj_list_erase(pending_tx); + + op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; + + on_data_sent(tls->ssock, op_key, -status); + } + + goto on_error; + } + + /* Retrieve SSL socket info, shutdown the transport if this is failed + * as the SSL socket info availability is rather critical here. + */ + status = pj_ssl_sock_get_info(tls->ssock, &ssl_info); + if (status != PJ_SUCCESS) + goto on_error; + + /* Update (again) local address, just in case local address currently + * set is different now that the socket is connected (could happen + * on some systems, like old Win32 probably?). + */ + tp_addr = (pj_sockaddr_in*)&tls->base.local_addr; + pj_sockaddr_cp((pj_sockaddr_t*)&addr, + (pj_sockaddr_t*)&ssl_info.local_addr); + if (tp_addr->sin_addr.s_addr != addr.sin_addr.s_addr) { + tp_addr->sin_addr.s_addr = addr.sin_addr.s_addr; + tp_addr->sin_port = addr.sin_port; + sockaddr_to_host_port(tls->base.pool, &tls->base.local_name, + tp_addr); + } + + /* Server identity verification based on server certificate. */ + if (ssl_info.remote_cert_info->version) { + pj_str_t *remote_name; + pj_ssl_cert_info *serv_cert = ssl_info.remote_cert_info; + pj_bool_t matched = PJ_FALSE; + unsigned i; + + /* Remote name may be hostname or IP address */ + if (tls->remote_name.slen) + remote_name = &tls->remote_name; + else + remote_name = &tls->base.remote_name.host; + + /* Start matching remote name with SubjectAltName fields of + * server certificate. + */ + for (i = 0; i < serv_cert->subj_alt_name.cnt && !matched; ++i) { + pj_str_t *cert_name = &serv_cert->subj_alt_name.entry[i].name; + + switch (serv_cert->subj_alt_name.entry[i].type) { + case PJ_SSL_CERT_NAME_DNS: + case PJ_SSL_CERT_NAME_IP: + matched = !pj_stricmp(remote_name, cert_name); + break; + case PJ_SSL_CERT_NAME_URI: + if (pj_strnicmp2(cert_name, "sip:", 4) == 0 || + pj_strnicmp2(cert_name, "sips:", 5) == 0) + { + pj_str_t host_part; + char *p; + + p = pj_strchr(cert_name, ':') + 1; + pj_strset(&host_part, p, cert_name->slen - + (p - cert_name->ptr)); + matched = !pj_stricmp(remote_name, &host_part); + } + break; + default: + break; + } + } + + /* When still not matched or no SubjectAltName fields in server + * certificate, try with Common Name of Subject field. + */ + if (!matched) { + matched = !pj_stricmp(remote_name, &serv_cert->subject.cn); + } + + if (!matched) + ssl_info.verify_status |= PJ_SSL_CERT_EIDENTITY_NOT_MATCH; + } + + /* Prevent immediate transport destroy as application may access it + * (getting info, etc) in transport state notification callback. + */ + pjsip_transport_add_ref(&tls->base); + + /* If there is verification error and verification is mandatory, shutdown + * and destroy the transport. + */ + if (ssl_info.verify_status && tls->verify_server) { + if (tls->close_reason == PJ_SUCCESS) + tls->close_reason = PJSIP_TLS_ECERTVERIF; + pjsip_transport_shutdown(&tls->base); + } + + /* Notify transport state to application */ + state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + pjsip_tls_state_info tls_info; + pjsip_transport_state tp_state; + + /* Init transport state info */ + pj_bzero(&state_info, sizeof(state_info)); + pj_bzero(&tls_info, sizeof(tls_info)); + state_info.ext_info = &tls_info; + tls_info.ssl_sock_info = &ssl_info; + + /* Set transport state based on verification status */ + if (ssl_info.verify_status && tls->verify_server) + { + tp_state = PJSIP_TP_STATE_DISCONNECTED; + state_info.status = PJSIP_TLS_ECERTVERIF; + } else { + tp_state = PJSIP_TP_STATE_CONNECTED; + state_info.status = PJ_SUCCESS; + } + + (*state_cb)(&tls->base, tp_state, &state_info); + } + + /* Release transport reference. If transport is shutting down, it may + * get destroyed here. + */ + is_shutdown = tls->base.is_shutdown; + pjsip_transport_dec_ref(&tls->base); + if (is_shutdown) + return PJ_FALSE; + + + /* Mark that pending connect() operation has completed. */ + tls->has_pending_connect = PJ_FALSE; + + PJ_LOG(4,(tls->base.obj_name, + "TLS transport %.*s:%d is connected to %.*s:%d", + (int)tls->base.local_name.host.slen, + tls->base.local_name.host.ptr, + tls->base.local_name.port, + (int)tls->base.remote_name.host.slen, + tls->base.remote_name.host.ptr, + tls->base.remote_name.port)); + + /* Start pending read */ + status = tls_start_read(tls); + if (status != PJ_SUCCESS) + goto on_error; + + /* Flush all pending send operations */ + tls_flush_pending_tx(tls); + + /* Start keep-alive timer */ + if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) { + pj_time_val delay = { PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0 }; + pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer, + &delay); + tls->ka_timer.id = PJ_TRUE; + pj_gettimeofday(&tls->last_activity); + } + + return PJ_TRUE; + +on_error: + tls_init_shutdown(tls, status); + + return PJ_FALSE; +} + + +/* Transport keep-alive timer callback */ +static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e) +{ + struct tls_transport *tls = (struct tls_transport*) e->user_data; + pj_time_val delay; + pj_time_val now; + pj_ssize_t size; + pj_status_t status; + + PJ_UNUSED_ARG(th); + + tls->ka_timer.id = PJ_TRUE; + + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, tls->last_activity); + + if (now.sec > 0 && now.sec < PJSIP_TLS_KEEP_ALIVE_INTERVAL) { + /* There has been activity, so don't send keep-alive */ + delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL - now.sec; + delay.msec = 0; + + pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer, + &delay); + tls->ka_timer.id = PJ_TRUE; + return; + } + + PJ_LOG(5,(tls->base.obj_name, "Sending %d byte(s) keep-alive to %.*s:%d", + (int)tls->ka_pkt.slen, (int)tls->base.remote_name.host.slen, + tls->base.remote_name.host.ptr, + tls->base.remote_name.port)); + + /* Send the data */ + size = tls->ka_pkt.slen; + status = pj_ssl_sock_send(tls->ssock, &tls->ka_op_key.key, + tls->ka_pkt.ptr, &size, 0); + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + tls_perror(tls->base.obj_name, + "Error sending keep-alive packet", status); + + tls_init_shutdown(tls, status); + return; + } + + /* Register next keep-alive */ + delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL; + delay.msec = 0; + + pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer, + &delay); + tls->ka_timer.id = PJ_TRUE; +} + +#endif /* PJSIP_HAS_TLS_TRANSPORT */ diff --git a/pjsip/src/pjsip/sip_transport_udp.c b/pjsip/src/pjsip/sip_transport_udp.c new file mode 100644 index 0000000..60b3175 --- /dev/null +++ b/pjsip/src/pjsip/sip_transport_udp.c @@ -0,0 +1,1095 @@ +/* $Id: sip_transport_udp.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_transport_udp.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pj/addr_resolv.h> +#include <pj/assert.h> +#include <pj/lock.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/sock.h> +#include <pj/compat/socket.h> +#include <pj/string.h> + + +#define THIS_FILE "sip_transport_udp.c" + +/** + * These are the target values for socket send and receive buffer sizes, + * respectively. They will be applied to UDP socket with setsockopt(). + * When transport failed to set these size, it will decrease it until + * sufficiently large number has been successfully set. + * + * The buffer size is important, especially in WinXP/2000 machines. + * Basicly the lower the size, the more packets will be lost (dropped?) + * when we're sending (receiving?) packets in large volumes. + * + * The figure here is taken based on my experiment on WinXP/2000 machine, + * and with this value, the rate of dropped packet is about 8% when + * sending 1800 requests simultaneously (percentage taken as average + * after 50K requests or so). + * + * More experiments are needed probably. + */ +/* 2010/01/14 + * Too many people complained about seeing "Error setting SNDBUF" log, + * so lets just remove this. People who want to have SNDBUF set can + * still do so by declaring these two macros in config_site.h + */ +#ifndef PJSIP_UDP_SO_SNDBUF_SIZE +/*# define PJSIP_UDP_SO_SNDBUF_SIZE (24*1024*1024)*/ +# define PJSIP_UDP_SO_SNDBUF_SIZE 0 +#endif + +#ifndef PJSIP_UDP_SO_RCVBUF_SIZE +/*# define PJSIP_UDP_SO_RCVBUF_SIZE (24*1024*1024)*/ +# define PJSIP_UDP_SO_RCVBUF_SIZE 0 +#endif + + +/* Struct udp_transport "inherits" struct pjsip_transport */ +struct udp_transport +{ + pjsip_transport base; + pj_sock_t sock; + pj_ioqueue_key_t *key; + int rdata_cnt; + pjsip_rx_data **rdata; + int is_closing; + pj_bool_t is_paused; +}; + + +/* + * Initialize transport's receive buffer from the specified pool. + */ +static void init_rdata(struct udp_transport *tp, unsigned rdata_index, + pj_pool_t *pool, pjsip_rx_data **p_rdata) +{ + pjsip_rx_data *rdata; + + /* Reset pool. */ + //note: already done by caller + //pj_pool_reset(pool); + + rdata = PJ_POOL_ZALLOC_T(pool, pjsip_rx_data); + + /* Init tp_info part. */ + rdata->tp_info.pool = pool; + rdata->tp_info.transport = &tp->base; + rdata->tp_info.tp_data = (void*)(long)rdata_index; + rdata->tp_info.op_key.rdata = rdata; + pj_ioqueue_op_key_init(&rdata->tp_info.op_key.op_key, + sizeof(pj_ioqueue_op_key_t)); + + tp->rdata[rdata_index] = rdata; + + if (p_rdata) + *p_rdata = rdata; +} + + +/* + * udp_on_read_complete() + * + * This is callback notification from ioqueue that a pending recvfrom() + * operation has completed. + */ +static void udp_on_read_complete( pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read) +{ + /* See https://trac.pjsip.org/repos/ticket/1197 */ + enum { MAX_IMMEDIATE_PACKET = 50 }; + pjsip_rx_data_op_key *rdata_op_key = (pjsip_rx_data_op_key*) op_key; + pjsip_rx_data *rdata = rdata_op_key->rdata; + struct udp_transport *tp = (struct udp_transport*)rdata->tp_info.transport; + int i; + pj_status_t status; + + /* Don't do anything if transport is closing. */ + if (tp->is_closing) { + tp->is_closing++; + return; + } + + /* Don't do anything if transport is being paused. */ + if (tp->is_paused) + return; + + /* + * The idea of the loop is to process immediate data received by + * pj_ioqueue_recvfrom(), as long as i < MAX_IMMEDIATE_PACKET. When + * i is >= MAX_IMMEDIATE_PACKET, we force the recvfrom() operation to + * complete asynchronously, to allow other sockets to get their data. + */ + for (i=0;; ++i) { + enum { MIN_SIZE = 32 }; + pj_uint32_t flags; + + /* Report the packet to transport manager. Only do so if packet size + * is relatively big enough for a SIP packet. + */ + if (bytes_read > MIN_SIZE) { + pj_size_t size_eaten; + const pj_sockaddr *src_addr = &rdata->pkt_info.src_addr; + + /* Init pkt_info part. */ + rdata->pkt_info.len = bytes_read; + rdata->pkt_info.zero = 0; + pj_gettimeofday(&rdata->pkt_info.timestamp); + if (src_addr->addr.sa_family == pj_AF_INET()) { + pj_ansi_strcpy(rdata->pkt_info.src_name, + pj_inet_ntoa(src_addr->ipv4.sin_addr)); + rdata->pkt_info.src_port = pj_ntohs(src_addr->ipv4.sin_port); + } else { + pj_inet_ntop(pj_AF_INET6(), + pj_sockaddr_get_addr(&rdata->pkt_info.src_addr), + rdata->pkt_info.src_name, + sizeof(rdata->pkt_info.src_name)); + rdata->pkt_info.src_port = pj_ntohs(src_addr->ipv6.sin6_port); + } + + size_eaten = + pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr, + rdata); + + if (size_eaten < 0) { + pj_assert(!"It shouldn't happen!"); + size_eaten = rdata->pkt_info.len; + } + + /* Since this is UDP, the whole buffer is the message. */ + rdata->pkt_info.len = 0; + + } else if (bytes_read <= MIN_SIZE) { + + /* TODO: */ + + } else if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) && + -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && + -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) + { + + /* Report error to endpoint. */ + PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt, + rdata->tp_info.transport->obj_name, + -bytes_read, + "Warning: pj_ioqueue_recvfrom()" + " callback error")); + } + + if (i >= MAX_IMMEDIATE_PACKET) { + /* Force ioqueue_recvfrom() to return PJ_EPENDING */ + flags = PJ_IOQUEUE_ALWAYS_ASYNC; + } else { + flags = 0; + } + + /* Reset pool. + * Need to copy rdata fields to temp variable because they will + * be invalid after pj_pool_reset(). + */ + { + pj_pool_t *rdata_pool = rdata->tp_info.pool; + struct udp_transport *rdata_tp ; + unsigned rdata_index; + + rdata_tp = (struct udp_transport*)rdata->tp_info.transport; + rdata_index = (unsigned)(unsigned long)rdata->tp_info.tp_data; + + pj_pool_reset(rdata_pool); + init_rdata(rdata_tp, rdata_index, rdata_pool, &rdata); + + /* Change some vars to point to new location after + * pool reset. + */ + op_key = &rdata->tp_info.op_key.op_key; + } + + /* Only read next packet if transport is not being paused. This + * check handles the case where transport is paused while endpoint + * is still processing a SIP message. + */ + if (tp->is_paused) + return; + + /* Read next packet. */ + bytes_read = sizeof(rdata->pkt_info.packet); + rdata->pkt_info.src_addr_len = sizeof(rdata->pkt_info.src_addr); + status = pj_ioqueue_recvfrom(key, op_key, + rdata->pkt_info.packet, + &bytes_read, flags, + &rdata->pkt_info.src_addr, + &rdata->pkt_info.src_addr_len); + + if (status == PJ_SUCCESS) { + /* Continue loop. */ + pj_assert(i < MAX_IMMEDIATE_PACKET); + + } else if (status == PJ_EPENDING) { + break; + + } else { + + if (i < MAX_IMMEDIATE_PACKET) { + + /* Report error to endpoint if this is not EWOULDBLOCK error.*/ + if (status != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) && + status != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && + status != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) + { + + PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt, + rdata->tp_info.transport->obj_name, + status, + "Warning: pj_ioqueue_recvfrom")); + } + + /* Continue loop. */ + bytes_read = 0; + } else { + /* This is fatal error. + * Ioqueue operation will stop for this transport! + */ + PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt, + rdata->tp_info.transport->obj_name, + status, + "FATAL: pj_ioqueue_recvfrom() error, " + "UDP transport stopping! Error")); + break; + } + } + } +} + +/* + * udp_on_write_complete() + * + * This is callback notification from ioqueue that a pending sendto() + * operation has completed. + */ +static void udp_on_write_complete( pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_sent) +{ + struct udp_transport *tp = (struct udp_transport*) + pj_ioqueue_get_user_data(key); + pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key; + + tdata_op_key->tdata = NULL; + + if (tdata_op_key->callback) { + tdata_op_key->callback(&tp->base, tdata_op_key->token, bytes_sent); + } +} + +/* + * udp_send_msg() + * + * This function is called by transport manager (by transport->send_msg()) + * to send outgoing message. + */ +static pj_status_t udp_send_msg( pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, + int addr_len, + void *token, + pjsip_transport_callback callback) +{ + struct udp_transport *tp = (struct udp_transport*)transport; + pj_ssize_t size; + pj_status_t status; + + PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX); + + /* Return error if transport is paused */ + if (tp->is_paused) + return PJSIP_ETPNOTAVAIL; + + /* Init op key. */ + tdata->op_key.tdata = tdata; + tdata->op_key.token = token; + tdata->op_key.callback = callback; + + /* Send to ioqueue! */ + size = tdata->buf.cur - tdata->buf.start; + status = pj_ioqueue_sendto(tp->key, (pj_ioqueue_op_key_t*)&tdata->op_key, + tdata->buf.start, &size, 0, + rem_addr, addr_len); + + if (status != PJ_EPENDING) + tdata->op_key.tdata = NULL; + + return status; +} + +/* + * udp_destroy() + * + * This function is called by transport manager (by transport->destroy()). + */ +static pj_status_t udp_destroy( pjsip_transport *transport ) +{ + struct udp_transport *tp = (struct udp_transport*)transport; + int i; + + /* Mark this transport as closing. */ + tp->is_closing = 1; + + /* Cancel all pending operations. */ + /* blp: NO NO NO... + * No need to post queued completion as we poll the ioqueue until + * we've got events anyway. Posting completion will only cause + * callback to be called twice with IOCP: one for the post completion + * and another one for closing the socket. + * + for (i=0; i<tp->rdata_cnt; ++i) { + pj_ioqueue_post_completion(tp->key, + &tp->rdata[i]->tp_info.op_key.op_key, -1); + } + */ + + /* Unregister from ioqueue. */ + if (tp->key) { + pj_ioqueue_unregister(tp->key); + tp->key = NULL; + } else { + /* Close socket. */ + if (tp->sock && tp->sock != PJ_INVALID_SOCKET) { + pj_sock_close(tp->sock); + tp->sock = PJ_INVALID_SOCKET; + } + } + + /* Must poll ioqueue because IOCP calls the callback when socket + * is closed. We poll the ioqueue until all pending callbacks + * have been called. + */ + for (i=0; i<50 && tp->is_closing < 1+tp->rdata_cnt; ++i) { + int cnt; + pj_time_val timeout = {0, 1}; + + cnt = pj_ioqueue_poll(pjsip_endpt_get_ioqueue(transport->endpt), + &timeout); + if (cnt == 0) + break; + } + + /* Destroy rdata */ + for (i=0; i<tp->rdata_cnt; ++i) { + pj_pool_release(tp->rdata[i]->tp_info.pool); + } + + /* Destroy reference counter. */ + if (tp->base.ref_cnt) + pj_atomic_destroy(tp->base.ref_cnt); + + /* Destroy lock */ + if (tp->base.lock) + pj_lock_destroy(tp->base.lock); + + /* Destroy pool. */ + pjsip_endpt_release_pool(tp->base.endpt, tp->base.pool); + + return PJ_SUCCESS; +} + + +/* + * udp_shutdown() + * + * Start graceful UDP shutdown. + */ +static pj_status_t udp_shutdown(pjsip_transport *transport) +{ + return pjsip_transport_dec_ref(transport); +} + + +/* Create socket */ +static pj_status_t create_socket(int af, const pj_sockaddr_t *local_a, + int addr_len, pj_sock_t *p_sock) +{ + pj_sock_t sock; + pj_sockaddr_in tmp_addr; + pj_sockaddr_in6 tmp_addr6; + pj_status_t status; + + status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &sock); + if (status != PJ_SUCCESS) + return status; + + if (local_a == NULL) { + if (af == pj_AF_INET6()) { + pj_bzero(&tmp_addr6, sizeof(tmp_addr6)); + tmp_addr6.sin6_family = (pj_uint16_t)af; + local_a = &tmp_addr6; + addr_len = sizeof(tmp_addr6); + } else { + pj_sockaddr_in_init(&tmp_addr, NULL, 0); + local_a = &tmp_addr; + addr_len = sizeof(tmp_addr); + } + } + + status = pj_sock_bind(sock, local_a, addr_len); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + *p_sock = sock; + return PJ_SUCCESS; +} + + +/* Generate transport's published address */ +static pj_status_t get_published_name(pj_sock_t sock, + char hostbuf[], + int hostbufsz, + pjsip_host_port *bound_name) +{ + pj_sockaddr tmp_addr; + int addr_len; + pj_status_t status; + + addr_len = sizeof(tmp_addr); + status = pj_sock_getsockname(sock, &tmp_addr, &addr_len); + if (status != PJ_SUCCESS) + return status; + + bound_name->host.ptr = hostbuf; + if (tmp_addr.addr.sa_family == pj_AF_INET()) { + bound_name->port = pj_ntohs(tmp_addr.ipv4.sin_port); + + /* If bound address specifies "0.0.0.0", get the IP address + * of local hostname. + */ + if (tmp_addr.ipv4.sin_addr.s_addr == PJ_INADDR_ANY) { + pj_sockaddr hostip; + + status = pj_gethostip(pj_AF_INET(), &hostip); + if (status != PJ_SUCCESS) + return status; + + pj_strcpy2(&bound_name->host, pj_inet_ntoa(hostip.ipv4.sin_addr)); + } else { + /* Otherwise use bound address. */ + pj_strcpy2(&bound_name->host, + pj_inet_ntoa(tmp_addr.ipv4.sin_addr)); + status = PJ_SUCCESS; + } + + } else { + /* If bound address specifies "INADDR_ANY" (IPv6), get the + * IP address of local hostname + */ + pj_uint32_t loop6[4] = { 0, 0, 0, 0}; + + bound_name->port = pj_ntohs(tmp_addr.ipv6.sin6_port); + + if (pj_memcmp(&tmp_addr.ipv6.sin6_addr, loop6, sizeof(loop6))==0) { + status = pj_gethostip(tmp_addr.addr.sa_family, &tmp_addr); + if (status != PJ_SUCCESS) + return status; + } + + status = pj_inet_ntop(tmp_addr.addr.sa_family, + pj_sockaddr_get_addr(&tmp_addr), + hostbuf, hostbufsz); + if (status == PJ_SUCCESS) { + bound_name->host.slen = pj_ansi_strlen(hostbuf); + } + } + + + return status; +} + +/* Set the published address of the transport */ +static void udp_set_pub_name(struct udp_transport *tp, + const pjsip_host_port *a_name) +{ + enum { INFO_LEN = 80 }; + char local_addr[PJ_INET6_ADDRSTRLEN+10]; + + pj_assert(a_name->host.slen != 0); + pj_strdup_with_null(tp->base.pool, &tp->base.local_name.host, + &a_name->host); + tp->base.local_name.port = a_name->port; + + /* Update transport info. */ + if (tp->base.info == NULL) { + tp->base.info = (char*) pj_pool_alloc(tp->base.pool, INFO_LEN); + } + + pj_sockaddr_print(&tp->base.local_addr, local_addr, sizeof(local_addr), 3); + + pj_ansi_snprintf( + tp->base.info, INFO_LEN, "udp %s [published as %s:%d]", + local_addr, + tp->base.local_name.host.ptr, + tp->base.local_name.port); +} + +/* Set the socket handle of the transport */ +static void udp_set_socket(struct udp_transport *tp, + pj_sock_t sock, + const pjsip_host_port *a_name) +{ +#if PJSIP_UDP_SO_RCVBUF_SIZE || PJSIP_UDP_SO_SNDBUF_SIZE + long sobuf_size; + pj_status_t status; +#endif + + /* Adjust socket rcvbuf size */ +#if PJSIP_UDP_SO_RCVBUF_SIZE + sobuf_size = PJSIP_UDP_SO_RCVBUF_SIZE; + status = pj_sock_setsockopt(sock, pj_SOL_SOCKET(), pj_SO_RCVBUF(), + &sobuf_size, sizeof(sobuf_size)); + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(THIS_FILE, "Error setting SO_RCVBUF: %s [%d]", errmsg, + status)); + } +#endif + + /* Adjust socket sndbuf size */ +#if PJSIP_UDP_SO_SNDBUF_SIZE + sobuf_size = PJSIP_UDP_SO_SNDBUF_SIZE; + status = pj_sock_setsockopt(sock, pj_SOL_SOCKET(), pj_SO_SNDBUF(), + &sobuf_size, sizeof(sobuf_size)); + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(THIS_FILE, "Error setting SO_SNDBUF: %s [%d]", errmsg, + status)); + } +#endif + + /* Set the socket. */ + tp->sock = sock; + + /* Init address name (published address) */ + udp_set_pub_name(tp, a_name); +} + +/* Register socket to ioqueue */ +static pj_status_t register_to_ioqueue(struct udp_transport *tp) +{ + pj_ioqueue_t *ioqueue; + pj_ioqueue_callback ioqueue_cb; + + /* Ignore if already registered */ + if (tp->key != NULL) + return PJ_SUCCESS; + + /* Register to ioqueue. */ + ioqueue = pjsip_endpt_get_ioqueue(tp->base.endpt); + pj_memset(&ioqueue_cb, 0, sizeof(ioqueue_cb)); + ioqueue_cb.on_read_complete = &udp_on_read_complete; + ioqueue_cb.on_write_complete = &udp_on_write_complete; + + return pj_ioqueue_register_sock(tp->base.pool, ioqueue, tp->sock, tp, + &ioqueue_cb, &tp->key); +} + +/* Start ioqueue asynchronous reading to all rdata */ +static pj_status_t start_async_read(struct udp_transport *tp) +{ + pj_ioqueue_t *ioqueue; + int i; + pj_status_t status; + + ioqueue = pjsip_endpt_get_ioqueue(tp->base.endpt); + + /* Start reading the ioqueue. */ + for (i=0; i<tp->rdata_cnt; ++i) { + pj_ssize_t size; + + size = sizeof(tp->rdata[i]->pkt_info.packet); + tp->rdata[i]->pkt_info.src_addr_len = sizeof(tp->rdata[i]->pkt_info.src_addr); + status = pj_ioqueue_recvfrom(tp->key, + &tp->rdata[i]->tp_info.op_key.op_key, + tp->rdata[i]->pkt_info.packet, + &size, PJ_IOQUEUE_ALWAYS_ASYNC, + &tp->rdata[i]->pkt_info.src_addr, + &tp->rdata[i]->pkt_info.src_addr_len); + if (status == PJ_SUCCESS) { + pj_assert(!"Shouldn't happen because PJ_IOQUEUE_ALWAYS_ASYNC!"); + udp_on_read_complete(tp->key, &tp->rdata[i]->tp_info.op_key.op_key, + size); + } else if (status != PJ_EPENDING) { + /* Error! */ + return status; + } + } + + return PJ_SUCCESS; +} + + +/* + * pjsip_udp_transport_attach() + * + * Attach UDP socket and start transport. + */ +static pj_status_t transport_attach( pjsip_endpoint *endpt, + pjsip_transport_type_e type, + pj_sock_t sock, + const pjsip_host_port *a_name, + unsigned async_cnt, + pjsip_transport **p_transport) +{ + pj_pool_t *pool; + struct udp_transport *tp; + const char *format, *ipv6_quoteb, *ipv6_quotee; + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(endpt && sock!=PJ_INVALID_SOCKET && a_name && async_cnt>0, + PJ_EINVAL); + + /* Object name. */ + if (type & PJSIP_TRANSPORT_IPV6) { + format = "udpv6%p"; + ipv6_quoteb = "["; + ipv6_quotee = "]"; + } else { + format = "udp%p"; + ipv6_quoteb = ipv6_quotee = ""; + } + + /* Create pool. */ + pool = pjsip_endpt_create_pool(endpt, format, PJSIP_POOL_LEN_TRANSPORT, + PJSIP_POOL_INC_TRANSPORT); + if (!pool) + return PJ_ENOMEM; + + /* Create the UDP transport object. */ + tp = PJ_POOL_ZALLOC_T(pool, struct udp_transport); + + /* Save pool. */ + tp->base.pool = pool; + + pj_memcpy(tp->base.obj_name, pool->obj_name, PJ_MAX_OBJ_NAME); + + /* Init reference counter. */ + status = pj_atomic_create(pool, 0, &tp->base.ref_cnt); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init lock. */ + status = pj_lock_create_recursive_mutex(pool, pool->obj_name, + &tp->base.lock); + if (status != PJ_SUCCESS) + goto on_error; + + /* Set type. */ + tp->base.key.type = type; + + /* Remote address is left zero (except the family) */ + tp->base.key.rem_addr.addr.sa_family = (pj_uint16_t) + ((type & PJSIP_TRANSPORT_IPV6) ? pj_AF_INET6() : pj_AF_INET()); + + /* Type name. */ + tp->base.type_name = "UDP"; + + /* Transport flag */ + tp->base.flag = pjsip_transport_get_flag_from_type(type); + + + /* Length of addressess. */ + tp->base.addr_len = sizeof(tp->base.local_addr); + + /* Init local address. */ + status = pj_sock_getsockname(sock, &tp->base.local_addr, + &tp->base.addr_len); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init remote name. */ + if (type == PJSIP_TRANSPORT_UDP) + tp->base.remote_name.host = pj_str("0.0.0.0"); + else + tp->base.remote_name.host = pj_str("::0"); + tp->base.remote_name.port = 0; + + /* Init direction */ + tp->base.dir = PJSIP_TP_DIR_NONE; + + /* Set endpoint. */ + tp->base.endpt = endpt; + + /* Transport manager and timer will be initialized by tpmgr */ + + /* Attach socket and assign name. */ + udp_set_socket(tp, sock, a_name); + + /* Register to ioqueue */ + status = register_to_ioqueue(tp); + if (status != PJ_SUCCESS) + goto on_error; + + /* Set functions. */ + tp->base.send_msg = &udp_send_msg; + tp->base.do_shutdown = &udp_shutdown; + tp->base.destroy = &udp_destroy; + + /* This is a permanent transport, so we initialize the ref count + * to one so that transport manager don't destroy this transport + * when there's no user! + */ + pj_atomic_inc(tp->base.ref_cnt); + + /* Register to transport manager. */ + tp->base.tpmgr = pjsip_endpt_get_tpmgr(endpt); + status = pjsip_transport_register( tp->base.tpmgr, (pjsip_transport*)tp); + if (status != PJ_SUCCESS) + goto on_error; + + + /* Create rdata and put it in the array. */ + tp->rdata_cnt = 0; + tp->rdata = (pjsip_rx_data**) + pj_pool_calloc(tp->base.pool, async_cnt, + sizeof(pjsip_rx_data*)); + for (i=0; i<async_cnt; ++i) { + pj_pool_t *rdata_pool = pjsip_endpt_create_pool(endpt, "rtd%p", + PJSIP_POOL_RDATA_LEN, + PJSIP_POOL_RDATA_INC); + if (!rdata_pool) { + pj_atomic_set(tp->base.ref_cnt, 0); + pjsip_transport_destroy(&tp->base); + return PJ_ENOMEM; + } + + init_rdata(tp, i, rdata_pool, NULL); + tp->rdata_cnt++; + } + + /* Start reading the ioqueue. */ + status = start_async_read(tp); + if (status != PJ_SUCCESS) { + pjsip_transport_destroy(&tp->base); + return status; + } + + /* Done. */ + if (p_transport) + *p_transport = &tp->base; + + PJ_LOG(4,(tp->base.obj_name, + "SIP %s started, published address is %s%.*s%s:%d", + pjsip_transport_get_type_desc((pjsip_transport_type_e)tp->base.key.type), + ipv6_quoteb, + (int)tp->base.local_name.host.slen, + tp->base.local_name.host.ptr, + ipv6_quotee, + tp->base.local_name.port)); + + return PJ_SUCCESS; + +on_error: + udp_destroy((pjsip_transport*)tp); + return status; +} + + +PJ_DEF(pj_status_t) pjsip_udp_transport_attach( pjsip_endpoint *endpt, + pj_sock_t sock, + const pjsip_host_port *a_name, + unsigned async_cnt, + pjsip_transport **p_transport) +{ + return transport_attach(endpt, PJSIP_TRANSPORT_UDP, sock, a_name, + async_cnt, p_transport); +} + +PJ_DEF(pj_status_t) pjsip_udp_transport_attach2( pjsip_endpoint *endpt, + pjsip_transport_type_e type, + pj_sock_t sock, + const pjsip_host_port *a_name, + unsigned async_cnt, + pjsip_transport **p_transport) +{ + return transport_attach(endpt, type, sock, a_name, + async_cnt, p_transport); +} + +/* + * pjsip_udp_transport_start() + * + * Create a UDP socket in the specified address and start a transport. + */ +PJ_DEF(pj_status_t) pjsip_udp_transport_start( pjsip_endpoint *endpt, + const pj_sockaddr_in *local_a, + const pjsip_host_port *a_name, + unsigned async_cnt, + pjsip_transport **p_transport) +{ + pj_sock_t sock; + pj_status_t status; + char addr_buf[PJ_INET6_ADDRSTRLEN]; + pjsip_host_port bound_name; + + PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL); + + status = create_socket(pj_AF_INET(), local_a, sizeof(pj_sockaddr_in), + &sock); + if (status != PJ_SUCCESS) + return status; + + if (a_name == NULL) { + /* Address name is not specified. + * Build a name based on bound address. + */ + status = get_published_name(sock, addr_buf, sizeof(addr_buf), + &bound_name); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + a_name = &bound_name; + } + + return pjsip_udp_transport_attach( endpt, sock, a_name, async_cnt, + p_transport ); +} + + +/* + * pjsip_udp_transport_start() + * + * Create a UDP socket in the specified address and start a transport. + */ +PJ_DEF(pj_status_t) pjsip_udp_transport_start6(pjsip_endpoint *endpt, + const pj_sockaddr_in6 *local_a, + const pjsip_host_port *a_name, + unsigned async_cnt, + pjsip_transport **p_transport) +{ + pj_sock_t sock; + pj_status_t status; + char addr_buf[PJ_INET6_ADDRSTRLEN]; + pjsip_host_port bound_name; + + PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL); + + status = create_socket(pj_AF_INET6(), local_a, sizeof(pj_sockaddr_in6), + &sock); + if (status != PJ_SUCCESS) + return status; + + if (a_name == NULL) { + /* Address name is not specified. + * Build a name based on bound address. + */ + status = get_published_name(sock, addr_buf, sizeof(addr_buf), + &bound_name); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + a_name = &bound_name; + } + + return pjsip_udp_transport_attach2(endpt, PJSIP_TRANSPORT_UDP6, + sock, a_name, async_cnt, p_transport); +} + +/* + * Retrieve the internal socket handle used by the UDP transport. + */ +PJ_DEF(pj_sock_t) pjsip_udp_transport_get_socket(pjsip_transport *transport) +{ + struct udp_transport *tp; + + PJ_ASSERT_RETURN(transport != NULL, PJ_INVALID_SOCKET); + + tp = (struct udp_transport*) transport; + + return tp->sock; +} + + +/* + * Temporarily pause or shutdown the transport. + */ +PJ_DEF(pj_status_t) pjsip_udp_transport_pause(pjsip_transport *transport, + unsigned option) +{ + struct udp_transport *tp; + unsigned i; + + PJ_ASSERT_RETURN(transport != NULL, PJ_EINVAL); + + /* Flag must be specified */ + PJ_ASSERT_RETURN((option & 0x03) != 0, PJ_EINVAL); + + tp = (struct udp_transport*) transport; + + /* Transport must not have been paused */ + PJ_ASSERT_RETURN(tp->is_paused==0, PJ_EINVALIDOP); + + /* Set transport to paused first, so that when the read callback is + * called by pj_ioqueue_post_completion() it will not try to + * re-register the rdata. + */ + tp->is_paused = PJ_TRUE; + + /* Cancel the ioqueue operation. */ + for (i=0; i<(unsigned)tp->rdata_cnt; ++i) { + pj_ioqueue_post_completion(tp->key, + &tp->rdata[i]->tp_info.op_key.op_key, -1); + } + + /* Destroy the socket? */ + if (option & PJSIP_UDP_TRANSPORT_DESTROY_SOCKET) { + if (tp->key) { + /* This implicitly closes the socket */ + pj_ioqueue_unregister(tp->key); + tp->key = NULL; + } else { + /* Close socket. */ + if (tp->sock && tp->sock != PJ_INVALID_SOCKET) { + pj_sock_close(tp->sock); + tp->sock = PJ_INVALID_SOCKET; + } + } + tp->sock = PJ_INVALID_SOCKET; + + } + + PJ_LOG(4,(tp->base.obj_name, "SIP UDP transport paused")); + + return PJ_SUCCESS; +} + + +/* + * Restart transport. + * + * If option is KEEP_SOCKET, just re-activate ioqueue operation. + * + * If option is DESTROY_SOCKET: + * - if socket is specified, replace. + * - if socket is not specified, create and replace. + */ +PJ_DEF(pj_status_t) pjsip_udp_transport_restart(pjsip_transport *transport, + unsigned option, + pj_sock_t sock, + const pj_sockaddr_in *local, + const pjsip_host_port *a_name) +{ + struct udp_transport *tp; + pj_status_t status; + + PJ_ASSERT_RETURN(transport != NULL, PJ_EINVAL); + /* Flag must be specified */ + PJ_ASSERT_RETURN((option & 0x03) != 0, PJ_EINVAL); + + tp = (struct udp_transport*) transport; + + if (option & PJSIP_UDP_TRANSPORT_DESTROY_SOCKET) { + char addr_buf[PJ_INET6_ADDRSTRLEN]; + pjsip_host_port bound_name; + + /* Request to recreate transport */ + + /* Destroy existing socket, if any. */ + if (tp->key) { + /* This implicitly closes the socket */ + pj_ioqueue_unregister(tp->key); + tp->key = NULL; + } else { + /* Close socket. */ + if (tp->sock && tp->sock != PJ_INVALID_SOCKET) { + pj_sock_close(tp->sock); + tp->sock = PJ_INVALID_SOCKET; + } + } + tp->sock = PJ_INVALID_SOCKET; + + /* Create the socket if it's not specified */ + if (sock == PJ_INVALID_SOCKET) { + status = create_socket(pj_AF_INET(), local, + sizeof(pj_sockaddr_in), &sock); + if (status != PJ_SUCCESS) + return status; + } + + /* If transport published name is not specified, calculate it + * from the bound address. + */ + if (a_name == NULL) { + status = get_published_name(sock, addr_buf, sizeof(addr_buf), + &bound_name); + if (status != PJ_SUCCESS) { + pj_sock_close(sock); + return status; + } + + a_name = &bound_name; + } + + /* Assign the socket and published address to transport. */ + udp_set_socket(tp, sock, a_name); + + } else { + + /* For KEEP_SOCKET, transport must have been paused before */ + PJ_ASSERT_RETURN(tp->is_paused, PJ_EINVALIDOP); + + /* If address name is specified, update it */ + if (a_name != NULL) + udp_set_pub_name(tp, a_name); + } + + /* Re-register new or existing socket to ioqueue. */ + status = register_to_ioqueue(tp); + if (status != PJ_SUCCESS) { + return status; + } + + /* Restart async read operation. */ + status = start_async_read(tp); + if (status != PJ_SUCCESS) + return status; + + /* Everything has been set up */ + tp->is_paused = PJ_FALSE; + + PJ_LOG(4,(tp->base.obj_name, + "SIP UDP transport restarted, published address is %.*s:%d", + (int)tp->base.local_name.host.slen, + tp->base.local_name.host.ptr, + tp->base.local_name.port)); + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsip/sip_transport_wrap.cpp b/pjsip/src/pjsip/sip_transport_wrap.cpp new file mode 100644 index 0000000..80d1e88 --- /dev/null +++ b/pjsip/src/pjsip/sip_transport_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: sip_transport_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * 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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "sip_transport.c" diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c new file mode 100644 index 0000000..7128891 --- /dev/null +++ b/pjsip/src/pjsip/sip_ua_layer.c @@ -0,0 +1,978 @@ +/* $Id: sip_ua_layer.c 3664 2011-07-19 03:42:28Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_ua_layer.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_dialog.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_transaction.h> +#include <pj/os.h> +#include <pj/hash.h> +#include <pj/assert.h> +#include <pj/string.h> +#include <pj/pool.h> +#include <pj/log.h> + + +#define THIS_FILE "sip_ua_layer.c" + +/* + * Static prototypes. + */ +static pj_status_t mod_ua_load(pjsip_endpoint *endpt); +static pj_status_t mod_ua_unload(void); +static pj_bool_t mod_ua_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t mod_ua_on_rx_response(pjsip_rx_data *rdata); +static void mod_ua_on_tsx_state(pjsip_transaction*, pjsip_event*); + + +extern long pjsip_dlg_lock_tls_id; /* defined in sip_dialog.c */ + +/* This struct is used to represent list of dialog inside a dialog set. + * We don't want to use pjsip_dialog for this purpose, to save some + * memory (about 100 bytes per dialog set). + */ +struct dlg_set_head +{ + PJ_DECL_LIST_MEMBER(pjsip_dialog); +}; + +/* This struct represents a dialog set. + * This is the value that will be put in the UA's hash table. + */ +struct dlg_set +{ + /* To put this node in free dlg_set nodes in UA. */ + PJ_DECL_LIST_MEMBER(struct dlg_set); + + /* This is the buffer to store this entry in the hash table. */ + pj_hash_entry_buf ht_entry; + + /* List of dialog in this dialog set. */ + struct dlg_set_head dlg_list; +}; + + +/* + * Module interface. + */ +static struct user_agent +{ + pjsip_module mod; + pj_pool_t *pool; + pjsip_endpoint *endpt; + pj_mutex_t *mutex; + pj_hash_table_t *dlg_table; + pjsip_ua_init_param param; + struct dlg_set free_dlgset_nodes; + +} mod_ua = +{ + { + NULL, NULL, /* prev, next. */ + { "mod-ua", 6 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_UA_PROXY_LAYER, /* Priority */ + &mod_ua_load, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + &mod_ua_unload, /* unload() */ + &mod_ua_on_rx_request, /* on_rx_request() */ + &mod_ua_on_rx_response, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + &mod_ua_on_tsx_state, /* on_tsx_state() */ + } +}; + +/* + * mod_ua_load() + * + * Called when module is being loaded by endpoint. + */ +static pj_status_t mod_ua_load(pjsip_endpoint *endpt) +{ + pj_status_t status; + + /* Initialize the user agent. */ + mod_ua.endpt = endpt; + mod_ua.pool = pjsip_endpt_create_pool( endpt, "ua%p", PJSIP_POOL_LEN_UA, + PJSIP_POOL_INC_UA); + if (mod_ua.pool == NULL) + return PJ_ENOMEM; + + status = pj_mutex_create_recursive(mod_ua.pool, " ua%p", &mod_ua.mutex); + if (status != PJ_SUCCESS) + return status; + + mod_ua.dlg_table = pj_hash_create(mod_ua.pool, PJSIP_MAX_DIALOG_COUNT); + if (mod_ua.dlg_table == NULL) + return PJ_ENOMEM; + + pj_list_init(&mod_ua.free_dlgset_nodes); + + /* Initialize dialog lock. */ + status = pj_thread_local_alloc(&pjsip_dlg_lock_tls_id); + if (status != PJ_SUCCESS) + return status; + + pj_thread_local_set(pjsip_dlg_lock_tls_id, NULL); + + return PJ_SUCCESS; + +} + +/* + * mod_ua_unload() + * + * Called when module is being unloaded. + */ +static pj_status_t mod_ua_unload(void) +{ + pj_thread_local_free(pjsip_dlg_lock_tls_id); + pj_mutex_destroy(mod_ua.mutex); + + /* Release pool */ + if (mod_ua.pool) { + pjsip_endpt_release_pool( mod_ua.endpt, mod_ua.pool ); + } + return PJ_SUCCESS; +} + +/* + * mod_ua_on_tsx_stats() + * + * Called on changed on transaction state. + */ +static void mod_ua_on_tsx_state( pjsip_transaction *tsx, pjsip_event *e) +{ + pjsip_dialog *dlg; + + /* Get the dialog where this transaction belongs. */ + dlg = (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id]; + + /* If dialog instance has gone, it could mean that the dialog + * may has been destroyed. + */ + if (dlg == NULL) + return; + + /* Hand over the event to the dialog. */ + pjsip_dlg_on_tsx_state(dlg, tsx, e); +} + + +/* + * Init user agent module and register it to the endpoint. + */ +PJ_DEF(pj_status_t) pjsip_ua_init_module( pjsip_endpoint *endpt, + const pjsip_ua_init_param *prm) +{ + pj_status_t status; + + /* Check if module already registered. */ + PJ_ASSERT_RETURN(mod_ua.mod.id == -1, PJ_EINVALIDOP); + + /* Copy param, if exists. */ + if (prm) + pj_memcpy(&mod_ua.param, prm, sizeof(pjsip_ua_init_param)); + + /* Register the module. */ + status = pjsip_endpt_register_module(endpt, &mod_ua.mod); + + return status; +} + +/* + * Get the instance of the user agent. + * + */ +PJ_DEF(pjsip_user_agent*) pjsip_ua_instance(void) +{ + return &mod_ua.mod; +} + + +/* + * Get the endpoint where this UA is currently registered. + */ +PJ_DEF(pjsip_endpoint*) pjsip_ua_get_endpt(pjsip_user_agent *ua) +{ + PJ_UNUSED_ARG(ua); + pj_assert(ua == &mod_ua.mod); + return mod_ua.endpt; +} + + +/* + * Destroy the user agent layer. + */ +PJ_DEF(pj_status_t) pjsip_ua_destroy(void) +{ + /* Check if module already destroyed. */ + PJ_ASSERT_RETURN(mod_ua.mod.id != -1, PJ_EINVALIDOP); + + return pjsip_endpt_unregister_module(mod_ua.endpt, &mod_ua.mod); +} + + + +/* + * Create key to identify dialog set. + */ +/* +PJ_DEF(void) pjsip_ua_create_dlg_set_key( pj_pool_t *pool, + pj_str_t *set_key, + const pj_str_t *call_id, + const pj_str_t *local_tag) +{ + PJ_ASSERT_ON_FAIL(pool && set_key && call_id && local_tag, return;); + + set_key->slen = call_id->slen + local_tag->slen + 1; + set_key->ptr = (char*) pj_pool_alloc(pool, set_key->slen); + pj_assert(set_key->ptr != NULL); + + pj_memcpy(set_key->ptr, call_id->ptr, call_id->slen); + set_key->ptr[call_id->slen] = '$'; + pj_memcpy(set_key->ptr + call_id->slen + 1, + local_tag->ptr, local_tag->slen); +} +*/ + +/* + * Acquire one dlg_set node to be put in the hash table. + * This will first look in the free nodes list, then allocate + * a new one from UA's pool when one is not available. + */ +static struct dlg_set *alloc_dlgset_node(void) +{ + struct dlg_set *set; + + if (!pj_list_empty(&mod_ua.free_dlgset_nodes)) { + set = mod_ua.free_dlgset_nodes.next; + pj_list_erase(set); + return set; + } else { + set = PJ_POOL_ALLOC_T(mod_ua.pool, struct dlg_set); + return set; + } +} + +/* + * Register new dialog. Called by pjsip_dlg_create_uac() and + * pjsip_dlg_create_uas(); + */ +PJ_DEF(pj_status_t) pjsip_ua_register_dlg( pjsip_user_agent *ua, + pjsip_dialog *dlg ) +{ + /* Sanity check. */ + PJ_ASSERT_RETURN(ua && dlg, PJ_EINVAL); + + /* For all dialogs, local tag (inc hash) must has been initialized. */ + PJ_ASSERT_RETURN(dlg->local.info && dlg->local.info->tag.slen && + dlg->local.tag_hval != 0, PJ_EBUG); + + /* For UAS dialog, remote tag (inc hash) must have been initialized. */ + //PJ_ASSERT_RETURN(dlg->role==PJSIP_ROLE_UAC || + // (dlg->role==PJSIP_ROLE_UAS && dlg->remote.info->tag.slen + // && dlg->remote.tag_hval != 0), PJ_EBUG); + + /* Lock the user agent. */ + pj_mutex_lock(mod_ua.mutex); + + /* For UAC, check if there is existing dialog in the same set. */ + if (dlg->role == PJSIP_ROLE_UAC) { + struct dlg_set *dlg_set; + + dlg_set = (struct dlg_set*) + pj_hash_get( mod_ua.dlg_table, dlg->local.info->tag.ptr, + dlg->local.info->tag.slen, + &dlg->local.tag_hval); + + if (dlg_set) { + /* This is NOT the first dialog in the dialog set. + * Just add this dialog in the list. + */ + pj_assert(dlg_set->dlg_list.next != (void*)&dlg_set->dlg_list); + pj_list_push_back(&dlg_set->dlg_list, dlg); + + dlg->dlg_set = dlg_set; + + } else { + /* This is the first dialog in the dialog set. + * Create the dialog set and add this dialog to it. + */ + dlg_set = alloc_dlgset_node(); + pj_list_init(&dlg_set->dlg_list); + pj_list_push_back(&dlg_set->dlg_list, dlg); + + dlg->dlg_set = dlg_set; + + /* Register the dialog set in the hash table. */ + pj_hash_set_np(mod_ua.dlg_table, + dlg->local.info->tag.ptr, dlg->local.info->tag.slen, + dlg->local.tag_hval, dlg_set->ht_entry, dlg_set); + } + + } else { + /* For UAS, create the dialog set with a single dialog as member. */ + struct dlg_set *dlg_set; + + dlg_set = alloc_dlgset_node(); + pj_list_init(&dlg_set->dlg_list); + pj_list_push_back(&dlg_set->dlg_list, dlg); + + dlg->dlg_set = dlg_set; + + pj_hash_set_np(mod_ua.dlg_table, + dlg->local.info->tag.ptr, dlg->local.info->tag.slen, + dlg->local.tag_hval, dlg_set->ht_entry, dlg_set); + } + + /* Unlock user agent. */ + pj_mutex_unlock(mod_ua.mutex); + + /* Done. */ + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsip_ua_unregister_dlg( pjsip_user_agent *ua, + pjsip_dialog *dlg ) +{ + struct dlg_set *dlg_set; + pjsip_dialog *d; + + /* Sanity-check arguments. */ + PJ_ASSERT_RETURN(ua && dlg, PJ_EINVAL); + + /* Check that dialog has been registered. */ + PJ_ASSERT_RETURN(dlg->dlg_set, PJ_EINVALIDOP); + + /* Lock user agent. */ + pj_mutex_lock(mod_ua.mutex); + + /* Find this dialog from the dialog set. */ + dlg_set = (struct dlg_set*) dlg->dlg_set; + d = dlg_set->dlg_list.next; + while (d != (pjsip_dialog*)&dlg_set->dlg_list && d != dlg) { + d = d->next; + } + + if (d != dlg) { + pj_assert(!"Dialog is not registered!"); + pj_mutex_unlock(mod_ua.mutex); + return PJ_EINVALIDOP; + } + + /* Remove this dialog from the list. */ + pj_list_erase(dlg); + + /* If dialog list is empty, remove the dialog set from the hash table. */ + if (pj_list_empty(&dlg_set->dlg_list)) { + pj_hash_set(NULL, mod_ua.dlg_table, dlg->local.info->tag.ptr, + dlg->local.info->tag.slen, dlg->local.tag_hval, NULL); + + /* Return dlg_set to free nodes. */ + pj_list_push_back(&mod_ua.free_dlgset_nodes, dlg_set); + } + + /* Unlock user agent. */ + pj_mutex_unlock(mod_ua.mutex); + + /* Done. */ + return PJ_SUCCESS; +} + + +PJ_DEF(pjsip_dialog*) pjsip_rdata_get_dlg( pjsip_rx_data *rdata ) +{ + return (pjsip_dialog*) rdata->endpt_info.mod_data[mod_ua.mod.id]; +} + +PJ_DEF(pjsip_dialog*) pjsip_tsx_get_dlg( pjsip_transaction *tsx ) +{ + return (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id]; +} + + +/* + * Retrieve the current number of dialog-set currently registered + * in the hash table. + */ +PJ_DEF(unsigned) pjsip_ua_get_dlg_set_count(void) +{ + unsigned count; + + PJ_ASSERT_RETURN(mod_ua.endpt, 0); + + pj_mutex_lock(mod_ua.mutex); + count = pj_hash_count(mod_ua.dlg_table); + pj_mutex_unlock(mod_ua.mutex); + + return count; +} + + +/* + * Find a dialog. + */ +PJ_DEF(pjsip_dialog*) pjsip_ua_find_dialog(const pj_str_t *call_id, + const pj_str_t *local_tag, + const pj_str_t *remote_tag, + pj_bool_t lock_dialog) +{ + struct dlg_set *dlg_set; + pjsip_dialog *dlg; + + PJ_ASSERT_RETURN(call_id && local_tag && remote_tag, NULL); + + /* Lock user agent. */ + pj_mutex_lock(mod_ua.mutex); + + /* Lookup the dialog set. */ + dlg_set = (struct dlg_set*) + pj_hash_get(mod_ua.dlg_table, local_tag->ptr, local_tag->slen, + NULL); + if (dlg_set == NULL) { + /* Not found */ + pj_mutex_unlock(mod_ua.mutex); + return NULL; + } + + /* Dialog set is found, now find the matching dialog based on the + * remote tag. + */ + dlg = dlg_set->dlg_list.next; + while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) { + if (pj_strcmp(&dlg->remote.info->tag, remote_tag) == 0) + break; + dlg = dlg->next; + } + + if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) { + /* Not found */ + pj_mutex_unlock(mod_ua.mutex); + return NULL; + } + + /* Dialog has been found. It SHOULD have the right Call-ID!! */ + PJ_ASSERT_ON_FAIL(pj_strcmp(&dlg->call_id->id, call_id)==0, + {pj_mutex_unlock(mod_ua.mutex); return NULL;}); + + if (lock_dialog) { + if (pjsip_dlg_try_inc_lock(dlg) != PJ_SUCCESS) { + + /* + * Unable to acquire dialog's lock while holding the user + * agent's mutex. Release the UA mutex before retrying once + * more. + * + * THIS MAY CAUSE RACE CONDITION! + */ + + /* Unlock user agent. */ + pj_mutex_unlock(mod_ua.mutex); + /* Lock dialog */ + pjsip_dlg_inc_lock(dlg); + + } else { + /* Unlock user agent. */ + pj_mutex_unlock(mod_ua.mutex); + } + + } else { + /* Unlock user agent. */ + pj_mutex_unlock(mod_ua.mutex); + } + + return dlg; +} + + +/* + * Find the first dialog in dialog set in hash table for an incoming message. + */ +static struct dlg_set *find_dlg_set_for_msg( pjsip_rx_data *rdata ) +{ + /* CANCEL message doesn't have To tag, so we must lookup the dialog + * by finding the INVITE UAS transaction being cancelled. + */ + if (rdata->msg_info.cseq->method.id == PJSIP_CANCEL_METHOD) { + + pjsip_dialog *dlg; + + /* Create key for the rdata, but this time, use INVITE as the + * method. + */ + pj_str_t key; + pjsip_role_e role; + pjsip_transaction *tsx; + + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) + role = PJSIP_ROLE_UAS; + else + role = PJSIP_ROLE_UAC; + + pjsip_tsx_create_key(rdata->tp_info.pool, &key, role, + pjsip_get_invite_method(), rdata); + + /* Lookup the INVITE transaction */ + tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); + + /* We should find the dialog attached to the INVITE transaction */ + if (tsx) { + dlg = (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id]; + pj_mutex_unlock(tsx->mutex); + + /* Dlg may be NULL on some extreme condition + * (e.g. during debugging where initially there is a dialog) + */ + return dlg ? (struct dlg_set*) dlg->dlg_set : NULL; + + } else { + return NULL; + } + + + } else { + pj_str_t *tag; + struct dlg_set *dlg_set; + + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) + tag = &rdata->msg_info.to->tag; + else + tag = &rdata->msg_info.from->tag; + + /* Lookup the dialog set. */ + dlg_set = (struct dlg_set*) + pj_hash_get(mod_ua.dlg_table, tag->ptr, tag->slen, NULL); + return dlg_set; + } +} + +/* On received requests. */ +static pj_bool_t mod_ua_on_rx_request(pjsip_rx_data *rdata) +{ + struct dlg_set *dlg_set; + pj_str_t *from_tag; + pjsip_dialog *dlg; + pj_status_t status; + + /* Optimized path: bail out early if request is not CANCEL and it doesn't + * have To tag + */ + if (rdata->msg_info.to->tag.slen == 0 && + rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD) + { + return PJ_FALSE; + } + + /* Incoming REGISTER may have tags in it */ + if (rdata->msg_info.msg->line.req.method.id == PJSIP_REGISTER_METHOD) + return PJ_FALSE; + +retry_on_deadlock: + + /* Lock user agent before looking up the dialog hash table. */ + pj_mutex_lock(mod_ua.mutex); + + /* Lookup the dialog set, based on the To tag header. */ + dlg_set = find_dlg_set_for_msg(rdata); + + /* If dialog is not found, respond with 481 (Call/Transaction + * Does Not Exist). + */ + if (dlg_set == NULL) { + /* Unable to find dialog. */ + pj_mutex_unlock(mod_ua.mutex); + + if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) { + PJ_LOG(5,(THIS_FILE, + "Unable to find dialogset for %s, answering with 481", + pjsip_rx_data_get_info(rdata))); + + /* Respond with 481 . */ + pjsip_endpt_respond_stateless( mod_ua.endpt, rdata, 481, NULL, + NULL, NULL ); + } + return PJ_TRUE; + } + + /* Dialog set has been found. + * Find the dialog in the dialog set based on the content of the remote + * tag. + */ + from_tag = &rdata->msg_info.from->tag; + dlg = dlg_set->dlg_list.next; + while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) { + + if (pj_strcmp(&dlg->remote.info->tag, from_tag) == 0) + break; + + dlg = dlg->next; + } + + /* Dialog may not be found, e.g. in this case: + * - UAC sends SUBSCRIBE, then UAS sends NOTIFY before answering + * SUBSCRIBE request with 2xx. + * + * In this case, we can accept the request ONLY when the original + * dialog still has empty To tag. + */ + if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) { + + pjsip_dialog *first_dlg = dlg_set->dlg_list.next; + + if (first_dlg->remote.info->tag.slen != 0) { + /* Not found. Mulfunction UAC? */ + pj_mutex_unlock(mod_ua.mutex); + + if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) { + PJ_LOG(5,(THIS_FILE, + "Unable to find dialog for %s, answering with 481", + pjsip_rx_data_get_info(rdata))); + + pjsip_endpt_respond_stateless(mod_ua.endpt, rdata, + PJSIP_SC_CALL_TSX_DOES_NOT_EXIST, + NULL, NULL, NULL); + } else { + PJ_LOG(5,(THIS_FILE, + "Unable to find dialog for %s", + pjsip_rx_data_get_info(rdata))); + } + return PJ_TRUE; + } + + dlg = first_dlg; + } + + /* Mark the dialog id of the request. */ + rdata->endpt_info.mod_data[mod_ua.mod.id] = dlg; + + /* Try to lock the dialog */ + PJ_LOG(6,(dlg->obj_name, "UA layer acquiring dialog lock for request")); + status = pjsip_dlg_try_inc_lock(dlg); + if (status != PJ_SUCCESS) { + /* Failed to acquire dialog mutex immediately, this could be + * because of deadlock. Release UA mutex, yield, and retry + * the whole thing once again. + */ + pj_mutex_unlock(mod_ua.mutex); + pj_thread_sleep(0); + goto retry_on_deadlock; + } + + /* Done with processing in UA layer, release lock */ + pj_mutex_unlock(mod_ua.mutex); + + /* Pass to dialog. */ + pjsip_dlg_on_rx_request(dlg, rdata); + + /* Unlock the dialog. This may destroy the dialog */ + pjsip_dlg_dec_lock(dlg); + + /* Report as handled. */ + return PJ_TRUE; +} + + +/* On rx response notification. + */ +static pj_bool_t mod_ua_on_rx_response(pjsip_rx_data *rdata) +{ + pjsip_transaction *tsx; + struct dlg_set *dlg_set; + pjsip_dialog *dlg; + pj_status_t status; + + /* + * Find the dialog instance for the response. + * All outgoing dialog requests are sent statefully, which means + * there will be an UAC transaction associated with this response, + * and the dialog instance will be recorded in that transaction. + * + * But even when transaction is found, there is possibility that + * the response is a forked response. + */ + +retry_on_deadlock: + + dlg = NULL; + + /* Lock user agent dlg table before we're doing anything. */ + pj_mutex_lock(mod_ua.mutex); + + /* Check if transaction is present. */ + tsx = pjsip_rdata_get_tsx(rdata); + if (tsx) { + /* Check if dialog is present in the transaction. */ + dlg = pjsip_tsx_get_dlg(tsx); + if (!dlg) { + /* Unlock dialog hash table. */ + pj_mutex_unlock(mod_ua.mutex); + return PJ_FALSE; + } + + /* Get the dialog set. */ + dlg_set = (struct dlg_set*) dlg->dlg_set; + + /* Even if transaction is found and (candidate) dialog has been + * identified, it's possible that the request has forked. + */ + + } else { + /* Transaction is not present. + * Check if this is a 2xx/OK response to INVITE, which in this + * case the response will be handled directly by the + * dialog. + */ + pjsip_cseq_hdr *cseq_hdr = rdata->msg_info.cseq; + + if (cseq_hdr->method.id != PJSIP_INVITE_METHOD || + rdata->msg_info.msg->line.status.code / 100 != 2) + { + /* Not a 2xx response to INVITE. + * This must be some stateless response sent by other modules, + * or a very late response. + */ + /* Unlock dialog hash table. */ + pj_mutex_unlock(mod_ua.mutex); + return PJ_FALSE; + } + + + /* Get the dialog set. */ + dlg_set = (struct dlg_set*) + pj_hash_get(mod_ua.dlg_table, + rdata->msg_info.from->tag.ptr, + rdata->msg_info.from->tag.slen, + NULL); + + if (!dlg_set) { + /* Unlock dialog hash table. */ + pj_mutex_unlock(mod_ua.mutex); + + /* Strayed 2xx response!! */ + PJ_LOG(4,(THIS_FILE, + "Received strayed 2xx response (no dialog is found)" + " from %s:%d: %s", + rdata->pkt_info.src_name, rdata->pkt_info.src_port, + pjsip_rx_data_get_info(rdata))); + + return PJ_TRUE; + } + } + + /* At this point, we must have the dialog set, and the dialog set + * must have a dialog in the list. + */ + pj_assert(dlg_set && !pj_list_empty(&dlg_set->dlg_list)); + + /* Check for forked response. + * Request will fork only for the initial INVITE request. + */ + + //This doesn't work when there is authentication challenge, since + //first_cseq evaluation will yield false. + //if (rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD && + // rdata->msg_info.cseq->cseq == dlg_set->dlg_list.next->local.first_cseq) + + if (rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD) { + + int st_code = rdata->msg_info.msg->line.status.code; + pj_str_t *to_tag = &rdata->msg_info.to->tag; + + dlg = dlg_set->dlg_list.next; + + while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) { + + /* If there is dialog with no remote tag (i.e. dialog has not + * been established yet), then send this response to that + * dialog. + */ + if (dlg->remote.info->tag.slen == 0) + break; + + /* Otherwise find the one with matching To tag. */ + if (pj_strcmp(to_tag, &dlg->remote.info->tag) == 0) + break; + + dlg = dlg->next; + } + + /* If no dialog with matching remote tag is found, this must be + * a forked response. Respond to this ONLY when response is non-100 + * provisional response OR a 2xx response. + */ + if (dlg == (pjsip_dialog*)&dlg_set->dlg_list && + ((st_code/100==1 && st_code!=100) || st_code/100==2)) + { + + PJ_LOG(5,(THIS_FILE, + "Received forked %s for existing dialog %s", + pjsip_rx_data_get_info(rdata), + dlg_set->dlg_list.next->obj_name)); + + /* Report to application about forked condition. + * Application can either create a dialog or ignore the response. + */ + if (mod_ua.param.on_dlg_forked) { + dlg = (*mod_ua.param.on_dlg_forked)(dlg_set->dlg_list.next, + rdata); + if (dlg == NULL) { + pj_mutex_unlock(mod_ua.mutex); + return PJ_TRUE; + } + } else { + dlg = dlg_set->dlg_list.next; + + PJ_LOG(4,(THIS_FILE, + "Unhandled forked %s from %s:%d, response will be " + "handed over to the first dialog", + pjsip_rx_data_get_info(rdata), + rdata->pkt_info.src_name, rdata->pkt_info.src_port)); + } + + } else if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) { + + /* For 100 or non-2xx response which has different To tag, + * pass the response to the first dialog. + */ + + dlg = dlg_set->dlg_list.next; + + } + + } else { + /* Either this is a non-INVITE response, or subsequent INVITE + * within dialog. The dialog should have been identified when + * the transaction was found. + */ + pj_assert(tsx != NULL); + pj_assert(dlg != NULL); + } + + /* The dialog must have been found. */ + pj_assert(dlg != NULL); + + /* Put the dialog instance in the rdata. */ + rdata->endpt_info.mod_data[mod_ua.mod.id] = dlg; + + /* Attempt to acquire lock to the dialog. */ + PJ_LOG(6,(dlg->obj_name, "UA layer acquiring dialog lock for response")); + status = pjsip_dlg_try_inc_lock(dlg); + if (status != PJ_SUCCESS) { + /* Failed to acquire dialog mutex. This could indicate a deadlock + * situation, and for safety, try to avoid deadlock by releasing + * UA mutex, yield, and retry the whole processing once again. + */ + pj_mutex_unlock(mod_ua.mutex); + pj_thread_sleep(0); + goto retry_on_deadlock; + } + + /* We're done with processing in the UA layer, we can release the mutex */ + pj_mutex_unlock(mod_ua.mutex); + + /* Pass the response to the dialog. */ + pjsip_dlg_on_rx_response(dlg, rdata); + + /* Unlock the dialog. This may destroy the dialog. */ + pjsip_dlg_dec_lock(dlg); + + /* Done. */ + return PJ_TRUE; +} + + +#if PJ_LOG_MAX_LEVEL >= 3 +static void print_dialog( const char *title, + pjsip_dialog *dlg, char *buf, pj_size_t size) +{ + int len; + char userinfo[128]; + + len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); + if (len < 0) + pj_ansi_strcpy(userinfo, "<--uri too long-->"); + else + userinfo[len] = '\0'; + + len = pj_ansi_snprintf(buf, size, "%s[%s] %s", + title, + (dlg->state==PJSIP_DIALOG_STATE_NULL ? " - " : + "est"), + userinfo); + if (len < 1 || len >= (int)size) { + pj_ansi_strcpy(buf, "<--uri too long-->"); + } else + buf[len] = '\0'; +} +#endif + +/* + * Dump user agent contents (e.g. all dialogs). + */ +PJ_DEF(void) pjsip_ua_dump(pj_bool_t detail) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + pj_hash_iterator_t itbuf, *it; + char dlginfo[128]; + + pj_mutex_lock(mod_ua.mutex); + + PJ_LOG(3, (THIS_FILE, "Number of dialog sets: %u", + pj_hash_count(mod_ua.dlg_table))); + + if (detail && pj_hash_count(mod_ua.dlg_table)) { + PJ_LOG(3, (THIS_FILE, "Dumping dialog sets:")); + it = pj_hash_first(mod_ua.dlg_table, &itbuf); + for (; it != NULL; it = pj_hash_next(mod_ua.dlg_table, it)) { + struct dlg_set *dlg_set; + pjsip_dialog *dlg; + const char *title; + + dlg_set = (struct dlg_set*) pj_hash_this(mod_ua.dlg_table, it); + if (!dlg_set || pj_list_empty(&dlg_set->dlg_list)) continue; + + /* First dialog in dialog set. */ + dlg = dlg_set->dlg_list.next; + if (dlg->role == PJSIP_ROLE_UAC) + title = " [out] "; + else + title = " [in] "; + + print_dialog(title, dlg, dlginfo, sizeof(dlginfo)); + PJ_LOG(3,(THIS_FILE, "%s", dlginfo)); + + /* Next dialog in dialog set (forked) */ + dlg = dlg->next; + while (dlg != (pjsip_dialog*) &dlg_set->dlg_list) { + print_dialog(" [forked] ", dlg, dlginfo, sizeof(dlginfo)); + dlg = dlg->next; + } + } + } + + pj_mutex_unlock(mod_ua.mutex); +#endif +} + diff --git a/pjsip/src/pjsip/sip_uri.c b/pjsip/src/pjsip/sip_uri.c new file mode 100644 index 0000000..fba163c --- /dev/null +++ b/pjsip/src/pjsip/sip_uri.c @@ -0,0 +1,729 @@ +/* $Id: sip_uri.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_uri.h> +#include <pjsip/sip_msg.h> +#include <pjsip/sip_parser.h> +#include <pjsip/print_util.h> +#include <pjsip/sip_errno.h> +#include <pjlib-util/string.h> +#include <pj/string.h> +#include <pj/pool.h> +#include <pj/assert.h> + +/* + * Generic parameter manipulation. + */ +PJ_DEF(pjsip_param*) pjsip_param_find( const pjsip_param *param_list, + const pj_str_t *name ) +{ + pjsip_param *p = (pjsip_param*)param_list->next; + while (p != param_list) { + if (pj_stricmp(&p->name, name)==0) + return p; + p = p->next; + } + return NULL; +} + +PJ_DEF(int) pjsip_param_cmp( const pjsip_param *param_list1, + const pjsip_param *param_list2, + pj_bool_t ig_nf) +{ + const pjsip_param *p1; + + if ((ig_nf & 1)==0 && pj_list_size(param_list1)!=pj_list_size(param_list2)) + return 1; + + p1 = param_list1->next; + while (p1 != param_list1) { + const pjsip_param *p2; + p2 = pjsip_param_find(param_list2, &p1->name); + if (p2 ) { + int rc = pj_stricmp(&p1->value, &p2->value); + if (rc != 0) + return rc; + } else if ((ig_nf & 1)==0) + return 1; + + p1 = p1->next; + } + + return 0; +} + +PJ_DEF(void) pjsip_param_clone( pj_pool_t *pool, pjsip_param *dst_list, + const pjsip_param *src_list) +{ + const pjsip_param *p = src_list->next; + + pj_list_init(dst_list); + while (p && p != src_list) { + pjsip_param *new_param = PJ_POOL_ALLOC_T(pool, pjsip_param); + pj_strdup(pool, &new_param->name, &p->name); + pj_strdup(pool, &new_param->value, &p->value); + pj_list_insert_before(dst_list, new_param); + p = p->next; + } +} + + +PJ_DEF(void) pjsip_param_shallow_clone( pj_pool_t *pool, + pjsip_param *dst_list, + const pjsip_param *src_list) +{ + const pjsip_param *p = src_list->next; + + pj_list_init(dst_list); + while (p != src_list) { + pjsip_param *new_param = PJ_POOL_ALLOC_T(pool, pjsip_param); + new_param->name = p->name; + new_param->value = p->value; + pj_list_insert_before(dst_list, new_param); + p = p->next; + } +} + +PJ_DEF(pj_ssize_t) pjsip_param_print_on( const pjsip_param *param_list, + char *buf, pj_size_t size, + const pj_cis_t *pname_spec, + const pj_cis_t *pvalue_spec, + int sep) +{ + const pjsip_param *p; + char *startbuf; + char *endbuf; + int printed; + + p = param_list->next; + if (p == NULL || p == param_list) + return 0; + + startbuf = buf; + endbuf = buf + size; + + PJ_UNUSED_ARG(pname_spec); + + do { + *buf++ = (char)sep; + copy_advance_escape(buf, p->name, (*pname_spec)); + if (p->value.slen) { + *buf++ = '='; + if (*p->value.ptr == '"') + copy_advance(buf, p->value); + else + copy_advance_escape(buf, p->value, (*pvalue_spec)); + } + p = p->next; + if (sep == '?') sep = '&'; + } while (p != param_list); + + return buf-startbuf; +} + + +/* + * URI stuffs + */ +#define IS_SIPS(url) ((url)->vptr==&sips_url_vptr) + +static const pj_str_t *pjsip_url_get_scheme( const pjsip_sip_uri* ); +static const pj_str_t *pjsips_url_get_scheme( const pjsip_sip_uri* ); +static const pj_str_t *pjsip_name_addr_get_scheme( const pjsip_name_addr * ); +static void *pjsip_get_uri( pjsip_uri *uri ); +static void *pjsip_name_addr_get_uri( pjsip_name_addr *name ); + +static pj_str_t sip_str = { "sip", 3 }; +static pj_str_t sips_str = { "sips", 4 }; + +static pjsip_name_addr* pjsip_name_addr_clone( pj_pool_t *pool, + const pjsip_name_addr *rhs); +static pj_ssize_t pjsip_name_addr_print(pjsip_uri_context_e context, + const pjsip_name_addr *name, + char *buf, pj_size_t size); +static int pjsip_name_addr_compare( pjsip_uri_context_e context, + const pjsip_name_addr *naddr1, + const pjsip_name_addr *naddr2); +static pj_ssize_t pjsip_url_print( pjsip_uri_context_e context, + const pjsip_sip_uri *url, + char *buf, pj_size_t size); +static int pjsip_url_compare( pjsip_uri_context_e context, + const pjsip_sip_uri *url1, + const pjsip_sip_uri *url2); +static pjsip_sip_uri* pjsip_url_clone(pj_pool_t *pool, + const pjsip_sip_uri *rhs); + +typedef const pj_str_t* (*P_GET_SCHEME)(const void*); +typedef void* (*P_GET_URI)(void*); +typedef pj_ssize_t (*P_PRINT_URI)(pjsip_uri_context_e,const void *, + char*,pj_size_t); +typedef int (*P_CMP_URI)(pjsip_uri_context_e, const void*, + const void*); +typedef void* (*P_CLONE)(pj_pool_t*, const void*); + + +static pjsip_uri_vptr sip_url_vptr = +{ + (P_GET_SCHEME) &pjsip_url_get_scheme, + (P_GET_URI) &pjsip_get_uri, + (P_PRINT_URI) &pjsip_url_print, + (P_CMP_URI) &pjsip_url_compare, + (P_CLONE) &pjsip_url_clone +}; + +static pjsip_uri_vptr sips_url_vptr = +{ + (P_GET_SCHEME) &pjsips_url_get_scheme, + (P_GET_URI) &pjsip_get_uri, + (P_PRINT_URI) &pjsip_url_print, + (P_CMP_URI) &pjsip_url_compare, + (P_CLONE) &pjsip_url_clone +}; + +static pjsip_uri_vptr name_addr_vptr = +{ + (P_GET_SCHEME) &pjsip_name_addr_get_scheme, + (P_GET_URI) &pjsip_name_addr_get_uri, + (P_PRINT_URI) &pjsip_name_addr_print, + (P_CMP_URI) &pjsip_name_addr_compare, + (P_CLONE) &pjsip_name_addr_clone +}; + +static const pj_str_t *pjsip_url_get_scheme(const pjsip_sip_uri *url) +{ + PJ_UNUSED_ARG(url); + return &sip_str; +} + +static const pj_str_t *pjsips_url_get_scheme(const pjsip_sip_uri *url) +{ + PJ_UNUSED_ARG(url); + return &sips_str; +} + +static void *pjsip_get_uri( pjsip_uri *uri ) +{ + return uri; +} + +static void *pjsip_name_addr_get_uri( pjsip_name_addr *name ) +{ + return pjsip_uri_get_uri(name->uri); +} + +PJ_DEF(void) pjsip_sip_uri_set_secure( pjsip_sip_uri *url, + pj_bool_t secure ) +{ + url->vptr = secure ? &sips_url_vptr : &sip_url_vptr; +} + +PJ_DEF(void) pjsip_sip_uri_init(pjsip_sip_uri *url, pj_bool_t secure) +{ + pj_bzero(url, sizeof(*url)); + url->ttl_param = -1; + pjsip_sip_uri_set_secure(url, secure); + pj_list_init(&url->other_param); + pj_list_init(&url->header_param); +} + +PJ_DEF(pjsip_sip_uri*) pjsip_sip_uri_create( pj_pool_t *pool, + pj_bool_t secure ) +{ + pjsip_sip_uri *url = PJ_POOL_ALLOC_T(pool, pjsip_sip_uri); + pjsip_sip_uri_init(url, secure); + return url; +} + +static pj_ssize_t pjsip_url_print( pjsip_uri_context_e context, + const pjsip_sip_uri *url, + char *buf, pj_size_t size) +{ + int printed; + char *startbuf = buf; + char *endbuf = buf+size; + const pj_str_t *scheme; + const pjsip_parser_const_t *pc = pjsip_parser_const(); + + *buf = '\0'; + + /* Print scheme ("sip:" or "sips:") */ + scheme = pjsip_uri_get_scheme(url); + copy_advance_check(buf, *scheme); + *buf++ = ':'; + + /* Print "user:password@", if any. */ + if (url->user.slen) { + copy_advance_escape(buf, url->user, pc->pjsip_USER_SPEC); + if (url->passwd.slen) { + *buf++ = ':'; + copy_advance_escape(buf, url->passwd, pc->pjsip_PASSWD_SPEC); + } + + *buf++ = '@'; + } + + /* Print host. */ + pj_assert(url->host.slen != 0); + /* Detect IPv6 IP address */ + if (pj_memchr(url->host.ptr, ':', url->host.slen)) { + copy_advance_pair_quote_cond(buf, "", 0, url->host, '[', ']'); + } else { + copy_advance_check(buf, url->host); + } + + /* Only print port if it is explicitly specified. + * Port is not allowed in To and From header, see Table 1 in + * RFC 3261 Section 19.1.1 + */ + /* Note: ticket #1141 adds run-time setting to allow port number to + * appear in From/To header. Default is still false. + */ + if (url->port && + (context != PJSIP_URI_IN_FROMTO_HDR || + pjsip_cfg()->endpt.allow_port_in_fromto_hdr)) + { + if (endbuf - buf < 10) + return -1; + + *buf++ = ':'; + printed = pj_utoa(url->port, buf); + buf += printed; + } + + /* User param is allowed in all contexes */ + copy_advance_pair_check(buf, ";user=", 6, url->user_param); + + /* Method param is only allowed in external/other context. */ + if (context == PJSIP_URI_IN_OTHER) { + copy_advance_pair_escape(buf, ";method=", 8, url->method_param, + pc->pjsip_PARAM_CHAR_SPEC); + } + + /* Transport is not allowed in From/To header. */ + if (context != PJSIP_URI_IN_FROMTO_HDR) { + copy_advance_pair_escape(buf, ";transport=", 11, url->transport_param, + pc->pjsip_PARAM_CHAR_SPEC); + } + + /* TTL param is not allowed in From, To, Route, and Record-Route header. */ + if (url->ttl_param >= 0 && context != PJSIP_URI_IN_FROMTO_HDR && + context != PJSIP_URI_IN_ROUTING_HDR) + { + if (endbuf - buf < 15) + return -1; + pj_memcpy(buf, ";ttl=", 5); + printed = pj_utoa(url->ttl_param, buf+5); + buf += printed + 5; + } + + /* maddr param is not allowed in From and To header. */ + if (context != PJSIP_URI_IN_FROMTO_HDR && url->maddr_param.slen) { + /* Detect IPv6 IP address */ + if (pj_memchr(url->maddr_param.ptr, ':', url->maddr_param.slen)) { + copy_advance_pair_quote_cond(buf, ";maddr=", 7, url->maddr_param, + '[', ']'); + } else { + copy_advance_pair_escape(buf, ";maddr=", 7, url->maddr_param, + pc->pjsip_PARAM_CHAR_SPEC); + } + } + + /* lr param is not allowed in From, To, and Contact header. */ + if (url->lr_param && context != PJSIP_URI_IN_FROMTO_HDR && + context != PJSIP_URI_IN_CONTACT_HDR) + { + pj_str_t lr = { ";lr", 3 }; + if (endbuf - buf < 3) + return -1; + copy_advance_check(buf, lr); + } + + /* Other param. */ + printed = pjsip_param_print_on(&url->other_param, buf, endbuf-buf, + &pc->pjsip_PARAM_CHAR_SPEC, + &pc->pjsip_PARAM_CHAR_SPEC, ';'); + if (printed < 0) + return -1; + buf += printed; + + /* Header param. + * Header param is only allowed in these contexts: + * - PJSIP_URI_IN_CONTACT_HDR + * - PJSIP_URI_IN_OTHER + */ + if (context == PJSIP_URI_IN_CONTACT_HDR || context == PJSIP_URI_IN_OTHER) { + printed = pjsip_param_print_on(&url->header_param, buf, endbuf-buf, + &pc->pjsip_HDR_CHAR_SPEC, + &pc->pjsip_HDR_CHAR_SPEC, '?'); + if (printed < 0) + return -1; + buf += printed; + } + + *buf = '\0'; + return buf-startbuf; +} + +static pj_status_t pjsip_url_compare( pjsip_uri_context_e context, + const pjsip_sip_uri *url1, + const pjsip_sip_uri *url2) +{ + const pjsip_param *p1; + + /* + * Compare two SIP URL's according to Section 19.1.4 of RFC 3261. + */ + + /* SIP and SIPS URI are never equivalent. + * Note: just compare the vptr to avoid string comparison. + * Pretty neat huh!! + */ + if (url1->vptr != url2->vptr) + return PJSIP_ECMPSCHEME; + + /* Comparison of the userinfo of SIP and SIPS URIs is case-sensitive. + * This includes userinfo containing passwords or formatted as + * telephone-subscribers. + */ + if (pj_strcmp(&url1->user, &url2->user) != 0) + return PJSIP_ECMPUSER; + if (pj_strcmp(&url1->passwd, &url2->passwd) != 0) + return PJSIP_ECMPPASSWD; + + /* Comparison of all other components of the URI is + * case-insensitive unless explicitly defined otherwise. + */ + + /* The ordering of parameters and header fields is not significant + * in comparing SIP and SIPS URIs. + */ + + /* Characters other than those in the reserved set (see RFC 2396 [5]) + * are equivalent to their encoding. + */ + + /* An IP address that is the result of a DNS lookup of a host name + * does not match that host name. + */ + if (pj_stricmp(&url1->host, &url2->host) != 0) + return PJSIP_ECMPHOST; + + /* A URI omitting any component with a default value will not match a URI + * explicitly containing that component with its default value. + * For instance, a URI omitting the optional port component will not match + * a URI explicitly declaring port 5060. + * The same is true for the transport-parameter, ttl-parameter, + * user-parameter, and method components. + */ + + /* Port is not allowed in To and From header. + */ + if (context != PJSIP_URI_IN_FROMTO_HDR) { + if (url1->port != url2->port) + return PJSIP_ECMPPORT; + } + /* Transport is not allowed in From/To header. */ + if (context != PJSIP_URI_IN_FROMTO_HDR) { + if (pj_stricmp(&url1->transport_param, &url2->transport_param) != 0) + return PJSIP_ECMPTRANSPORTPRM; + } + /* TTL param is not allowed in From, To, Route, and Record-Route header. */ + if (context != PJSIP_URI_IN_FROMTO_HDR && + context != PJSIP_URI_IN_ROUTING_HDR) + { + if (url1->ttl_param != url2->ttl_param) + return PJSIP_ECMPTTLPARAM; + } + /* User param is allowed in all contexes */ + if (pj_stricmp(&url1->user_param, &url2->user_param) != 0) + return PJSIP_ECMPUSERPARAM; + /* Method param is only allowed in external/other context. */ + if (context == PJSIP_URI_IN_OTHER) { + if (pj_stricmp(&url1->method_param, &url2->method_param) != 0) + return PJSIP_ECMPMETHODPARAM; + } + /* maddr param is not allowed in From and To header. */ + if (context != PJSIP_URI_IN_FROMTO_HDR) { + if (pj_stricmp(&url1->maddr_param, &url2->maddr_param) != 0) + return PJSIP_ECMPMADDRPARAM; + } + + /* lr parameter is ignored (?) */ + /* lr param is not allowed in From, To, and Contact header. */ + + + /* All other uri-parameters appearing in only one URI are ignored when + * comparing the URIs. + */ + if (pjsip_param_cmp(&url1->other_param, &url2->other_param, 1)!=0) + return PJSIP_ECMPOTHERPARAM; + + /* URI header components are never ignored. Any present header component + * MUST be present in both URIs and match for the URIs to match. + * The matching rules are defined for each header field in Section 20. + */ + p1 = url1->header_param.next; + while (p1 != &url1->header_param) { + const pjsip_param *p2; + p2 = pjsip_param_find(&url2->header_param, &p1->name); + if (p2) { + /* It seems too much to compare two header params according to + * the rule of each header. We'll just compare them string to + * string.. + */ + if (pj_stricmp(&p1->value, &p2->value) != 0) + return PJSIP_ECMPHEADERPARAM; + } else { + return PJSIP_ECMPHEADERPARAM; + } + p1 = p1->next; + } + + /* Equal!! Pheuww.. */ + return PJ_SUCCESS; +} + + +PJ_DEF(void) pjsip_sip_uri_assign(pj_pool_t *pool, pjsip_sip_uri *url, + const pjsip_sip_uri *rhs) +{ + pj_strdup( pool, &url->user, &rhs->user); + pj_strdup( pool, &url->passwd, &rhs->passwd); + pj_strdup( pool, &url->host, &rhs->host); + url->port = rhs->port; + pj_strdup( pool, &url->user_param, &rhs->user_param); + pj_strdup( pool, &url->method_param, &rhs->method_param); + pj_strdup( pool, &url->transport_param, &rhs->transport_param); + url->ttl_param = rhs->ttl_param; + pj_strdup( pool, &url->maddr_param, &rhs->maddr_param); + pjsip_param_clone(pool, &url->other_param, &rhs->other_param); + pjsip_param_clone(pool, &url->header_param, &rhs->header_param); + url->lr_param = rhs->lr_param; +} + +static pjsip_sip_uri* pjsip_url_clone(pj_pool_t *pool, const pjsip_sip_uri *rhs) +{ + pjsip_sip_uri *url = PJ_POOL_ALLOC_T(pool, pjsip_sip_uri); + if (!url) + return NULL; + + pjsip_sip_uri_init(url, IS_SIPS(rhs)); + pjsip_sip_uri_assign(pool, url, rhs); + return url; +} + +static const pj_str_t *pjsip_name_addr_get_scheme(const pjsip_name_addr *name) +{ + pj_assert(name->uri != NULL); + return pjsip_uri_get_scheme(name->uri); +} + +PJ_DEF(void) pjsip_name_addr_init(pjsip_name_addr *name) +{ + name->vptr = &name_addr_vptr; + name->uri = NULL; + name->display.slen = 0; + name->display.ptr = NULL; +} + +PJ_DEF(pjsip_name_addr*) pjsip_name_addr_create(pj_pool_t *pool) +{ + pjsip_name_addr *name_addr = PJ_POOL_ALLOC_T(pool, pjsip_name_addr); + pjsip_name_addr_init(name_addr); + return name_addr; +} + +static pj_ssize_t pjsip_name_addr_print(pjsip_uri_context_e context, + const pjsip_name_addr *name, + char *buf, pj_size_t size) +{ + int printed; + char *startbuf = buf; + char *endbuf = buf + size; + pjsip_uri *uri; + + uri = (pjsip_uri*) pjsip_uri_get_uri(name->uri); + pj_assert(uri != NULL); + + if (context != PJSIP_URI_IN_REQ_URI) { + if (name->display.slen) { + if (endbuf-buf < 8) return -1; + *buf++ = '"'; + copy_advance(buf, name->display); + *buf++ = '"'; + *buf++ = ' '; + } + *buf++ = '<'; + } + + printed = pjsip_uri_print(context,uri, buf, size-(buf-startbuf)); + if (printed < 1) + return -1; + buf += printed; + + if (context != PJSIP_URI_IN_REQ_URI) { + *buf++ = '>'; + } + + *buf = '\0'; + return buf-startbuf; +} + +PJ_DEF(void) pjsip_name_addr_assign(pj_pool_t *pool, pjsip_name_addr *dst, + const pjsip_name_addr *src) +{ + pj_strdup( pool, &dst->display, &src->display); + dst->uri = (pjsip_uri*) pjsip_uri_clone(pool, src->uri); +} + +static pjsip_name_addr* pjsip_name_addr_clone( pj_pool_t *pool, + const pjsip_name_addr *rhs) +{ + pjsip_name_addr *addr = PJ_POOL_ALLOC_T(pool, pjsip_name_addr); + if (!addr) + return NULL; + + pjsip_name_addr_init(addr); + pjsip_name_addr_assign(pool, addr, rhs); + return addr; +} + +static int pjsip_name_addr_compare( pjsip_uri_context_e context, + const pjsip_name_addr *naddr1, + const pjsip_name_addr *naddr2) +{ + int d; + + /* Check that naddr2 is also a name_addr */ + if (naddr1->vptr != naddr2->vptr) + return -1; + + /* I'm not sure whether display name is included in the comparison. */ + if (pj_strcmp(&naddr1->display, &naddr2->display) != 0) { + return -1; + } + + pj_assert( naddr1->uri != NULL ); + pj_assert( naddr2->uri != NULL ); + + /* Compare name-addr as URL */ + d = pjsip_uri_cmp( context, naddr1->uri, naddr2->uri); + if (d) + return d; + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +static const pj_str_t *other_uri_get_scheme( const pjsip_other_uri*); +static void *other_uri_get_uri( pjsip_other_uri*); +static pj_ssize_t other_uri_print( pjsip_uri_context_e context, + const pjsip_other_uri *url, + char *buf, pj_size_t size); +static int other_uri_cmp( pjsip_uri_context_e context, + const pjsip_other_uri *url1, + const pjsip_other_uri *url2); +static pjsip_other_uri* other_uri_clone( pj_pool_t *pool, + const pjsip_other_uri *rhs); + +static pjsip_uri_vptr other_uri_vptr = +{ + (P_GET_SCHEME) &other_uri_get_scheme, + (P_GET_URI) &other_uri_get_uri, + (P_PRINT_URI) &other_uri_print, + (P_CMP_URI) &other_uri_cmp, + (P_CLONE) &other_uri_clone +}; + + +PJ_DEF(pjsip_other_uri*) pjsip_other_uri_create(pj_pool_t *pool) +{ + pjsip_other_uri *uri = PJ_POOL_ZALLOC_T(pool, pjsip_other_uri); + uri->vptr = &other_uri_vptr; + return uri; +} + +static const pj_str_t *other_uri_get_scheme( const pjsip_other_uri *uri ) +{ + return &uri->scheme; +} + +static void *other_uri_get_uri( pjsip_other_uri *uri ) +{ + return uri; +} + +static pj_ssize_t other_uri_print(pjsip_uri_context_e context, + const pjsip_other_uri *uri, + char *buf, pj_size_t size) +{ + char *startbuf = buf; + char *endbuf = buf + size; + + PJ_UNUSED_ARG(context); + + if (uri->scheme.slen + uri->content.slen + 1 > (int)size) + return -1; + + /* Print scheme. */ + copy_advance(buf, uri->scheme); + *buf++ = ':'; + + /* Print content. */ + copy_advance(buf, uri->content); + + return (buf - startbuf); +} + +static int other_uri_cmp(pjsip_uri_context_e context, + const pjsip_other_uri *uri1, + const pjsip_other_uri *uri2) +{ + PJ_UNUSED_ARG(context); + + /* Check that uri2 is also an other_uri */ + if (uri1->vptr != uri2->vptr) + return -1; + + /* Scheme must match. */ + if (pj_stricmp(&uri1->scheme, &uri2->scheme) != 0) { + return PJSIP_ECMPSCHEME; + } + + /* Content must match. */ + if(pj_stricmp(&uri1->content, &uri2->content) != 0) { + return -1; + } + + /* Equal. */ + return 0; +} + +/* Clone *: URI */ +static pjsip_other_uri* other_uri_clone(pj_pool_t *pool, + const pjsip_other_uri *rhs) +{ + pjsip_other_uri *uri = pjsip_other_uri_create(pool); + pj_strdup(pool, &uri->scheme, &rhs->scheme); + pj_strdup(pool, &uri->content, &rhs->content); + + return uri; +} + diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c new file mode 100644 index 0000000..60f2568 --- /dev/null +++ b/pjsip/src/pjsip/sip_util.c @@ -0,0 +1,1850 @@ +/* $Id: sip_util.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_util.h> +#include <pjsip/sip_transport.h> +#include <pjsip/sip_msg.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_errno.h> +#include <pj/log.h> +#include <pj/string.h> +#include <pj/guid.h> +#include <pj/pool.h> +#include <pj/except.h> +#include <pj/rand.h> +#include <pj/assert.h> +#include <pj/errno.h> + +#define THIS_FILE "endpoint" + +static const char *event_str[] = +{ + "UNIDENTIFIED", + "TIMER", + "TX_MSG", + "RX_MSG", + "TRANSPORT_ERROR", + "TSX_STATE", + "USER", +}; + +static pj_str_t str_TEXT = { "text", 4}, + str_PLAIN = { "plain", 5 }; + +/* Add URI to target-set */ +PJ_DEF(pj_status_t) pjsip_target_set_add_uri( pjsip_target_set *tset, + pj_pool_t *pool, + const pjsip_uri *uri, + int q1000) +{ + pjsip_target *t, *pos = NULL; + + PJ_ASSERT_RETURN(tset && pool && uri, PJ_EINVAL); + + /* Set q-value to 1 if it is not set */ + if (q1000 <= 0) + q1000 = 1000; + + /* Scan all the elements to see for duplicates, and at the same time + * get the position where the new element should be inserted to + * based on the q-value. + */ + t = tset->head.next; + while (t != &tset->head) { + if (pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI, t->uri, uri)==PJ_SUCCESS) + return PJ_EEXISTS; + if (pos==NULL && t->q1000 < q1000) + pos = t; + t = t->next; + } + + /* Create new element */ + t = PJ_POOL_ZALLOC_T(pool, pjsip_target); + t->uri = (pjsip_uri*)pjsip_uri_clone(pool, uri); + t->q1000 = q1000; + + /* Insert */ + if (pos == NULL) + pj_list_push_back(&tset->head, t); + else + pj_list_insert_before(pos, t); + + /* Set current target if this is the first URI */ + if (tset->current == NULL) + tset->current = t; + + return PJ_SUCCESS; +} + +/* Add URI's in the Contact header in the message to target-set */ +PJ_DEF(pj_status_t) pjsip_target_set_add_from_msg( pjsip_target_set *tset, + pj_pool_t *pool, + const pjsip_msg *msg) +{ + const pjsip_hdr *hdr; + unsigned added = 0; + + PJ_ASSERT_RETURN(tset && pool && msg, PJ_EINVAL); + + /* Scan for Contact headers and add the URI */ + hdr = msg->hdr.next; + while (hdr != &msg->hdr) { + if (hdr->type == PJSIP_H_CONTACT) { + const pjsip_contact_hdr *cn_hdr = (const pjsip_contact_hdr*)hdr; + + if (!cn_hdr->star) { + pj_status_t rc; + rc = pjsip_target_set_add_uri(tset, pool, cn_hdr->uri, + cn_hdr->q1000); + if (rc == PJ_SUCCESS) + ++added; + } + } + hdr = hdr->next; + } + + return added ? PJ_SUCCESS : PJ_EEXISTS; +} + + +/* Get next target, if any */ +PJ_DEF(pjsip_target*) pjsip_target_set_get_next(const pjsip_target_set *tset) +{ + const pjsip_target *t, *next = NULL; + + t = tset->head.next; + while (t != &tset->head) { + if (PJSIP_IS_STATUS_IN_CLASS(t->code, 200)) { + /* No more target since one target has been successful */ + return NULL; + } + if (PJSIP_IS_STATUS_IN_CLASS(t->code, 600)) { + /* No more target since one target returned global error */ + return NULL; + } + if (t->code==0 && next==NULL) { + /* This would be the next target as long as we don't find + * targets with 2xx or 6xx status after this. + */ + next = t; + } + t = t->next; + } + + return (pjsip_target*)next; +} + + +/* Set current target */ +PJ_DEF(pj_status_t) pjsip_target_set_set_current( pjsip_target_set *tset, + pjsip_target *target) +{ + PJ_ASSERT_RETURN(tset && target, PJ_EINVAL); + PJ_ASSERT_RETURN(pj_list_find_node(tset, target) != NULL, PJ_ENOTFOUND); + + tset->current = target; + + return PJ_SUCCESS; +} + + +/* Assign status to a target */ +PJ_DEF(pj_status_t) pjsip_target_assign_status( pjsip_target *target, + pj_pool_t *pool, + int status_code, + const pj_str_t *reason) +{ + PJ_ASSERT_RETURN(target && pool && status_code && reason, PJ_EINVAL); + + target->code = (pjsip_status_code)status_code; + pj_strdup(pool, &target->reason, reason); + + return PJ_SUCCESS; +} + + + +/* + * Initialize transmit data (msg) with the headers and optional body. + * This will just put the headers in the message as it is. Be carefull + * when calling this function because once a header is put in a message, + * it CAN NOT be put in other message until the first message is deleted, + * because the way the header is put in the list. + * That's why the session will shallow_clone it's headers before calling + * this function. + */ +static void init_request_throw( pjsip_endpoint *endpt, + pjsip_tx_data *tdata, + pjsip_method *method, + pjsip_uri *param_target, + pjsip_from_hdr *param_from, + pjsip_to_hdr *param_to, + pjsip_contact_hdr *param_contact, + pjsip_cid_hdr *param_call_id, + pjsip_cseq_hdr *param_cseq, + const pj_str_t *param_text) +{ + pjsip_msg *msg; + pjsip_msg_body *body; + pjsip_via_hdr *via; + const pjsip_hdr *endpt_hdr; + + /* Create the message. */ + msg = tdata->msg = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG); + + /* Init request URI. */ + pj_memcpy(&msg->line.req.method, method, sizeof(*method)); + msg->line.req.uri = param_target; + + /* Add additional request headers from endpoint. */ + endpt_hdr = pjsip_endpt_get_request_headers(endpt)->next; + while (endpt_hdr != pjsip_endpt_get_request_headers(endpt)) { + pjsip_hdr *hdr = (pjsip_hdr*) + pjsip_hdr_shallow_clone(tdata->pool, endpt_hdr); + pjsip_msg_add_hdr( tdata->msg, hdr ); + endpt_hdr = endpt_hdr->next; + } + + /* Add From header. */ + if (param_from->tag.slen == 0) + pj_create_unique_string(tdata->pool, ¶m_from->tag); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_from); + + /* Add To header. */ + pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_to); + + /* Add Contact header. */ + if (param_contact) { + pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_contact); + } + + /* Add Call-ID header. */ + pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_call_id); + + /* Add CSeq header. */ + pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_cseq); + + /* Add a blank Via header in the front of the message. */ + via = pjsip_via_hdr_create(tdata->pool); + via->rport_param = pjsip_cfg()->endpt.disable_rport ? -1 : 0; + pjsip_msg_insert_first_hdr(msg, (pjsip_hdr*)via); + + /* Add header params as request headers */ + if (PJSIP_URI_SCHEME_IS_SIP(param_target) || + PJSIP_URI_SCHEME_IS_SIPS(param_target)) + { + pjsip_sip_uri *uri = (pjsip_sip_uri*) pjsip_uri_get_uri(param_target); + pjsip_param *hparam; + + hparam = uri->header_param.next; + while (hparam != &uri->header_param) { + pjsip_generic_string_hdr *hdr; + + hdr = pjsip_generic_string_hdr_create(tdata->pool, + &hparam->name, + &hparam->value); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)hdr); + hparam = hparam->next; + } + } + + /* Create message body. */ + if (param_text) { + body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body); + body->content_type.type = str_TEXT; + body->content_type.subtype = str_PLAIN; + body->data = pj_pool_alloc(tdata->pool, param_text->slen ); + pj_memcpy(body->data, param_text->ptr, param_text->slen); + body->len = param_text->slen; + body->print_body = &pjsip_print_text_body; + msg->body = body; + } + + PJ_LOG(5,(THIS_FILE, "%s created.", + pjsip_tx_data_get_info(tdata))); + +} + +/* + * Create arbitrary request. + */ +PJ_DEF(pj_status_t) pjsip_endpt_create_request( pjsip_endpoint *endpt, + const pjsip_method *method, + const pj_str_t *param_target, + const pj_str_t *param_from, + const pj_str_t *param_to, + const pj_str_t *param_contact, + const pj_str_t *param_call_id, + int param_cseq, + const pj_str_t *param_text, + pjsip_tx_data **p_tdata) +{ + pjsip_uri *target; + pjsip_tx_data *tdata; + pjsip_from_hdr *from; + pjsip_to_hdr *to; + pjsip_contact_hdr *contact; + pjsip_cseq_hdr *cseq = NULL; /* = NULL, warning in VC6 */ + pjsip_cid_hdr *call_id; + pj_str_t tmp; + pj_status_t status; + const pj_str_t STR_CONTACT = { "Contact", 7 }; + PJ_USE_EXCEPTION; + + status = pjsip_endpt_create_tdata(endpt, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Init reference counter to 1. */ + pjsip_tx_data_add_ref(tdata); + + PJ_TRY { + /* Request target. */ + pj_strdup_with_null(tdata->pool, &tmp, param_target); + target = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, 0); + if (target == NULL) { + status = PJSIP_EINVALIDREQURI; + goto on_error; + } + + /* From */ + from = pjsip_from_hdr_create(tdata->pool); + pj_strdup_with_null(tdata->pool, &tmp, param_from); + from->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (from->uri == NULL) { + status = PJSIP_EINVALIDHDR; + goto on_error; + } + pj_create_unique_string(tdata->pool, &from->tag); + + /* To */ + to = pjsip_to_hdr_create(tdata->pool); + pj_strdup_with_null(tdata->pool, &tmp, param_to); + to->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (to->uri == NULL) { + status = PJSIP_EINVALIDHDR; + goto on_error; + } + + /* Contact. */ + if (param_contact) { + pj_strdup_with_null(tdata->pool, &tmp, param_contact); + contact = (pjsip_contact_hdr*) + pjsip_parse_hdr(tdata->pool, &STR_CONTACT, tmp.ptr, + tmp.slen, NULL); + if (contact == NULL) { + status = PJSIP_EINVALIDHDR; + goto on_error; + } + } else { + contact = NULL; + } + + /* Call-ID */ + call_id = pjsip_cid_hdr_create(tdata->pool); + if (param_call_id != NULL && param_call_id->slen) + pj_strdup(tdata->pool, &call_id->id, param_call_id); + else + pj_create_unique_string(tdata->pool, &call_id->id); + + /* CSeq */ + cseq = pjsip_cseq_hdr_create(tdata->pool); + if (param_cseq >= 0) + cseq->cseq = param_cseq; + else + cseq->cseq = pj_rand() & 0xFFFF; + + /* Method */ + pjsip_method_copy(tdata->pool, &cseq->method, method); + + /* Create the request. */ + init_request_throw( endpt, tdata, &cseq->method, target, from, to, + contact, call_id, cseq, param_text); + } + PJ_CATCH_ANY { + status = PJ_ENOMEM; + goto on_error; + } + PJ_END + + *p_tdata = tdata; + return PJ_SUCCESS; + +on_error: + pjsip_tx_data_dec_ref(tdata); + return status; +} + +PJ_DEF(pj_status_t) pjsip_endpt_create_request_from_hdr( pjsip_endpoint *endpt, + const pjsip_method *method, + const pjsip_uri *param_target, + const pjsip_from_hdr *param_from, + const pjsip_to_hdr *param_to, + const pjsip_contact_hdr *param_contact, + const pjsip_cid_hdr *param_call_id, + int param_cseq, + const pj_str_t *param_text, + pjsip_tx_data **p_tdata) +{ + pjsip_uri *target; + pjsip_tx_data *tdata; + pjsip_from_hdr *from; + pjsip_to_hdr *to; + pjsip_contact_hdr *contact; + pjsip_cid_hdr *call_id; + pjsip_cseq_hdr *cseq = NULL; /* The NULL because warning in VC6 */ + pj_status_t status; + PJ_USE_EXCEPTION; + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt && method && param_target && param_from && + param_to && p_tdata, PJ_EINVAL); + + /* Create new transmit data. */ + status = pjsip_endpt_create_tdata(endpt, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Set initial reference counter to 1. */ + pjsip_tx_data_add_ref(tdata); + + PJ_TRY { + /* Duplicate target URI and headers. */ + target = (pjsip_uri*) pjsip_uri_clone(tdata->pool, param_target); + from = (pjsip_from_hdr*) pjsip_hdr_clone(tdata->pool, param_from); + pjsip_fromto_hdr_set_from(from); + to = (pjsip_to_hdr*) pjsip_hdr_clone(tdata->pool, param_to); + pjsip_fromto_hdr_set_to(to); + if (param_contact) { + contact = (pjsip_contact_hdr*) + pjsip_hdr_clone(tdata->pool, param_contact); + } else { + contact = NULL; + } + call_id = pjsip_cid_hdr_create(tdata->pool); + if (param_call_id != NULL && param_call_id->id.slen) + pj_strdup(tdata->pool, &call_id->id, ¶m_call_id->id); + else + pj_create_unique_string(tdata->pool, &call_id->id); + + cseq = pjsip_cseq_hdr_create(tdata->pool); + if (param_cseq >= 0) + cseq->cseq = param_cseq; + else + cseq->cseq = pj_rand() % 0xFFFF; + pjsip_method_copy(tdata->pool, &cseq->method, method); + + /* Copy headers to the request. */ + init_request_throw(endpt, tdata, &cseq->method, target, from, to, + contact, call_id, cseq, param_text); + } + PJ_CATCH_ANY { + status = PJ_ENOMEM; + goto on_error; + } + PJ_END; + + *p_tdata = tdata; + return PJ_SUCCESS; + +on_error: + pjsip_tx_data_dec_ref(tdata); + return status; +} + +/* + * Construct a minimal response message for the received request. + */ +PJ_DEF(pj_status_t) pjsip_endpt_create_response( pjsip_endpoint *endpt, + const pjsip_rx_data *rdata, + int st_code, + const pj_str_t *st_text, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsip_msg *msg, *req_msg; + pjsip_hdr *hdr; + pjsip_to_hdr *to_hdr; + pjsip_via_hdr *top_via = NULL, *via; + pjsip_rr_hdr *rr; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL); + + /* Check status code. */ + PJ_ASSERT_RETURN(st_code >= 100 && st_code <= 699, PJ_EINVAL); + + /* rdata must be a request message. */ + req_msg = rdata->msg_info.msg; + pj_assert(req_msg->type == PJSIP_REQUEST_MSG); + + /* Request MUST NOT be ACK request! */ + PJ_ASSERT_RETURN(req_msg->line.req.method.id != PJSIP_ACK_METHOD, + PJ_EINVALIDOP); + + /* Create a new transmit buffer. */ + status = pjsip_endpt_create_tdata( endpt, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Set initial reference count to 1. */ + pjsip_tx_data_add_ref(tdata); + + /* Create new response message. */ + tdata->msg = msg = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG); + + /* Set status code and reason text. */ + msg->line.status.code = st_code; + if (st_text) + pj_strdup(tdata->pool, &msg->line.status.reason, st_text); + else + msg->line.status.reason = *pjsip_get_status_text(st_code); + + /* Set TX data attributes. */ + tdata->rx_timestamp = rdata->pkt_info.timestamp; + + /* Copy all the via headers, in order. */ + via = rdata->msg_info.via; + while (via) { + pjsip_via_hdr *new_via; + + new_via = (pjsip_via_hdr*)pjsip_hdr_clone(tdata->pool, via); + if (top_via == NULL) + top_via = new_via; + + pjsip_msg_add_hdr( msg, (pjsip_hdr*)new_via); + via = via->next; + if (via != (void*)&req_msg->hdr) + via = (pjsip_via_hdr*) + pjsip_msg_find_hdr(req_msg, PJSIP_H_VIA, via); + else + break; + } + + /* Copy all Record-Route headers, in order. */ + rr = (pjsip_rr_hdr*) + pjsip_msg_find_hdr(req_msg, PJSIP_H_RECORD_ROUTE, NULL); + while (rr) { + pjsip_msg_add_hdr(msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rr)); + rr = rr->next; + if (rr != (void*)&req_msg->hdr) + rr = (pjsip_rr_hdr*) pjsip_msg_find_hdr(req_msg, + PJSIP_H_RECORD_ROUTE, rr); + else + break; + } + + /* Copy Call-ID header. */ + hdr = (pjsip_hdr*) pjsip_msg_find_hdr( req_msg, PJSIP_H_CALL_ID, NULL); + pjsip_msg_add_hdr(msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr)); + + /* Copy From header. */ + hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.from); + pjsip_msg_add_hdr( msg, hdr); + + /* Copy To header. */ + to_hdr = (pjsip_to_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.to); + pjsip_msg_add_hdr( msg, (pjsip_hdr*)to_hdr); + + /* Must add To tag in the response (Section 8.2.6.2), except if this is + * 100 (Trying) response. Same tag must be created for the same request + * (e.g. same tag in provisional and final response). The easiest way + * to do this is to derive the tag from Via branch parameter (or to + * use it directly). + */ + if (to_hdr->tag.slen==0 && st_code > 100 && top_via) { + to_hdr->tag = top_via->branch_param; + } + + /* Copy CSeq header. */ + hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.cseq); + pjsip_msg_add_hdr( msg, hdr); + + /* All done. */ + *p_tdata = tdata; + + PJ_LOG(5,(THIS_FILE, "%s created", pjsip_tx_data_get_info(tdata))); + return PJ_SUCCESS; +} + + +/* + * Construct ACK for 3xx-6xx final response (according to chapter 17.1.1 of + * RFC3261). Note that the generation of ACK for 2xx response is different, + * and one must not use this function to generate such ACK. + */ +PJ_DEF(pj_status_t) pjsip_endpt_create_ack( pjsip_endpoint *endpt, + const pjsip_tx_data *tdata, + const pjsip_rx_data *rdata, + pjsip_tx_data **ack_tdata) +{ + pjsip_tx_data *ack = NULL; + const pjsip_msg *invite_msg; + const pjsip_from_hdr *from_hdr; + const pjsip_to_hdr *to_hdr; + const pjsip_cid_hdr *cid_hdr; + const pjsip_cseq_hdr *cseq_hdr; + const pjsip_hdr *hdr; + pjsip_hdr *via; + pjsip_to_hdr *to; + pj_status_t status; + + /* rdata must be a non-2xx final response. */ + pj_assert(rdata->msg_info.msg->type==PJSIP_RESPONSE_MSG && + rdata->msg_info.msg->line.status.code >= 300); + + /* Initialize return value to NULL. */ + *ack_tdata = NULL; + + /* The original INVITE message. */ + invite_msg = tdata->msg; + + /* Get the headers from original INVITE request. */ +# define FIND_HDR(m,HNAME) pjsip_msg_find_hdr(m, PJSIP_H_##HNAME, NULL) + + from_hdr = (const pjsip_from_hdr*) FIND_HDR(invite_msg, FROM); + PJ_ASSERT_ON_FAIL(from_hdr != NULL, goto on_missing_hdr); + + to_hdr = (const pjsip_to_hdr*) FIND_HDR(invite_msg, TO); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); + + cid_hdr = (const pjsip_cid_hdr*) FIND_HDR(invite_msg, CALL_ID); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); + + cseq_hdr = (const pjsip_cseq_hdr*) FIND_HDR(invite_msg, CSEQ); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); + +# undef FIND_HDR + + /* Create new request message from the headers. */ + status = pjsip_endpt_create_request_from_hdr(endpt, + pjsip_get_ack_method(), + tdata->msg->line.req.uri, + from_hdr, to_hdr, + NULL, cid_hdr, + cseq_hdr->cseq, NULL, + &ack); + + if (status != PJ_SUCCESS) + return status; + + /* Update tag in To header with the one from the response (if any). */ + to = (pjsip_to_hdr*) pjsip_msg_find_hdr(ack->msg, PJSIP_H_TO, NULL); + pj_strdup(ack->pool, &to->tag, &rdata->msg_info.to->tag); + + + /* Clear Via headers in the new request. */ + while ((via=(pjsip_hdr*)pjsip_msg_find_hdr(ack->msg, PJSIP_H_VIA, NULL)) != NULL) + pj_list_erase(via); + + /* Must contain single Via, just as the original INVITE. */ + hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_VIA, NULL); + pjsip_msg_insert_first_hdr( ack->msg, + (pjsip_hdr*) pjsip_hdr_clone(ack->pool,hdr) ); + + /* If the original INVITE has Route headers, those header fields MUST + * appear in the ACK. + */ + hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_ROUTE, NULL); + while (hdr != NULL) { + pjsip_msg_add_hdr( ack->msg, + (pjsip_hdr*) pjsip_hdr_clone(ack->pool, hdr) ); + hdr = hdr->next; + if (hdr == &invite_msg->hdr) + break; + hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_ROUTE, hdr); + } + + /* We're done. + * "tdata" parameter now contains the ACK message. + */ + *ack_tdata = ack; + return PJ_SUCCESS; + +on_missing_hdr: + if (ack) + pjsip_tx_data_dec_ref(ack); + return PJSIP_EMISSINGHDR; +} + + +/* + * Construct CANCEL request for the previously sent request, according to + * chapter 9.1 of RFC3261. + */ +PJ_DEF(pj_status_t) pjsip_endpt_create_cancel( pjsip_endpoint *endpt, + const pjsip_tx_data *req_tdata, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *cancel_tdata = NULL; + const pjsip_from_hdr *from_hdr; + const pjsip_to_hdr *to_hdr; + const pjsip_cid_hdr *cid_hdr; + const pjsip_cseq_hdr *cseq_hdr; + const pjsip_hdr *hdr; + pjsip_hdr *via; + pj_status_t status; + + /* The transmit buffer must INVITE request. */ + PJ_ASSERT_RETURN(req_tdata->msg->type == PJSIP_REQUEST_MSG && + req_tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD, + PJ_EINVAL); + + /* Get the headers from original INVITE request. */ +# define FIND_HDR(m,HNAME) pjsip_msg_find_hdr(m, PJSIP_H_##HNAME, NULL) + + from_hdr = (const pjsip_from_hdr*) FIND_HDR(req_tdata->msg, FROM); + PJ_ASSERT_ON_FAIL(from_hdr != NULL, goto on_missing_hdr); + + to_hdr = (const pjsip_to_hdr*) FIND_HDR(req_tdata->msg, TO); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); + + cid_hdr = (const pjsip_cid_hdr*) FIND_HDR(req_tdata->msg, CALL_ID); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); + + cseq_hdr = (const pjsip_cseq_hdr*) FIND_HDR(req_tdata->msg, CSEQ); + PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr); + +# undef FIND_HDR + + /* Create new request message from the headers. */ + status = pjsip_endpt_create_request_from_hdr(endpt, + pjsip_get_cancel_method(), + req_tdata->msg->line.req.uri, + from_hdr, to_hdr, + NULL, cid_hdr, + cseq_hdr->cseq, NULL, + &cancel_tdata); + + if (status != PJ_SUCCESS) + return status; + + /* Clear Via headers in the new request. */ + while ((via=(pjsip_hdr*)pjsip_msg_find_hdr(cancel_tdata->msg, PJSIP_H_VIA, NULL)) != NULL) + pj_list_erase(via); + + + /* Must only have single Via which matches the top-most Via in the + * request being cancelled. + */ + hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg, PJSIP_H_VIA, NULL); + if (hdr) { + pjsip_msg_insert_first_hdr(cancel_tdata->msg, + (pjsip_hdr*)pjsip_hdr_clone(cancel_tdata->pool, hdr)); + } + + /* If the original request has Route header, the CANCEL request must also + * has exactly the same. + * Copy "Route" header from the request. + */ + hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg, PJSIP_H_ROUTE, NULL); + while (hdr != NULL) { + pjsip_msg_add_hdr(cancel_tdata->msg, + (pjsip_hdr*) pjsip_hdr_clone(cancel_tdata->pool, hdr)); + hdr = hdr->next; + if (hdr != &req_tdata->msg->hdr) + hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg, + PJSIP_H_ROUTE, hdr); + else + break; + } + + /* Must also copy the saved strict route header, otherwise CANCEL will be + * sent with swapped Route and request URI! + */ + if (req_tdata->saved_strict_route) { + cancel_tdata->saved_strict_route = (pjsip_route_hdr*) + pjsip_hdr_clone(cancel_tdata->pool, req_tdata->saved_strict_route); + } + + /* Copy the destination host name from the original request */ + pj_strdup(cancel_tdata->pool, &cancel_tdata->dest_info.name, + &req_tdata->dest_info.name); + + /* Finally copy the destination info from the original request */ + pj_memcpy(&cancel_tdata->dest_info, &req_tdata->dest_info, + sizeof(req_tdata->dest_info)); + + /* Done. + * Return the transmit buffer containing the CANCEL request. + */ + *p_tdata = cancel_tdata; + return PJ_SUCCESS; + +on_missing_hdr: + if (cancel_tdata) + pjsip_tx_data_dec_ref(cancel_tdata); + return PJSIP_EMISSINGHDR; +} + + +/* Fill-up destination information from a target URI */ +static pj_status_t get_dest_info(const pjsip_uri *target_uri, + pj_pool_t *pool, + pjsip_host_info *dest_info) +{ + /* The target URI must be a SIP/SIPS URL so we can resolve it's address. + * Otherwise we're in trouble (i.e. there's no host part in tel: URL). + */ + pj_bzero(dest_info, sizeof(*dest_info)); + + if (PJSIP_URI_SCHEME_IS_SIPS(target_uri)) { + pjsip_uri *uri = (pjsip_uri*) target_uri; + const pjsip_sip_uri *url=(const pjsip_sip_uri*)pjsip_uri_get_uri(uri); + unsigned flag; + + dest_info->flag |= (PJSIP_TRANSPORT_SECURE | PJSIP_TRANSPORT_RELIABLE); + if (url->maddr_param.slen) + pj_strdup(pool, &dest_info->addr.host, &url->maddr_param); + else + pj_strdup(pool, &dest_info->addr.host, &url->host); + dest_info->addr.port = url->port; + dest_info->type = + pjsip_transport_get_type_from_name(&url->transport_param); + /* Double-check that the transport parameter match. + * Sample case: sips:host;transport=tcp + * See https://trac.pjsip.org/repos/ticket/1319 + */ + flag = pjsip_transport_get_flag_from_type(dest_info->type); + if ((flag & dest_info->flag) != dest_info->flag) { + pjsip_transport_type_e t; + + t = pjsip_transport_get_type_from_flag(dest_info->flag); + if (t != PJSIP_TRANSPORT_UNSPECIFIED) + dest_info->type = t; + } + + } else if (PJSIP_URI_SCHEME_IS_SIP(target_uri)) { + pjsip_uri *uri = (pjsip_uri*) target_uri; + const pjsip_sip_uri *url=(const pjsip_sip_uri*)pjsip_uri_get_uri(uri); + if (url->maddr_param.slen) + pj_strdup(pool, &dest_info->addr.host, &url->maddr_param); + else + pj_strdup(pool, &dest_info->addr.host, &url->host); + dest_info->addr.port = url->port; + dest_info->type = + pjsip_transport_get_type_from_name(&url->transport_param); + dest_info->flag = + pjsip_transport_get_flag_from_type(dest_info->type); + } else { + /* Should have never reached here; app should have configured route + * set when sending to tel: URI + pj_assert(!"Unsupported URI scheme!"); + */ + PJ_TODO(SUPPORT_REQUEST_ADDR_RESOLUTION_FOR_TEL_URI); + return PJSIP_ENOROUTESET; + } + + /* Handle IPv6 (http://trac.pjsip.org/repos/ticket/861) */ + if (dest_info->type != PJSIP_TRANSPORT_UNSPECIFIED && + pj_strchr(&dest_info->addr.host, ':')) + { + dest_info->type = (pjsip_transport_type_e) + ((int)dest_info->type | PJSIP_TRANSPORT_IPV6); + } + + return PJ_SUCCESS; +} + + +/* + * Find which destination to be used to send the request message, based + * on the request URI and Route headers in the message. The procedure + * used here follows the guidelines on sending the request in RFC 3261 + * chapter 8.1.2. + */ +PJ_DEF(pj_status_t) pjsip_get_request_dest(const pjsip_tx_data *tdata, + pjsip_host_info *dest_info ) +{ + const pjsip_uri *target_uri; + const pjsip_route_hdr *first_route_hdr; + + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + PJ_ASSERT_RETURN(dest_info != NULL, PJ_EINVAL); + + /* Get the first "Route" header from the message. + */ + first_route_hdr = (const pjsip_route_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL); + if (first_route_hdr) { + target_uri = first_route_hdr->name_addr.uri; + } else { + target_uri = tdata->msg->line.req.uri; + } + + return get_dest_info(target_uri, (pj_pool_t*)tdata->pool, dest_info); +} + + +/* + * Process route-set found in the request and calculate + * the destination to be used to send the request message, based + * on the request URI and Route headers in the message. The procedure + * used here follows the guidelines on sending the request in RFC 3261 + * chapter 8.1.2. + */ +PJ_DEF(pj_status_t) pjsip_process_route_set(pjsip_tx_data *tdata, + pjsip_host_info *dest_info ) +{ + const pjsip_uri *new_request_uri, *target_uri; + const pjsip_name_addr *topmost_route_uri; + pjsip_route_hdr *first_route_hdr, *last_route_hdr; + pj_status_t status; + + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + PJ_ASSERT_RETURN(dest_info != NULL, PJ_EINVAL); + + /* If the request contains strict route, check that the strict route + * has been restored to its original values before processing the + * route set. The strict route is restored to the original values + * with pjsip_restore_strict_route_set(). If caller did not restore + * the strict route before calling this function, we need to call it + * here, or otherwise the strict-route and Request-URI will be swapped + * twice! + */ + if (tdata->saved_strict_route != NULL) { + pjsip_restore_strict_route_set(tdata); + } + PJ_ASSERT_RETURN(tdata->saved_strict_route==NULL, PJ_EBUG); + + /* Find the first and last "Route" headers from the message. */ + last_route_hdr = first_route_hdr = (pjsip_route_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL); + if (first_route_hdr) { + topmost_route_uri = &first_route_hdr->name_addr; + while (last_route_hdr->next != (void*)&tdata->msg->hdr) { + pjsip_route_hdr *hdr; + hdr = (pjsip_route_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, + last_route_hdr->next); + if (!hdr) + break; + last_route_hdr = hdr; + } + } else { + topmost_route_uri = NULL; + } + + /* If Route headers exist, and the first element indicates loose-route, + * the URI is taken from the Request-URI, and we keep all existing Route + * headers intact. + * If Route headers exist, and the first element DOESN'T indicate loose + * route, the URI is taken from the first Route header, and remove the + * first Route header from the message. + * Otherwise if there's no Route headers, the URI is taken from the + * Request-URI. + */ + if (topmost_route_uri) { + pj_bool_t has_lr_param; + + if (PJSIP_URI_SCHEME_IS_SIP(topmost_route_uri) || + PJSIP_URI_SCHEME_IS_SIPS(topmost_route_uri)) + { + const pjsip_sip_uri *url = (const pjsip_sip_uri*) + pjsip_uri_get_uri((const void*)topmost_route_uri); + has_lr_param = url->lr_param; + } else { + has_lr_param = 0; + } + + if (has_lr_param) { + new_request_uri = tdata->msg->line.req.uri; + /* We shouldn't need to delete topmost Route if it has lr param. + * But seems like it breaks some proxy implementation, so we + * delete it anyway. + */ + /* + pj_list_erase(first_route_hdr); + if (first_route_hdr == last_route_hdr) + last_route_hdr = NULL; + */ + } else { + new_request_uri = (const pjsip_uri*) + pjsip_uri_get_uri((pjsip_uri*)topmost_route_uri); + pj_list_erase(first_route_hdr); + tdata->saved_strict_route = first_route_hdr; + if (first_route_hdr == last_route_hdr) + first_route_hdr = last_route_hdr = NULL; + } + + target_uri = (pjsip_uri*)topmost_route_uri; + + } else { + target_uri = new_request_uri = tdata->msg->line.req.uri; + } + + /* Fill up the destination host/port from the URI. */ + status = get_dest_info(target_uri, tdata->pool, dest_info); + if (status != PJ_SUCCESS) + return status; + + /* If target URI is different than request URI, replace + * request URI add put the original URI in the last Route header. + */ + if (new_request_uri && new_request_uri!=tdata->msg->line.req.uri) { + pjsip_route_hdr *route = pjsip_route_hdr_create(tdata->pool); + route->name_addr.uri = (pjsip_uri*) + pjsip_uri_get_uri(tdata->msg->line.req.uri); + if (last_route_hdr) + pj_list_insert_after(last_route_hdr, route); + else + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)route); + tdata->msg->line.req.uri = (pjsip_uri*)new_request_uri; + } + + /* Success. */ + return PJ_SUCCESS; +} + + +/* + * Swap the request URI and strict route back to the original position + * before #pjsip_process_route_set() function is called. This function + * should only used internally by PJSIP client authentication module. + */ +PJ_DEF(void) pjsip_restore_strict_route_set(pjsip_tx_data *tdata) +{ + pjsip_route_hdr *first_route_hdr, *last_route_hdr; + + /* Check if we have found strict route before */ + if (tdata->saved_strict_route == NULL) { + /* This request doesn't contain strict route */ + return; + } + + /* Find the first "Route" headers from the message. */ + first_route_hdr = (pjsip_route_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL); + + if (first_route_hdr == NULL) { + /* User has modified message route? We don't expect this! */ + pj_assert(!"Message route was modified?"); + tdata->saved_strict_route = NULL; + return; + } + + /* Find last Route header */ + last_route_hdr = first_route_hdr; + while (last_route_hdr->next != (void*)&tdata->msg->hdr) { + pjsip_route_hdr *hdr; + hdr = (pjsip_route_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, + last_route_hdr->next); + if (!hdr) + break; + last_route_hdr = hdr; + } + + /* Put the last Route header as request URI, delete last Route + * header, and insert the saved strict route as the first Route. + */ + tdata->msg->line.req.uri = last_route_hdr->name_addr.uri; + pj_list_insert_before(first_route_hdr, tdata->saved_strict_route); + pj_list_erase(last_route_hdr); + + /* Reset */ + tdata->saved_strict_route = NULL; +} + + +/* Transport callback for sending stateless request. + * This is one of the most bizzare function in pjsip, so + * good luck if you happen to debug this function!! + */ +static void stateless_send_transport_cb( void *token, + pjsip_tx_data *tdata, + pj_ssize_t sent ) +{ + pjsip_send_state *stateless_data = (pjsip_send_state*) token; + + PJ_UNUSED_ARG(tdata); + pj_assert(tdata == stateless_data->tdata); + + for (;;) { + pj_status_t status; + pj_bool_t cont; + + pj_sockaddr_t *cur_addr; + pjsip_transport_type_e cur_addr_type; + int cur_addr_len; + + pjsip_via_hdr *via; + + if (sent == -PJ_EPENDING) { + /* This is the initial process. + * When the process started, this function will be called by + * stateless_send_resolver_callback() with sent argument set to + * -PJ_EPENDING. + */ + cont = PJ_TRUE; + } else { + /* There are two conditions here: + * (1) Message is sent (i.e. sent > 0), + * (2) Failure (i.e. sent <= 0) + */ + cont = (sent > 0) ? PJ_FALSE : + (tdata->dest_info.cur_addr<tdata->dest_info.addr.count-1); + if (stateless_data->app_cb) { + (*stateless_data->app_cb)(stateless_data, sent, &cont); + } else { + /* Doesn't have application callback. + * Terminate the process. + */ + cont = PJ_FALSE; + } + } + + /* Finished with this transport. */ + if (stateless_data->cur_transport) { + pjsip_transport_dec_ref(stateless_data->cur_transport); + stateless_data->cur_transport = NULL; + } + + /* Done if application doesn't want to continue. */ + if (sent > 0 || !cont) { + pjsip_tx_data_dec_ref(tdata); + return; + } + + /* Try next address, if any, and only when this is not the + * first invocation. + */ + if (sent != -PJ_EPENDING) { + tdata->dest_info.cur_addr++; + } + + /* Have next address? */ + if (tdata->dest_info.cur_addr >= tdata->dest_info.addr.count) { + /* This only happens when a rather buggy application has + * sent 'cont' to PJ_TRUE when the initial value was PJ_FALSE. + * In this case just stop the processing; we don't need to + * call the callback again as application has been informed + * before. + */ + pjsip_tx_data_dec_ref(tdata); + return; + } + + /* Keep current server address information handy. */ + cur_addr = &tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr; + cur_addr_type = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].type; + cur_addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len; + + /* Acquire transport. */ + status = pjsip_endpt_acquire_transport2(stateless_data->endpt, + cur_addr_type, + cur_addr, + cur_addr_len, + &tdata->tp_sel, + tdata, + &stateless_data->cur_transport); + if (status != PJ_SUCCESS) { + sent = -status; + continue; + } + + /* Modify Via header. */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr( tdata->msg, + PJSIP_H_VIA, NULL); + if (!via) { + /* Shouldn't happen if request was created with PJSIP API! + * But we handle the case anyway for robustness. + */ + pj_assert(!"Via header not found!"); + via = pjsip_via_hdr_create(tdata->pool); + pjsip_msg_insert_first_hdr(tdata->msg, (pjsip_hdr*)via); + } + + if (via->branch_param.slen == 0) { + pj_str_t tmp; + via->branch_param.ptr = (char*)pj_pool_alloc(tdata->pool, + PJSIP_MAX_BRANCH_LEN); + via->branch_param.slen = PJSIP_MAX_BRANCH_LEN; + pj_memcpy(via->branch_param.ptr, PJSIP_RFC3261_BRANCH_ID, + PJSIP_RFC3261_BRANCH_LEN); + tmp.ptr = via->branch_param.ptr + PJSIP_RFC3261_BRANCH_LEN + 2; + *(tmp.ptr-2) = 80; *(tmp.ptr-1) = 106; + pj_generate_unique_string(&tmp); + } + + via->transport = pj_str(stateless_data->cur_transport->type_name); + if (tdata->via_addr.host.slen > 0 && + tdata->via_tp == (void *)stateless_data->cur_transport) + { + via->sent_by = tdata->via_addr; + } else { + via->sent_by = stateless_data->cur_transport->local_name; + } + via->rport_param = pjsip_cfg()->endpt.disable_rport ? -1 : 0; + + pjsip_tx_data_invalidate_msg(tdata); + + /* Send message using this transport. */ + status = pjsip_transport_send( stateless_data->cur_transport, + tdata, + cur_addr, + cur_addr_len, + stateless_data, + &stateless_send_transport_cb); + if (status == PJ_SUCCESS) { + /* Recursively call this function. */ + sent = tdata->buf.cur - tdata->buf.start; + stateless_send_transport_cb( stateless_data, tdata, sent ); + return; + } else if (status == PJ_EPENDING) { + /* This callback will be called later. */ + return; + } else { + /* Recursively call this function. */ + sent = -status; + stateless_send_transport_cb( stateless_data, tdata, sent ); + return; + } + } + +} + +/* Resolver callback for sending stateless request. */ +static void +stateless_send_resolver_callback( pj_status_t status, + void *token, + const struct pjsip_server_addresses *addr) +{ + pjsip_send_state *stateless_data = (pjsip_send_state*) token; + pjsip_tx_data *tdata = stateless_data->tdata; + + /* Fail on server resolution. */ + if (status != PJ_SUCCESS) { + if (stateless_data->app_cb) { + pj_bool_t cont = PJ_FALSE; + (*stateless_data->app_cb)(stateless_data, -status, &cont); + } + pjsip_tx_data_dec_ref(tdata); + return; + } + + /* Copy server addresses */ + if (addr && addr != &tdata->dest_info.addr) { + pj_memcpy( &tdata->dest_info.addr, addr, + sizeof(pjsip_server_addresses)); + } + pj_assert(tdata->dest_info.addr.count != 0); + + /* RFC 3261 section 18.1.1: + * If a request is within 200 bytes of the path MTU, or if it is larger + * than 1300 bytes and the path MTU is unknown, the request MUST be sent + * using an RFC 2914 [43] congestion controlled transport protocol, such + * as TCP. + */ + if (pjsip_cfg()->endpt.disable_tcp_switch==0 && + tdata->msg->type == PJSIP_REQUEST_MSG && + tdata->dest_info.addr.count > 0 && + tdata->dest_info.addr.entry[0].type == PJSIP_TRANSPORT_UDP) + { + int len; + + /* Encode the request */ + status = pjsip_tx_data_encode(tdata); + if (status != PJ_SUCCESS) { + if (stateless_data->app_cb) { + pj_bool_t cont = PJ_FALSE; + (*stateless_data->app_cb)(stateless_data, -status, &cont); + } + pjsip_tx_data_dec_ref(tdata); + return; + } + + /* Check if request message is larger than 1300 bytes. */ + len = tdata->buf.cur - tdata->buf.start; + if (len >= PJSIP_UDP_SIZE_THRESHOLD) { + int i; + int count = tdata->dest_info.addr.count; + + PJ_LOG(5,(THIS_FILE, "%s exceeds UDP size threshold (%u), " + "sending with TCP", + pjsip_tx_data_get_info(tdata), + PJSIP_UDP_SIZE_THRESHOLD)); + + /* Insert "TCP version" of resolved UDP addresses at the + * beginning. + */ + if (count * 2 > PJSIP_MAX_RESOLVED_ADDRESSES) + count = PJSIP_MAX_RESOLVED_ADDRESSES / 2; + for (i = 0; i < count; ++i) { + pj_memcpy(&tdata->dest_info.addr.entry[i+count], + &tdata->dest_info.addr.entry[i], + sizeof(tdata->dest_info.addr.entry[0])); + tdata->dest_info.addr.entry[i].type = PJSIP_TRANSPORT_TCP; + } + tdata->dest_info.addr.count = count * 2; + } + } + + /* Process the addresses. */ + stateless_send_transport_cb( stateless_data, tdata, -PJ_EPENDING); +} + +/* + * Send stateless request. + * The sending process consists of several stages: + * - determine which host to contact (#pjsip_get_request_addr). + * - resolve the host (#pjsip_endpt_resolve) + * - establish transport (#pjsip_endpt_acquire_transport) + * - send the message (#pjsip_transport_send) + */ +PJ_DEF(pj_status_t) pjsip_endpt_send_request_stateless(pjsip_endpoint *endpt, + pjsip_tx_data *tdata, + void *token, + pjsip_send_callback cb) +{ + pjsip_host_info dest_info; + pjsip_send_state *stateless_data; + pj_status_t status; + + PJ_ASSERT_RETURN(endpt && tdata, PJ_EINVAL); + + /* Get destination name to contact. */ + status = pjsip_process_route_set(tdata, &dest_info); + if (status != PJ_SUCCESS) + return status; + + /* Keep stateless data. */ + stateless_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_send_state); + stateless_data->token = token; + stateless_data->endpt = endpt; + stateless_data->tdata = tdata; + stateless_data->app_cb = cb; + + /* If destination info has not been initialized (this applies for most + * all requests except CANCEL), resolve destination host. The processing + * then resumed when the resolving callback is called. For CANCEL, the + * destination info must have been copied from the original INVITE so + * proceed to sending the request directly. + */ + if (tdata->dest_info.addr.count == 0) { + /* Copy the destination host name to TX data */ + pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host); + + pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, stateless_data, + &stateless_send_resolver_callback); + } else { + PJ_LOG(5,(THIS_FILE, "%s: skipping target resolution because " + "address is already set", + pjsip_tx_data_get_info(tdata))); + stateless_send_resolver_callback(PJ_SUCCESS, stateless_data, + &tdata->dest_info.addr); + } + return PJ_SUCCESS; +} + + +/* + * Send raw data to a destination. + */ +PJ_DEF(pj_status_t) pjsip_endpt_send_raw( pjsip_endpoint *endpt, + pjsip_transport_type_e tp_type, + const pjsip_tpselector *sel, + const void *raw_data, + pj_size_t data_len, + const pj_sockaddr_t *addr, + int addr_len, + void *token, + pjsip_tp_send_callback cb) +{ + return pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(endpt), tp_type, sel, + NULL, raw_data, data_len, addr, addr_len, + token, cb); +} + + +/* Callback data for sending raw data */ +struct send_raw_data +{ + pjsip_endpoint *endpt; + pjsip_tx_data *tdata; + pjsip_tpselector *sel; + void *app_token; + pjsip_tp_send_callback app_cb; +}; + + +/* Resolver callback for sending raw data. */ +static void send_raw_resolver_callback( pj_status_t status, + void *token, + const pjsip_server_addresses *addr) +{ + struct send_raw_data *sraw_data = (struct send_raw_data*) token; + + if (status != PJ_SUCCESS) { + if (sraw_data->app_cb) { + (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata, + -status); + } + } else { + pj_size_t data_len; + + pj_assert(addr->count != 0); + + /* Avoid tdata destroyed by pjsip_tpmgr_send_raw(). */ + pjsip_tx_data_add_ref(sraw_data->tdata); + + data_len = sraw_data->tdata->buf.cur - sraw_data->tdata->buf.start; + status = pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(sraw_data->endpt), + addr->entry[0].type, + sraw_data->sel, sraw_data->tdata, + sraw_data->tdata->buf.start, data_len, + &addr->entry[0].addr, + addr->entry[0].addr_len, + sraw_data->app_token, + sraw_data->app_cb); + if (status == PJ_SUCCESS) { + (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata, + data_len); + } else if (status != PJ_EPENDING) { + (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata, + -status); + } + } + + if (sraw_data->sel) { + pjsip_tpselector_dec_ref(sraw_data->sel); + } + pjsip_tx_data_dec_ref(sraw_data->tdata); +} + + +/* + * Send raw data to the specified destination URI. + */ +PJ_DEF(pj_status_t) pjsip_endpt_send_raw_to_uri(pjsip_endpoint *endpt, + const pj_str_t *p_dst_uri, + const pjsip_tpselector *sel, + const void *raw_data, + pj_size_t data_len, + void *token, + pjsip_tp_send_callback cb) +{ + pjsip_tx_data *tdata; + struct send_raw_data *sraw_data; + pj_str_t dst_uri; + pjsip_uri *uri; + pjsip_host_info dest_info; + pj_status_t status; + + /* Allocate buffer */ + status = pjsip_endpt_create_tdata(endpt, &tdata); + if (status != PJ_SUCCESS) + return status; + + pjsip_tx_data_add_ref(tdata); + + /* Duplicate URI since parser requires URI to be NULL terminated */ + pj_strdup_with_null(tdata->pool, &dst_uri, p_dst_uri); + + /* Parse URI */ + uri = pjsip_parse_uri(tdata->pool, dst_uri.ptr, dst_uri.slen, 0); + if (uri == NULL) { + pjsip_tx_data_dec_ref(tdata); + return PJSIP_EINVALIDURI; + } + + /* Build destination info. */ + status = get_dest_info(uri, tdata->pool, &dest_info); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return status; + } + + /* Copy data (note: data_len may be zero!) */ + tdata->buf.start = (char*) pj_pool_alloc(tdata->pool, data_len+1); + tdata->buf.end = tdata->buf.start + data_len + 1; + if (data_len) + pj_memcpy(tdata->buf.start, raw_data, data_len); + tdata->buf.cur = tdata->buf.start + data_len; + + /* Init send_raw_data */ + sraw_data = PJ_POOL_ZALLOC_T(tdata->pool, struct send_raw_data); + sraw_data->endpt = endpt; + sraw_data->tdata = tdata; + sraw_data->app_token = token; + sraw_data->app_cb = cb; + + if (sel) { + sraw_data->sel = PJ_POOL_ALLOC_T(tdata->pool, pjsip_tpselector); + pj_memcpy(sraw_data->sel, sel, sizeof(pjsip_tpselector)); + pjsip_tpselector_add_ref(sraw_data->sel); + } + + /* Copy the destination host name to TX data */ + pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host); + + /* Resolve destination host. + * The processing then resumed when the resolving callback is called. + */ + pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, sraw_data, + &send_raw_resolver_callback); + return PJ_SUCCESS; +} + + +/* + * Determine which address (and transport) to use to send response message + * based on the received request. This function follows the specification + * in section 18.2.2 of RFC 3261 and RFC 3581 for calculating the destination + * address and transport. + */ +PJ_DEF(pj_status_t) pjsip_get_response_addr( pj_pool_t *pool, + pjsip_rx_data *rdata, + pjsip_response_addr *res_addr ) +{ + pjsip_transport *src_transport = rdata->tp_info.transport; + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && rdata && res_addr, PJ_EINVAL); + + /* rdata must be a request message! */ + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJ_EINVAL); + + /* All requests must have "received" parameter. + * This must always be done in transport layer. + */ + pj_assert(rdata->msg_info.via->recvd_param.slen != 0); + + /* Do the calculation based on RFC 3261 Section 18.2.2 and RFC 3581 */ + + if (PJSIP_TRANSPORT_IS_RELIABLE(src_transport)) { + /* For reliable protocol such as TCP or SCTP, or TLS over those, the + * response MUST be sent using the existing connection to the source + * of the original request that created the transaction, if that + * connection is still open. + * If that connection is no longer open, the server SHOULD open a + * connection to the IP address in the received parameter, if present, + * using the port in the sent-by value, or the default port for that + * transport, if no port is specified. + * If that connection attempt fails, the server SHOULD use the + * procedures in [4] for servers in order to determine the IP address + * and port to open the connection and send the response to. + */ + res_addr->transport = rdata->tp_info.transport; + pj_memcpy(&res_addr->addr, &rdata->pkt_info.src_addr, + rdata->pkt_info.src_addr_len); + res_addr->addr_len = rdata->pkt_info.src_addr_len; + res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type; + res_addr->dst_host.flag = src_transport->flag; + pj_strdup( pool, &res_addr->dst_host.addr.host, + &rdata->msg_info.via->recvd_param); + res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port; + if (res_addr->dst_host.addr.port == 0) { + res_addr->dst_host.addr.port = + pjsip_transport_get_default_port_for_type(res_addr->dst_host.type); + } + + } else if (rdata->msg_info.via->maddr_param.slen) { + /* Otherwise, if the Via header field value contains a maddr parameter, + * the response MUST be forwarded to the address listed there, using + * the port indicated in sent-by, or port 5060 if none is present. + * If the address is a multicast address, the response SHOULD be sent + * using the TTL indicated in the ttl parameter, or with a TTL of 1 if + * that parameter is not present. + */ + res_addr->transport = NULL; + res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type; + res_addr->dst_host.flag = src_transport->flag; + pj_strdup( pool, &res_addr->dst_host.addr.host, + &rdata->msg_info.via->maddr_param); + res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port; + if (res_addr->dst_host.addr.port == 0) + res_addr->dst_host.addr.port = 5060; + + } else if (rdata->msg_info.via->rport_param >= 0) { + /* There is both a "received" parameter and an "rport" parameter, + * the response MUST be sent to the IP address listed in the "received" + * parameter, and the port in the "rport" parameter. + * The response MUST be sent from the same address and port that the + * corresponding request was received on. + */ + res_addr->transport = rdata->tp_info.transport; + pj_memcpy(&res_addr->addr, &rdata->pkt_info.src_addr, + rdata->pkt_info.src_addr_len); + res_addr->addr_len = rdata->pkt_info.src_addr_len; + res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type; + res_addr->dst_host.flag = src_transport->flag; + pj_strdup( pool, &res_addr->dst_host.addr.host, + &rdata->msg_info.via->recvd_param); + res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port; + if (res_addr->dst_host.addr.port == 0) { + res_addr->dst_host.addr.port = + pjsip_transport_get_default_port_for_type(res_addr->dst_host.type); + } + + } else { + res_addr->transport = NULL; + res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type; + res_addr->dst_host.flag = src_transport->flag; + pj_strdup( pool, &res_addr->dst_host.addr.host, + &rdata->msg_info.via->recvd_param); + res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port; + if (res_addr->dst_host.addr.port == 0) { + res_addr->dst_host.addr.port = + pjsip_transport_get_default_port_for_type(res_addr->dst_host.type); + } + } + + return PJ_SUCCESS; +} + +/* + * Callback called by transport during send_response. + */ +static void send_response_transport_cb(void *token, pjsip_tx_data *tdata, + pj_ssize_t sent) +{ + pjsip_send_state *send_state = (pjsip_send_state*) token; + pj_bool_t cont = PJ_FALSE; + + /* Call callback, if any. */ + if (send_state->app_cb) + (*send_state->app_cb)(send_state, sent, &cont); + + /* Decrement transport reference counter. */ + pjsip_transport_dec_ref(send_state->cur_transport); + + /* Decrement transmit data ref counter. */ + pjsip_tx_data_dec_ref(tdata); +} + +/* + * Resolver calback during send_response. + */ +static void send_response_resolver_cb( pj_status_t status, void *token, + const pjsip_server_addresses *addr ) +{ + pjsip_send_state *send_state = (pjsip_send_state*) token; + + if (status != PJ_SUCCESS) { + if (send_state->app_cb) { + pj_bool_t cont = PJ_FALSE; + (*send_state->app_cb)(send_state, -status, &cont); + } + pjsip_tx_data_dec_ref(send_state->tdata); + return; + } + + /* Only handle the first address resolved. */ + + /* Acquire transport. */ + status = pjsip_endpt_acquire_transport2(send_state->endpt, + addr->entry[0].type, + &addr->entry[0].addr, + addr->entry[0].addr_len, + &send_state->tdata->tp_sel, + send_state->tdata, + &send_state->cur_transport); + if (status != PJ_SUCCESS) { + if (send_state->app_cb) { + pj_bool_t cont = PJ_FALSE; + (*send_state->app_cb)(send_state, -status, &cont); + } + pjsip_tx_data_dec_ref(send_state->tdata); + return; + } + + /* Update address in send_state. */ + pj_memcpy(&send_state->tdata->dest_info.addr, addr, sizeof(*addr)); + + /* Send response using the transoprt. */ + status = pjsip_transport_send( send_state->cur_transport, + send_state->tdata, + &addr->entry[0].addr, + addr->entry[0].addr_len, + send_state, + &send_response_transport_cb); + if (status == PJ_SUCCESS) { + pj_ssize_t sent = send_state->tdata->buf.cur - + send_state->tdata->buf.start; + send_response_transport_cb(send_state, send_state->tdata, sent); + + } else if (status == PJ_EPENDING) { + /* Transport callback will be called later. */ + } else { + send_response_transport_cb(send_state, send_state->tdata, -status); + } +} + +/* + * Send response. + */ +PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt, + pjsip_response_addr *res_addr, + pjsip_tx_data *tdata, + void *token, + pjsip_send_callback cb) +{ + /* Determine which transports and addresses to send the response, + * based on Section 18.2.2 of RFC 3261. + */ + pjsip_send_state *send_state; + pj_status_t status; + + /* Create structure to keep the sending state. */ + send_state = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_send_state); + send_state->endpt = endpt; + send_state->tdata = tdata; + send_state->token = token; + send_state->app_cb = cb; + + if (res_addr->transport != NULL) { + send_state->cur_transport = res_addr->transport; + pjsip_transport_add_ref(send_state->cur_transport); + + status = pjsip_transport_send( send_state->cur_transport, tdata, + &res_addr->addr, + res_addr->addr_len, + send_state, + &send_response_transport_cb ); + if (status == PJ_SUCCESS) { + pj_ssize_t sent = tdata->buf.cur - tdata->buf.start; + send_response_transport_cb(send_state, tdata, sent); + return PJ_SUCCESS; + } else if (status == PJ_EPENDING) { + /* Callback will be called later. */ + return PJ_SUCCESS; + } else { + pjsip_transport_dec_ref(send_state->cur_transport); + return status; + } + } else { + /* Copy the destination host name to TX data */ + pj_strdup(tdata->pool, &tdata->dest_info.name, + &res_addr->dst_host.addr.host); + + pjsip_endpt_resolve(endpt, tdata->pool, &res_addr->dst_host, + send_state, &send_response_resolver_cb); + return PJ_SUCCESS; + } +} + +/* + * Send response combo + */ +PJ_DEF(pj_status_t) pjsip_endpt_send_response2( pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + pjsip_tx_data *tdata, + void *token, + pjsip_send_callback cb) +{ + pjsip_response_addr res_addr; + pj_status_t status; + + status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return PJ_SUCCESS; + } + + status = pjsip_endpt_send_response(endpt, &res_addr, tdata, token, cb); + return status; +} + + +/* + * Send response + */ +PJ_DEF(pj_status_t) pjsip_endpt_respond_stateless( pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + int st_code, + const pj_str_t *st_text, + const pjsip_hdr *hdr_list, + const pjsip_msg_body *body) +{ + pj_status_t status; + pjsip_response_addr res_addr; + pjsip_tx_data *tdata; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(endpt && rdata, PJ_EINVAL); + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Check that no UAS transaction has been created for this request. + * If UAS transaction has been created for this request, application + * MUST send the response statefully using that transaction. + */ + PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata)==NULL, PJ_EINVALIDOP); + + /* Create response message */ + status = pjsip_endpt_create_response( endpt, rdata, st_code, st_text, + &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Add the message headers, 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; + } + } + + /* Add the message body, if any. */ + if (body) { + tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body ); + if (tdata->msg->body == NULL) { + pjsip_tx_data_dec_ref(tdata); + return status; + } + } + + /* Get where to send request. */ + status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr ); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return status; + } + + /* Send! */ + status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL ); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return status; + } + + return PJ_SUCCESS; +} + + +/* + * Get the event string from the event ID. + */ +PJ_DEF(const char *) pjsip_event_str(pjsip_event_id_e e) +{ + return event_str[e]; +} + diff --git a/pjsip/src/pjsip/sip_util_proxy.c b/pjsip/src/pjsip/sip_util_proxy.c new file mode 100644 index 0000000..7a34534 --- /dev/null +++ b/pjsip/src/pjsip/sip_util_proxy.c @@ -0,0 +1,389 @@ +/* $Id: sip_util_proxy.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_util.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_msg.h> +#include <pj/assert.h> +#include <pj/ctype.h> +#include <pj/except.h> +#include <pj/guid.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pjlib-util/md5.h> + + +/** + * Clone the incoming SIP request or response message. A forwarding proxy + * typically would need to clone the incoming SIP message before processing + * the message. + * + * Once a transmit data is created, the reference counter is initialized to 1. + * + * @param endpt The endpoint instance. + * @param rdata The incoming SIP message. + * @param p_tdata Pointer to receive the transmit data containing + * the duplicated message. + * + * @return PJ_SUCCESS on success. + */ +/* +PJ_DEF(pj_status_t) pjsip_endpt_clone_msg( pjsip_endpoint *endpt, + const pjsip_rx_data *rdata, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_endpt_create_tdata(endpt, &tdata); + if (status != PJ_SUCCESS) + return status; + + tdata->msg = pjsip_msg_clone(tdata->pool, rdata->msg_info.msg); + + pjsip_tx_data_add_ref(tdata); + + *p_tdata = tdata; + + return PJ_SUCCESS; +} +*/ + + +/* + * Create new request message to be forwarded upstream to new destination URI + * in uri. + */ +PJ_DEF(pj_status_t) pjsip_endpt_create_request_fwd(pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + const pjsip_uri *uri, + const pj_str_t *branch, + unsigned options, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pj_status_t status; + PJ_USE_EXCEPTION; + + + PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + PJ_UNUSED_ARG(options); + + + /* Request forwarding rule in RFC 3261 section 16.6: + * + * For each target, the proxy forwards the request following these + * steps: + * + * 1. Make a copy of the received request + * 2. Update the Request-URI + * 3. Update the Max-Forwards header field + * 4. Optionally add a Record-route header field value + * 5. Optionally add additional header fields + * 6. Postprocess routing information + * 7. Determine the next-hop address, port, and transport + * 8. Add a Via header field value + * 9. Add a Content-Length header field if necessary + * 10. Forward the new request + * + * Of these steps, we only do step 1-3, since the later will be + * done by application. + */ + + status = pjsip_endpt_create_tdata(endpt, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Always increment ref counter to 1 */ + pjsip_tx_data_add_ref(tdata); + + /* Duplicate the request */ + PJ_TRY { + pjsip_msg *dst; + const pjsip_msg *src = rdata->msg_info.msg; + const pjsip_hdr *hsrc; + + /* Create the request */ + tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG); + + /* Duplicate request method */ + pjsip_method_copy(tdata->pool, &tdata->msg->line.req.method, + &src->line.req.method); + + /* Set request URI */ + if (uri) { + dst->line.req.uri = (pjsip_uri*) + pjsip_uri_clone(tdata->pool, uri); + } else { + dst->line.req.uri= (pjsip_uri*) + pjsip_uri_clone(tdata->pool, src->line.req.uri); + } + + /* Clone ALL headers */ + hsrc = src->hdr.next; + while (hsrc != &src->hdr) { + + pjsip_hdr *hdst; + + /* If this is the top-most Via header, insert our own before + * cloning the header. + */ + if (hsrc == (pjsip_hdr*)rdata->msg_info.via) { + pjsip_via_hdr *hvia; + hvia = pjsip_via_hdr_create(tdata->pool); + if (branch) + pj_strdup(tdata->pool, &hvia->branch_param, branch); + else { + pj_str_t new_branch = pjsip_calculate_branch_id(rdata); + pj_strdup(tdata->pool, &hvia->branch_param, &new_branch); + } + pjsip_msg_add_hdr(dst, (pjsip_hdr*)hvia); + + } + /* Skip Content-Type and Content-Length as these would be + * generated when the the message is printed. + */ + else if (hsrc->type == PJSIP_H_CONTENT_LENGTH || + hsrc->type == PJSIP_H_CONTENT_TYPE) { + + hsrc = hsrc->next; + continue; + + } +#if 0 + /* If this is the top-most Route header and it indicates loose + * route, remove the header. + */ + else if (hsrc == (pjsip_hdr*)rdata->msg_info.route) { + + const pjsip_route_hdr *hroute = (const pjsip_route_hdr*) hsrc; + const pjsip_sip_uri *sip_uri; + + if (!PJSIP_URI_SCHEME_IS_SIP(hroute->name_addr.uri) && + !PJSIP_URI_SCHEME_IS_SIPS(hroute->name_addr.uri)) + { + /* This is a bad request! */ + status = PJSIP_EINVALIDHDR; + goto on_error; + } + + sip_uri = (pjsip_sip_uri*) hroute->name_addr.uri; + + if (sip_uri->lr_param) { + /* Yes lr param is present, skip this Route header */ + hsrc = hsrc->next; + continue; + } + } +#endif + + /* Clone the header */ + hdst = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hsrc); + + /* If this is Max-Forward header, decrement the value */ + if (hdst->type == PJSIP_H_MAX_FORWARDS) { + pjsip_max_fwd_hdr *hmaxfwd = (pjsip_max_fwd_hdr*)hdst; + --hmaxfwd->ivalue; + } + + /* Append header to new request */ + pjsip_msg_add_hdr(dst, hdst); + + + hsrc = hsrc->next; + } + + /* 16.6.3: + * If the copy does not contain a Max-Forwards header field, the + * proxy MUST add one with a field value, which SHOULD be 70. + */ + if (rdata->msg_info.max_fwd == NULL) { + pjsip_max_fwd_hdr *hmaxfwd = + pjsip_max_fwd_hdr_create(tdata->pool, 70); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hmaxfwd); + } + + /* Clone request body */ + if (src->body) { + dst->body = pjsip_msg_body_clone(tdata->pool, src->body); + } + + } + PJ_CATCH_ANY { + status = PJ_ENOMEM; + goto on_error; + } + PJ_END + + + /* Done */ + *p_tdata = tdata; + return PJ_SUCCESS; + +on_error: + pjsip_tx_data_dec_ref(tdata); + return status; +} + + +PJ_DEF(pj_status_t) pjsip_endpt_create_response_fwd( pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + unsigned options, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pj_status_t status; + PJ_USE_EXCEPTION; + + PJ_UNUSED_ARG(options); + + status = pjsip_endpt_create_tdata(endpt, &tdata); + if (status != PJ_SUCCESS) + return status; + + pjsip_tx_data_add_ref(tdata); + + PJ_TRY { + pjsip_msg *dst; + const pjsip_msg *src = rdata->msg_info.msg; + const pjsip_hdr *hsrc; + + /* Create the request */ + tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG); + + /* Clone the status line */ + dst->line.status.code = src->line.status.code; + pj_strdup(tdata->pool, &dst->line.status.reason, + &src->line.status.reason); + + /* Duplicate all headers */ + hsrc = src->hdr.next; + while (hsrc != &src->hdr) { + + /* Skip Content-Type and Content-Length as these would be + * generated when the the message is printed. + */ + if (hsrc->type == PJSIP_H_CONTENT_LENGTH || + hsrc->type == PJSIP_H_CONTENT_TYPE) { + + hsrc = hsrc->next; + continue; + + } + /* Remove the first Via header */ + else if (hsrc == (pjsip_hdr*) rdata->msg_info.via) { + + hsrc = hsrc->next; + continue; + } + + pjsip_msg_add_hdr(dst, + (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hsrc)); + + hsrc = hsrc->next; + } + + /* Clone message body */ + if (src->body) + dst->body = pjsip_msg_body_clone(tdata->pool, src->body); + + + } + PJ_CATCH_ANY { + status = PJ_ENOMEM; + goto on_error; + } + PJ_END; + + *p_tdata = tdata; + return PJ_SUCCESS; + +on_error: + pjsip_tx_data_dec_ref(tdata); + return status; +} + + +static void digest2str(const unsigned char digest[], char *output) +{ + int i; + for (i = 0; i<16; ++i) { + pj_val_to_hex_digit(digest[i], output); + output += 2; + } +} + + +PJ_DEF(pj_str_t) pjsip_calculate_branch_id( pjsip_rx_data *rdata ) +{ + pj_md5_context ctx; + pj_uint8_t digest[16]; + pj_str_t branch; + pj_str_t rfc3261_branch = {PJSIP_RFC3261_BRANCH_ID, + PJSIP_RFC3261_BRANCH_LEN}; + + /* If incoming request does not have RFC 3261 branch value, create + * a branch value from GUID . + */ + if (pj_strncmp(&rdata->msg_info.via->branch_param, + &rfc3261_branch, PJSIP_RFC3261_BRANCH_LEN) != 0 ) + { + pj_str_t tmp; + + branch.ptr = (char*) + pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_BRANCH_LEN); + branch.slen = PJSIP_RFC3261_BRANCH_LEN; + pj_memcpy(branch.ptr, PJSIP_RFC3261_BRANCH_ID, + PJSIP_RFC3261_BRANCH_LEN); + + tmp.ptr = branch.ptr + PJSIP_RFC3261_BRANCH_LEN + 2; + *(tmp.ptr-2) = (pj_int8_t)(branch.slen+73); + *(tmp.ptr-1) = (pj_int8_t)(branch.slen+99); + pj_generate_unique_string( &tmp ); + + branch.slen = PJSIP_MAX_BRANCH_LEN; + return branch; + } + + /* Create branch ID for new request by calculating MD5 hash + * of the branch parameter in top-most Via header. + */ + pj_md5_init(&ctx); + pj_md5_update(&ctx, (pj_uint8_t*)rdata->msg_info.via->branch_param.ptr, + rdata->msg_info.via->branch_param.slen); + pj_md5_final(&ctx, digest); + + branch.ptr = (char*) + pj_pool_alloc(rdata->tp_info.pool, + 34 + PJSIP_RFC3261_BRANCH_LEN); + pj_memcpy(branch.ptr, PJSIP_RFC3261_BRANCH_ID, PJSIP_RFC3261_BRANCH_LEN); + branch.slen = PJSIP_RFC3261_BRANCH_LEN; + *(branch.ptr+PJSIP_RFC3261_BRANCH_LEN) = (pj_int8_t)(branch.slen+73); + *(branch.ptr+PJSIP_RFC3261_BRANCH_LEN+1) = (pj_int8_t)(branch.slen+99); + digest2str(digest, branch.ptr+PJSIP_RFC3261_BRANCH_LEN+2); + branch.slen = 34 + PJSIP_RFC3261_BRANCH_LEN; + + return branch; +} + + diff --git a/pjsip/src/pjsip/sip_util_proxy_wrap.cpp b/pjsip/src/pjsip/sip_util_proxy_wrap.cpp new file mode 100644 index 0000000..e77786f --- /dev/null +++ b/pjsip/src/pjsip/sip_util_proxy_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: sip_util_proxy_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * 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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "sip_util_proxy.c" diff --git a/pjsip/src/pjsip/sip_util_statefull.c b/pjsip/src/pjsip/sip_util_statefull.c new file mode 100644 index 0000000..3212cab --- /dev/null +++ b/pjsip/src/pjsip/sip_util_statefull.c @@ -0,0 +1,192 @@ +/* $Id: sip_util_statefull.c 4169 2012-06-18 09:19:58Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip/sip_util.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_errno.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> + +struct tsx_data +{ + void *token; + void (*cb)(void*, pjsip_event*); +}; + +static void mod_util_on_tsx_state(pjsip_transaction*, pjsip_event*); + +/* This module will be registered in pjsip_endpt.c */ + +pjsip_module mod_stateful_util = +{ + NULL, NULL, /* prev, next. */ + { "mod-stateful-util", 17 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* 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() */ + &mod_util_on_tsx_state, /* on_tsx_state() */ +}; + +static void mod_util_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event) +{ + struct tsx_data *tsx_data; + + /* Check if the module has been unregistered (see ticket #1535) and also + * verify the event type. + */ + if (mod_stateful_util.id < 0 || event->type != PJSIP_EVENT_TSX_STATE) + return; + + tsx_data = (struct tsx_data*) tsx->mod_data[mod_stateful_util.id]; + if (tsx_data == NULL) + return; + + if (tsx->status_code < 200) + return; + + /* Call the callback, if any, and prevent the callback to be called again + * by clearing the transaction's module_data. + */ + tsx->mod_data[mod_stateful_util.id] = NULL; + + if (tsx_data->cb) { + (*tsx_data->cb)(tsx_data->token, event); + } +} + + +PJ_DEF(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, + pjsip_tx_data *tdata, + pj_int32_t timeout, + void *token, + pjsip_endpt_send_callback cb) +{ + pjsip_transaction *tsx; + struct tsx_data *tsx_data; + pj_status_t status; + + PJ_ASSERT_RETURN(endpt && tdata && (timeout==-1 || timeout>0), PJ_EINVAL); + + /* Check that transaction layer module is registered to endpoint */ + PJ_ASSERT_RETURN(mod_stateful_util.id != -1, PJ_EINVALIDOP); + + PJ_UNUSED_ARG(timeout); + + status = pjsip_tsx_create_uac(&mod_stateful_util, tdata, &tsx); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return status; + } + + pjsip_tsx_set_transport(tsx, &tdata->tp_sel); + + tsx_data = PJ_POOL_ALLOC_T(tsx->pool, struct tsx_data); + tsx_data->token = token; + tsx_data->cb = cb; + + tsx->mod_data[mod_stateful_util.id] = tsx_data; + + status = pjsip_tsx_send_msg(tsx, NULL); + if (status != PJ_SUCCESS) + pjsip_tx_data_dec_ref(tdata); + + return status; +} + + +/* + * Send response statefully. + */ +PJ_DEF(pj_status_t) pjsip_endpt_respond( pjsip_endpoint *endpt, + pjsip_module *tsx_user, + pjsip_rx_data *rdata, + int st_code, + const pj_str_t *st_text, + const pjsip_hdr *hdr_list, + const pjsip_msg_body *body, + pjsip_transaction **p_tsx ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + pjsip_transaction *tsx; + + /* Validate arguments. */ + PJ_ASSERT_RETURN(endpt && rdata, PJ_EINVAL); + + if (p_tsx) *p_tsx = NULL; + + /* Create response message */ + status = pjsip_endpt_create_response( endpt, rdata, st_code, st_text, + &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Add the message headers, 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; + } + } + + /* Add the message body, if any. */ + if (body) { + tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body ); + if (tdata->msg->body == NULL) { + pjsip_tx_data_dec_ref(tdata); + return status; + } + } + + /* Create UAS transaction. */ + status = pjsip_tsx_create_uas(tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + return status; + } + + /* Feed the request to the transaction. */ + pjsip_tsx_recv_msg(tsx, rdata); + + /* Send the message. */ + status = pjsip_tsx_send_msg(tsx, tdata); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + } else if (p_tsx) { + *p_tsx = tsx; + } + + return status; +} + + diff --git a/pjsip/src/pjsip/sip_util_wrap.cpp b/pjsip/src/pjsip/sip_util_wrap.cpp new file mode 100644 index 0000000..140e907 --- /dev/null +++ b/pjsip/src/pjsip/sip_util_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: sip_util_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * + * 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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "sip_util.c" diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c new file mode 100644 index 0000000..c5278c4 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -0,0 +1,3078 @@ +/* $Id: pjsua_acc.c 4185 2012-06-28 14:16:05Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + + +#define THIS_FILE "pjsua_acc.c" + +enum +{ + OUTBOUND_UNKNOWN, // status unknown + OUTBOUND_WANTED, // initiated in registration + OUTBOUND_ACTIVE, // got positive response from server + OUTBOUND_NA // not wanted or got negative response from server +}; + + +static void schedule_reregistration(pjsua_acc *acc); +static void keep_alive_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te); + +/* + * Get number of current accounts. + */ +PJ_DEF(unsigned) pjsua_acc_get_count(void) +{ + return pjsua_var.acc_cnt; +} + + +/* + * Check if the specified account ID is valid. + */ +PJ_DEF(pj_bool_t) pjsua_acc_is_valid(pjsua_acc_id acc_id) +{ + return acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc) && + pjsua_var.acc[acc_id].valid; +} + + +/* + * Set default account + */ +PJ_DEF(pj_status_t) pjsua_acc_set_default(pjsua_acc_id acc_id) +{ + pjsua_var.default_acc = acc_id; + return PJ_SUCCESS; +} + + +/* + * Get default account. + */ +PJ_DEF(pjsua_acc_id) pjsua_acc_get_default(void) +{ + return pjsua_var.default_acc; +} + + +/* + * Copy account configuration. + */ +PJ_DEF(void) pjsua_acc_config_dup( pj_pool_t *pool, + pjsua_acc_config *dst, + const pjsua_acc_config *src) +{ + unsigned i; + + pj_memcpy(dst, src, sizeof(pjsua_acc_config)); + + pj_strdup_with_null(pool, &dst->id, &src->id); + pj_strdup_with_null(pool, &dst->reg_uri, &src->reg_uri); + pj_strdup_with_null(pool, &dst->force_contact, &src->force_contact); + pj_strdup_with_null(pool, &dst->contact_params, &src->contact_params); + pj_strdup_with_null(pool, &dst->contact_uri_params, + &src->contact_uri_params); + pj_strdup_with_null(pool, &dst->pidf_tuple_id, &src->pidf_tuple_id); + pj_strdup_with_null(pool, &dst->rfc5626_instance_id, + &src->rfc5626_instance_id); + pj_strdup_with_null(pool, &dst->rfc5626_reg_id, &src->rfc5626_reg_id); + + dst->proxy_cnt = src->proxy_cnt; + for (i=0; i<src->proxy_cnt; ++i) + pj_strdup_with_null(pool, &dst->proxy[i], &src->proxy[i]); + + dst->reg_timeout = src->reg_timeout; + dst->reg_delay_before_refresh = src->reg_delay_before_refresh; + dst->cred_count = src->cred_count; + + for (i=0; i<src->cred_count; ++i) { + pjsip_cred_dup(pool, &dst->cred_info[i], &src->cred_info[i]); + } + + pj_list_init(&dst->reg_hdr_list); + if (!pj_list_empty(&src->reg_hdr_list)) { + const pjsip_hdr *hdr; + + hdr = src->reg_hdr_list.next; + while (hdr != &src->reg_hdr_list) { + pj_list_push_back(&dst->reg_hdr_list, pjsip_hdr_clone(pool, hdr)); + hdr = hdr->next; + } + } + + pj_list_init(&dst->sub_hdr_list); + if (!pj_list_empty(&src->sub_hdr_list)) { + const pjsip_hdr *hdr; + + hdr = src->sub_hdr_list.next; + while (hdr != &src->sub_hdr_list) { + pj_list_push_back(&dst->sub_hdr_list, pjsip_hdr_clone(pool, hdr)); + hdr = hdr->next; + } + } + + pjsip_auth_clt_pref_dup(pool, &dst->auth_pref, &src->auth_pref); + + pjsua_transport_config_dup(pool, &dst->rtp_cfg, &src->rtp_cfg); + + pj_strdup(pool, &dst->ka_data, &src->ka_data); +} + +/* + * Calculate CRC of proxy list. + */ +static pj_uint32_t calc_proxy_crc(const pj_str_t proxy[], pj_size_t cnt) +{ + pj_crc32_context ctx; + unsigned i; + + pj_crc32_init(&ctx); + for (i=0; i<cnt; ++i) { + pj_crc32_update(&ctx, (pj_uint8_t*)proxy[i].ptr, proxy[i].slen); + } + + return pj_crc32_final(&ctx); +} + +/* + * Initialize a new account (after configuration is set). + */ +static pj_status_t initialize_acc(unsigned acc_id) +{ + pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg; + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsip_name_addr *name_addr; + pjsip_sip_uri *sip_reg_uri; + pj_status_t status; + unsigned i; + + /* Need to parse local_uri to get the elements: */ + + name_addr = (pjsip_name_addr*) + pjsip_parse_uri(acc->pool, acc_cfg->id.ptr, + acc_cfg->id.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (name_addr == NULL) { + pjsua_perror(THIS_FILE, "Invalid local URI", + PJSIP_EINVALIDURI); + return PJSIP_EINVALIDURI; + } + + /* Local URI MUST be a SIP or SIPS: */ + if (!PJSIP_URI_SCHEME_IS_SIP(name_addr) && + !PJSIP_URI_SCHEME_IS_SIPS(name_addr)) + { + acc->display = name_addr->display; + acc->user_part = name_addr->display; + acc->srv_domain = pj_str(""); + acc->srv_port = 0; + } else { + pjsip_sip_uri *sip_uri; + + /* Get the SIP URI object: */ + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(name_addr); + + /* Save the user and domain part. These will be used when finding an + * account for incoming requests. + */ + acc->display = name_addr->display; + acc->user_part = sip_uri->user; + acc->srv_domain = sip_uri->host; + acc->srv_port = 0; + } + + + /* Parse registrar URI, if any */ + if (acc_cfg->reg_uri.slen) { + pjsip_uri *reg_uri; + + reg_uri = pjsip_parse_uri(acc->pool, acc_cfg->reg_uri.ptr, + acc_cfg->reg_uri.slen, 0); + if (reg_uri == NULL) { + pjsua_perror(THIS_FILE, "Invalid registrar URI", + PJSIP_EINVALIDURI); + return PJSIP_EINVALIDURI; + } + + /* Registrar URI MUST be a SIP or SIPS: */ + if (!PJSIP_URI_SCHEME_IS_SIP(reg_uri) && + !PJSIP_URI_SCHEME_IS_SIPS(reg_uri)) + { + pjsua_perror(THIS_FILE, "Invalid registar URI", + PJSIP_EINVALIDSCHEME); + return PJSIP_EINVALIDSCHEME; + } + + sip_reg_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(reg_uri); + + } else { + sip_reg_uri = NULL; + } + + if (sip_reg_uri) { + acc->srv_port = sip_reg_uri->port; + } + + /* Create Contact header if not present. */ + //if (acc_cfg->contact.slen == 0) { + // acc_cfg->contact = acc_cfg->id; + //} + + /* Build account route-set from outbound proxies and route set from + * account configuration. + */ + pj_list_init(&acc->route_set); + + if (!pj_list_empty(&pjsua_var.outbound_proxy)) { + pjsip_route_hdr *r; + + r = pjsua_var.outbound_proxy.next; + while (r != &pjsua_var.outbound_proxy) { + pj_list_push_back(&acc->route_set, + pjsip_hdr_shallow_clone(acc->pool, r)); + r = r->next; + } + } + + for (i=0; i<acc_cfg->proxy_cnt; ++i) { + pj_str_t hname = { "Route", 5}; + pjsip_route_hdr *r; + pj_str_t tmp; + + pj_strdup_with_null(acc->pool, &tmp, &acc_cfg->proxy[i]); + r = (pjsip_route_hdr*) + pjsip_parse_hdr(acc->pool, &hname, tmp.ptr, tmp.slen, NULL); + if (r == NULL) { + pjsua_perror(THIS_FILE, "Invalid URI in account route set", + PJ_EINVAL); + return PJ_EINVAL; + } + pj_list_push_back(&acc->route_set, r); + } + + /* Concatenate credentials from account config and global config */ + acc->cred_cnt = 0; + for (i=0; i<acc_cfg->cred_count; ++i) { + acc->cred[acc->cred_cnt++] = acc_cfg->cred_info[i]; + } + for (i=0; i<pjsua_var.ua_cfg.cred_count && + acc->cred_cnt < PJ_ARRAY_SIZE(acc->cred); ++i) + { + acc->cred[acc->cred_cnt++] = pjsua_var.ua_cfg.cred_info[i]; + } + + /* If ICE is enabled, add "+sip.ice" media feature tag in account's + * contact params. + */ +#if PJSUA_ADD_ICE_TAGS + if (pjsua_var.media_cfg.enable_ice) { + unsigned new_len; + pj_str_t new_prm; + + new_len = acc_cfg->contact_params.slen + 10; + new_prm.ptr = (char*)pj_pool_alloc(acc->pool, new_len); + pj_strcpy(&new_prm, &acc_cfg->contact_params); + pj_strcat2(&new_prm, ";+sip.ice"); + acc_cfg->contact_params = new_prm; + } +#endif + + status = pjsua_pres_init_acc(acc_id); + if (status != PJ_SUCCESS) + return status; + + /* If SIP outbound is enabled, generate instance and reg ID if they are + * not specified + */ + if (acc_cfg->use_rfc5626) { + if (acc_cfg->rfc5626_instance_id.slen==0) { + const pj_str_t *hostname; + pj_uint32_t hval, pos; + char instprm[] = ";+sip.instance=\"<urn:uuid:00000000-0000-0000-0000-0000CCDDEEFF>\""; + + hostname = pj_gethostname(); + pos = pj_ansi_strlen(instprm) - 10; + hval = pj_hash_calc(0, hostname->ptr, hostname->slen); + pj_val_to_hex_digit( ((char*)&hval)[0], instprm+pos+0); + pj_val_to_hex_digit( ((char*)&hval)[1], instprm+pos+2); + pj_val_to_hex_digit( ((char*)&hval)[2], instprm+pos+4); + pj_val_to_hex_digit( ((char*)&hval)[3], instprm+pos+6); + + pj_strdup2(acc->pool, &acc->rfc5626_instprm, instprm); + } else { + const char *prmname = ";+sip.instance=\""; + unsigned len; + + len = pj_ansi_strlen(prmname) + acc_cfg->rfc5626_instance_id.slen + 1; + acc->rfc5626_instprm.ptr = (char*)pj_pool_alloc(acc->pool, len+1); + pj_ansi_snprintf(acc->rfc5626_instprm.ptr, len+1, + "%s%.*s\"", + prmname, + (int)acc_cfg->rfc5626_instance_id.slen, + acc_cfg->rfc5626_instance_id.ptr); + acc->rfc5626_instprm.slen = len; + } + + if (acc_cfg->rfc5626_reg_id.slen==0) { + acc->rfc5626_regprm = pj_str(";reg-id=1"); + } else { + const char *prmname = ";reg-id="; + unsigned len; + + len = pj_ansi_strlen(prmname) + acc_cfg->rfc5626_reg_id.slen; + acc->rfc5626_regprm.ptr = (char*)pj_pool_alloc(acc->pool, len+1); + pj_ansi_snprintf(acc->rfc5626_regprm.ptr, len+1, + "%s%.*s\"", + prmname, + (int)acc_cfg->rfc5626_reg_id.slen, + acc_cfg->rfc5626_reg_id.ptr); + acc->rfc5626_regprm.slen = len; + } + } + + /* Mark account as valid */ + pjsua_var.acc[acc_id].valid = PJ_TRUE; + + /* Insert account ID into account ID array, sorted by priority */ + for (i=0; i<pjsua_var.acc_cnt; ++i) { + if ( pjsua_var.acc[pjsua_var.acc_ids[i]].cfg.priority < + pjsua_var.acc[acc_id].cfg.priority) + { + break; + } + } + pj_array_insert(pjsua_var.acc_ids, sizeof(pjsua_var.acc_ids[0]), + pjsua_var.acc_cnt, i, &acc_id); + + return PJ_SUCCESS; +} + + +/* + * Add a new account to pjsua. + */ +PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg, + pj_bool_t is_default, + pjsua_acc_id *p_acc_id) +{ + pjsua_acc *acc; + unsigned i, id; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(cfg, PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc_cnt < PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_ETOOMANY); + + /* Must have a transport */ + PJ_ASSERT_RETURN(pjsua_var.tpdata[0].data.ptr != NULL, PJ_EINVALIDOP); + + PJ_LOG(4,(THIS_FILE, "Adding account: id=%.*s", + (int)cfg->id.slen, cfg->id.ptr)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + /* Find empty account id. */ + for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.acc); ++id) { + if (pjsua_var.acc[id].valid == PJ_FALSE) + break; + } + + /* Expect to find a slot */ + PJ_ASSERT_ON_FAIL( id < PJ_ARRAY_SIZE(pjsua_var.acc), + {PJSUA_UNLOCK(); return PJ_EBUG;}); + + acc = &pjsua_var.acc[id]; + + /* Create pool for this account. */ + if (acc->pool) + pj_pool_reset(acc->pool); + else + acc->pool = pjsua_pool_create("acc%p", 512, 256); + + /* Copy config */ + pjsua_acc_config_dup(acc->pool, &pjsua_var.acc[id].cfg, cfg); + + /* Normalize registration timeout and refresh delay */ + if (pjsua_var.acc[id].cfg.reg_uri.slen) { + if (pjsua_var.acc[id].cfg.reg_timeout == 0) { + pjsua_var.acc[id].cfg.reg_timeout = PJSUA_REG_INTERVAL; + } + if (pjsua_var.acc[id].cfg.reg_delay_before_refresh == 0) { + pjsua_var.acc[id].cfg.reg_delay_before_refresh = + PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH; + } + } + + /* Check the route URI's and force loose route if required */ + for (i=0; i<acc->cfg.proxy_cnt; ++i) { + status = normalize_route_uri(acc->pool, &acc->cfg.proxy[i]); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; + } + } + + /* Get CRC of account proxy setting */ + acc->local_route_crc = calc_proxy_crc(acc->cfg.proxy, acc->cfg.proxy_cnt); + + /* Get CRC of global outbound proxy setting */ + acc->global_route_crc=calc_proxy_crc(pjsua_var.ua_cfg.outbound_proxy, + pjsua_var.ua_cfg.outbound_proxy_cnt); + + status = initialize_acc(id); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error adding account", status); + pj_pool_release(acc->pool); + acc->pool = NULL; + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; + } + + if (is_default) + pjsua_var.default_acc = id; + + if (p_acc_id) + *p_acc_id = id; + + pjsua_var.acc_cnt++; + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "Account %.*s added with id %d", + (int)cfg->id.slen, cfg->id.ptr, id)); + + /* If accounts has registration enabled, start registration */ + if (pjsua_var.acc[id].cfg.reg_uri.slen) { + if (pjsua_var.acc[id].cfg.register_on_acc_add) + pjsua_acc_set_registration(id, PJ_TRUE); + } else { + /* Otherwise subscribe to MWI, if it's enabled */ + if (pjsua_var.acc[id].cfg.mwi_enabled) + pjsua_start_mwi(id, PJ_TRUE); + } + + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Add local account + */ +PJ_DEF(pj_status_t) pjsua_acc_add_local( pjsua_transport_id tid, + pj_bool_t is_default, + pjsua_acc_id *p_acc_id) +{ + pjsua_acc_config cfg; + pjsua_transport_data *t = &pjsua_var.tpdata[tid]; + const char *beginquote, *endquote; + char transport_param[32]; + char uri[PJSIP_MAX_URL_SIZE]; + + /* ID must be valid */ + PJ_ASSERT_RETURN(tid>=0 && tid<(int)PJ_ARRAY_SIZE(pjsua_var.tpdata), + PJ_EINVAL); + + /* Transport must be valid */ + PJ_ASSERT_RETURN(t->data.ptr != NULL, PJ_EINVAL); + + pjsua_acc_config_default(&cfg); + + /* Lower the priority of local account */ + --cfg.priority; + + /* Enclose IPv6 address in square brackets */ + if (t->type & PJSIP_TRANSPORT_IPV6) { + beginquote = "["; + endquote = "]"; + } else { + beginquote = endquote = ""; + } + + /* Don't add transport parameter if it's UDP */ + if (t->type!=PJSIP_TRANSPORT_UDP && t->type!=PJSIP_TRANSPORT_UDP6) { + pj_ansi_snprintf(transport_param, sizeof(transport_param), + ";transport=%s", + pjsip_transport_get_type_name(t->type)); + } else { + transport_param[0] = '\0'; + } + + /* Build URI for the account */ + pj_ansi_snprintf(uri, PJSIP_MAX_URL_SIZE, + "<sip:%s%.*s%s:%d%s>", + beginquote, + (int)t->local_name.host.slen, + t->local_name.host.ptr, + endquote, + t->local_name.port, + transport_param); + + cfg.id = pj_str(uri); + + return pjsua_acc_add(&cfg, is_default, p_acc_id); +} + + +/* + * Set arbitrary data to be associated with the account. + */ +PJ_DEF(pj_status_t) pjsua_acc_set_user_data(pjsua_acc_id acc_id, + void *user_data) +{ + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJSUA_LOCK(); + + pjsua_var.acc[acc_id].cfg.user_data = user_data; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * Retrieve arbitrary data associated with the account. + */ +PJ_DEF(void*) pjsua_acc_get_user_data(pjsua_acc_id acc_id) +{ + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + NULL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, NULL); + + return pjsua_var.acc[acc_id].cfg.user_data; +} + + +/* + * Delete account. + */ +PJ_DEF(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_id) +{ + pjsua_acc *acc; + unsigned i; + + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJ_LOG(4,(THIS_FILE, "Deleting account %d..", acc_id)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + acc = &pjsua_var.acc[acc_id]; + + /* Cancel keep-alive timer, if any */ + if (acc->ka_timer.id) { + pjsip_endpt_cancel_timer(pjsua_var.endpt, &acc->ka_timer); + acc->ka_timer.id = PJ_FALSE; + } + if (acc->ka_transport) { + pjsip_transport_dec_ref(acc->ka_transport); + acc->ka_transport = NULL; + } + + /* Cancel any re-registration timer */ + if (acc->auto_rereg.timer.id) { + acc->auto_rereg.timer.id = PJ_FALSE; + pjsua_cancel_timer(&acc->auto_rereg.timer); + } + + /* Delete registration */ + if (acc->regc != NULL) { + pjsua_acc_set_registration(acc_id, PJ_FALSE); + if (acc->regc) { + pjsip_regc_destroy(acc->regc); + } + acc->regc = NULL; + } + + /* Terminate mwi subscription */ + if (acc->cfg.mwi_enabled) { + acc->cfg.mwi_enabled = PJ_FALSE; + pjsua_start_mwi(acc_id, PJ_FALSE); + } + + /* Delete server presence subscription */ + pjsua_pres_delete_acc(acc_id, 0); + + /* Release account pool */ + if (acc->pool) { + pj_pool_release(acc->pool); + acc->pool = NULL; + } + + /* Invalidate */ + acc->valid = PJ_FALSE; + acc->contact.slen = 0; + pj_bzero(&acc->via_addr, sizeof(acc->via_addr)); + acc->via_tp = NULL; + + /* Remove from array */ + for (i=0; i<pjsua_var.acc_cnt; ++i) { + if (pjsua_var.acc_ids[i] == acc_id) + break; + } + if (i != pjsua_var.acc_cnt) { + pj_array_erase(pjsua_var.acc_ids, sizeof(pjsua_var.acc_ids[0]), + pjsua_var.acc_cnt, i); + --pjsua_var.acc_cnt; + } + + /* Leave the calls intact, as I don't think calls need to + * access account once it's created + */ + + /* Update default account */ + if (pjsua_var.default_acc == acc_id) + pjsua_var.default_acc = 0; + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "Account id %d deleted", acc_id)); + + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* Get config */ +PJ_DEF(pj_status_t) pjsua_acc_get_config(pjsua_acc_id acc_id, + pjsua_acc_config *acc_cfg) +{ + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc) + && pjsua_var.acc[acc_id].valid, PJ_EINVAL); + pj_memcpy(acc_cfg, &pjsua_var.acc[acc_id].cfg, sizeof(*acc_cfg)); + return PJ_SUCCESS; +} + +/* + * Modify account information. + */ +PJ_DEF(pj_status_t) pjsua_acc_modify( pjsua_acc_id acc_id, + const pjsua_acc_config *cfg) +{ + pjsua_acc *acc; + pjsip_name_addr *id_name_addr = NULL; + pjsip_sip_uri *id_sip_uri = NULL; + pjsip_sip_uri *reg_sip_uri = NULL; + pj_uint32_t local_route_crc, global_route_crc; + pjsip_route_hdr global_route; + pjsip_route_hdr local_route; + pj_str_t acc_proxy[PJSUA_ACC_MAX_PROXIES]; + pj_bool_t update_reg = PJ_FALSE; + pj_bool_t unreg_first = PJ_FALSE; + pj_bool_t update_mwi = PJ_FALSE; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Modifying accunt %d", acc_id)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + acc = &pjsua_var.acc[acc_id]; + if (!acc->valid) { + status = PJ_EINVAL; + goto on_return; + } + + /* == Validate first == */ + + /* Account id */ + if (pj_strcmp(&acc->cfg.id, &cfg->id)) { + /* Need to parse id to get the elements: */ + id_name_addr = (pjsip_name_addr*) + pjsip_parse_uri(acc->pool, cfg->id.ptr, cfg->id.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + if (id_name_addr == NULL) { + status = PJSIP_EINVALIDURI; + pjsua_perror(THIS_FILE, "Invalid local URI", status); + goto on_return; + } + + /* URI MUST be a SIP or SIPS: */ + if (!PJSIP_URI_SCHEME_IS_SIP(id_name_addr) && + !PJSIP_URI_SCHEME_IS_SIPS(id_name_addr)) + { + status = PJSIP_EINVALIDSCHEME; + pjsua_perror(THIS_FILE, "Invalid local URI", status); + goto on_return; + } + + /* Get the SIP URI object: */ + id_sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(id_name_addr); + } + + /* Registrar URI */ + if (pj_strcmp(&acc->cfg.reg_uri, &cfg->reg_uri) && cfg->reg_uri.slen) { + pjsip_uri *reg_uri; + + /* Need to parse reg_uri to get the elements: */ + reg_uri = pjsip_parse_uri(acc->pool, cfg->reg_uri.ptr, + cfg->reg_uri.slen, 0); + if (reg_uri == NULL) { + status = PJSIP_EINVALIDURI; + pjsua_perror(THIS_FILE, "Invalid registrar URI", status); + goto on_return; + } + + /* Registrar URI MUST be a SIP or SIPS: */ + if (!PJSIP_URI_SCHEME_IS_SIP(reg_uri) && + !PJSIP_URI_SCHEME_IS_SIPS(reg_uri)) + { + status = PJSIP_EINVALIDSCHEME; + pjsua_perror(THIS_FILE, "Invalid registar URI", status); + goto on_return; + } + + reg_sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(reg_uri); + } + + /* Global outbound proxy */ + global_route_crc = calc_proxy_crc(pjsua_var.ua_cfg.outbound_proxy, + pjsua_var.ua_cfg.outbound_proxy_cnt); + if (global_route_crc != acc->global_route_crc) { + pjsip_route_hdr *r; + + /* Copy from global outbound proxies */ + pj_list_init(&global_route); + r = pjsua_var.outbound_proxy.next; + while (r != &pjsua_var.outbound_proxy) { + pj_list_push_back(&global_route, + pjsip_hdr_shallow_clone(acc->pool, r)); + r = r->next; + } + } + + /* Account proxy */ + local_route_crc = calc_proxy_crc(cfg->proxy, cfg->proxy_cnt); + if (local_route_crc != acc->local_route_crc) { + pjsip_route_hdr *r; + unsigned i; + + /* Validate the local route and save it to temporary var */ + pj_list_init(&local_route); + for (i=0; i<cfg->proxy_cnt; ++i) { + pj_str_t hname = { "Route", 5}; + + pj_strdup_with_null(acc->pool, &acc_proxy[i], &cfg->proxy[i]); + status = normalize_route_uri(acc->pool, &acc_proxy[i]); + if (status != PJ_SUCCESS) + goto on_return; + r = (pjsip_route_hdr*) + pjsip_parse_hdr(acc->pool, &hname, acc_proxy[i].ptr, + acc_proxy[i].slen, NULL); + if (r == NULL) { + status = PJSIP_EINVALIDURI; + pjsua_perror(THIS_FILE, "Invalid URI in account route set", + status); + goto on_return; + } + + pj_list_push_back(&local_route, r); + } + + /* Recalculate the CRC again after route URI normalization */ + local_route_crc = calc_proxy_crc(acc_proxy, cfg->proxy_cnt); + } + + + /* == Apply the new config == */ + + /* Account ID. */ + if (id_name_addr && id_sip_uri) { + pj_strdup_with_null(acc->pool, &acc->cfg.id, &cfg->id); + pj_strdup_with_null(acc->pool, &acc->display, &id_name_addr->display); + pj_strdup_with_null(acc->pool, &acc->user_part, &id_sip_uri->user); + pj_strdup_with_null(acc->pool, &acc->srv_domain, &id_sip_uri->host); + acc->srv_port = 0; + update_reg = PJ_TRUE; + unreg_first = PJ_TRUE; + } + + /* User data */ + acc->cfg.user_data = cfg->user_data; + + /* Priority */ + if (acc->cfg.priority != cfg->priority) { + unsigned i; + + acc->cfg.priority = cfg->priority; + + /* Resort accounts priority */ + for (i=0; i<pjsua_var.acc_cnt; ++i) { + if (pjsua_var.acc_ids[i] == acc_id) + break; + } + pj_assert(i < pjsua_var.acc_cnt); + pj_array_erase(pjsua_var.acc_ids, sizeof(acc_id), + pjsua_var.acc_cnt, i); + for (i=0; i<pjsua_var.acc_cnt; ++i) { + if (pjsua_var.acc[pjsua_var.acc_ids[i]].cfg.priority < + acc->cfg.priority) + { + break; + } + } + pj_array_insert(pjsua_var.acc_ids, sizeof(acc_id), + pjsua_var.acc_cnt, i, &acc_id); + } + + /* MWI */ + if (acc->cfg.mwi_enabled != cfg->mwi_enabled) { + acc->cfg.mwi_enabled = cfg->mwi_enabled; + update_mwi = PJ_TRUE; + } + if (acc->cfg.mwi_expires != cfg->mwi_expires && cfg->mwi_expires > 0) { + acc->cfg.mwi_expires = cfg->mwi_expires; + update_mwi = PJ_TRUE; + } + + /* PIDF tuple ID */ + if (pj_strcmp(&acc->cfg.pidf_tuple_id, &cfg->pidf_tuple_id)) + pj_strdup_with_null(acc->pool, &acc->cfg.pidf_tuple_id, + &cfg->pidf_tuple_id); + + /* Publish */ + acc->cfg.publish_opt = cfg->publish_opt; + acc->cfg.unpublish_max_wait_time_msec = cfg->unpublish_max_wait_time_msec; + if (acc->cfg.publish_enabled != cfg->publish_enabled) { + acc->cfg.publish_enabled = cfg->publish_enabled; + if (!acc->cfg.publish_enabled) + pjsua_pres_unpublish(acc, 0); + else + update_reg = PJ_TRUE; + } + + /* Force contact URI */ + if (pj_strcmp(&acc->cfg.force_contact, &cfg->force_contact)) { + pj_strdup_with_null(acc->pool, &acc->cfg.force_contact, + &cfg->force_contact); + update_reg = PJ_TRUE; + unreg_first = PJ_TRUE; + } + + /* Contact param */ + if (pj_strcmp(&acc->cfg.contact_params, &cfg->contact_params)) { + pj_strdup_with_null(acc->pool, &acc->cfg.contact_params, + &cfg->contact_params); + update_reg = PJ_TRUE; + } + + /* Contact URI params */ + if (pj_strcmp(&acc->cfg.contact_uri_params, &cfg->contact_uri_params)) { + pj_strdup_with_null(acc->pool, &acc->cfg.contact_uri_params, + &cfg->contact_uri_params); + update_reg = PJ_TRUE; + } + + /* Reliable provisional response */ + acc->cfg.require_100rel = cfg->require_100rel; + + /* Session timer */ + acc->cfg.use_timer = cfg->use_timer; + acc->cfg.timer_setting = cfg->timer_setting; + + /* Transport */ + if (acc->cfg.transport_id != cfg->transport_id) { + acc->cfg.transport_id = cfg->transport_id; + update_reg = PJ_TRUE; + } + + /* Update keep-alive */ + if (acc->cfg.ka_interval != cfg->ka_interval || + pj_strcmp(&acc->cfg.ka_data, &cfg->ka_data)) + { + pjsip_transport *ka_transport = acc->ka_transport; + + if (acc->ka_timer.id) { + pjsip_endpt_cancel_timer(pjsua_var.endpt, &acc->ka_timer); + acc->ka_timer.id = PJ_FALSE; + } + if (acc->ka_transport) { + pjsip_transport_dec_ref(acc->ka_transport); + acc->ka_transport = NULL; + } + + acc->cfg.ka_interval = cfg->ka_interval; + + if (cfg->ka_interval) { + if (ka_transport) { + /* Keep-alive has been running so we can just restart it */ + pj_time_val delay; + + pjsip_transport_add_ref(ka_transport); + acc->ka_transport = ka_transport; + + acc->ka_timer.cb = &keep_alive_timer_cb; + acc->ka_timer.user_data = (void*)acc; + + delay.sec = acc->cfg.ka_interval; + delay.msec = 0; + status = pjsua_schedule_timer(&acc->ka_timer, &delay); + if (status == PJ_SUCCESS) { + acc->ka_timer.id = PJ_TRUE; + } else { + pjsip_transport_dec_ref(ka_transport); + acc->ka_transport = NULL; + pjsua_perror(THIS_FILE, "Error starting keep-alive timer", + status); + } + + } else { + /* Keep-alive has not been running, we need to (re)register + * first. + */ + update_reg = PJ_TRUE; + } + } + } + + if (pj_strcmp(&acc->cfg.ka_data, &cfg->ka_data)) + pj_strdup(acc->pool, &acc->cfg.ka_data, &cfg->ka_data); +#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) + acc->cfg.use_srtp = cfg->use_srtp; + acc->cfg.srtp_secure_signaling = cfg->srtp_secure_signaling; + acc->cfg.srtp_optional_dup_offer = cfg->srtp_optional_dup_offer; +#endif + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0) + acc->cfg.use_stream_ka = cfg->use_stream_ka; +#endif + + /* Use of proxy */ + if (acc->cfg.reg_use_proxy != cfg->reg_use_proxy) { + acc->cfg.reg_use_proxy = cfg->reg_use_proxy; + update_reg = PJ_TRUE; + } + + /* Global outbound proxy */ + if (global_route_crc != acc->global_route_crc) { + unsigned i, rcnt; + + /* Remove the outbound proxies from the route set */ + rcnt = pj_list_size(&acc->route_set); + for (i=0; i < rcnt - acc->cfg.proxy_cnt; ++i) { + pjsip_route_hdr *r = acc->route_set.next; + pj_list_erase(r); + } + + /* Insert the outbound proxies to the beginning of route set */ + pj_list_merge_first(&acc->route_set, &global_route); + + /* Update global route CRC */ + acc->global_route_crc = global_route_crc; + + update_reg = PJ_TRUE; + } + + /* Account proxy */ + if (local_route_crc != acc->local_route_crc) { + unsigned i; + + /* Remove the current account proxies from the route set */ + for (i=0; i < acc->cfg.proxy_cnt; ++i) { + pjsip_route_hdr *r = acc->route_set.prev; + pj_list_erase(r); + } + + /* Insert new proxy setting to the route set */ + pj_list_merge_last(&acc->route_set, &local_route); + + /* Update the proxy setting */ + acc->cfg.proxy_cnt = cfg->proxy_cnt; + for (i = 0; i < cfg->proxy_cnt; ++i) + acc->cfg.proxy[i] = acc_proxy[i]; + + /* Update local route CRC */ + acc->local_route_crc = local_route_crc; + + update_reg = PJ_TRUE; + } + + /* Credential info */ + { + unsigned i; + + /* Selective update credential info. */ + for (i = 0; i < cfg->cred_count; ++i) { + unsigned j; + pjsip_cred_info ci; + + /* Find if this credential is already listed */ + for (j = i; j < acc->cfg.cred_count; ++j) { + if (pjsip_cred_info_cmp(&acc->cfg.cred_info[j], + &cfg->cred_info[i]) == 0) + { + /* Found, but different index/position, swap */ + if (j != i) { + ci = acc->cfg.cred_info[i]; + acc->cfg.cred_info[i] = acc->cfg.cred_info[j]; + acc->cfg.cred_info[j] = ci; + } + break; + } + } + + /* Not found, insert this */ + if (j == acc->cfg.cred_count) { + /* If account credential is full, discard the last one. */ + if (acc->cfg.cred_count == PJ_ARRAY_SIZE(acc->cfg.cred_info)) { + pj_array_erase(acc->cfg.cred_info, sizeof(pjsip_cred_info), + acc->cfg.cred_count, acc->cfg.cred_count-1); + acc->cfg.cred_count--; + } + + /* Insert this */ + pjsip_cred_info_dup(acc->pool, &ci, &cfg->cred_info[i]); + pj_array_insert(acc->cfg.cred_info, sizeof(pjsip_cred_info), + acc->cfg.cred_count, i, &ci); + } + } + acc->cfg.cred_count = cfg->cred_count; + + /* Concatenate credentials from account config and global config */ + acc->cred_cnt = 0; + for (i=0; i<acc->cfg.cred_count; ++i) { + acc->cred[acc->cred_cnt++] = acc->cfg.cred_info[i]; + } + for (i=0; i<pjsua_var.ua_cfg.cred_count && + acc->cred_cnt < PJ_ARRAY_SIZE(acc->cred); ++i) + { + acc->cred[acc->cred_cnt++] = pjsua_var.ua_cfg.cred_info[i]; + } + } + + /* Authentication preference */ + acc->cfg.auth_pref.initial_auth = cfg->auth_pref.initial_auth; + if (pj_strcmp(&acc->cfg.auth_pref.algorithm, &cfg->auth_pref.algorithm)) + pj_strdup_with_null(acc->pool, &acc->cfg.auth_pref.algorithm, + &cfg->auth_pref.algorithm); + + /* Registration */ + if (acc->cfg.reg_timeout != cfg->reg_timeout) { + acc->cfg.reg_timeout = cfg->reg_timeout; + if (acc->regc != NULL) + pjsip_regc_update_expires(acc->regc, acc->cfg.reg_timeout); + + update_reg = PJ_TRUE; + } + acc->cfg.unreg_timeout = cfg->unreg_timeout; + acc->cfg.allow_contact_rewrite = cfg->allow_contact_rewrite; + acc->cfg.reg_retry_interval = cfg->reg_retry_interval; + acc->cfg.reg_first_retry_interval = cfg->reg_first_retry_interval; + acc->cfg.drop_calls_on_reg_fail = cfg->drop_calls_on_reg_fail; + acc->cfg.register_on_acc_add = cfg->register_on_acc_add; + if (acc->cfg.reg_delay_before_refresh != cfg->reg_delay_before_refresh) { + acc->cfg.reg_delay_before_refresh = cfg->reg_delay_before_refresh; + if (acc->regc != NULL) + pjsip_regc_set_delay_before_refresh(acc->regc, + cfg->reg_delay_before_refresh); + } + + /* Allow via rewrite */ + if (acc->cfg.allow_via_rewrite != cfg->allow_via_rewrite) { + if (acc->regc != NULL) { + if (cfg->allow_via_rewrite) { + pjsip_regc_set_via_sent_by(acc->regc, &acc->via_addr, + acc->via_tp); + } else + pjsip_regc_set_via_sent_by(acc->regc, NULL, NULL); + } + if (acc->publish_sess != NULL) { + if (cfg->allow_via_rewrite) { + pjsip_publishc_set_via_sent_by(acc->publish_sess, + &acc->via_addr, acc->via_tp); + } else + pjsip_publishc_set_via_sent_by(acc->publish_sess, NULL, NULL); + } + acc->cfg.allow_via_rewrite = cfg->allow_via_rewrite; + } + + /* Normalize registration timeout and refresh delay */ + if (acc->cfg.reg_uri.slen ) { + if (acc->cfg.reg_timeout == 0) { + acc->cfg.reg_timeout = PJSUA_REG_INTERVAL; + } + if (acc->cfg.reg_delay_before_refresh == 0) { + acc->cfg.reg_delay_before_refresh = + PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH; + } + } + + /* Registrar URI */ + if (pj_strcmp(&acc->cfg.reg_uri, &cfg->reg_uri)) { + if (cfg->reg_uri.slen) { + pj_strdup_with_null(acc->pool, &acc->cfg.reg_uri, &cfg->reg_uri); + if (reg_sip_uri) + acc->srv_port = reg_sip_uri->port; + } else { + /* Unregister if registration was set */ + if (acc->cfg.reg_uri.slen) + pjsua_acc_set_registration(acc->index, PJ_FALSE); + pj_bzero(&acc->cfg.reg_uri, sizeof(acc->cfg.reg_uri)); + } + update_reg = PJ_TRUE; + unreg_first = PJ_TRUE; + } + + /* SIP outbound setting */ + if (acc->cfg.use_rfc5626 != cfg->use_rfc5626 || + pj_strcmp(&acc->cfg.rfc5626_instance_id, &cfg->rfc5626_instance_id) || + pj_strcmp(&acc->cfg.rfc5626_reg_id, &cfg->rfc5626_reg_id)) + { + update_reg = PJ_TRUE; + } + + /* Unregister first */ + if (unreg_first) { + pjsua_acc_set_registration(acc->index, PJ_FALSE); + if (acc->regc != NULL) { + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + acc->contact.slen = 0; + } + } + + /* Update registration */ + if (update_reg) { + /* If accounts has registration enabled, start registration */ + if (acc->cfg.reg_uri.slen) + pjsua_acc_set_registration(acc->index, PJ_TRUE); + } + + /* Update MWI subscription */ + if (update_mwi) { + pjsua_start_mwi(acc_id, PJ_TRUE); + } + + /* Video settings */ + acc->cfg.vid_in_auto_show = cfg->vid_in_auto_show; + acc->cfg.vid_out_auto_transmit = cfg->vid_out_auto_transmit; + acc->cfg.vid_wnd_flags = cfg->vid_wnd_flags; + acc->cfg.vid_cap_dev = cfg->vid_cap_dev; + acc->cfg.vid_rend_dev = cfg->vid_rend_dev; + + /* Call hold type */ + acc->cfg.call_hold_type = cfg->call_hold_type; + +on_return: + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; +} + + +/* + * Modify account's presence status to be advertised to remote/presence + * subscribers. + */ +PJ_DEF(pj_status_t) pjsua_acc_set_online_status( pjsua_acc_id acc_id, + pj_bool_t is_online) +{ + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJ_LOG(4,(THIS_FILE, "Acc %d: setting online status to %d..", + acc_id, is_online)); + pj_log_push_indent(); + + pjsua_var.acc[acc_id].online_status = is_online; + pj_bzero(&pjsua_var.acc[acc_id].rpid, sizeof(pjrpid_element)); + pjsua_pres_update_acc(acc_id, PJ_FALSE); + + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Set online status with extended information + */ +PJ_DEF(pj_status_t) pjsua_acc_set_online_status2( pjsua_acc_id acc_id, + pj_bool_t is_online, + const pjrpid_element *pr) +{ + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJ_LOG(4,(THIS_FILE, "Acc %d: setting online status to %d..", + acc_id, is_online)); + pj_log_push_indent(); + + PJSUA_LOCK(); + pjsua_var.acc[acc_id].online_status = is_online; + pjrpid_element_dup(pjsua_var.acc[acc_id].pool, &pjsua_var.acc[acc_id].rpid, pr); + PJSUA_UNLOCK(); + + pjsua_pres_update_acc(acc_id, PJ_TRUE); + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + +/* Create reg_contact, mainly for SIP outbound */ +static void update_regc_contact(pjsua_acc *acc) +{ + pjsua_acc_config *acc_cfg = &acc->cfg; + pj_bool_t need_outbound = PJ_FALSE; + const pj_str_t tcp_param = pj_str(";transport=tcp"); + const pj_str_t tls_param = pj_str(";transport=tls"); + + if (!acc_cfg->use_rfc5626) + goto done; + + /* Check if outbound has been requested and rejected */ + if (acc->rfc5626_status == OUTBOUND_NA) + goto done; + + if (pj_stristr(&acc->contact, &tcp_param)==NULL && + pj_stristr(&acc->contact, &tls_param)==NULL) + { + /* Currently we can only do SIP outbound for TCP + * and TLS. + */ + goto done; + } + + /* looks like we can use outbound */ + need_outbound = PJ_TRUE; + +done: + if (!need_outbound) { + /* Outbound is not needed/wanted for the account. acc->reg_contact + * is set to the same as acc->contact. + */ + acc->reg_contact = acc->contact; + acc->rfc5626_status = OUTBOUND_NA; + } else { + /* Need to use outbound, append the contact with +sip.instance and + * reg-id parameters. + */ + unsigned len; + pj_str_t reg_contact; + + acc->rfc5626_status = OUTBOUND_WANTED; + len = acc->contact.slen + acc->rfc5626_instprm.slen + + acc->rfc5626_regprm.slen; + reg_contact.ptr = (char*) pj_pool_alloc(acc->pool, len); + + pj_strcpy(®_contact, &acc->contact); + pj_strcat(®_contact, &acc->rfc5626_regprm); + pj_strcat(®_contact, &acc->rfc5626_instprm); + + acc->reg_contact = reg_contact; + + PJ_LOG(4,(THIS_FILE, + "Contact for acc %d updated for SIP outbound: %.*s", + acc->index, + (int)acc->reg_contact.slen, + acc->reg_contact.ptr)); + } +} + +/* Check if IP is private IP address */ +static pj_bool_t is_private_ip(const pj_str_t *addr) +{ + const pj_str_t private_net[] = + { + { "10.", 3 }, + { "127.", 4 }, + { "172.16.", 7 }, + { "192.168.", 8 } + }; + unsigned i; + + for (i=0; i<PJ_ARRAY_SIZE(private_net); ++i) { + if (pj_strncmp(addr, &private_net[i], private_net[i].slen)==0) + return PJ_TRUE; + } + + return PJ_FALSE; +} + +/* Update NAT address from the REGISTER response */ +static pj_bool_t acc_check_nat_addr(pjsua_acc *acc, + struct pjsip_regc_cbparam *param) +{ + pjsip_transport *tp; + const pj_str_t *via_addr; + pj_pool_t *pool; + int rport; + pjsip_sip_uri *uri; + pjsip_via_hdr *via; + pj_sockaddr contact_addr; + pj_sockaddr recv_addr; + pj_status_t status; + pj_bool_t matched; + pj_str_t srv_ip; + pjsip_contact_hdr *contact_hdr; + const pj_str_t STR_CONTACT = { "Contact", 7 }; + + tp = param->rdata->tp_info.transport; + + /* Get the received and rport info */ + via = param->rdata->msg_info.via; + if (via->rport_param < 1) { + /* Remote doesn't support rport */ + rport = via->sent_by.port; + if (rport==0) { + pjsip_transport_type_e tp_type; + tp_type = (pjsip_transport_type_e) tp->key.type; + rport = pjsip_transport_get_default_port_for_type(tp_type); + } + } else + rport = via->rport_param; + + if (via->recvd_param.slen != 0) + via_addr = &via->recvd_param; + else + via_addr = &via->sent_by.host; + + /* If allow_via_rewrite is enabled, we save the Via "received" address + * from the response. + */ + if (acc->cfg.allow_via_rewrite && + (acc->via_addr.host.slen == 0 || acc->via_tp != tp)) + { + if (pj_strcmp(&acc->via_addr.host, via_addr)) + pj_strdup(acc->pool, &acc->via_addr.host, via_addr); + acc->via_addr.port = rport; + acc->via_tp = tp; + pjsip_regc_set_via_sent_by(acc->regc, &acc->via_addr, acc->via_tp); + if (acc->publish_sess != NULL) { + pjsip_publishc_set_via_sent_by(acc->publish_sess, + &acc->via_addr, acc->via_tp); + } + } + + /* Only update if account is configured to auto-update */ + if (acc->cfg.allow_contact_rewrite == PJ_FALSE) + return PJ_FALSE; + + /* If SIP outbound is active, no need to update */ + if (acc->rfc5626_status == OUTBOUND_ACTIVE) { + PJ_LOG(4,(THIS_FILE, "Acc %d has SIP outbound active, no need to " + "update registration Contact", acc->index)); + return PJ_FALSE; + } + +#if 0 + // Always update + // See http://lists.pjsip.org/pipermail/pjsip_lists.pjsip.org/2008-March/002178.html + + /* For UDP, only update if STUN is enabled (for now). + * For TCP/TLS, always check. + */ + if ((tp->key.type == PJSIP_TRANSPORT_UDP && + (pjsua_var.ua_cfg.stun_domain.slen != 0 || + (pjsua_var.ua_cfg.stun_host.slen != 0)) || + (tp->key.type == PJSIP_TRANSPORT_TCP) || + (tp->key.type == PJSIP_TRANSPORT_TLS)) + { + /* Yes we will check */ + } else { + return PJ_FALSE; + } +#endif + + /* Compare received and rport with the URI in our registration */ + pool = pjsua_pool_create("tmp", 512, 512); + contact_hdr = (pjsip_contact_hdr*) + pjsip_parse_hdr(pool, &STR_CONTACT, acc->contact.ptr, + acc->contact.slen, NULL); + pj_assert(contact_hdr != NULL); + uri = (pjsip_sip_uri*) contact_hdr->uri; + pj_assert(uri != NULL); + uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri); + + if (uri->port == 0) { + pjsip_transport_type_e tp_type; + tp_type = (pjsip_transport_type_e) tp->key.type; + uri->port = pjsip_transport_get_default_port_for_type(tp_type); + } + + /* Convert IP address strings into sockaddr for comparison. + * (http://trac.pjsip.org/repos/ticket/863) + */ + status = pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &uri->host, + &contact_addr); + if (status == PJ_SUCCESS) + status = pj_sockaddr_parse(pj_AF_UNSPEC(), 0, via_addr, + &recv_addr); + if (status == PJ_SUCCESS) { + /* Compare the addresses as sockaddr according to the ticket above */ + matched = (uri->port == rport && + pj_sockaddr_cmp(&contact_addr, &recv_addr)==0); + } else { + /* Compare the addresses as string, as before */ + matched = (uri->port == rport && + pj_stricmp(&uri->host, via_addr)==0); + } + + if (matched) { + /* Address doesn't change */ + pj_pool_release(pool); + return PJ_FALSE; + } + + /* Get server IP */ + srv_ip = pj_str(param->rdata->pkt_info.src_name); + + /* At this point we've detected that the address as seen by registrar. + * has changed. + */ + + /* Do not switch if both Contact and server's IP address are + * public but response contains private IP. A NAT in the middle + * might have messed up with the SIP packets. See: + * http://trac.pjsip.org/repos/ticket/643 + * + * This exception can be disabled by setting allow_contact_rewrite + * to 2. In this case, the switch will always be done whenever there + * is difference in the IP address in the response. + */ + if (acc->cfg.allow_contact_rewrite != 2 && !is_private_ip(&uri->host) && + !is_private_ip(&srv_ip) && is_private_ip(via_addr)) + { + /* Don't switch */ + pj_pool_release(pool); + return PJ_FALSE; + } + + /* Also don't switch if only the port number part is different, and + * the Via received address is private. + * See http://trac.pjsip.org/repos/ticket/864 + */ + if (acc->cfg.allow_contact_rewrite != 2 && + pj_sockaddr_cmp(&contact_addr, &recv_addr)==0 && + is_private_ip(via_addr)) + { + /* Don't switch */ + pj_pool_release(pool); + return PJ_FALSE; + } + + PJ_LOG(3,(THIS_FILE, "IP address change detected for account %d " + "(%.*s:%d --> %.*s:%d). Updating registration " + "(using method %d)", + acc->index, + (int)uri->host.slen, + uri->host.ptr, + uri->port, + (int)via_addr->slen, + via_addr->ptr, + rport, + acc->cfg.contact_rewrite_method)); + + pj_assert(acc->cfg.contact_rewrite_method == 1 || + acc->cfg.contact_rewrite_method == 2); + + if (acc->cfg.contact_rewrite_method == 1) { + /* Unregister current contact */ + pjsua_acc_set_registration(acc->index, PJ_FALSE); + if (acc->regc != NULL) { + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + acc->contact.slen = 0; + } + } + + /* + * Build new Contact header + */ + { + const char *ob = ";ob"; + char *tmp; + const char *beginquote, *endquote; + int len; + + /* Enclose IPv6 address in square brackets */ + if (tp->key.type & PJSIP_TRANSPORT_IPV6) { + beginquote = "["; + endquote = "]"; + } else { + beginquote = endquote = ""; + } + + tmp = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); + len = pj_ansi_snprintf(tmp, PJSIP_MAX_URL_SIZE, + "<sip:%.*s%s%s%.*s%s:%d;transport=%s%.*s%s>%.*s", + (int)acc->user_part.slen, + acc->user_part.ptr, + (acc->user_part.slen? "@" : ""), + beginquote, + (int)via_addr->slen, + via_addr->ptr, + endquote, + rport, + tp->type_name, + (int)acc->cfg.contact_uri_params.slen, + acc->cfg.contact_uri_params.ptr, + (acc->cfg.use_rfc5626? ob: ""), + (int)acc->cfg.contact_params.slen, + acc->cfg.contact_params.ptr); + if (len < 1) { + PJ_LOG(1,(THIS_FILE, "URI too long")); + pj_pool_release(pool); + return PJ_FALSE; + } + pj_strdup2_with_null(acc->pool, &acc->contact, tmp); + + update_regc_contact(acc); + + /* Always update, by http://trac.pjsip.org/repos/ticket/864. */ + /* Since the Via address will now be overwritten to the correct + * address by https://trac.pjsip.org/repos/ticket/1537, we do + * not need to update the transport address. + */ + /* + pj_strdup_with_null(tp->pool, &tp->local_name.host, via_addr); + tp->local_name.port = rport; + */ + + } + + if (acc->cfg.contact_rewrite_method == 2 && acc->regc != NULL) { + pjsip_regc_update_contact(acc->regc, 1, &acc->reg_contact); + } + + /* Perform new registration */ + pjsua_acc_set_registration(acc->index, PJ_TRUE); + + pj_pool_release(pool); + + return PJ_TRUE; +} + +/* Check and update Service-Route header */ +void update_service_route(pjsua_acc *acc, pjsip_rx_data *rdata) +{ + pjsip_generic_string_hdr *hsr = NULL; + pjsip_route_hdr *hr, *h; + const pj_str_t HNAME = { "Service-Route", 13 }; + const pj_str_t HROUTE = { "Route", 5 }; + pjsip_uri *uri[PJSUA_ACC_MAX_PROXIES]; + unsigned i, uri_cnt = 0, rcnt; + + /* Find and parse Service-Route headers */ + for (;;) { + char saved; + int parsed_len; + + /* Find Service-Route header */ + hsr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &HNAME, hsr); + if (!hsr) + break; + + /* Parse as Route header since the syntax is similar. This may + * return more than one headers. + */ + saved = hsr->hvalue.ptr[hsr->hvalue.slen]; + hsr->hvalue.ptr[hsr->hvalue.slen] = '\0'; + hr = (pjsip_route_hdr*) + pjsip_parse_hdr(rdata->tp_info.pool, &HROUTE, hsr->hvalue.ptr, + hsr->hvalue.slen, &parsed_len); + hsr->hvalue.ptr[hsr->hvalue.slen] = saved; + + if (hr == NULL) { + /* Error */ + PJ_LOG(1,(THIS_FILE, "Error parsing Service-Route header")); + return; + } + + /* Save each URI in the result */ + h = hr; + do { + if (!PJSIP_URI_SCHEME_IS_SIP(h->name_addr.uri) && + !PJSIP_URI_SCHEME_IS_SIPS(h->name_addr.uri)) + { + PJ_LOG(1,(THIS_FILE,"Error: non SIP URI in Service-Route: %.*s", + (int)hsr->hvalue.slen, hsr->hvalue.ptr)); + return; + } + + uri[uri_cnt++] = h->name_addr.uri; + h = h->next; + } while (h != hr && uri_cnt != PJ_ARRAY_SIZE(uri)); + + if (h != hr) { + PJ_LOG(1,(THIS_FILE, "Error: too many Service-Route headers")); + return; + } + + /* Prepare to find next Service-Route header */ + hsr = hsr->next; + if ((void*)hsr == (void*)&rdata->msg_info.msg->hdr) + break; + } + + if (uri_cnt == 0) + return; + + /* + * Update account's route set + */ + + /* First remove all routes which are not the outbound proxies */ + rcnt = pj_list_size(&acc->route_set); + if (rcnt != pjsua_var.ua_cfg.outbound_proxy_cnt + acc->cfg.proxy_cnt) { + for (i=pjsua_var.ua_cfg.outbound_proxy_cnt + acc->cfg.proxy_cnt, + hr=acc->route_set.prev; + i<rcnt; + ++i) + { + pjsip_route_hdr *prev = hr->prev; + pj_list_erase(hr); + hr = prev; + } + } + + /* Then append the Service-Route URIs */ + for (i=0; i<uri_cnt; ++i) { + hr = pjsip_route_hdr_create(acc->pool); + hr->name_addr.uri = (pjsip_uri*)pjsip_uri_clone(acc->pool, uri[i]); + pj_list_push_back(&acc->route_set, hr); + } + + /* Done */ + + PJ_LOG(4,(THIS_FILE, "Service-Route updated for acc %d with %d URI(s)", + acc->index, uri_cnt)); +} + + +/* Keep alive timer callback */ +static void keep_alive_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te) +{ + pjsua_acc *acc; + pjsip_tpselector tp_sel; + pj_time_val delay; + char addrtxt[PJ_INET6_ADDRSTRLEN]; + pj_status_t status; + + PJ_UNUSED_ARG(th); + + PJSUA_LOCK(); + + te->id = PJ_FALSE; + + acc = (pjsua_acc*) te->user_data; + + /* Select the transport to send the packet */ + pj_bzero(&tp_sel, sizeof(tp_sel)); + tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT; + tp_sel.u.transport = acc->ka_transport; + + PJ_LOG(5,(THIS_FILE, + "Sending %d bytes keep-alive packet for acc %d to %s", + acc->cfg.ka_data.slen, acc->index, + pj_sockaddr_print(&acc->ka_target, addrtxt, sizeof(addrtxt),3))); + + /* Send raw packet */ + status = pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(pjsua_var.endpt), + PJSIP_TRANSPORT_UDP, &tp_sel, + NULL, acc->cfg.ka_data.ptr, + acc->cfg.ka_data.slen, + &acc->ka_target, acc->ka_target_len, + NULL, NULL); + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + pjsua_perror(THIS_FILE, "Error sending keep-alive packet", status); + } + + /* Check just in case keep-alive has been disabled. This shouldn't happen + * though as when ka_interval is changed this timer should have been + * cancelled. + */ + if (acc->cfg.ka_interval == 0) + goto on_return; + + /* Reschedule next timer */ + delay.sec = acc->cfg.ka_interval; + delay.msec = 0; + status = pjsip_endpt_schedule_timer(pjsua_var.endpt, te, &delay); + if (status == PJ_SUCCESS) { + te->id = PJ_TRUE; + } else { + pjsua_perror(THIS_FILE, "Error starting keep-alive timer", status); + } + +on_return: + PJSUA_UNLOCK(); +} + + +/* Update keep-alive for the account */ +static void update_keep_alive(pjsua_acc *acc, pj_bool_t start, + struct pjsip_regc_cbparam *param) +{ + /* In all cases, stop keep-alive timer if it's running. */ + if (acc->ka_timer.id) { + pjsip_endpt_cancel_timer(pjsua_var.endpt, &acc->ka_timer); + acc->ka_timer.id = PJ_FALSE; + + pjsip_transport_dec_ref(acc->ka_transport); + acc->ka_transport = NULL; + } + + if (start) { + pj_time_val delay; + pj_status_t status; + + /* Only do keep-alive if: + * - ka_interval is not zero in the account, and + * - transport is UDP. + * + * Previously we only enabled keep-alive when STUN is enabled, since + * we thought that keep-alive is only needed in Internet situation. + * But it has been discovered that Windows Firewall on WinXP also + * needs to be kept-alive, otherwise incoming packets will be dropped. + * So because of this, now keep-alive is always enabled for UDP, + * regardless of whether STUN is enabled or not. + * + * Note that this applies only for UDP. For TCP/TLS, the keep-alive + * is done by the transport layer. + */ + if (/*pjsua_var.stun_srv.ipv4.sin_family == 0 ||*/ + acc->cfg.ka_interval == 0 || + param->rdata->tp_info.transport->key.type != PJSIP_TRANSPORT_UDP) + { + /* Keep alive is not necessary */ + return; + } + + /* Save transport and destination address. */ + acc->ka_transport = param->rdata->tp_info.transport; + pjsip_transport_add_ref(acc->ka_transport); + pj_memcpy(&acc->ka_target, ¶m->rdata->pkt_info.src_addr, + param->rdata->pkt_info.src_addr_len); + acc->ka_target_len = param->rdata->pkt_info.src_addr_len; + + /* Setup and start the timer */ + acc->ka_timer.cb = &keep_alive_timer_cb; + acc->ka_timer.user_data = (void*)acc; + + delay.sec = acc->cfg.ka_interval; + delay.msec = 0; + status = pjsip_endpt_schedule_timer(pjsua_var.endpt, &acc->ka_timer, + &delay); + if (status == PJ_SUCCESS) { + acc->ka_timer.id = PJ_TRUE; + PJ_LOG(4,(THIS_FILE, "Keep-alive timer started for acc %d, " + "destination:%s:%d, interval:%ds", + acc->index, + param->rdata->pkt_info.src_name, + param->rdata->pkt_info.src_port, + acc->cfg.ka_interval)); + } else { + acc->ka_timer.id = PJ_FALSE; + pjsip_transport_dec_ref(acc->ka_transport); + acc->ka_transport = NULL; + pjsua_perror(THIS_FILE, "Error starting keep-alive timer", status); + } + } +} + + +/* Update the status of SIP outbound registration request */ +static void update_rfc5626_status(pjsua_acc *acc, pjsip_rx_data *rdata) +{ + pjsip_require_hdr *hreq; + const pj_str_t STR_OUTBOUND = {"outbound", 8}; + unsigned i; + + if (acc->rfc5626_status == OUTBOUND_UNKNOWN) { + goto on_return; + } + + hreq = rdata->msg_info.require; + if (!hreq) { + acc->rfc5626_status = OUTBOUND_NA; + goto on_return; + } + + for (i=0; i<hreq->count; ++i) { + if (pj_stricmp(&hreq->values[i], &STR_OUTBOUND)==0) { + acc->rfc5626_status = OUTBOUND_ACTIVE; + goto on_return; + } + } + + /* Server does not support outbound */ + acc->rfc5626_status = OUTBOUND_NA; + +on_return: + if (acc->rfc5626_status != OUTBOUND_ACTIVE) { + acc->reg_contact = acc->contact; + } + PJ_LOG(4,(THIS_FILE, "SIP outbound status for acc %d is %s", + acc->index, (acc->rfc5626_status==OUTBOUND_ACTIVE? + "active": "not active"))); +} + +/* + * This callback is called by pjsip_regc when outgoing register + * request has completed. + */ +static void regc_cb(struct pjsip_regc_cbparam *param) +{ + + pjsua_acc *acc = (pjsua_acc*) param->token; + + PJSUA_LOCK(); + + if (param->regc != acc->regc) { + PJSUA_UNLOCK(); + return; + } + + pj_log_push_indent(); + + /* + * Print registration status. + */ + if (param->status!=PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "SIP registration error", + param->status); + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + acc->contact.slen = 0; + + /* Stop keep-alive timer if any. */ + update_keep_alive(acc, PJ_FALSE, NULL); + + } else if (param->code < 0 || param->code >= 300) { + PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%.*s)", + param->code, + (int)param->reason.slen, param->reason.ptr)); + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + acc->contact.slen = 0; + + /* Stop keep-alive timer if any. */ + update_keep_alive(acc, PJ_FALSE, NULL); + + } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { + + /* Update auto registration flag */ + acc->auto_rereg.active = PJ_FALSE; + acc->auto_rereg.attempt_cnt = 0; + + if (param->expiration < 1) { + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + acc->contact.slen = 0; + + /* Stop keep-alive timer if any. */ + update_keep_alive(acc, PJ_FALSE, NULL); + + PJ_LOG(3,(THIS_FILE, "%s: unregistration success", + pjsua_var.acc[acc->index].cfg.id.ptr)); + } else { + /* Check and update SIP outbound status first, since the result + * will determine if we should update re-registration + */ + update_rfc5626_status(acc, param->rdata); + + /* Check NAT bound address */ + if (acc_check_nat_addr(acc, param)) { + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return; + } + + /* Check and update Service-Route header */ + update_service_route(acc, param->rdata); + + PJ_LOG(3, (THIS_FILE, + "%s: registration success, status=%d (%.*s), " + "will re-register in %d seconds", + pjsua_var.acc[acc->index].cfg.id.ptr, + param->code, + (int)param->reason.slen, param->reason.ptr, + param->expiration)); + + /* Start keep-alive timer if necessary. */ + update_keep_alive(acc, PJ_TRUE, param); + + /* Send initial PUBLISH if it is enabled */ + if (acc->cfg.publish_enabled && acc->publish_sess==NULL) + pjsua_pres_init_publish_acc(acc->index); + + /* Subscribe to MWI, if it's enabled */ + if (acc->cfg.mwi_enabled) + pjsua_start_mwi(acc->index, PJ_FALSE); + } + + } else { + PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code)); + } + + acc->reg_last_err = param->status; + acc->reg_last_code = param->code; + + /* Check if we need to auto retry registration. Basically, registration + * failure codes triggering auto-retry are those of temporal failures + * considered to be recoverable in relatively short term. + */ + if (acc->cfg.reg_retry_interval && + (param->code == PJSIP_SC_REQUEST_TIMEOUT || + param->code == PJSIP_SC_INTERNAL_SERVER_ERROR || + param->code == PJSIP_SC_BAD_GATEWAY || + param->code == PJSIP_SC_SERVICE_UNAVAILABLE || + param->code == PJSIP_SC_SERVER_TIMEOUT || + PJSIP_IS_STATUS_IN_CLASS(param->code, 600))) /* Global failure */ + { + schedule_reregistration(acc); + } + + /* Call the registration status callback */ + + if (pjsua_var.ua_cfg.cb.on_reg_state) { + (*pjsua_var.ua_cfg.cb.on_reg_state)(acc->index); + } + + if (pjsua_var.ua_cfg.cb.on_reg_state2) { + pjsua_reg_info reg_info; + + reg_info.cbparam = param; + (*pjsua_var.ua_cfg.cb.on_reg_state2)(acc->index, ®_info); + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); +} + + +/* + * Initialize client registration. + */ +static pj_status_t pjsua_regc_init(int acc_id) +{ + pjsua_acc *acc; + pj_pool_t *pool; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL); + acc = &pjsua_var.acc[acc_id]; + + if (acc->cfg.reg_uri.slen == 0) { + PJ_LOG(3,(THIS_FILE, "Registrar URI is not specified")); + return PJ_SUCCESS; + } + + /* Destroy existing session, if any */ + if (acc->regc) { + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + acc->contact.slen = 0; + } + + /* initialize SIP registration if registrar is configured */ + + status = pjsip_regc_create( pjsua_var.endpt, + acc, ®c_cb, &acc->regc); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create client registration", + status); + return status; + } + + pool = pjsua_pool_create("tmpregc", 512, 512); + + if (acc->contact.slen == 0) { + pj_str_t tmp_contact; + + status = pjsua_acc_create_uac_contact( pool, &tmp_contact, + acc_id, &acc->cfg.reg_uri); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate suitable Contact header" + " for registration", + status); + pjsip_regc_destroy(acc->regc); + pj_pool_release(pool); + acc->regc = NULL; + return status; + } + + pj_strdup_with_null(acc->pool, &acc->contact, &tmp_contact); + update_regc_contact(acc); + } + + status = pjsip_regc_init( acc->regc, + &acc->cfg.reg_uri, + &acc->cfg.id, + &acc->cfg.id, + 1, &acc->reg_contact, + acc->cfg.reg_timeout); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Client registration initialization error", + status); + pjsip_regc_destroy(acc->regc); + pj_pool_release(pool); + acc->regc = NULL; + acc->contact.slen = 0; + return status; + } + + /* If account is locked to specific transport, then set transport to + * the client registration. + */ + if (pjsua_var.acc[acc_id].cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel); + pjsip_regc_set_transport(acc->regc, &tp_sel); + } + + + /* Set credentials + */ + if (acc->cred_cnt) { + pjsip_regc_set_credentials( acc->regc, acc->cred_cnt, acc->cred); + } + + /* Set delay before registration refresh */ + pjsip_regc_set_delay_before_refresh(acc->regc, + acc->cfg.reg_delay_before_refresh); + + /* Set authentication preference */ + pjsip_regc_set_prefs(acc->regc, &acc->cfg.auth_pref); + + /* Set route-set + */ + if (acc->cfg.reg_use_proxy) { + pjsip_route_hdr route_set; + const pjsip_route_hdr *r; + + pj_list_init(&route_set); + + if (acc->cfg.reg_use_proxy & PJSUA_REG_USE_OUTBOUND_PROXY) { + r = pjsua_var.outbound_proxy.next; + while (r != &pjsua_var.outbound_proxy) { + pj_list_push_back(&route_set, pjsip_hdr_shallow_clone(pool, r)); + r = r->next; + } + } + + if (acc->cfg.reg_use_proxy & PJSUA_REG_USE_ACC_PROXY && + acc->cfg.proxy_cnt) + { + int cnt = acc->cfg.proxy_cnt; + pjsip_route_hdr *pos = route_set.prev; + int i; + + r = acc->route_set.prev; + for (i=0; i<cnt; ++i) { + pj_list_push_front(pos, pjsip_hdr_shallow_clone(pool, r)); + r = r->prev; + } + } + + if (!pj_list_empty(&route_set)) + pjsip_regc_set_route_set( acc->regc, &route_set ); + } + + /* Add custom request headers specified in the account config */ + pjsip_regc_add_headers(acc->regc, &acc->cfg.reg_hdr_list); + + /* Add other request headers. */ + if (pjsua_var.ua_cfg.user_agent.slen) { + pjsip_hdr hdr_list; + const pj_str_t STR_USER_AGENT = { "User-Agent", 10 }; + pjsip_generic_string_hdr *h; + + pj_list_init(&hdr_list); + + h = pjsip_generic_string_hdr_create(pool, &STR_USER_AGENT, + &pjsua_var.ua_cfg.user_agent); + pj_list_push_back(&hdr_list, (pjsip_hdr*)h); + + pjsip_regc_add_headers(acc->regc, &hdr_list); + } + + /* If SIP outbound is used, add "Supported: outbound, path header" */ + if (acc->rfc5626_status == OUTBOUND_WANTED) { + pjsip_hdr hdr_list; + pjsip_supported_hdr *hsup; + + pj_list_init(&hdr_list); + hsup = pjsip_supported_hdr_create(pool); + pj_list_push_back(&hdr_list, hsup); + + hsup->count = 2; + hsup->values[0] = pj_str("outbound"); + hsup->values[1] = pj_str("path"); + + pjsip_regc_add_headers(acc->regc, &hdr_list); + } + + pj_pool_release(pool); + + return PJ_SUCCESS; +} + + +/* + * Update registration or perform unregistration. + */ +PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id, + pj_bool_t renew) +{ + pj_status_t status = 0; + pjsip_tx_data *tdata = 0; + + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJ_LOG(4,(THIS_FILE, "Acc %d: setting %sregistration..", + acc_id, (renew? "" : "un"))); + pj_log_push_indent(); + + PJSUA_LOCK(); + + /* Cancel any re-registration timer */ + if (pjsua_var.acc[acc_id].auto_rereg.timer.id) { + pjsua_var.acc[acc_id].auto_rereg.timer.id = PJ_FALSE; + pjsua_cancel_timer(&pjsua_var.acc[acc_id].auto_rereg.timer); + } + + /* Reset pointer to registration transport */ + pjsua_var.acc[acc_id].auto_rereg.reg_tp = NULL; + + if (renew) { + if (pjsua_var.acc[acc_id].regc == NULL) { + status = pjsua_regc_init(acc_id); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create registration", + status); + goto on_return; + } + } + if (!pjsua_var.acc[acc_id].regc) { + status = PJ_EINVALIDOP; + goto on_return; + } + + status = pjsip_regc_register(pjsua_var.acc[acc_id].regc, 1, + &tdata); + + if (0 && status == PJ_SUCCESS && pjsua_var.acc[acc_id].cred_cnt) { + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsip_authorization_hdr *h; + char *uri; + int d; + + uri = (char*) pj_pool_alloc(tdata->pool, acc->cfg.reg_uri.slen+10); + d = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, tdata->msg->line.req.uri, + uri, acc->cfg.reg_uri.slen+10); + pj_assert(d > 0); + + h = pjsip_authorization_hdr_create(tdata->pool); + h->scheme = pj_str("Digest"); + h->credential.digest.username = acc->cred[0].username; + h->credential.digest.realm = acc->srv_domain; + h->credential.digest.uri = pj_str(uri); + h->credential.digest.algorithm = pj_str("md5"); + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)h); + } + + } else { + if (pjsua_var.acc[acc_id].regc == NULL) { + PJ_LOG(3,(THIS_FILE, "Currently not registered")); + status = PJ_EINVALIDOP; + goto on_return; + } + + pjsua_pres_unpublish(&pjsua_var.acc[acc_id], 0); + + status = pjsip_regc_unregister(pjsua_var.acc[acc_id].regc, &tdata); + } + + if (status == PJ_SUCCESS) { + if (pjsua_var.acc[acc_id].cfg.allow_via_rewrite && + pjsua_var.acc[acc_id].via_addr.host.slen > 0) + { + pjsip_regc_set_via_sent_by(pjsua_var.acc[acc_id].regc, + &pjsua_var.acc[acc_id].via_addr, + pjsua_var.acc[acc_id].via_tp); + } + + //pjsua_process_msg_data(tdata, NULL); + status = pjsip_regc_send( pjsua_var.acc[acc_id].regc, tdata ); + } + + /* Update pointer to registration transport */ + if (status == PJ_SUCCESS) { + pjsip_regc_info reg_info; + + pjsip_regc_get_info(pjsua_var.acc[acc_id].regc, ®_info); + pjsua_var.acc[acc_id].auto_rereg.reg_tp = reg_info.transport; + + if (pjsua_var.ua_cfg.cb.on_reg_started) { + (*pjsua_var.ua_cfg.cb.on_reg_started)(acc_id, renew); + } + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create/send REGISTER", + status); + } else { + PJ_LOG(4,(THIS_FILE, "Acc %d: %s sent", acc_id, + (renew? "Registration" : "Unregistration"))); + } + +on_return: + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; +} + + +/* + * Get account information. + */ +PJ_DEF(pj_status_t) pjsua_acc_get_info( pjsua_acc_id acc_id, + pjsua_acc_info *info) +{ + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg; + + PJ_ASSERT_RETURN(info != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL); + + pj_bzero(info, sizeof(pjsua_acc_info)); + + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJSUA_LOCK(); + + if (pjsua_var.acc[acc_id].valid == PJ_FALSE) { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } + + info->id = acc_id; + info->is_default = (pjsua_var.default_acc == acc_id); + info->acc_uri = acc_cfg->id; + info->has_registration = (acc->cfg.reg_uri.slen > 0); + info->online_status = acc->online_status; + pj_memcpy(&info->rpid, &acc->rpid, sizeof(pjrpid_element)); + if (info->rpid.note.slen) + info->online_status_text = info->rpid.note; + else if (info->online_status) + info->online_status_text = pj_str("Online"); + else + info->online_status_text = pj_str("Offline"); + + if (acc->reg_last_code) { + if (info->has_registration) { + info->status = (pjsip_status_code) acc->reg_last_code; + info->status_text = *pjsip_get_status_text(acc->reg_last_code); + if (acc->reg_last_err) + info->reg_last_err = acc->reg_last_err; + } else { + info->status = (pjsip_status_code) 0; + info->status_text = pj_str("not registered"); + } + } else if (acc->cfg.reg_uri.slen) { + info->status = PJSIP_SC_TRYING; + info->status_text = pj_str("In Progress"); + } else { + info->status = (pjsip_status_code) 0; + info->status_text = pj_str("does not register"); + } + + if (acc->regc) { + pjsip_regc_info regc_info; + pjsip_regc_get_info(acc->regc, ®c_info); + info->expires = regc_info.next_reg; + } else { + info->expires = -1; + } + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; + +} + + +/* + * Enum accounts all account ids. + */ +PJ_DEF(pj_status_t) pjsua_enum_accs(pjsua_acc_id ids[], + unsigned *count ) +{ + unsigned i, c; + + PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL); + + PJSUA_LOCK(); + + for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + ids[c] = i; + ++c; + } + + *count = c; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * Enum accounts info. + */ +PJ_DEF(pj_status_t) pjsua_acc_enum_info( pjsua_acc_info info[], + unsigned *count ) +{ + unsigned i, c; + + PJ_ASSERT_RETURN(info && *count, PJ_EINVAL); + + PJSUA_LOCK(); + + for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + + pjsua_acc_get_info(i, &info[c]); + ++c; + } + + *count = c; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * This is an internal function to find the most appropriate account to + * used to reach to the specified URL. + */ +PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *url) +{ + pj_str_t tmp; + pjsip_uri *uri; + pjsip_sip_uri *sip_uri; + pj_pool_t *tmp_pool; + unsigned i; + + PJSUA_LOCK(); + + tmp_pool = pjsua_pool_create("tmpacc10", 256, 256); + + pj_strdup_with_null(tmp_pool, &tmp, url); + + uri = pjsip_parse_uri(tmp_pool, tmp.ptr, tmp.slen, 0); + if (!uri) { + pj_pool_release(tmp_pool); + PJSUA_UNLOCK(); + return pjsua_var.default_acc; + } + + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && + !PJSIP_URI_SCHEME_IS_SIPS(uri)) + { + /* Return the first account with proxy */ + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + if (!pj_list_empty(&pjsua_var.acc[i].route_set)) + break; + } + + if (i != PJ_ARRAY_SIZE(pjsua_var.acc)) { + /* Found rather matching account */ + pj_pool_release(tmp_pool); + PJSUA_UNLOCK(); + return i; + } + + /* Not found, use default account */ + pj_pool_release(tmp_pool); + PJSUA_UNLOCK(); + return pjsua_var.default_acc; + } + + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri); + + /* Find matching domain AND port */ + for (i=0; i<pjsua_var.acc_cnt; ++i) { + unsigned acc_id = pjsua_var.acc_ids[i]; + if (pj_stricmp(&pjsua_var.acc[acc_id].srv_domain, &sip_uri->host)==0 && + pjsua_var.acc[acc_id].srv_port == sip_uri->port) + { + pj_pool_release(tmp_pool); + PJSUA_UNLOCK(); + return acc_id; + } + } + + /* If no match, try to match the domain part only */ + for (i=0; i<pjsua_var.acc_cnt; ++i) { + unsigned acc_id = pjsua_var.acc_ids[i]; + if (pj_stricmp(&pjsua_var.acc[acc_id].srv_domain, &sip_uri->host)==0) + { + pj_pool_release(tmp_pool); + PJSUA_UNLOCK(); + return acc_id; + } + } + + + /* Still no match, just use default account */ + pj_pool_release(tmp_pool); + PJSUA_UNLOCK(); + return pjsua_var.default_acc; +} + + +/* + * This is an internal function to find the most appropriate account to be + * used to handle incoming calls. + */ +PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata) +{ + pjsip_uri *uri; + pjsip_sip_uri *sip_uri; + unsigned i; + + /* Check that there's at least one account configured */ + PJ_ASSERT_RETURN(pjsua_var.acc_cnt!=0, pjsua_var.default_acc); + + uri = rdata->msg_info.to->uri; + + /* Just return default account if To URI is not SIP: */ + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && + !PJSIP_URI_SCHEME_IS_SIPS(uri)) + { + return pjsua_var.default_acc; + } + + + PJSUA_LOCK(); + + sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri); + + /* Find account which has matching username and domain. */ + for (i=0; i < pjsua_var.acc_cnt; ++i) { + unsigned acc_id = pjsua_var.acc_ids[i]; + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + + if (acc->valid && pj_stricmp(&acc->user_part, &sip_uri->user)==0 && + pj_stricmp(&acc->srv_domain, &sip_uri->host)==0) + { + /* Match ! */ + PJSUA_UNLOCK(); + return acc_id; + } + } + + /* No matching account, try match domain part only. */ + for (i=0; i < pjsua_var.acc_cnt; ++i) { + unsigned acc_id = pjsua_var.acc_ids[i]; + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + + if (acc->valid && pj_stricmp(&acc->srv_domain, &sip_uri->host)==0) { + /* Match ! */ + PJSUA_UNLOCK(); + return acc_id; + } + } + + /* No matching account, try match user part (and transport type) only. */ + for (i=0; i < pjsua_var.acc_cnt; ++i) { + unsigned acc_id = pjsua_var.acc_ids[i]; + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + + if (acc->valid && pj_stricmp(&acc->user_part, &sip_uri->user)==0) { + + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_transport_type_e type; + type = pjsip_transport_get_type_from_name(&sip_uri->transport_param); + if (type == PJSIP_TRANSPORT_UNSPECIFIED) + type = PJSIP_TRANSPORT_UDP; + + if (pjsua_var.tpdata[acc->cfg.transport_id].type != type) + continue; + } + + /* Match ! */ + PJSUA_UNLOCK(); + return acc_id; + } + } + + /* Still no match, use default account */ + PJSUA_UNLOCK(); + return pjsua_var.default_acc; +} + + +/* + * Create arbitrary requests for this account. + */ +PJ_DEF(pj_status_t) pjsua_acc_create_request(pjsua_acc_id acc_id, + const pjsip_method *method, + const pj_str_t *target, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsua_acc *acc; + pjsip_route_hdr *r; + pj_status_t status; + + PJ_ASSERT_RETURN(method && target && p_tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL); + + acc = &pjsua_var.acc[acc_id]; + + status = pjsip_endpt_create_request(pjsua_var.endpt, method, target, + &acc->cfg.id, target, + NULL, NULL, -1, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create request", status); + return status; + } + + /* Copy routeset */ + r = acc->route_set.next; + while (r != &acc->route_set) { + pjsip_msg_add_hdr(tdata->msg, + (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, r)); + r = r->next; + } + + /* If account is locked to specific transport, then set that transport to + * the transmit data. + */ + if (pjsua_var.acc[acc_id].cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_tx_data_set_transport(tdata, &tp_sel); + } + + /* If via_addr is set, use this address for the Via header. */ + if (pjsua_var.acc[acc_id].cfg.allow_via_rewrite && + pjsua_var.acc[acc_id].via_addr.host.slen > 0) + { + tdata->via_addr = pjsua_var.acc[acc_id].via_addr; + tdata->via_tp = pjsua_var.acc[acc_id].via_tp; + } + + /* Done */ + *p_tdata = tdata; + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool, + pj_str_t *contact, + pjsua_acc_id acc_id, + const pj_str_t *suri) +{ + pjsua_acc *acc; + pjsip_sip_uri *sip_uri; + pj_status_t status; + pjsip_transport_type_e tp_type = PJSIP_TRANSPORT_UNSPECIFIED; + pj_str_t local_addr; + pjsip_tpselector tp_sel; + unsigned flag; + int secure; + int local_port; + const char *beginquote, *endquote; + char transport_param[32]; + const char *ob = ";ob"; + + + PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL); + acc = &pjsua_var.acc[acc_id]; + + /* If force_contact is configured, then use use it */ + if (acc->cfg.force_contact.slen) { + *contact = acc->cfg.force_contact; + return PJ_SUCCESS; + } + + /* If route-set is configured for the account, then URI is the + * first entry of the route-set. + */ + if (!pj_list_empty(&acc->route_set)) { + sip_uri = (pjsip_sip_uri*) + pjsip_uri_get_uri(acc->route_set.next->name_addr.uri); + } else { + pj_str_t tmp; + pjsip_uri *uri; + + pj_strdup_with_null(pool, &tmp, suri); + + uri = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, 0); + if (uri == NULL) + return PJSIP_EINVALIDURI; + + /* For non-SIP scheme, route set should be configured */ + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) + return PJSIP_ENOROUTESET; + + sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri); + } + + /* Get transport type of the URI */ + if (PJSIP_URI_SCHEME_IS_SIPS(sip_uri)) + tp_type = PJSIP_TRANSPORT_TLS; + else if (sip_uri->transport_param.slen == 0) { + tp_type = PJSIP_TRANSPORT_UDP; + } else + tp_type = pjsip_transport_get_type_from_name(&sip_uri->transport_param); + + if (tp_type == PJSIP_TRANSPORT_UNSPECIFIED) + return PJSIP_EUNSUPTRANSPORT; + + /* If destination URI specifies IPv6, then set transport type + * to use IPv6 as well. + */ + if (pj_strchr(&sip_uri->host, ':')) + tp_type = (pjsip_transport_type_e)(((int)tp_type) + PJSIP_TRANSPORT_IPV6); + + flag = pjsip_transport_get_flag_from_type(tp_type); + secure = (flag & PJSIP_TRANSPORT_SECURE) != 0; + + /* Init transport selector. */ + pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel); + + /* Get local address suitable to send request from */ + status = pjsip_tpmgr_find_local_addr(pjsip_endpt_get_tpmgr(pjsua_var.endpt), + pool, tp_type, &tp_sel, + &local_addr, &local_port); + if (status != PJ_SUCCESS) + return status; + + /* Enclose IPv6 address in square brackets */ + if (tp_type & PJSIP_TRANSPORT_IPV6) { + beginquote = "["; + endquote = "]"; + } else { + beginquote = endquote = ""; + } + + /* Don't add transport parameter if it's UDP */ + if (tp_type!=PJSIP_TRANSPORT_UDP && tp_type!=PJSIP_TRANSPORT_UDP6) { + pj_ansi_snprintf(transport_param, sizeof(transport_param), + ";transport=%s", + pjsip_transport_get_type_name(tp_type)); + } else { + transport_param[0] = '\0'; + } + + + /* Create the contact header */ + contact->ptr = (char*)pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); + contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE, + "%s%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s%s>%.*s", + (acc->display.slen?"\"" : ""), + (int)acc->display.slen, + acc->display.ptr, + (acc->display.slen?"\" " : ""), + (secure ? PJSUA_SECURE_SCHEME : "sip"), + (int)acc->user_part.slen, + acc->user_part.ptr, + (acc->user_part.slen?"@":""), + beginquote, + (int)local_addr.slen, + local_addr.ptr, + endquote, + local_port, + transport_param, + (int)acc->cfg.contact_uri_params.slen, + acc->cfg.contact_uri_params.ptr, + (acc->cfg.use_rfc5626? ob: ""), + (int)acc->cfg.contact_params.slen, + acc->cfg.contact_params.ptr); + + return PJ_SUCCESS; +} + + + +PJ_DEF(pj_status_t) pjsua_acc_create_uas_contact( pj_pool_t *pool, + pj_str_t *contact, + pjsua_acc_id acc_id, + pjsip_rx_data *rdata ) +{ + /* + * Section 12.1.1, paragraph about using SIPS URI in Contact. + * If the request that initiated the dialog contained a SIPS URI + * in the Request-URI or in the top Record-Route header field value, + * if there was any, or the Contact header field if there was no + * Record-Route header field, the Contact header field in the response + * MUST be a SIPS URI. + */ + pjsua_acc *acc; + pjsip_sip_uri *sip_uri; + pj_status_t status; + pjsip_transport_type_e tp_type = PJSIP_TRANSPORT_UNSPECIFIED; + pj_str_t local_addr; + pjsip_tpselector tp_sel; + unsigned flag; + int secure; + int local_port; + const char *beginquote, *endquote; + char transport_param[32]; + + PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL); + acc = &pjsua_var.acc[acc_id]; + + /* If force_contact is configured, then use use it */ + if (acc->cfg.force_contact.slen) { + *contact = acc->cfg.force_contact; + return PJ_SUCCESS; + } + + /* If Record-Route is present, then URI is the top Record-Route. */ + if (rdata->msg_info.record_route) { + sip_uri = (pjsip_sip_uri*) + pjsip_uri_get_uri(rdata->msg_info.record_route->name_addr.uri); + } else { + pjsip_hdr *pos = NULL; + pjsip_contact_hdr *h_contact; + pjsip_uri *uri = NULL; + + /* Otherwise URI is Contact URI. + * Iterate the Contact URI until we find sip: or sips: scheme. + */ + do { + h_contact = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, + pos); + if (h_contact) { + if (h_contact->uri) + uri = (pjsip_uri*) pjsip_uri_get_uri(h_contact->uri); + else + uri = NULL; + if (!uri || (!PJSIP_URI_SCHEME_IS_SIP(uri) && + !PJSIP_URI_SCHEME_IS_SIPS(uri))) + { + pos = (pjsip_hdr*)h_contact->next; + if (pos == &rdata->msg_info.msg->hdr) + h_contact = NULL; + } else { + break; + } + } + } while (h_contact); + + + /* Or if Contact URI is not present, take the remote URI from + * the From URI. + */ + if (uri == NULL) + uri = (pjsip_uri*) pjsip_uri_get_uri(rdata->msg_info.from->uri); + + + /* Can only do sip/sips scheme at present. */ + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) + return PJSIP_EINVALIDREQURI; + + sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri); + } + + /* Get transport type of the URI */ + if (PJSIP_URI_SCHEME_IS_SIPS(sip_uri)) + tp_type = PJSIP_TRANSPORT_TLS; + else if (sip_uri->transport_param.slen == 0) { + tp_type = PJSIP_TRANSPORT_UDP; + } else + tp_type = pjsip_transport_get_type_from_name(&sip_uri->transport_param); + + if (tp_type == PJSIP_TRANSPORT_UNSPECIFIED) + return PJSIP_EUNSUPTRANSPORT; + + /* If destination URI specifies IPv6, then set transport type + * to use IPv6 as well. + */ + if (pj_strchr(&sip_uri->host, ':')) + tp_type = (pjsip_transport_type_e)(((int)tp_type) + PJSIP_TRANSPORT_IPV6); + + flag = pjsip_transport_get_flag_from_type(tp_type); + secure = (flag & PJSIP_TRANSPORT_SECURE) != 0; + + /* Init transport selector. */ + pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel); + + /* Get local address suitable to send request from */ + status = pjsip_tpmgr_find_local_addr(pjsip_endpt_get_tpmgr(pjsua_var.endpt), + pool, tp_type, &tp_sel, + &local_addr, &local_port); + if (status != PJ_SUCCESS) + return status; + + /* Enclose IPv6 address in square brackets */ + if (tp_type & PJSIP_TRANSPORT_IPV6) { + beginquote = "["; + endquote = "]"; + } else { + beginquote = endquote = ""; + } + + /* Don't add transport parameter if it's UDP */ + if (tp_type!=PJSIP_TRANSPORT_UDP && tp_type!=PJSIP_TRANSPORT_UDP6) { + pj_ansi_snprintf(transport_param, sizeof(transport_param), + ";transport=%s", + pjsip_transport_get_type_name(tp_type)); + } else { + transport_param[0] = '\0'; + } + + + /* Create the contact header */ + contact->ptr = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); + contact->slen = pj_ansi_snprintf(contact->ptr, PJSIP_MAX_URL_SIZE, + "%s%.*s%s<%s:%.*s%s%s%.*s%s:%d%s%.*s>%.*s", + (acc->display.slen?"\"" : ""), + (int)acc->display.slen, + acc->display.ptr, + (acc->display.slen?"\" " : ""), + (secure ? PJSUA_SECURE_SCHEME : "sip"), + (int)acc->user_part.slen, + acc->user_part.ptr, + (acc->user_part.slen?"@":""), + beginquote, + (int)local_addr.slen, + local_addr.ptr, + endquote, + local_port, + transport_param, + (int)acc->cfg.contact_uri_params.slen, + acc->cfg.contact_uri_params.ptr, + (int)acc->cfg.contact_params.slen, + acc->cfg.contact_params.ptr); + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsua_acc_set_transport( pjsua_acc_id acc_id, + pjsua_transport_id tp_id) +{ + pjsua_acc *acc; + + PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL); + acc = &pjsua_var.acc[acc_id]; + + PJ_ASSERT_RETURN(tp_id >= 0 && tp_id < (int)PJ_ARRAY_SIZE(pjsua_var.tpdata), + PJ_EINVAL); + + acc->cfg.transport_id = tp_id; + + return PJ_SUCCESS; +} + + +/* Auto re-registration timeout callback */ +static void auto_rereg_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te) +{ + pjsua_acc *acc; + pj_status_t status; + + PJ_UNUSED_ARG(th); + acc = (pjsua_acc*) te->user_data; + pj_assert(acc); + + PJSUA_LOCK(); + + /* Check if the reregistration timer is still valid, e.g: while waiting + * timeout timer application might have deleted the account or disabled + * the auto-reregistration. + */ + if (!acc->valid || !acc->auto_rereg.active || + acc->cfg.reg_retry_interval == 0) + { + goto on_return; + } + + /* Start re-registration */ + acc->auto_rereg.attempt_cnt++; + status = pjsua_acc_set_registration(acc->index, PJ_TRUE); + if (status != PJ_SUCCESS) + schedule_reregistration(acc); + +on_return: + PJSUA_UNLOCK(); +} + + +/* Schedule reregistration for specified account. Note that the first + * re-registration after a registration failure will be done immediately. + * Also note that this function should be called within PJSUA mutex. + */ +static void schedule_reregistration(pjsua_acc *acc) +{ + pj_time_val delay; + + pj_assert(acc); + + /* Validate the account and re-registration feature status */ + if (!acc->valid || acc->cfg.reg_retry_interval == 0) { + return; + } + + /* If configured, disconnect calls of this account after the first + * reregistration attempt failed. + */ + if (acc->cfg.drop_calls_on_reg_fail && acc->auto_rereg.attempt_cnt >= 1) + { + unsigned i, cnt; + + for (i = 0, cnt = 0; i < pjsua_var.ua_cfg.max_calls; ++i) { + if (pjsua_var.calls[i].acc_id == acc->index) { + pjsua_call_hangup(i, 0, NULL, NULL); + ++cnt; + } + } + + if (cnt) { + PJ_LOG(3, (THIS_FILE, "Disconnecting %d call(s) of account #%d " + "after reregistration attempt failed", + cnt, acc->index)); + } + } + + /* Cancel any re-registration timer */ + if (acc->auto_rereg.timer.id) { + acc->auto_rereg.timer.id = PJ_FALSE; + pjsua_cancel_timer(&acc->auto_rereg.timer); + } + + /* Update re-registration flag */ + acc->auto_rereg.active = PJ_TRUE; + + /* Set up timer for reregistration */ + acc->auto_rereg.timer.cb = &auto_rereg_timer_cb; + acc->auto_rereg.timer.user_data = acc; + + /* Reregistration attempt. The first attempt will be done immediately. */ + delay.sec = acc->auto_rereg.attempt_cnt? acc->cfg.reg_retry_interval : + acc->cfg.reg_first_retry_interval; + delay.msec = 0; + + /* Randomize interval by +/- 10 secs */ + if (delay.sec >= 10) { + delay.msec = -10000 + (pj_rand() % 20000); + } else { + delay.sec = 0; + delay.msec = (pj_rand() % 10000); + } + pj_time_val_normalize(&delay); + + PJ_LOG(4,(THIS_FILE, + "Scheduling re-registration retry for acc %d in %u seconds..", + acc->index, delay.sec)); + + acc->auto_rereg.timer.id = PJ_TRUE; + if (pjsua_schedule_timer(&acc->auto_rereg.timer, &delay) != PJ_SUCCESS) + acc->auto_rereg.timer.id = PJ_FALSE; +} + + +/* Internal function to perform auto-reregistration on transport + * connection/disconnection events. + */ +void pjsua_acc_on_tp_state_changed(pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info) +{ + unsigned i; + + PJ_UNUSED_ARG(info); + + /* Only care for transport disconnection events */ + if (state != PJSIP_TP_STATE_DISCONNECTED) + return; + + PJ_LOG(4,(THIS_FILE, "Disconnected notification for transport %s", + tp->obj_name)); + pj_log_push_indent(); + + /* Shutdown this transport, to make sure that the transport manager + * will create a new transport for reconnection. + */ + pjsip_transport_shutdown(tp); + + PJSUA_LOCK(); + + /* Enumerate accounts using this transport and perform actions + * based on the transport state. + */ + for (i = 0; i < PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + pjsua_acc *acc = &pjsua_var.acc[i]; + + /* Skip if this account is not valid OR auto re-registration + * feature is disabled OR this transport is not used by this account. + */ + if (!acc->valid || !acc->cfg.reg_retry_interval || + tp != acc->auto_rereg.reg_tp) + { + continue; + } + + /* Release regc transport immediately + * See https://trac.pjsip.org/repos/ticket/1481 + */ + if (pjsua_var.acc[i].regc) { + pjsip_regc_release_transport(pjsua_var.acc[i].regc); + } + + /* Schedule reregistration for this account */ + schedule_reregistration(acc); + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); +} diff --git a/pjsip/src/pjsua-lib/pjsua_aud.c b/pjsip/src/pjsua-lib/pjsua_aud.c new file mode 100644 index 0000000..557a147 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_aud.c @@ -0,0 +1,2148 @@ +/* $Id: pjsua_aud.c 4145 2012-05-22 23:13:22Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + +#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0 + +#define THIS_FILE "pjsua_aud.c" +#define NULL_SND_DEV_ID -99 + +/***************************************************************************** + * + * Prototypes + */ +/* Open sound dev */ +static pj_status_t open_snd_dev(pjmedia_snd_port_param *param); +/* Close existing sound device */ +static void close_snd_dev(void); +/* Create audio device param */ +static pj_status_t create_aud_param(pjmedia_aud_param *param, + pjmedia_aud_dev_index capture_dev, + pjmedia_aud_dev_index playback_dev, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample); + +/***************************************************************************** + * + * Call API that are closely tied to PJMEDIA + */ +/* + * Check if call has an active media session. + */ +PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + return call->audio_idx >= 0 && call->media[call->audio_idx].strm.a.stream; +} + + +/* + * Get the conference port identification associated with the call. + */ +PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id) +{ + pjsua_call *call; + pjsua_conf_port_id port_id = PJSUA_INVALID_ID; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + /* Use PJSUA_LOCK() instead of acquire_call(): + * https://trac.pjsip.org/repos/ticket/1371 + */ + PJSUA_LOCK(); + + if (!pjsua_call_is_active(call_id)) + goto on_return; + + call = &pjsua_var.calls[call_id]; + port_id = call->media[call->audio_idx].strm.a.conf_slot; + +on_return: + PJSUA_UNLOCK(); + + return port_id; +} + + +/* + * Get media stream info for the specified media index. + */ +PJ_DEF(pj_status_t) pjsua_call_get_stream_info( pjsua_call_id call_id, + unsigned med_idx, + pjsua_stream_info *psi) +{ + pjsua_call *call; + pjsua_call_media *call_med; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + PJ_ASSERT_RETURN(psi, PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (med_idx >= call->med_cnt) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + call_med = &call->media[med_idx]; + psi->type = call_med->type; + switch (call_med->type) { + case PJMEDIA_TYPE_AUDIO: + status = pjmedia_stream_get_info(call_med->strm.a.stream, + &psi->info.aud); + break; +#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + case PJMEDIA_TYPE_VIDEO: + status = pjmedia_vid_stream_get_info(call_med->strm.v.stream, + &psi->info.vid); + break; +#endif + default: + status = PJMEDIA_EINVALIMEDIATYPE; + break; + } + + PJSUA_UNLOCK(); + return status; +} + + +/* + * Get media stream statistic for the specified media index. + */ +PJ_DEF(pj_status_t) pjsua_call_get_stream_stat( pjsua_call_id call_id, + unsigned med_idx, + pjsua_stream_stat *stat) +{ + pjsua_call *call; + pjsua_call_media *call_med; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + PJ_ASSERT_RETURN(stat, PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (med_idx >= call->med_cnt) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + call_med = &call->media[med_idx]; + switch (call_med->type) { + case PJMEDIA_TYPE_AUDIO: + status = pjmedia_stream_get_stat(call_med->strm.a.stream, + &stat->rtcp); + if (status == PJ_SUCCESS) + status = pjmedia_stream_get_stat_jbuf(call_med->strm.a.stream, + &stat->jbuf); + break; +#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + case PJMEDIA_TYPE_VIDEO: + status = pjmedia_vid_stream_get_stat(call_med->strm.v.stream, + &stat->rtcp); + if (status == PJ_SUCCESS) + status = pjmedia_vid_stream_get_stat_jbuf(call_med->strm.v.stream, + &stat->jbuf); + break; +#endif + default: + status = PJMEDIA_EINVALIMEDIATYPE; + break; + } + + PJSUA_UNLOCK(); + return status; +} + +/* + * Send DTMF digits to remote using RFC 2833 payload formats. + */ +PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id, + const pj_str_t *digits) +{ + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Call %d dialing DTMF %.*s", + call_id, (int)digits->slen, digits->ptr)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_dial_dtmf()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + if (!pjsua_call_has_media(call_id)) { + PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); + status = PJ_EINVALIDOP; + goto on_return; + } + + status = pjmedia_stream_dial_dtmf( + call->media[call->audio_idx].strm.a.stream, digits); + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; +} + + +/***************************************************************************** + * + * Audio media with PJMEDIA backend + */ + +/* Init pjmedia audio subsystem */ +pj_status_t pjsua_aud_subsys_init() +{ + pj_str_t codec_id = {NULL, 0}; + unsigned opt; + pjmedia_audio_codec_config codec_cfg; + pj_status_t status; + + /* To suppress warning about unused var when all codecs are disabled */ + PJ_UNUSED_ARG(codec_id); + + /* + * Register all codecs + */ + pjmedia_audio_codec_config_default(&codec_cfg); + codec_cfg.speex.quality = pjsua_var.media_cfg.quality; + codec_cfg.speex.complexity = -1; + codec_cfg.ilbc.mode = pjsua_var.media_cfg.ilbc_mode; + +#if PJMEDIA_HAS_PASSTHROUGH_CODECS + /* Register passthrough codecs */ + { + unsigned aud_idx; + unsigned ext_fmt_cnt = 0; + pjmedia_format ext_fmts[32]; + + /* List extended formats supported by audio devices */ + for (aud_idx = 0; aud_idx < pjmedia_aud_dev_count(); ++aud_idx) { + pjmedia_aud_dev_info aud_info; + unsigned i; + + status = pjmedia_aud_dev_get_info(aud_idx, &aud_info); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error querying audio device info", + status); + goto on_error; + } + + /* Collect extended formats supported by this audio device */ + for (i = 0; i < aud_info.ext_fmt_cnt; ++i) { + unsigned j; + pj_bool_t is_listed = PJ_FALSE; + + /* See if this extended format is already in the list */ + for (j = 0; j < ext_fmt_cnt && !is_listed; ++j) { + if (ext_fmts[j].id == aud_info.ext_fmt[i].id && + ext_fmts[j].det.aud.avg_bps == + aud_info.ext_fmt[i].det.aud.avg_bps) + { + is_listed = PJ_TRUE; + } + } + + /* Put this format into the list, if it is not in the list */ + if (!is_listed) + ext_fmts[ext_fmt_cnt++] = aud_info.ext_fmt[i]; + + pj_assert(ext_fmt_cnt <= PJ_ARRAY_SIZE(ext_fmts)); + } + } + + /* Init the passthrough codec with supported formats only */ + codec_cfg.passthrough.setting.fmt_cnt = ext_fmt_cnt; + codec_cfg.passthrough.setting.fmts = ext_fmts; + codec_cfg.passthrough.setting.ilbc_mode = cfg->ilbc_mode; + } +#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */ + + /* Register all codecs */ + status = pjmedia_codec_register_audio_codecs(pjsua_var.med_endpt, + &codec_cfg); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, "Error registering codecs")); + goto on_error; + } + + /* Set speex/16000 to higher priority*/ + codec_id = pj_str("speex/16000"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), + &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+2); + + /* Set speex/8000 to next higher priority*/ + codec_id = pj_str("speex/8000"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), + &codec_id, PJMEDIA_CODEC_PRIO_NORMAL+1); + + /* Disable ALL L16 codecs */ + codec_id = pj_str("L16"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt), + &codec_id, PJMEDIA_CODEC_PRIO_DISABLED); + + + /* Save additional conference bridge parameters for future + * reference. + */ + pjsua_var.mconf_cfg.channel_count = pjsua_var.media_cfg.channel_count; + pjsua_var.mconf_cfg.bits_per_sample = 16; + pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate * + pjsua_var.mconf_cfg.channel_count * + pjsua_var.media_cfg.audio_frame_ptime / + 1000; + + /* Init options for conference bridge. */ + opt = PJMEDIA_CONF_NO_DEVICE; + if (pjsua_var.media_cfg.quality >= 3 && + pjsua_var.media_cfg.quality <= 4) + { + opt |= PJMEDIA_CONF_SMALL_FILTER; + } + else if (pjsua_var.media_cfg.quality < 3) { + opt |= PJMEDIA_CONF_USE_LINEAR; + } + + /* Init conference bridge. */ + status = pjmedia_conf_create(pjsua_var.pool, + pjsua_var.media_cfg.max_media_ports, + pjsua_var.media_cfg.clock_rate, + pjsua_var.mconf_cfg.channel_count, + pjsua_var.mconf_cfg.samples_per_frame, + pjsua_var.mconf_cfg.bits_per_sample, + opt, &pjsua_var.mconf); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating conference bridge", + status); + goto on_error; + } + + /* Are we using the audio switchboard (a.k.a APS-Direct)? */ + pjsua_var.is_mswitch = pjmedia_conf_get_master_port(pjsua_var.mconf) + ->info.signature == PJMEDIA_CONF_SWITCH_SIGNATURE; + + /* Create null port just in case user wants to use null sound. */ + status = pjmedia_null_port_create(pjsua_var.pool, + pjsua_var.media_cfg.clock_rate, + pjsua_var.mconf_cfg.channel_count, + pjsua_var.mconf_cfg.samples_per_frame, + pjsua_var.mconf_cfg.bits_per_sample, + &pjsua_var.null_port); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + return status; + +on_error: + return status; +} + +/* Check if sound device is idle. */ +void pjsua_check_snd_dev_idle() +{ + unsigned call_cnt; + + /* Check if the sound device auto-close feature is disabled. */ + if (pjsua_var.media_cfg.snd_auto_close_time < 0) + return; + + /* Check if the sound device is currently closed. */ + if (!pjsua_var.snd_is_on) + return; + + /* Get the call count, we shouldn't close the sound device when there is + * any calls active. + */ + call_cnt = pjsua_call_get_count(); + + /* When this function is called from pjsua_media_channel_deinit() upon + * disconnecting call, actually the call count hasn't been updated/ + * decreased. So we put additional check here, if there is only one + * call and it's in DISCONNECTED state, there is actually no active + * call. + */ + if (call_cnt == 1) { + pjsua_call_id call_id; + pj_status_t status; + + status = pjsua_enum_calls(&call_id, &call_cnt); + if (status == PJ_SUCCESS && call_cnt > 0 && + !pjsua_call_is_active(call_id)) + { + call_cnt = 0; + } + } + + /* Activate sound device auto-close timer if sound device is idle. + * It is idle when there is no port connection in the bridge and + * there is no active call. + */ + if (pjsua_var.snd_idle_timer.id == PJ_FALSE && + call_cnt == 0 && + pjmedia_conf_get_connect_count(pjsua_var.mconf) == 0) + { + pj_time_val delay; + + delay.msec = 0; + delay.sec = pjsua_var.media_cfg.snd_auto_close_time; + + pjsua_var.snd_idle_timer.id = PJ_TRUE; + pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer, + &delay); + } +} + +/* Timer callback to close sound device */ +static void close_snd_timer_cb( pj_timer_heap_t *th, + pj_timer_entry *entry) +{ + PJ_UNUSED_ARG(th); + + PJSUA_LOCK(); + if (entry->id) { + PJ_LOG(4,(THIS_FILE,"Closing sound device after idle for %d second(s)", + pjsua_var.media_cfg.snd_auto_close_time)); + + entry->id = PJ_FALSE; + + close_snd_dev(); + } + PJSUA_UNLOCK(); +} + +pj_status_t pjsua_aud_subsys_start(void) +{ + pj_status_t status = PJ_SUCCESS; + + pj_timer_entry_init(&pjsua_var.snd_idle_timer, PJ_FALSE, NULL, + &close_snd_timer_cb); + + return status; +} + +pj_status_t pjsua_aud_subsys_destroy() +{ + unsigned i; + + close_snd_dev(); + + if (pjsua_var.mconf) { + pjmedia_conf_destroy(pjsua_var.mconf); + pjsua_var.mconf = NULL; + } + + if (pjsua_var.null_port) { + pjmedia_port_destroy(pjsua_var.null_port); + pjsua_var.null_port = NULL; + } + + /* Destroy file players */ + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) { + if (pjsua_var.player[i].port) { + pjmedia_port_destroy(pjsua_var.player[i].port); + pjsua_var.player[i].port = NULL; + } + } + + /* Destroy file recorders */ + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) { + if (pjsua_var.recorder[i].port) { + pjmedia_port_destroy(pjsua_var.recorder[i].port); + pjsua_var.recorder[i].port = NULL; + } + } + + return PJ_SUCCESS; +} + +void pjsua_aud_stop_stream(pjsua_call_media *call_med) +{ + pjmedia_stream *strm = call_med->strm.a.stream; + pjmedia_rtcp_stat stat; + + if (strm) { + pjmedia_stream_send_rtcp_bye(strm); + + if (call_med->strm.a.conf_slot != PJSUA_INVALID_ID) { + if (pjsua_var.mconf) { + pjsua_conf_remove_port(call_med->strm.a.conf_slot); + } + call_med->strm.a.conf_slot = PJSUA_INVALID_ID; + } + + if ((call_med->dir & PJMEDIA_DIR_ENCODING) && + (pjmedia_stream_get_stat(strm, &stat) == PJ_SUCCESS)) + { + /* Save RTP timestamp & sequence, so when media session is + * restarted, those values will be restored as the initial + * RTP timestamp & sequence of the new media session. So in + * the same call session, RTP timestamp and sequence are + * guaranteed to be contigue. + */ + call_med->rtp_tx_seq_ts_set = 1 | (1 << 1); + call_med->rtp_tx_seq = stat.rtp_tx_last_seq; + call_med->rtp_tx_ts = stat.rtp_tx_last_ts; + } + + if (pjsua_var.ua_cfg.cb.on_stream_destroyed) { + pjsua_var.ua_cfg.cb.on_stream_destroyed(call_med->call->index, + strm, call_med->idx); + } + + pjmedia_stream_destroy(strm); + call_med->strm.a.stream = NULL; + } + + pjsua_check_snd_dev_idle(); +} + +/* + * DTMF callback from the stream. + */ +static void dtmf_callback(pjmedia_stream *strm, void *user_data, + int digit) +{ + PJ_UNUSED_ARG(strm); + + pj_log_push_indent(); + + /* For discussions about call mutex protection related to this + * callback, please see ticket #460: + * http://trac.pjsip.org/repos/ticket/460#comment:4 + */ + if (pjsua_var.ua_cfg.cb.on_dtmf_digit) { + pjsua_call_id call_id; + + call_id = (pjsua_call_id)(long)user_data; + pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit); + } + + pj_log_pop_indent(); +} + + +pj_status_t pjsua_aud_channel_update(pjsua_call_media *call_med, + pj_pool_t *tmp_pool, + pjmedia_stream_info *si, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *remote_sdp) +{ + pjsua_call *call = call_med->call; + pjmedia_port *media_port; + unsigned strm_idx = call_med->idx; + pj_status_t status = PJ_SUCCESS; + + PJ_UNUSED_ARG(tmp_pool); + PJ_UNUSED_ARG(local_sdp); + PJ_UNUSED_ARG(remote_sdp); + + PJ_LOG(4,(THIS_FILE,"Audio channel update..")); + pj_log_push_indent(); + + si->rtcp_sdes_bye_disabled = PJ_TRUE; + + /* Check if no media is active */ + if (si->dir != PJMEDIA_DIR_NONE) { + + /* Override ptime, if this option is specified. */ + if (pjsua_var.media_cfg.ptime != 0) { + si->param->setting.frm_per_pkt = (pj_uint8_t) + (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime); + if (si->param->setting.frm_per_pkt == 0) + si->param->setting.frm_per_pkt = 1; + } + + /* Disable VAD, if this option is specified. */ + if (pjsua_var.media_cfg.no_vad) { + si->param->setting.vad = 0; + } + + + /* Optionally, application may modify other stream settings here + * (such as jitter buffer parameters, codec ptime, etc.) + */ + si->jb_init = pjsua_var.media_cfg.jb_init; + si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre; + si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre; + si->jb_max = pjsua_var.media_cfg.jb_max; + + /* Set SSRC */ + si->ssrc = call_med->ssrc; + + /* Set RTP timestamp & sequence, normally these value are intialized + * automatically when stream session created, but for some cases (e.g: + * call reinvite, call update) timestamp and sequence need to be kept + * contigue. + */ + si->rtp_ts = call_med->rtp_tx_ts; + si->rtp_seq = call_med->rtp_tx_seq; + si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set; + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 + /* Enable/disable stream keep-alive and NAT hole punch. */ + si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka; +#endif + + /* Create session based on session info. */ + status = pjmedia_stream_create(pjsua_var.med_endpt, NULL, si, + call_med->tp, NULL, + &call_med->strm.a.stream); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Start stream */ + status = pjmedia_stream_start(call_med->strm.a.stream); + if (status != PJ_SUCCESS) { + goto on_return; + } + + if (call_med->prev_state == PJSUA_CALL_MEDIA_NONE) + pjmedia_stream_send_rtcp_sdes(call_med->strm.a.stream); + + /* If DTMF callback is installed by application, install our + * callback to the session. + */ + if (pjsua_var.ua_cfg.cb.on_dtmf_digit) { + pjmedia_stream_set_dtmf_callback(call_med->strm.a.stream, + &dtmf_callback, + (void*)(long)(call->index)); + } + + /* Get the port interface of the first stream in the session. + * We need the port interface to add to the conference bridge. + */ + pjmedia_stream_get_port(call_med->strm.a.stream, &media_port); + + /* Notify application about stream creation. + * Note: application may modify media_port to point to different + * media port + */ + if (pjsua_var.ua_cfg.cb.on_stream_created) { + pjsua_var.ua_cfg.cb.on_stream_created(call->index, + call_med->strm.a.stream, + strm_idx, &media_port); + } + + /* + * Add the call to conference bridge. + */ + { + char tmp[PJSIP_MAX_URL_SIZE]; + pj_str_t port_name; + + port_name.ptr = tmp; + port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + call->inv->dlg->remote.info->uri, + tmp, sizeof(tmp)); + if (port_name.slen < 1) { + port_name = pj_str("call"); + } + status = pjmedia_conf_add_port( pjsua_var.mconf, + call->inv->pool_prov, + media_port, + &port_name, + (unsigned*) + &call_med->strm.a.conf_slot); + if (status != PJ_SUCCESS) { + goto on_return; + } + } + } + +on_return: + pj_log_pop_indent(); + return status; +} + + +/* + * Get maxinum number of conference ports. + */ +PJ_DEF(unsigned) pjsua_conf_get_max_ports(void) +{ + return pjsua_var.media_cfg.max_media_ports; +} + + +/* + * Get current number of active ports in the bridge. + */ +PJ_DEF(unsigned) pjsua_conf_get_active_ports(void) +{ + unsigned ports[PJSUA_MAX_CONF_PORTS]; + unsigned count = PJ_ARRAY_SIZE(ports); + pj_status_t status; + + status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count); + if (status != PJ_SUCCESS) + count = 0; + + return count; +} + + +/* + * Enumerate all conference ports. + */ +PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[], + unsigned *count) +{ + return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count); +} + + +/* + * Get information about the specified conference port + */ +PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id, + pjsua_conf_port_info *info) +{ + pjmedia_conf_port_info cinfo; + unsigned i; + pj_status_t status; + + status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo); + if (status != PJ_SUCCESS) + return status; + + pj_bzero(info, sizeof(*info)); + info->slot_id = id; + info->name = cinfo.name; + info->clock_rate = cinfo.clock_rate; + info->channel_count = cinfo.channel_count; + info->samples_per_frame = cinfo.samples_per_frame; + info->bits_per_sample = cinfo.bits_per_sample; + + /* Build array of listeners */ + info->listener_cnt = cinfo.listener_cnt; + for (i=0; i<cinfo.listener_cnt; ++i) { + info->listeners[i] = cinfo.listener_slots[i]; + } + + return PJ_SUCCESS; +} + + +/* + * Add arbitrary media port to PJSUA's conference bridge. + */ +PJ_DEF(pj_status_t) pjsua_conf_add_port( pj_pool_t *pool, + pjmedia_port *port, + pjsua_conf_port_id *p_id) +{ + pj_status_t status; + + status = pjmedia_conf_add_port(pjsua_var.mconf, pool, + port, NULL, (unsigned*)p_id); + if (status != PJ_SUCCESS) { + if (p_id) + *p_id = PJSUA_INVALID_ID; + } + + return status; +} + + +/* + * Remove arbitrary slot from the conference bridge. + */ +PJ_DEF(pj_status_t) pjsua_conf_remove_port(pjsua_conf_port_id id) +{ + pj_status_t status; + + status = pjmedia_conf_remove_port(pjsua_var.mconf, (unsigned)id); + pjsua_check_snd_dev_idle(); + + return status; +} + + +/* + * Establish unidirectional media flow from souce to sink. + */ +PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source, + pjsua_conf_port_id sink) +{ + pj_status_t status = PJ_SUCCESS; + + PJ_LOG(4,(THIS_FILE, "%s connect: %d --> %d", + (pjsua_var.is_mswitch ? "Switch" : "Conf"), + source, sink)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + /* If sound device idle timer is active, cancel it first. */ + if (pjsua_var.snd_idle_timer.id) { + pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.snd_idle_timer); + pjsua_var.snd_idle_timer.id = PJ_FALSE; + } + + + /* For audio switchboard (i.e. APS-Direct): + * Check if sound device need to be reopened, i.e: its attributes + * (format, clock rate, channel count) must match to peer's. + * Note that sound device can be reopened only if it doesn't have + * any connection. + */ + if (pjsua_var.is_mswitch) { + pjmedia_conf_port_info port0_info; + pjmedia_conf_port_info peer_info; + unsigned peer_id; + pj_bool_t need_reopen = PJ_FALSE; + + peer_id = (source!=0)? source : sink; + status = pjmedia_conf_get_port_info(pjsua_var.mconf, peer_id, + &peer_info); + pj_assert(status == PJ_SUCCESS); + + status = pjmedia_conf_get_port_info(pjsua_var.mconf, 0, &port0_info); + pj_assert(status == PJ_SUCCESS); + + /* Check if sound device is instantiated. */ + need_reopen = (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL && + !pjsua_var.no_snd); + + /* Check if sound device need to reopen because it needs to modify + * settings to match its peer. Sound device must be idle in this case + * though. + */ + if (!need_reopen && + port0_info.listener_cnt==0 && port0_info.transmitter_cnt==0) + { + need_reopen = (peer_info.format.id != port0_info.format.id || + peer_info.format.det.aud.avg_bps != + port0_info.format.det.aud.avg_bps || + peer_info.clock_rate != port0_info.clock_rate || + peer_info.channel_count!=port0_info.channel_count); + } + + if (need_reopen) { + if (pjsua_var.cap_dev != NULL_SND_DEV_ID) { + pjmedia_snd_port_param param; + + pjmedia_snd_port_param_default(¶m); + param.ec_options = pjsua_var.media_cfg.ec_options; + + /* Create parameter based on peer info */ + status = create_aud_param(¶m.base, pjsua_var.cap_dev, + pjsua_var.play_dev, + peer_info.clock_rate, + peer_info.channel_count, + peer_info.samples_per_frame, + peer_info.bits_per_sample); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error opening sound device", + status); + goto on_return; + } + + /* And peer format */ + if (peer_info.format.id != PJMEDIA_FORMAT_PCM) { + param.base.flags |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT; + param.base.ext_fmt = peer_info.format; + } + + param.options = 0; + status = open_snd_dev(¶m); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error opening sound device", + status); + goto on_return; + } + } else { + /* Null-audio */ + status = pjsua_set_snd_dev(pjsua_var.cap_dev, + pjsua_var.play_dev); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error opening sound device", + status); + goto on_return; + } + } + } else if (pjsua_var.no_snd) { + if (!pjsua_var.snd_is_on) { + pjsua_var.snd_is_on = PJ_TRUE; + /* Notify app */ + if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) { + (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1); + } + } + } + + } else { + /* The bridge version */ + + /* Create sound port if none is instantiated */ + if (pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL && + !pjsua_var.no_snd) + { + status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error opening sound device", status); + goto on_return; + } + } else if (pjsua_var.no_snd && !pjsua_var.snd_is_on) { + pjsua_var.snd_is_on = PJ_TRUE; + /* Notify app */ + if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) { + (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1); + } + } + } + +on_return: + PJSUA_UNLOCK(); + + if (status == PJ_SUCCESS) { + status = pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0); + } + + pj_log_pop_indent(); + return status; +} + + +/* + * Disconnect media flow from the source to destination port. + */ +PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source, + pjsua_conf_port_id sink) +{ + pj_status_t status; + + PJ_LOG(4,(THIS_FILE, "%s disconnect: %d -x- %d", + (pjsua_var.is_mswitch ? "Switch" : "Conf"), + source, sink)); + pj_log_push_indent(); + + status = pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink); + pjsua_check_snd_dev_idle(); + + pj_log_pop_indent(); + return status; +} + + +/* + * Adjust the signal level to be transmitted from the bridge to the + * specified port by making it louder or quieter. + */ +PJ_DEF(pj_status_t) pjsua_conf_adjust_tx_level(pjsua_conf_port_id slot, + float level) +{ + return pjmedia_conf_adjust_tx_level(pjsua_var.mconf, slot, + (int)((level-1) * 128)); +} + +/* + * Adjust the signal level to be received from the specified port (to + * the bridge) by making it louder or quieter. + */ +PJ_DEF(pj_status_t) pjsua_conf_adjust_rx_level(pjsua_conf_port_id slot, + float level) +{ + return pjmedia_conf_adjust_rx_level(pjsua_var.mconf, slot, + (int)((level-1) * 128)); +} + + +/* + * Get last signal level transmitted to or received from the specified port. + */ +PJ_DEF(pj_status_t) pjsua_conf_get_signal_level(pjsua_conf_port_id slot, + unsigned *tx_level, + unsigned *rx_level) +{ + return pjmedia_conf_get_signal_level(pjsua_var.mconf, slot, + tx_level, rx_level); +} + +/***************************************************************************** + * File player. + */ + +static char* get_basename(const char *path, unsigned len) +{ + char *p = ((char*)path) + len; + + if (len==0) + return p; + + for (--p; p!=path && *p!='/' && *p!='\\'; ) --p; + + return (p==path) ? p : p+1; +} + + +/* + * Create a file player, and automatically connect this player to + * the conference bridge. + */ +PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename, + unsigned options, + pjsua_player_id *p_id) +{ + unsigned slot, file_id; + char path[PJ_MAXPATH]; + pj_pool_t *pool = NULL; + pjmedia_port *port; + pj_status_t status = PJ_SUCCESS; + + if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player)) + return PJ_ETOOMANY; + + PJ_LOG(4,(THIS_FILE, "Creating file player: %.*s..", + (int)filename->slen, filename->ptr)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) { + if (pjsua_var.player[file_id].port == NULL) + break; + } + + if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) { + /* This is unexpected */ + pj_assert(0); + status = PJ_EBUG; + goto on_error; + } + + pj_memcpy(path, filename->ptr, filename->slen); + path[filename->slen] = '\0'; + + pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000); + if (!pool) { + status = PJ_ENOMEM; + goto on_error; + } + + status = pjmedia_wav_player_port_create( + pool, path, + pjsua_var.mconf_cfg.samples_per_frame * + 1000 / pjsua_var.media_cfg.channel_count / + pjsua_var.media_cfg.clock_rate, + options, 0, &port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to open file for playback", status); + goto on_error; + } + + status = pjmedia_conf_add_port(pjsua_var.mconf, pool, + port, filename, &slot); + if (status != PJ_SUCCESS) { + pjmedia_port_destroy(port); + pjsua_perror(THIS_FILE, "Unable to add file to conference bridge", + status); + goto on_error; + } + + pjsua_var.player[file_id].type = 0; + pjsua_var.player[file_id].pool = pool; + pjsua_var.player[file_id].port = port; + pjsua_var.player[file_id].slot = slot; + + if (p_id) *p_id = file_id; + + ++pjsua_var.player_cnt; + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "Player created, id=%d, slot=%d", file_id, slot)); + + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + PJSUA_UNLOCK(); + if (pool) pj_pool_release(pool); + pj_log_pop_indent(); + return status; +} + + +/* + * Create a file playlist media port, and automatically add the port + * to the conference bridge. + */ +PJ_DEF(pj_status_t) pjsua_playlist_create( const pj_str_t file_names[], + unsigned file_count, + const pj_str_t *label, + unsigned options, + pjsua_player_id *p_id) +{ + unsigned slot, file_id, ptime; + pj_pool_t *pool = NULL; + pjmedia_port *port; + pj_status_t status = PJ_SUCCESS; + + if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player)) + return PJ_ETOOMANY; + + PJ_LOG(4,(THIS_FILE, "Creating playlist with %d file(s)..", file_count)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) { + if (pjsua_var.player[file_id].port == NULL) + break; + } + + if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) { + /* This is unexpected */ + pj_assert(0); + status = PJ_EBUG; + goto on_error; + } + + + ptime = pjsua_var.mconf_cfg.samples_per_frame * 1000 / + pjsua_var.media_cfg.clock_rate; + + pool = pjsua_pool_create("playlist", 1000, 1000); + if (!pool) { + status = PJ_ENOMEM; + goto on_error; + } + + status = pjmedia_wav_playlist_create(pool, label, + file_names, file_count, + ptime, options, 0, &port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create playlist", status); + goto on_error; + } + + status = pjmedia_conf_add_port(pjsua_var.mconf, pool, + port, &port->info.name, &slot); + if (status != PJ_SUCCESS) { + pjmedia_port_destroy(port); + pjsua_perror(THIS_FILE, "Unable to add port", status); + goto on_error; + } + + pjsua_var.player[file_id].type = 1; + pjsua_var.player[file_id].pool = pool; + pjsua_var.player[file_id].port = port; + pjsua_var.player[file_id].slot = slot; + + if (p_id) *p_id = file_id; + + ++pjsua_var.player_cnt; + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "Playlist created, id=%d, slot=%d", file_id, slot)); + + pj_log_pop_indent(); + + return PJ_SUCCESS; + +on_error: + PJSUA_UNLOCK(); + if (pool) pj_pool_release(pool); + pj_log_pop_indent(); + + return status; +} + + +/* + * Get conference port ID associated with player. + */ +PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id) +{ + PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL); + + return pjsua_var.player[id].slot; +} + +/* + * Get the media port for the player. + */ +PJ_DEF(pj_status_t) pjsua_player_get_port( pjsua_player_id id, + pjmedia_port **p_port) +{ + PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL); + + *p_port = pjsua_var.player[id].port; + + return PJ_SUCCESS; +} + +/* + * Set playback position. + */ +PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id, + pj_uint32_t samples) +{ + PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.player[id].type == 0, PJ_EINVAL); + + return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples); +} + + +/* + * Close the file, remove the player from the bridge, and free + * resources associated with the file player. + */ +PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id) +{ + PJ_ASSERT_RETURN(id>=0&&id<(int)PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Destroying player %d..", id)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + if (pjsua_var.player[id].port) { + pjsua_conf_remove_port(pjsua_var.player[id].slot); + pjmedia_port_destroy(pjsua_var.player[id].port); + pjsua_var.player[id].port = NULL; + pjsua_var.player[id].slot = 0xFFFF; + pj_pool_release(pjsua_var.player[id].pool); + pjsua_var.player[id].pool = NULL; + pjsua_var.player_cnt--; + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * File recorder. + */ + +/* + * Create a file recorder, and automatically connect this recorder to + * the conference bridge. + */ +PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename, + unsigned enc_type, + void *enc_param, + pj_ssize_t max_size, + unsigned options, + pjsua_recorder_id *p_id) +{ + enum Format + { + FMT_UNKNOWN, + FMT_WAV, + FMT_MP3, + }; + unsigned slot, file_id; + char path[PJ_MAXPATH]; + pj_str_t ext; + int file_format; + pj_pool_t *pool = NULL; + pjmedia_port *port; + pj_status_t status = PJ_SUCCESS; + + /* Filename must present */ + PJ_ASSERT_RETURN(filename != NULL, PJ_EINVAL); + + /* Don't support max_size at present */ + PJ_ASSERT_RETURN(max_size == 0 || max_size == -1, PJ_EINVAL); + + /* Don't support encoding type at present */ + PJ_ASSERT_RETURN(enc_type == 0, PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Creating recorder %.*s..", + (int)filename->slen, filename->ptr)); + pj_log_push_indent(); + + if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder)) { + pj_log_pop_indent(); + return PJ_ETOOMANY; + } + + /* Determine the file format */ + ext.ptr = filename->ptr + filename->slen - 4; + ext.slen = 4; + + if (pj_stricmp2(&ext, ".wav") == 0) + file_format = FMT_WAV; + else if (pj_stricmp2(&ext, ".mp3") == 0) + file_format = FMT_MP3; + else { + PJ_LOG(1,(THIS_FILE, "pjsua_recorder_create() error: unable to " + "determine file format for %.*s", + (int)filename->slen, filename->ptr)); + pj_log_pop_indent(); + return PJ_ENOTSUP; + } + + PJSUA_LOCK(); + + for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) { + if (pjsua_var.recorder[file_id].port == NULL) + break; + } + + if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) { + /* This is unexpected */ + pj_assert(0); + status = PJ_EBUG; + goto on_return; + } + + pj_memcpy(path, filename->ptr, filename->slen); + path[filename->slen] = '\0'; + + pool = pjsua_pool_create(get_basename(path, filename->slen), 1000, 1000); + if (!pool) { + status = PJ_ENOMEM; + goto on_return; + } + + if (file_format == FMT_WAV) { + status = pjmedia_wav_writer_port_create(pool, path, + pjsua_var.media_cfg.clock_rate, + pjsua_var.mconf_cfg.channel_count, + pjsua_var.mconf_cfg.samples_per_frame, + pjsua_var.mconf_cfg.bits_per_sample, + options, 0, &port); + } else { + PJ_UNUSED_ARG(enc_param); + port = NULL; + status = PJ_ENOTSUP; + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to open file for recording", status); + goto on_return; + } + + status = pjmedia_conf_add_port(pjsua_var.mconf, pool, + port, filename, &slot); + if (status != PJ_SUCCESS) { + pjmedia_port_destroy(port); + goto on_return; + } + + pjsua_var.recorder[file_id].port = port; + pjsua_var.recorder[file_id].slot = slot; + pjsua_var.recorder[file_id].pool = pool; + + if (p_id) *p_id = file_id; + + ++pjsua_var.rec_cnt; + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "Recorder created, id=%d, slot=%d", file_id, slot)); + + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_return: + PJSUA_UNLOCK(); + if (pool) pj_pool_release(pool); + pj_log_pop_indent(); + return status; +} + + +/* + * Get conference port associated with recorder. + */ +PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL); + + return pjsua_var.recorder[id].slot; +} + +/* + * Get the media port for the recorder. + */ +PJ_DEF(pj_status_t) pjsua_recorder_get_port( pjsua_recorder_id id, + pjmedia_port **p_port) +{ + PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(p_port != NULL, PJ_EINVAL); + + *p_port = pjsua_var.recorder[id].port; + return PJ_SUCCESS; +} + +/* + * Destroy recorder (this will complete recording). + */ +PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.recorder), + PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Destroying recorder %d..", id)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + if (pjsua_var.recorder[id].port) { + pjsua_conf_remove_port(pjsua_var.recorder[id].slot); + pjmedia_port_destroy(pjsua_var.recorder[id].port); + pjsua_var.recorder[id].port = NULL; + pjsua_var.recorder[id].slot = 0xFFFF; + pj_pool_release(pjsua_var.recorder[id].pool); + pjsua_var.recorder[id].pool = NULL; + pjsua_var.rec_cnt--; + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * Sound devices. + */ + +/* + * Enum sound devices. + */ + +PJ_DEF(pj_status_t) pjsua_enum_aud_devs( pjmedia_aud_dev_info info[], + unsigned *count) +{ + unsigned i, dev_count; + + dev_count = pjmedia_aud_dev_count(); + + if (dev_count > *count) dev_count = *count; + + for (i=0; i<dev_count; ++i) { + pj_status_t status; + + status = pjmedia_aud_dev_get_info(i, &info[i]); + if (status != PJ_SUCCESS) + return status; + } + + *count = dev_count; + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[], + unsigned *count) +{ + unsigned i, dev_count; + + dev_count = pjmedia_aud_dev_count(); + + if (dev_count > *count) dev_count = *count; + pj_bzero(info, dev_count * sizeof(pjmedia_snd_dev_info)); + + for (i=0; i<dev_count; ++i) { + pjmedia_aud_dev_info ai; + pj_status_t status; + + status = pjmedia_aud_dev_get_info(i, &ai); + if (status != PJ_SUCCESS) + return status; + + strncpy(info[i].name, ai.name, sizeof(info[i].name)); + info[i].name[sizeof(info[i].name)-1] = '\0'; + info[i].input_count = ai.input_count; + info[i].output_count = ai.output_count; + info[i].default_samples_per_sec = ai.default_samples_per_sec; + } + + *count = dev_count; + + return PJ_SUCCESS; +} + +/* Create audio device parameter to open the device */ +static pj_status_t create_aud_param(pjmedia_aud_param *param, + pjmedia_aud_dev_index capture_dev, + pjmedia_aud_dev_index playback_dev, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample) +{ + pj_status_t status; + + /* Normalize device ID with new convention about default device ID */ + if (playback_dev == PJMEDIA_AUD_DEFAULT_CAPTURE_DEV) + playback_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; + + /* Create default parameters for the device */ + status = pjmedia_aud_dev_default_param(capture_dev, param); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error retrieving default audio " + "device parameters", status); + return status; + } + param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param->rec_id = capture_dev; + param->play_id = playback_dev; + param->clock_rate = clock_rate; + param->channel_count = channel_count; + param->samples_per_frame = samples_per_frame; + param->bits_per_sample = bits_per_sample; + + /* Update the setting with user preference */ +#define update_param(cap, field) \ + if (pjsua_var.aud_param.flags & cap) { \ + param->flags |= cap; \ + param->field = pjsua_var.aud_param.field; \ + } + update_param( PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol); + update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol); + update_param( PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route); + update_param( PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route); +#undef update_param + + /* Latency settings */ + param->flags |= (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | + PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY); + param->input_latency_ms = pjsua_var.media_cfg.snd_rec_latency; + param->output_latency_ms = pjsua_var.media_cfg.snd_play_latency; + + /* EC settings */ + if (pjsua_var.media_cfg.ec_tail_len) { + param->flags |= (PJMEDIA_AUD_DEV_CAP_EC | PJMEDIA_AUD_DEV_CAP_EC_TAIL); + param->ec_enabled = PJ_TRUE; + param->ec_tail_ms = pjsua_var.media_cfg.ec_tail_len; + } else { + param->flags &= ~(PJMEDIA_AUD_DEV_CAP_EC|PJMEDIA_AUD_DEV_CAP_EC_TAIL); + } + + return PJ_SUCCESS; +} + +/* Internal: the first time the audio device is opened (during app + * startup), retrieve the audio settings such as volume level + * so that aud_get_settings() will work. + */ +static pj_status_t update_initial_aud_param() +{ + pjmedia_aud_stream *strm; + pjmedia_aud_param param; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_var.snd_port != NULL, PJ_EBUG); + + strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port); + + status = pjmedia_aud_stream_get_param(strm, ¶m); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error audio stream " + "device parameters", status); + return status; + } + +#define update_saved_param(cap, field) \ + if (param.flags & cap) { \ + pjsua_var.aud_param.flags |= cap; \ + pjsua_var.aud_param.field = param.field; \ + } + + update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, input_vol); + update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, output_vol); + update_saved_param(PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, input_route); + update_saved_param(PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, output_route); +#undef update_saved_param + + return PJ_SUCCESS; +} + +/* Get format name */ +static const char *get_fmt_name(pj_uint32_t id) +{ + static char name[8]; + + if (id == PJMEDIA_FORMAT_L16) + return "PCM"; + pj_memcpy(name, &id, 4); + name[4] = '\0'; + return name; +} + +/* Open sound device with the setting. */ +static pj_status_t open_snd_dev(pjmedia_snd_port_param *param) +{ + pjmedia_port *conf_port; + pj_status_t status; + + PJ_ASSERT_RETURN(param, PJ_EINVAL); + + /* Check if NULL sound device is used */ + if (NULL_SND_DEV_ID==param->base.rec_id || + NULL_SND_DEV_ID==param->base.play_id) + { + return pjsua_set_null_snd_dev(); + } + + /* Close existing sound port */ + close_snd_dev(); + + /* Notify app */ + if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) { + (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1); + } + + /* Create memory pool for sound device. */ + pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000); + PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM); + + + PJ_LOG(4,(THIS_FILE, "Opening sound device %s@%d/%d/%dms", + get_fmt_name(param->base.ext_fmt.id), + param->base.clock_rate, param->base.channel_count, + param->base.samples_per_frame / param->base.channel_count * + 1000 / param->base.clock_rate)); + pj_log_push_indent(); + + status = pjmedia_snd_port_create2( pjsua_var.snd_pool, + param, &pjsua_var.snd_port); + if (status != PJ_SUCCESS) + goto on_error; + + /* Get the port0 of the conference bridge. */ + conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf); + pj_assert(conf_port != NULL); + + /* For conference bridge, resample if necessary if the bridge's + * clock rate is different than the sound device's clock rate. + */ + if (!pjsua_var.is_mswitch && + param->base.ext_fmt.id == PJMEDIA_FORMAT_PCM && + PJMEDIA_PIA_SRATE(&conf_port->info) != param->base.clock_rate) + { + pjmedia_port *resample_port; + unsigned resample_opt = 0; + + if (pjsua_var.media_cfg.quality >= 3 && + pjsua_var.media_cfg.quality <= 4) + { + resample_opt |= PJMEDIA_RESAMPLE_USE_SMALL_FILTER; + } + else if (pjsua_var.media_cfg.quality < 3) { + resample_opt |= PJMEDIA_RESAMPLE_USE_LINEAR; + } + + status = pjmedia_resample_port_create(pjsua_var.snd_pool, + conf_port, + param->base.clock_rate, + resample_opt, + &resample_port); + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4, (THIS_FILE, + "Error creating resample port: %s", + errmsg)); + close_snd_dev(); + goto on_error; + } + + conf_port = resample_port; + } + + /* Otherwise for audio switchboard, the switch's port0 setting is + * derived from the sound device setting, so update the setting. + */ + if (pjsua_var.is_mswitch) { + if (param->base.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) { + conf_port->info.fmt = param->base.ext_fmt; + } else { + unsigned bps, ptime_usec; + bps = param->base.clock_rate * param->base.bits_per_sample; + ptime_usec = param->base.samples_per_frame / + param->base.channel_count * 1000000 / + param->base.clock_rate; + pjmedia_format_init_audio(&conf_port->info.fmt, + PJMEDIA_FORMAT_PCM, + param->base.clock_rate, + param->base.channel_count, + param->base.bits_per_sample, + ptime_usec, + bps, bps); + } + } + + + /* Connect sound port to the bridge */ + status = pjmedia_snd_port_connect(pjsua_var.snd_port, + conf_port ); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to connect conference port to " + "sound device", status); + pjmedia_snd_port_destroy(pjsua_var.snd_port); + pjsua_var.snd_port = NULL; + goto on_error; + } + + /* Save the device IDs */ + pjsua_var.cap_dev = param->base.rec_id; + pjsua_var.play_dev = param->base.play_id; + + /* Update sound device name. */ + { + pjmedia_aud_dev_info rec_info; + pjmedia_aud_stream *strm; + pjmedia_aud_param si; + pj_str_t tmp; + + strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port); + status = pjmedia_aud_stream_get_param(strm, &si); + if (status == PJ_SUCCESS) + status = pjmedia_aud_dev_get_info(si.rec_id, &rec_info); + + if (status==PJ_SUCCESS) { + if (param->base.clock_rate != pjsua_var.media_cfg.clock_rate) { + char tmp_buf[128]; + int tmp_buf_len = sizeof(tmp_buf); + + tmp_buf_len = pj_ansi_snprintf(tmp_buf, sizeof(tmp_buf)-1, + "%s (%dKHz)", + rec_info.name, + param->base.clock_rate/1000); + pj_strset(&tmp, tmp_buf, tmp_buf_len); + pjmedia_conf_set_port0_name(pjsua_var.mconf, &tmp); + } else { + pjmedia_conf_set_port0_name(pjsua_var.mconf, + pj_cstr(&tmp, rec_info.name)); + } + } + + /* Any error is not major, let it through */ + status = PJ_SUCCESS; + } + + /* If this is the first time the audio device is open, retrieve some + * settings from the device (such as volume settings) so that the + * pjsua_snd_get_setting() work. + */ + if (pjsua_var.aud_open_cnt == 0) { + update_initial_aud_param(); + ++pjsua_var.aud_open_cnt; + } + + pjsua_var.snd_is_on = PJ_TRUE; + + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + pj_log_pop_indent(); + return status; +} + + +/* Close existing sound device */ +static void close_snd_dev(void) +{ + pj_log_push_indent(); + + /* Notify app */ + if (pjsua_var.snd_is_on && pjsua_var.ua_cfg.cb.on_snd_dev_operation) { + (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(0); + } + + /* Close sound device */ + if (pjsua_var.snd_port) { + pjmedia_aud_dev_info cap_info, play_info; + pjmedia_aud_stream *strm; + pjmedia_aud_param param; + + strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port); + pjmedia_aud_stream_get_param(strm, ¶m); + + if (pjmedia_aud_dev_get_info(param.rec_id, &cap_info) != PJ_SUCCESS) + cap_info.name[0] = '\0'; + if (pjmedia_aud_dev_get_info(param.play_id, &play_info) != PJ_SUCCESS) + play_info.name[0] = '\0'; + + PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and " + "%s sound capture device", + play_info.name, cap_info.name)); + + pjmedia_snd_port_disconnect(pjsua_var.snd_port); + pjmedia_snd_port_destroy(pjsua_var.snd_port); + pjsua_var.snd_port = NULL; + } + + /* Close null sound device */ + if (pjsua_var.null_snd) { + PJ_LOG(4,(THIS_FILE, "Closing null sound device..")); + pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE); + pjsua_var.null_snd = NULL; + } + + if (pjsua_var.snd_pool) + pj_pool_release(pjsua_var.snd_pool); + + pjsua_var.snd_pool = NULL; + pjsua_var.snd_is_on = PJ_FALSE; + + pj_log_pop_indent(); +} + + +/* + * Select or change sound device. Application may call this function at + * any time to replace current sound device. + */ +PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev, + int playback_dev) +{ + unsigned alt_cr_cnt = 1; + unsigned alt_cr[] = {0, 44100, 48000, 32000, 16000, 8000}; + unsigned i; + pj_status_t status = -1; + + PJ_LOG(4,(THIS_FILE, "Set sound device: capture=%d, playback=%d", + capture_dev, playback_dev)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + /* Null-sound */ + if (capture_dev==NULL_SND_DEV_ID && playback_dev==NULL_SND_DEV_ID) { + PJSUA_UNLOCK(); + status = pjsua_set_null_snd_dev(); + pj_log_pop_indent(); + return status; + } + + /* Set default clock rate */ + alt_cr[0] = pjsua_var.media_cfg.snd_clock_rate; + if (alt_cr[0] == 0) + alt_cr[0] = pjsua_var.media_cfg.clock_rate; + + /* Allow retrying of different clock rate if we're using conference + * bridge (meaning audio format is always PCM), otherwise lock on + * to one clock rate. + */ + if (pjsua_var.is_mswitch) { + alt_cr_cnt = 1; + } else { + alt_cr_cnt = PJ_ARRAY_SIZE(alt_cr); + } + + /* Attempts to open the sound device with different clock rates */ + for (i=0; i<alt_cr_cnt; ++i) { + pjmedia_snd_port_param param; + unsigned samples_per_frame; + + /* Create the default audio param */ + samples_per_frame = alt_cr[i] * + pjsua_var.media_cfg.audio_frame_ptime * + pjsua_var.media_cfg.channel_count / 1000; + pjmedia_snd_port_param_default(¶m); + param.ec_options = pjsua_var.media_cfg.ec_options; + status = create_aud_param(¶m.base, capture_dev, playback_dev, + alt_cr[i], pjsua_var.media_cfg.channel_count, + samples_per_frame, 16); + if (status != PJ_SUCCESS) + goto on_error; + + /* Open! */ + param.options = 0; + status = open_snd_dev(¶m); + if (status == PJ_SUCCESS) + break; + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to open sound device", status); + goto on_error; + } + + pjsua_var.no_snd = PJ_FALSE; + pjsua_var.snd_is_on = PJ_TRUE; + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; +} + + +/* + * Get currently active sound devices. If sound devices has not been created + * (for example when pjsua_start() is not called), it is possible that + * the function returns PJ_SUCCESS with -1 as device IDs. + */ +PJ_DEF(pj_status_t) pjsua_get_snd_dev(int *capture_dev, + int *playback_dev) +{ + PJSUA_LOCK(); + + if (capture_dev) { + *capture_dev = pjsua_var.cap_dev; + } + if (playback_dev) { + *playback_dev = pjsua_var.play_dev; + } + + PJSUA_UNLOCK(); + return PJ_SUCCESS; +} + + +/* + * Use null sound device. + */ +PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void) +{ + pjmedia_port *conf_port; + pj_status_t status; + + PJ_LOG(4,(THIS_FILE, "Setting null sound device..")); + pj_log_push_indent(); + + PJSUA_LOCK(); + + /* Close existing sound device */ + close_snd_dev(); + + /* Notify app */ + if (pjsua_var.ua_cfg.cb.on_snd_dev_operation) { + (*pjsua_var.ua_cfg.cb.on_snd_dev_operation)(1); + } + + /* Create memory pool for sound device. */ + pjsua_var.snd_pool = pjsua_pool_create("pjsua_snd", 4000, 4000); + PJ_ASSERT_RETURN(pjsua_var.snd_pool, PJ_ENOMEM); + + PJ_LOG(4,(THIS_FILE, "Opening null sound device..")); + + /* Get the port0 of the conference bridge. */ + conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf); + pj_assert(conf_port != NULL); + + /* Create master port, connecting port0 of the conference bridge to + * a null port. + */ + status = pjmedia_master_port_create(pjsua_var.snd_pool, pjsua_var.null_port, + conf_port, 0, &pjsua_var.null_snd); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create null sound device", + status); + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; + } + + /* Start the master port */ + status = pjmedia_master_port_start(pjsua_var.null_snd); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + pjsua_var.cap_dev = NULL_SND_DEV_ID; + pjsua_var.play_dev = NULL_SND_DEV_ID; + + pjsua_var.no_snd = PJ_FALSE; + pjsua_var.snd_is_on = PJ_TRUE; + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + + +/* + * Use no device! + */ +PJ_DEF(pjmedia_port*) pjsua_set_no_snd_dev(void) +{ + PJSUA_LOCK(); + + /* Close existing sound device */ + close_snd_dev(); + pjsua_var.no_snd = PJ_TRUE; + + PJSUA_UNLOCK(); + + return pjmedia_conf_get_master_port(pjsua_var.mconf); +} + + +/* + * Configure the AEC settings of the sound port. + */ +PJ_DEF(pj_status_t) pjsua_set_ec(unsigned tail_ms, unsigned options) +{ + pj_status_t status = PJ_SUCCESS; + + PJSUA_LOCK(); + + pjsua_var.media_cfg.ec_tail_len = tail_ms; + pjsua_var.media_cfg.ec_options = options; + + if (pjsua_var.snd_port) + status = pjmedia_snd_port_set_ec(pjsua_var.snd_port, pjsua_var.pool, + tail_ms, options); + + PJSUA_UNLOCK(); + return status; +} + + +/* + * Get current AEC tail length. + */ +PJ_DEF(pj_status_t) pjsua_get_ec_tail(unsigned *p_tail_ms) +{ + *p_tail_ms = pjsua_var.media_cfg.ec_tail_len; + return PJ_SUCCESS; +} + + +/* + * Check whether the sound device is currently active. + */ +PJ_DEF(pj_bool_t) pjsua_snd_is_active(void) +{ + return pjsua_var.snd_port != NULL; +} + + +/* + * Configure sound device setting to the sound device being used. + */ +PJ_DEF(pj_status_t) pjsua_snd_set_setting( pjmedia_aud_dev_cap cap, + const void *pval, + pj_bool_t keep) +{ + pj_status_t status; + + /* Check if we are allowed to set the cap */ + if ((cap & pjsua_var.aud_svmask) == 0) { + return PJMEDIA_EAUD_INVCAP; + } + + PJSUA_LOCK(); + + /* If sound is active, set it immediately */ + if (pjsua_snd_is_active()) { + pjmedia_aud_stream *strm; + + strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port); + status = pjmedia_aud_stream_set_cap(strm, cap, pval); + } else { + status = PJ_SUCCESS; + } + + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + return status; + } + + /* Save in internal param for later device open */ + if (keep) { + status = pjmedia_aud_param_set_cap(&pjsua_var.aud_param, + cap, pval); + } + + PJSUA_UNLOCK(); + return status; +} + +/* + * Retrieve a sound device setting. + */ +PJ_DEF(pj_status_t) pjsua_snd_get_setting( pjmedia_aud_dev_cap cap, + void *pval) +{ + pj_status_t status; + + PJSUA_LOCK(); + + /* If sound device has never been opened before, open it to + * retrieve the initial setting from the device (e.g. audio + * volume) + */ + if (pjsua_var.aud_open_cnt==0) { + PJ_LOG(4,(THIS_FILE, "Opening sound device to get initial settings")); + pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev); + close_snd_dev(); + } + + if (pjsua_snd_is_active()) { + /* Sound is active, retrieve from device directly */ + pjmedia_aud_stream *strm; + + strm = pjmedia_snd_port_get_snd_stream(pjsua_var.snd_port); + status = pjmedia_aud_stream_get_cap(strm, cap, pval); + } else { + /* Otherwise retrieve from internal param */ + status = pjmedia_aud_param_get_cap(&pjsua_var.aud_param, + cap, pval); + } + + PJSUA_UNLOCK(); + return status; +} + +#endif /* PJSUA_MEDIA_HAS_PJMEDIA */ diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c new file mode 100644 index 0000000..6f14709 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -0,0 +1,4397 @@ +/* $Id: pjsua_call.c 4176 2012-06-23 03:06:52Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + + +#define THIS_FILE "pjsua_call.c" + + +/* Retry interval of sending re-INVITE for locking a codec when remote + * SDP answer contains multiple codec, in milliseconds. + */ +#define LOCK_CODEC_RETRY_INTERVAL 200 + +/* + * Max UPDATE/re-INVITE retry to lock codec + */ +#define LOCK_CODEC_MAX_RETRY 5 + + +/* + * The INFO method. + */ +const pjsip_method pjsip_info_method = +{ + PJSIP_OTHER_METHOD, + { "INFO", 4 } +}; + + +/* This callback receives notification from invite session when the + * session state has changed. + */ +static void pjsua_call_on_state_changed(pjsip_inv_session *inv, + pjsip_event *e); + +/* This callback is called by invite session framework when UAC session + * has forked. + */ +static void pjsua_call_on_forked( pjsip_inv_session *inv, + pjsip_event *e); + +/* + * Callback to be called when SDP offer/answer negotiation has just completed + * in the session. This function will start/update media if negotiation + * has succeeded. + */ +static void pjsua_call_on_media_update(pjsip_inv_session *inv, + pj_status_t status); + +/* + * Called when session received new offer. + */ +static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer); + +/* + * Called to generate new offer. + */ +static void pjsua_call_on_create_offer(pjsip_inv_session *inv, + pjmedia_sdp_session **offer); + +/* + * This callback is called when transaction state has changed in INVITE + * session. We use this to trap: + * - incoming REFER request. + * - incoming MESSAGE request. + */ +static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_event *e); + +/* + * Redirection handler. + */ +static pjsip_redirect_op pjsua_call_on_redirected(pjsip_inv_session *inv, + const pjsip_uri *target, + const pjsip_event *e); + + +/* Create SDP for call hold. */ +static pj_status_t create_sdp_of_call_hold(pjsua_call *call, + pjmedia_sdp_session **p_sdp); + +/* + * Callback called by event framework when the xfer subscription state + * has changed. + */ +static void xfer_client_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); +static void xfer_server_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); + +/* + * Reset call descriptor. + */ +static void reset_call(pjsua_call_id id) +{ + pjsua_call *call = &pjsua_var.calls[id]; + unsigned i; + + pj_bzero(call, sizeof(*call)); + call->index = id; + call->last_text.ptr = call->last_text_buf_; + for (i=0; i<PJ_ARRAY_SIZE(call->media); ++i) { + pjsua_call_media *call_med = &call->media[i]; + call_med->ssrc = pj_rand(); + call_med->strm.a.conf_slot = PJSUA_INVALID_ID; + call_med->strm.v.cap_win_id = PJSUA_INVALID_ID; + call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID; + call_med->call = call; + call_med->idx = i; + call_med->tp_auto_del = PJ_TRUE; + } + pjsua_call_setting_default(&call->opt); +} + + +/* + * Init call subsystem. + */ +pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg) +{ + pjsip_inv_callback inv_cb; + unsigned i; + const pj_str_t str_norefersub = { "norefersub", 10 }; + pj_status_t status; + + /* Init calls array. */ + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.calls); ++i) + reset_call(i); + + /* Copy config */ + pjsua_config_dup(pjsua_var.pool, &pjsua_var.ua_cfg, cfg); + + /* Verify settings */ + if (pjsua_var.ua_cfg.max_calls >= PJSUA_MAX_CALLS) { + pjsua_var.ua_cfg.max_calls = PJSUA_MAX_CALLS; + } + + /* Check the route URI's and force loose route if required */ + for (i=0; i<pjsua_var.ua_cfg.outbound_proxy_cnt; ++i) { + status = normalize_route_uri(pjsua_var.pool, + &pjsua_var.ua_cfg.outbound_proxy[i]); + if (status != PJ_SUCCESS) + return status; + } + + /* Initialize invite session callback. */ + pj_bzero(&inv_cb, sizeof(inv_cb)); + inv_cb.on_state_changed = &pjsua_call_on_state_changed; + inv_cb.on_new_session = &pjsua_call_on_forked; + inv_cb.on_media_update = &pjsua_call_on_media_update; + inv_cb.on_rx_offer = &pjsua_call_on_rx_offer; + inv_cb.on_create_offer = &pjsua_call_on_create_offer; + inv_cb.on_tsx_state_changed = &pjsua_call_on_tsx_state_changed; + inv_cb.on_redirected = &pjsua_call_on_redirected; + + /* Initialize invite session module: */ + status = pjsip_inv_usage_init(pjsua_var.endpt, &inv_cb); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Add "norefersub" in Supported header */ + pjsip_endpt_add_capability(pjsua_var.endpt, NULL, PJSIP_H_SUPPORTED, + NULL, 1, &str_norefersub); + + return status; +} + + +/* + * Start call subsystem. + */ +pj_status_t pjsua_call_subsys_start(void) +{ + /* Nothing to do */ + return PJ_SUCCESS; +} + + +/* + * Get maximum number of calls configured in pjsua. + */ +PJ_DEF(unsigned) pjsua_call_get_max_count(void) +{ + return pjsua_var.ua_cfg.max_calls; +} + + +/* + * Get number of currently active calls. + */ +PJ_DEF(unsigned) pjsua_call_get_count(void) +{ + return pjsua_var.call_cnt; +} + + +/* + * Enum calls. + */ +PJ_DEF(pj_status_t) pjsua_enum_calls( pjsua_call_id ids[], + unsigned *count) +{ + unsigned i, c; + + PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL); + + PJSUA_LOCK(); + + for (i=0, c=0; c<*count && i<pjsua_var.ua_cfg.max_calls; ++i) { + if (!pjsua_var.calls[i].inv) + continue; + ids[c] = i; + ++c; + } + + *count = c; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* Allocate one call id */ +static pjsua_call_id alloc_call_id(void) +{ + pjsua_call_id cid; + +#if 1 + /* New algorithm: round-robin */ + if (pjsua_var.next_call_id >= (int)pjsua_var.ua_cfg.max_calls || + pjsua_var.next_call_id < 0) + { + pjsua_var.next_call_id = 0; + } + + for (cid=pjsua_var.next_call_id; + cid<(int)pjsua_var.ua_cfg.max_calls; + ++cid) + { + if (pjsua_var.calls[cid].inv == NULL && + pjsua_var.calls[cid].async_call.dlg == NULL) + { + ++pjsua_var.next_call_id; + return cid; + } + } + + for (cid=0; cid < pjsua_var.next_call_id; ++cid) { + if (pjsua_var.calls[cid].inv == NULL && + pjsua_var.calls[cid].async_call.dlg == NULL) + { + ++pjsua_var.next_call_id; + return cid; + } + } + +#else + /* Old algorithm */ + for (cid=0; cid<(int)pjsua_var.ua_cfg.max_calls; ++cid) { + if (pjsua_var.calls[cid].inv == NULL) + return cid; + } +#endif + + return PJSUA_INVALID_ID; +} + +/* Get signaling secure level. + * Return: + * 0: if signaling is not secure + * 1: if TLS transport is used for immediate hop + * 2: if end-to-end signaling is secure. + */ +static int get_secure_level(pjsua_acc_id acc_id, const pj_str_t *dst_uri) +{ + const pj_str_t tls = pj_str(";transport=tls"); + const pj_str_t sips = pj_str("sips:"); + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + + if (pj_stristr(dst_uri, &sips)) + return 2; + + if (!pj_list_empty(&acc->route_set)) { + pjsip_route_hdr *r = acc->route_set.next; + pjsip_uri *uri = r->name_addr.uri; + pjsip_sip_uri *sip_uri; + + sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri); + if (pj_stricmp2(&sip_uri->transport_param, "tls")==0) + return 1; + + } else { + if (pj_stristr(dst_uri, &tls)) + return 1; + } + + return 0; +} + +/* +static int call_get_secure_level(pjsua_call *call) +{ + if (call->inv->dlg->secure) + return 2; + + if (!pj_list_empty(&call->inv->dlg->route_set)) { + pjsip_route_hdr *r = call->inv->dlg->route_set.next; + pjsip_uri *uri = r->name_addr.uri; + pjsip_sip_uri *sip_uri; + + sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri); + if (pj_stricmp2(&sip_uri->transport_param, "tls")==0) + return 1; + + } else { + pjsip_sip_uri *sip_uri; + + if (PJSIP_URI_SCHEME_IS_SIPS(call->inv->dlg->target)) + return 2; + if (!PJSIP_URI_SCHEME_IS_SIP(call->inv->dlg->target)) + return 0; + + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(call->inv->dlg->target); + if (pj_stricmp2(&sip_uri->transport_param, "tls")==0) + return 1; + } + + return 0; +} +*/ + +/* Outgoing call callback when media transport creation is completed. */ +static pj_status_t +on_make_call_med_tp_complete(pjsua_call_id call_id, + const pjsua_med_tp_state_info *info) +{ + pjmedia_sdp_session *offer; + pjsip_inv_session *inv = NULL; + pjsua_call *call = &pjsua_var.calls[call_id]; + pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; + pjsip_dialog *dlg = call->async_call.dlg; + unsigned options = 0; + pjsip_tx_data *tdata; + pj_status_t status = (info? info->status: PJ_SUCCESS); + + PJSUA_LOCK(); + + /* Increment the dialog's lock otherwise when invite session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(dlg); + + /* Decrement dialog session. */ + pjsip_dlg_dec_session(dlg, &pjsua_var.mod); + + if (status != PJ_SUCCESS) { + pj_str_t err_str; + int title_len; + + call->last_code = PJSIP_SC_TEMPORARILY_UNAVAILABLE; + pj_strcpy2(&call->last_text, "Media init error: "); + + title_len = call->last_text.slen; + err_str = pj_strerror(status, call->last_text_buf_ + title_len, + sizeof(call->last_text_buf_) - title_len); + call->last_text.slen += err_str.slen; + + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + goto on_error; + } + + /* pjsua_media_channel_deinit() has been called. */ + if (call->async_call.med_ch_deinit) + goto on_error; + + /* Create offer */ + status = pjsua_media_channel_create_sdp(call->index, dlg->pool, NULL, + &offer, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + goto on_error; + } + + /* Create the INVITE session: */ + options |= PJSIP_INV_SUPPORT_100REL; + if (acc->cfg.require_100rel) + options |= PJSIP_INV_REQUIRE_100REL; + if (acc->cfg.use_timer != PJSUA_SIP_TIMER_INACTIVE) { + options |= PJSIP_INV_SUPPORT_TIMER; + if (acc->cfg.use_timer == PJSUA_SIP_TIMER_REQUIRED) + options |= PJSIP_INV_REQUIRE_TIMER; + else if (acc->cfg.use_timer == PJSUA_SIP_TIMER_ALWAYS) + options |= PJSIP_INV_ALWAYS_USE_TIMER; + } + + status = pjsip_inv_create_uac( dlg, offer, options, &inv); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Invite session creation failed", status); + goto on_error; + } + + /* Init Session Timers */ + status = pjsip_timer_init_session(inv, &acc->cfg.timer_setting); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Session Timer init failed", status); + goto on_error; + } + + /* Create and associate our data in the session. */ + call->inv = inv; + + dlg->mod_data[pjsua_var.mod.id] = call; + inv->mod_data[pjsua_var.mod.id] = call; + + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(dlg, &tp_sel); + } + + /* Set dialog Route-Set: */ + if (!pj_list_empty(&acc->route_set)) + pjsip_dlg_set_route_set(dlg, &acc->route_set); + + + /* Set credentials: */ + if (acc->cred_cnt) { + pjsip_auth_clt_set_credentials( &dlg->auth_sess, + acc->cred_cnt, acc->cred); + } + + /* Set authentication preference */ + pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref); + + /* Create initial INVITE: */ + + status = pjsip_inv_invite(inv, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create initial INVITE request", + status); + goto on_error; + } + + + /* Add additional headers etc */ + + pjsua_process_msg_data( tdata, + call->async_call.call_var.out_call.msg_data); + + /* Must increment call counter now */ + ++pjsua_var.call_cnt; + + /* Send initial INVITE: */ + + status = pjsip_inv_send_msg(inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send initial INVITE request", + status); + + /* Upon failure to send first request, the invite + * session would have been cleared. + */ + inv = NULL; + goto on_error; + } + + /* Done. */ + + pjsip_dlg_dec_lock(dlg); + PJSUA_UNLOCK(); + + return PJ_SUCCESS; + +on_error: + if (inv == NULL && call_id != -1 && pjsua_var.ua_cfg.cb.on_call_state) + (*pjsua_var.ua_cfg.cb.on_call_state)(call_id, NULL); + + if (dlg) { + /* This may destroy the dialog */ + pjsip_dlg_dec_lock(dlg); + } + + if (inv != NULL) { + pjsip_inv_terminate(inv, PJSIP_SC_OK, PJ_FALSE); + } + + if (call_id != -1) { + reset_call(call_id); + pjsua_media_channel_deinit(call_id); + } + + PJSUA_UNLOCK(); + return status; +} + + +/* + * Initialize call settings based on account ID. + */ +PJ_DEF(void) pjsua_call_setting_default(pjsua_call_setting *opt) +{ + pj_assert(opt); + + pj_bzero(opt, sizeof(*opt)); + opt->flag = PJSUA_CALL_INCLUDE_DISABLED_MEDIA; + opt->aud_cnt = 1; + +#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + opt->vid_cnt = 1; + opt->req_keyframe_method = PJSUA_VID_REQ_KEYFRAME_SIP_INFO | + PJSUA_VID_REQ_KEYFRAME_RTCP_PLI; +#endif +} + +static pj_status_t apply_call_setting(pjsua_call *call, + const pjsua_call_setting *opt, + const pjmedia_sdp_session *rem_sdp) +{ + pj_assert(call); + + if (!opt) + return PJ_SUCCESS; + +#if !PJMEDIA_HAS_VIDEO + pj_assert(opt->vid_cnt == 0); +#endif + + /* If call is established, reinit media channel */ + if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) { + pjsua_call_setting old_opt; + pj_status_t status; + + old_opt = call->opt; + call->opt = *opt; + + /* Reinit media channel when media count is changed or we are the + * answerer (as remote offer may 'extremely' modify the existing + * media session, e.g: media type order). + */ + if (rem_sdp || + opt->aud_cnt!=old_opt.aud_cnt || opt->vid_cnt!=old_opt.vid_cnt) + { + pjsip_role_e role = rem_sdp? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC; + status = pjsua_media_channel_init(call->index, role, + call->secure_level, + call->inv->pool_prov, + rem_sdp, NULL, + PJ_FALSE, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error re-initializing media channel", + status); + return status; + } + } + } else { + call->opt = *opt; + } + + return PJ_SUCCESS; +} + +/* + * Make outgoing call to the specified URI using the specified account. + */ +PJ_DEF(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id, + const pj_str_t *dest_uri, + const pjsua_call_setting *opt, + void *user_data, + const pjsua_msg_data *msg_data, + pjsua_call_id *p_call_id) +{ + pj_pool_t *tmp_pool = NULL; + pjsip_dialog *dlg = NULL; + pjsua_acc *acc; + pjsua_call *call; + int call_id = -1; + pj_str_t contact; + pj_status_t status; + + + /* Check that account is valid */ + PJ_ASSERT_RETURN(acc_id>=0 || acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + + /* Check arguments */ + PJ_ASSERT_RETURN(dest_uri, PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Making call with acc #%d to %.*s", acc_id, + (int)dest_uri->slen, dest_uri->ptr)); + + pj_log_push_indent(); + + PJSUA_LOCK(); + + /* Create sound port if none is instantiated, to check if sound device + * can be used. But only do this with the conference bridge, as with + * audio switchboard (i.e. APS-Direct), we can only open the sound + * device once the correct format has been known + */ + if (!pjsua_var.is_mswitch && pjsua_var.snd_port==NULL && + pjsua_var.null_snd==NULL && !pjsua_var.no_snd) + { + status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev); + if (status != PJ_SUCCESS) + goto on_error; + } + + acc = &pjsua_var.acc[acc_id]; + if (!acc->valid) { + pjsua_perror(THIS_FILE, "Unable to make call because account " + "is not valid", PJ_EINVALIDOP); + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Find free call slot. */ + call_id = alloc_call_id(); + + if (call_id == PJSUA_INVALID_ID) { + pjsua_perror(THIS_FILE, "Error making call", PJ_ETOOMANY); + status = PJ_ETOOMANY; + goto on_error; + } + + call = &pjsua_var.calls[call_id]; + + /* Associate session with account */ + call->acc_id = acc_id; + call->call_hold_type = acc->cfg.call_hold_type; + + /* Apply call setting */ + status = apply_call_setting(call, opt, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Failed to apply call setting", status); + goto on_error; + } + + /* Create temporary pool */ + tmp_pool = pjsua_pool_create("tmpcall10", 512, 256); + + /* Verify that destination URI is valid before calling + * pjsua_acc_create_uac_contact, or otherwise there + * a misleading "Invalid Contact URI" error will be printed + * when pjsua_acc_create_uac_contact() fails. + */ + if (1) { + pjsip_uri *uri; + pj_str_t dup; + + pj_strdup_with_null(tmp_pool, &dup, dest_uri); + uri = pjsip_parse_uri(tmp_pool, dup.ptr, dup.slen, 0); + + if (uri == NULL) { + pjsua_perror(THIS_FILE, "Unable to make call", + PJSIP_EINVALIDREQURI); + status = PJSIP_EINVALIDREQURI; + goto on_error; + } + } + + /* Mark call start time. */ + pj_gettimeofday(&call->start_time); + + /* Reset first response time */ + call->res_time.sec = 0; + + /* Create suitable Contact header unless a Contact header has been + * set in the account. + */ + if (acc->contact.slen) { + contact = acc->contact; + } else { + status = pjsua_acc_create_uac_contact(tmp_pool, &contact, + acc_id, dest_uri); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + goto on_error; + } + } + + /* Create outgoing dialog: */ + status = pjsip_dlg_create_uac( pjsip_ua_instance(), + &acc->cfg.id, &contact, + dest_uri, dest_uri, &dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Dialog creation failed", status); + goto on_error; + } + + /* Increment the dialog's lock otherwise when invite session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(dlg); + + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) + pjsip_dlg_set_via_sent_by(dlg, &acc->via_addr, acc->via_tp); + + /* Calculate call's secure level */ + call->secure_level = get_secure_level(acc_id, dest_uri); + + /* Attach user data */ + call->user_data = user_data; + + /* Store variables required for the callback after the async + * media transport creation is completed. + */ + if (msg_data) { + call->async_call.call_var.out_call.msg_data = pjsua_msg_data_clone( + dlg->pool, msg_data); + } + call->async_call.dlg = dlg; + + /* Temporarily increment dialog session. Without this, dialog will be + * prematurely destroyed if dec_lock() is called on the dialog before + * the invite session is created. + */ + pjsip_dlg_inc_session(dlg, &pjsua_var.mod); + + /* Init media channel */ + status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC, + call->secure_level, dlg->pool, + NULL, NULL, PJ_TRUE, + &on_make_call_med_tp_complete); + if (status == PJ_SUCCESS) { + status = on_make_call_med_tp_complete(call->index, NULL); + if (status != PJ_SUCCESS) + goto on_error; + } else if (status != PJ_EPENDING) { + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + pjsip_dlg_dec_session(dlg, &pjsua_var.mod); + goto on_error; + } + + /* Done. */ + + if (p_call_id) + *p_call_id = call_id; + + pjsip_dlg_dec_lock(dlg); + pj_pool_release(tmp_pool); + PJSUA_UNLOCK(); + + pj_log_pop_indent(); + + return PJ_SUCCESS; + + +on_error: + if (dlg) { + /* This may destroy the dialog */ + pjsip_dlg_dec_lock(dlg); + } + + if (call_id != -1) { + reset_call(call_id); + pjsua_media_channel_deinit(call_id); + } + + if (tmp_pool) + pj_pool_release(tmp_pool); + PJSUA_UNLOCK(); + + pj_log_pop_indent(); + return status; +} + + +/* Get the NAT type information in remote's SDP */ +static void update_remote_nat_type(pjsua_call *call, + const pjmedia_sdp_session *sdp) +{ + const pjmedia_sdp_attr *xnat; + + xnat = pjmedia_sdp_attr_find2(sdp->attr_count, sdp->attr, "X-nat", NULL); + if (xnat) { + call->rem_nat_type = (pj_stun_nat_type) (xnat->value.ptr[0] - '0'); + } else { + call->rem_nat_type = PJ_STUN_NAT_TYPE_UNKNOWN; + } + + PJ_LOG(5,(THIS_FILE, "Call %d: remote NAT type is %d (%s)", call->index, + call->rem_nat_type, pj_stun_get_nat_name(call->rem_nat_type))); +} + + +static pj_status_t process_incoming_call_replace(pjsua_call *call, + pjsip_dialog *replaced_dlg) +{ + pjsip_inv_session *replaced_inv; + struct pjsua_call *replaced_call; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Get the invite session in the dialog */ + replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg); + + /* Get the replaced call instance */ + replaced_call = (pjsua_call*) replaced_dlg->mod_data[pjsua_var.mod.id]; + + /* Notify application */ + if (pjsua_var.ua_cfg.cb.on_call_replaced) + pjsua_var.ua_cfg.cb.on_call_replaced(replaced_call->index, + call->index); + + PJ_LOG(4,(THIS_FILE, "Answering replacement call %d with 200/OK", + call->index)); + + /* Answer the new call with 200 response */ + status = pjsip_inv_answer(call->inv, 200, NULL, NULL, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_inv_send_msg(call->inv, tdata); + + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error answering session", status); + + /* Note that inv may be invalid if 200/OK has caused error in + * starting the media. + */ + + PJ_LOG(4,(THIS_FILE, "Disconnecting replaced call %d", + replaced_call->index)); + + /* Disconnect replaced invite session */ + status = pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, NULL, + &tdata); + if (status == PJ_SUCCESS && tdata) + status = pjsip_inv_send_msg(replaced_inv, tdata); + + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error terminating session", status); + + return status; +} + + +static void process_pending_call_answer(pjsua_call *call) +{ + struct call_answer *answer, *next; + + answer = call->async_call.call_var.inc_call.answers.next; + while (answer != &call->async_call.call_var.inc_call.answers) { + next = answer->next; + pjsua_call_answer2(call->index, answer->opt, answer->code, + answer->reason, answer->msg_data); + + /* Call might have been disconnected if application is answering + * with 200/OK and the media failed to start. + * See pjsua_call_answer() below. + */ + if (!call->inv || !call->inv->pool_prov) + break; + + pj_list_erase(answer); + answer = next; + } +} + + +/* Incoming call callback when media transport creation is completed. */ +static pj_status_t +on_incoming_call_med_tp_complete(pjsua_call_id call_id, + const pjsua_med_tp_state_info *info) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + const pjmedia_sdp_session *offer=NULL; + pjmedia_sdp_session *answer; + pjsip_tx_data *response = NULL; + unsigned options = 0; + int sip_err_code = (info? info->sip_err_code: 0); + pj_status_t status = (info? info->status: PJ_SUCCESS); + + PJSUA_LOCK(); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + goto on_return; + } + + /* pjsua_media_channel_deinit() has been called. */ + if (call->async_call.med_ch_deinit) { + pjsua_media_channel_deinit(call->index); + call->med_ch_cb = NULL; + PJSUA_UNLOCK(); + return PJ_SUCCESS; + } + + /* Get remote SDP offer (if any). */ + if (call->inv->neg) + pjmedia_sdp_neg_get_neg_remote(call->inv->neg, &offer); + + status = pjsua_media_channel_create_sdp(call_id, + call->async_call.dlg->pool, + offer, &answer, &sip_err_code); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating SDP answer", status); + goto on_return; + } + + status = pjsip_inv_set_local_sdp(call->inv, answer); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error setting local SDP", status); + sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE; + goto on_return; + } + + /* Verify that we can handle the request. */ + status = pjsip_inv_verify_request3(NULL, + call->inv->pool_prov, &options, offer, + answer, NULL, pjsua_var.endpt, &response); + if (status != PJ_SUCCESS) { + /* + * No we can't handle the incoming INVITE request. + */ + sip_err_code = PJSIP_ERRNO_TO_SIP_STATUS(status); + goto on_return; + } + +on_return: + if (status != PJ_SUCCESS) { + /* If the callback is called from pjsua_call_on_incoming(), the + * invite's state is PJSIP_INV_STATE_NULL, so the invite session + * will be terminated later, otherwise we end the session here. + */ + if (call->inv->state > PJSIP_INV_STATE_NULL) { + pjsip_tx_data *tdata; + pj_status_t status_; + + status_ = pjsip_inv_end_session(call->inv, sip_err_code, NULL, + &tdata); + if (status_ == PJ_SUCCESS && tdata) + status_ = pjsip_inv_send_msg(call->inv, tdata); + } + + pjsua_media_channel_deinit(call->index); + } + + /* Set the callback to NULL to indicate that the async operation + * has completed. + */ + call->med_ch_cb = NULL; + + /* Finish any pending process */ + if (status == PJ_SUCCESS) { + if (call->async_call.call_var.inc_call.replaced_dlg) { + /* Process pending call replace */ + pjsip_dialog *replaced_dlg = + call->async_call.call_var.inc_call.replaced_dlg; + process_incoming_call_replace(call, replaced_dlg); + } else { + /* Process pending call answers */ + process_pending_call_answer(call); + } + } + + PJSUA_UNLOCK(); + return status; +} + + +/** + * Handle incoming INVITE request. + * Called by pjsua_core.c + */ +pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) +{ + pj_str_t contact; + pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); + pjsip_dialog *replaced_dlg = NULL; + pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata); + pjsip_msg *msg = rdata->msg_info.msg; + pjsip_tx_data *response = NULL; + unsigned options = 0; + pjsip_inv_session *inv = NULL; + int acc_id; + pjsua_call *call; + int call_id = -1; + int sip_err_code; + pjmedia_sdp_session *offer=NULL; + pj_status_t status; + + /* Don't want to handle anything but INVITE */ + if (msg->line.req.method.id != PJSIP_INVITE_METHOD) + return PJ_FALSE; + + /* Don't want to handle anything that's already associated with + * existing dialog or transaction. + */ + if (dlg || tsx) + return PJ_FALSE; + + /* Don't want to accept the call if shutdown is in progress */ + if (pjsua_var.thread_quit_flag) { + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, + PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL, + NULL, NULL); + return PJ_TRUE; + } + + PJ_LOG(4,(THIS_FILE, "Incoming %s", rdata->msg_info.info)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + /* Find free call slot. */ + call_id = alloc_call_id(); + + if (call_id == PJSUA_INVALID_ID) { + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, + PJSIP_SC_BUSY_HERE, NULL, + NULL, NULL); + PJ_LOG(2,(THIS_FILE, + "Unable to accept incoming call (too many calls)")); + goto on_return; + } + + /* Clear call descriptor */ + reset_call(call_id); + + call = &pjsua_var.calls[call_id]; + + /* Mark call start time. */ + pj_gettimeofday(&call->start_time); + + /* Check INVITE request for Replaces header. If Replaces header is + * present, the function will make sure that we can handle the request. + */ + status = pjsip_replaces_verify_request(rdata, &replaced_dlg, PJ_FALSE, + &response); + if (status != PJ_SUCCESS) { + /* + * Something wrong with the Replaces header. + */ + if (response) { + pjsip_response_addr res_addr; + + pjsip_get_response_addr(response->pool, rdata, &res_addr); + pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response, + NULL, NULL); + + } else { + + /* Respond with 500 (Internal Server Error) */ + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, + NULL, NULL); + } + + goto on_return; + } + + /* If this INVITE request contains Replaces header, notify application + * about the request so that application can do subsequent checking + * if it wants to. + */ + if (replaced_dlg != NULL && + (pjsua_var.ua_cfg.cb.on_call_replace_request || + pjsua_var.ua_cfg.cb.on_call_replace_request2)) + { + pjsua_call *replaced_call; + int st_code = 200; + pj_str_t st_text = { "OK", 2 }; + + /* Get the replaced call instance */ + replaced_call = (pjsua_call*) replaced_dlg->mod_data[pjsua_var.mod.id]; + + /* Copy call setting from the replaced call */ + call->opt = replaced_call->opt; + + /* Notify application */ + if (pjsua_var.ua_cfg.cb.on_call_replace_request) { + pjsua_var.ua_cfg.cb.on_call_replace_request(replaced_call->index, + rdata, + &st_code, &st_text); + } + + if (pjsua_var.ua_cfg.cb.on_call_replace_request2) { + pjsua_var.ua_cfg.cb.on_call_replace_request2(replaced_call->index, + rdata, + &st_code, &st_text, + &call->opt); + } + + /* Must specify final response */ + PJ_ASSERT_ON_FAIL(st_code >= 200, st_code = 200); + + /* Check if application rejects this request. */ + if (st_code >= 300) { + + if (st_text.slen == 2) + st_text = *pjsip_get_status_text(st_code); + + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, + st_code, &st_text, NULL, NULL, NULL); + goto on_return; + } + } + + /* + * Get which account is most likely to be associated with this incoming + * call. We need the account to find which contact URI to put for + * the call. + */ + acc_id = call->acc_id = pjsua_acc_find_for_incoming(rdata); + call->call_hold_type = pjsua_var.acc[acc_id].cfg.call_hold_type; + + /* Get call's secure level */ + if (PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.msg->line.req.uri)) + call->secure_level = 2; + else if (PJSIP_TRANSPORT_IS_SECURE(rdata->tp_info.transport)) + call->secure_level = 1; + else + call->secure_level = 0; + + /* Parse SDP from incoming request */ + if (rdata->msg_info.msg->body) { + pjsip_rdata_sdp_info *sdp_info; + + sdp_info = pjsip_rdata_get_sdp_info(rdata); + offer = sdp_info->sdp; + + status = sdp_info->sdp_err; + if (status==PJ_SUCCESS && sdp_info->sdp==NULL) + status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE); + + if (status != PJ_SUCCESS) { + const pj_str_t reason = pj_str("Bad SDP"); + pjsip_hdr hdr_list; + pjsip_warning_hdr *w; + + pjsua_perror(THIS_FILE, "Bad SDP in incoming INVITE", + status); + + w = pjsip_warning_hdr_create_from_status(rdata->tp_info.pool, + pjsip_endpt_name(pjsua_var.endpt), + status); + pj_list_init(&hdr_list); + pj_list_push_back(&hdr_list, w); + + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 400, + &reason, &hdr_list, NULL, NULL); + goto on_return; + } + + /* Do quick checks on SDP before passing it to transports. More elabore + * checks will be done in pjsip_inv_verify_request2() below. + */ + if (offer->media_count==0) { + const pj_str_t reason = pj_str("Missing media in SDP"); + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 400, &reason, + NULL, NULL, NULL); + goto on_return; + } + + } else { + offer = NULL; + } + + /* Verify that we can handle the request. */ + options |= PJSIP_INV_SUPPORT_100REL; + options |= PJSIP_INV_SUPPORT_TIMER; + if (pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_MANDATORY) + options |= PJSIP_INV_REQUIRE_100REL; + if (pjsua_var.media_cfg.enable_ice) + options |= PJSIP_INV_SUPPORT_ICE; + if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_REQUIRED) + options |= PJSIP_INV_REQUIRE_TIMER; + else if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_ALWAYS) + options |= PJSIP_INV_ALWAYS_USE_TIMER; + + status = pjsip_inv_verify_request2(rdata, &options, offer, NULL, NULL, + pjsua_var.endpt, &response); + if (status != PJ_SUCCESS) { + + /* + * No we can't handle the incoming INVITE request. + */ + if (response) { + pjsip_response_addr res_addr; + + pjsip_get_response_addr(response->pool, rdata, &res_addr); + pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response, + NULL, NULL); + + } else { + /* Respond with 500 (Internal Server Error) */ + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, 500, NULL, + NULL, NULL, NULL); + } + + goto on_return; + } + + /* Get suitable Contact header */ + if (pjsua_var.acc[acc_id].contact.slen) { + contact = pjsua_var.acc[acc_id].contact; + } else { + status = pjsua_acc_create_uas_contact(rdata->tp_info.pool, &contact, + acc_id, rdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, + NULL, NULL); + goto on_return; + } + } + + /* Create dialog: */ + status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, + &contact, &dlg); + if (status != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, + NULL, NULL); + goto on_return; + } + + if (pjsua_var.acc[acc_id].cfg.allow_via_rewrite && + pjsua_var.acc[acc_id].via_addr.host.slen > 0) + { + pjsip_dlg_set_via_sent_by(dlg, &pjsua_var.acc[acc_id].via_addr, + pjsua_var.acc[acc_id].via_tp); + } + + /* Set credentials */ + if (pjsua_var.acc[acc_id].cred_cnt) { + pjsip_auth_clt_set_credentials(&dlg->auth_sess, + pjsua_var.acc[acc_id].cred_cnt, + pjsua_var.acc[acc_id].cred); + } + + /* Set preference */ + pjsip_auth_clt_set_prefs(&dlg->auth_sess, + &pjsua_var.acc[acc_id].cfg.auth_pref); + + /* Disable Session Timers if not prefered and the incoming INVITE request + * did not require it. + */ + if (pjsua_var.acc[acc_id].cfg.use_timer == PJSUA_SIP_TIMER_INACTIVE && + (options & PJSIP_INV_REQUIRE_TIMER) == 0) + { + options &= ~(PJSIP_INV_SUPPORT_TIMER); + } + + /* If 100rel is optional and UAC supports it, use it. */ + if ((options & PJSIP_INV_REQUIRE_100REL)==0 && + pjsua_var.acc[acc_id].cfg.require_100rel == PJSUA_100REL_OPTIONAL) + { + const pj_str_t token = { "100rel", 6}; + pjsip_dialog_cap_status cap_status; + + cap_status = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_SUPPORTED, NULL, + &token); + if (cap_status == PJSIP_DIALOG_CAP_SUPPORTED) + options |= PJSIP_INV_REQUIRE_100REL; + } + + /* Create invite session: */ + status = pjsip_inv_create_uas( dlg, rdata, NULL, options, &inv); + if (status != PJ_SUCCESS) { + pjsip_hdr hdr_list; + pjsip_warning_hdr *w; + + w = pjsip_warning_hdr_create_from_status(dlg->pool, + pjsip_endpt_name(pjsua_var.endpt), + status); + pj_list_init(&hdr_list); + pj_list_push_back(&hdr_list, w); + + pjsip_dlg_respond(dlg, rdata, 500, NULL, &hdr_list, NULL); + + /* Can't terminate dialog because transaction is in progress. + pjsip_dlg_terminate(dlg); + */ + goto on_return; + } + + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (pjsua_var.acc[acc_id].cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(pjsua_var.acc[acc_id].cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(dlg, &tp_sel); + } + + /* Create and attach pjsua_var data to the dialog */ + call->inv = inv; + + /* Store variables required for the callback after the async + * media transport creation is completed. + */ + call->async_call.dlg = dlg; + pj_list_init(&call->async_call.call_var.inc_call.answers); + + /* Init media channel, only when there is offer or call replace request. + * For incoming call without SDP offer, media channel init will be done + * in pjsua_call_answer(), see ticket #1526. + */ + if (offer || replaced_dlg) { + status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS, + call->secure_level, + rdata->tp_info.pool, + offer, + &sip_err_code, PJ_TRUE, + &on_incoming_call_med_tp_complete); + if (status == PJ_SUCCESS) { + status = on_incoming_call_med_tp_complete(call_id, NULL); + if (status != PJ_SUCCESS) { + sip_err_code = PJSIP_SC_NOT_ACCEPTABLE; + /* Since the call invite's state is still PJSIP_INV_STATE_NULL, + * the invite session was not ended in + * on_incoming_call_med_tp_complete(), so we need to send + * a response message and terminate the invite here. + */ + pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL); + pjsip_inv_terminate(call->inv, sip_err_code, PJ_FALSE); + call->inv = NULL; + goto on_return; + } + } else if (status != PJ_EPENDING) { + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL); + pjsip_inv_terminate(call->inv, sip_err_code, PJ_FALSE); + call->inv = NULL; + goto on_return; + } + } + + /* Create answer */ +/* + status = pjsua_media_channel_create_sdp(call->index, rdata->tp_info.pool, + offer, &answer, &sip_err_code); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating SDP answer", status); + pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, + sip_err_code, NULL, NULL, NULL, NULL); + goto on_return; + } +*/ + + /* Init Session Timers */ + status = pjsip_timer_init_session(inv, + &pjsua_var.acc[acc_id].cfg.timer_setting); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Session Timer init failed", status); + pjsip_dlg_respond(dlg, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); + pjsip_inv_terminate(inv, PJSIP_SC_INTERNAL_SERVER_ERROR, PJ_FALSE); + + pjsua_media_channel_deinit(call->index); + call->inv = NULL; + + goto on_return; + } + + /* Update NAT type of remote endpoint, only when there is SDP in + * incoming INVITE! + */ + if (pjsua_var.ua_cfg.nat_type_in_sdp && inv->neg && + pjmedia_sdp_neg_get_state(inv->neg) > PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) + { + const pjmedia_sdp_session *remote_sdp; + + if (pjmedia_sdp_neg_get_neg_remote(inv->neg, &remote_sdp)==PJ_SUCCESS) + update_remote_nat_type(call, remote_sdp); + } + + /* Must answer with some response to initial INVITE. We'll do this before + * attaching the call to the invite session/dialog, so that the application + * will not get notification about this event (on another scenario, it is + * also possible that inv_send_msg() fails and causes the invite session to + * be disconnected. If we have the call attached at this time, this will + * cause the disconnection callback to be called before on_incoming_call() + * callback is called, which is not right). + */ + status = pjsip_inv_initial_answer(inv, rdata, + 100, NULL, NULL, &response); + if (status != PJ_SUCCESS) { + if (response == NULL) { + pjsua_perror(THIS_FILE, "Unable to send answer to incoming INVITE", + status); + pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL); + pjsip_inv_terminate(inv, 500, PJ_FALSE); + } else { + pjsip_inv_send_msg(inv, response); + pjsip_inv_terminate(inv, response->msg->line.status.code, + PJ_FALSE); + } + pjsua_media_channel_deinit(call->index); + call->inv = NULL; + goto on_return; + + } else { + status = pjsip_inv_send_msg(inv, response); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send 100 response", status); + pjsua_media_channel_deinit(call->index); + call->inv = NULL; + goto on_return; + } + } + + /* Only do this after sending 100/Trying (really! see the long comment + * above) + */ + dlg->mod_data[pjsua_var.mod.id] = call; + inv->mod_data[pjsua_var.mod.id] = call; + + ++pjsua_var.call_cnt; + + /* Check if this request should replace existing call */ + if (replaced_dlg) { + /* Process call replace. If the media channel init has been completed, + * just process now, otherwise, just queue the replaced dialog so + * it will be processed once the media channel async init is finished + * successfully. + */ + if (call->med_ch_cb == NULL) { + process_incoming_call_replace(call, replaced_dlg); + } else { + call->async_call.call_var.inc_call.replaced_dlg = replaced_dlg; + } + } else { + /* Notify application if on_incoming_call() is overriden, + * otherwise hangup the call with 480 + */ + if (pjsua_var.ua_cfg.cb.on_incoming_call) { + pjsua_var.ua_cfg.cb.on_incoming_call(acc_id, call_id, rdata); + } else { + pjsua_call_hangup(call_id, PJSIP_SC_TEMPORARILY_UNAVAILABLE, + NULL, NULL); + } + } + + + /* This INVITE request has been handled. */ +on_return: + pj_log_pop_indent(); + PJSUA_UNLOCK(); + return PJ_TRUE; +} + + + +/* + * Check if the specified call has active INVITE session and the INVITE + * session has not been disconnected. + */ +PJ_DEF(pj_bool_t) pjsua_call_is_active(pjsua_call_id call_id) +{ + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + return pjsua_var.calls[call_id].inv != NULL && + pjsua_var.calls[call_id].inv->state != PJSIP_INV_STATE_DISCONNECTED; +} + + +/* Acquire lock to the specified call_id */ +pj_status_t acquire_call(const char *title, + pjsua_call_id call_id, + pjsua_call **p_call, + pjsip_dialog **p_dlg) +{ + unsigned retry; + pjsua_call *call = NULL; + pj_bool_t has_pjsua_lock = PJ_FALSE; + pj_status_t status = PJ_SUCCESS; + pj_time_val time_start, timeout; + + pj_gettimeofday(&time_start); + timeout.sec = 0; + timeout.msec = PJSUA_ACQUIRE_CALL_TIMEOUT; + pj_time_val_normalize(&timeout); + + for (retry=0; ; ++retry) { + + if (retry % 10 == 9) { + pj_time_val dtime; + + pj_gettimeofday(&dtime); + PJ_TIME_VAL_SUB(dtime, time_start); + if (!PJ_TIME_VAL_LT(dtime, timeout)) + break; + } + + has_pjsua_lock = PJ_FALSE; + + status = PJSUA_TRY_LOCK(); + if (status != PJ_SUCCESS) { + pj_thread_sleep(retry/10); + continue; + } + + has_pjsua_lock = PJ_TRUE; + call = &pjsua_var.calls[call_id]; + + if (call->inv == NULL) { + PJSUA_UNLOCK(); + PJ_LOG(3,(THIS_FILE, "Invalid call_id %d in %s", call_id, title)); + return PJSIP_ESESSIONTERMINATED; + } + + status = pjsip_dlg_try_inc_lock(call->inv->dlg); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + pj_thread_sleep(retry/10); + continue; + } + + PJSUA_UNLOCK(); + + break; + } + + if (status != PJ_SUCCESS) { + if (has_pjsua_lock == PJ_FALSE) + PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire PJSUA mutex " + "(possibly system has deadlocked) in %s", + title)); + else + PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire dialog mutex " + "(possibly system has deadlocked) in %s", + title)); + return PJ_ETIMEDOUT; + } + + *p_call = call; + *p_dlg = call->inv->dlg; + + return PJ_SUCCESS; +} + + +/* + * Obtain detail information about the specified call. + */ +PJ_DEF(pj_status_t) pjsua_call_get_info( pjsua_call_id call_id, + pjsua_call_info *info) +{ + pjsua_call *call; + pjsip_dialog *dlg; + unsigned mi; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + pj_bzero(info, sizeof(*info)); + + /* Use PJSUA_LOCK() instead of acquire_call(): + * https://trac.pjsip.org/repos/ticket/1371 + */ + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + dlg = (call->inv ? call->inv->dlg : call->async_call.dlg); + if (!dlg) { + PJSUA_UNLOCK(); + return PJSIP_ESESSIONTERMINATED; + } + + /* id and role */ + info->id = call_id; + info->role = dlg->role; + info->acc_id = call->acc_id; + + /* local info */ + info->local_info.ptr = info->buf_.local_info; + pj_strncpy(&info->local_info, &dlg->local.info_str, + sizeof(info->buf_.local_info)); + + /* local contact */ + info->local_contact.ptr = info->buf_.local_contact; + info->local_contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, + dlg->local.contact->uri, + info->local_contact.ptr, + sizeof(info->buf_.local_contact)); + + /* remote info */ + info->remote_info.ptr = info->buf_.remote_info; + pj_strncpy(&info->remote_info, &dlg->remote.info_str, + sizeof(info->buf_.remote_info)); + + /* remote contact */ + if (dlg->remote.contact) { + int len; + info->remote_contact.ptr = info->buf_.remote_contact; + len = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, + dlg->remote.contact->uri, + info->remote_contact.ptr, + sizeof(info->buf_.remote_contact)); + if (len < 0) len = 0; + info->remote_contact.slen = len; + } else { + info->remote_contact.slen = 0; + } + + /* call id */ + info->call_id.ptr = info->buf_.call_id; + pj_strncpy(&info->call_id, &dlg->call_id->id, + sizeof(info->buf_.call_id)); + + /* call setting */ + pj_memcpy(&info->setting, &call->opt, sizeof(call->opt)); + + /* state, state_text */ + if (call->inv) { + info->state = call->inv->state; + } else if (call->async_call.dlg && call->last_code==0) { + info->state = PJSIP_INV_STATE_NULL; + } else { + info->state = PJSIP_INV_STATE_DISCONNECTED; + } + info->state_text = pj_str((char*)pjsip_inv_state_name(info->state)); + + /* If call is disconnected, set the last_status from the cause code */ + if (call->inv && call->inv->state >= PJSIP_INV_STATE_DISCONNECTED) { + /* last_status, last_status_text */ + info->last_status = call->inv->cause; + + info->last_status_text.ptr = info->buf_.last_status_text; + pj_strncpy(&info->last_status_text, &call->inv->cause_text, + sizeof(info->buf_.last_status_text)); + } else { + /* last_status, last_status_text */ + info->last_status = call->last_code; + + info->last_status_text.ptr = info->buf_.last_status_text; + pj_strncpy(&info->last_status_text, &call->last_text, + sizeof(info->buf_.last_status_text)); + } + + /* Audio & video count offered by remote */ + info->rem_offerer = call->rem_offerer; + if (call->rem_offerer) { + info->rem_aud_cnt = call->rem_aud_cnt; + info->rem_vid_cnt = call->rem_vid_cnt; + } + + /* Build array of active media info */ + info->media_cnt = 0; + for (mi=0; mi < call->med_cnt && + info->media_cnt < PJ_ARRAY_SIZE(info->media); ++mi) + { + pjsua_call_media *call_med = &call->media[mi]; + + info->media[info->media_cnt].index = mi; + info->media[info->media_cnt].status = call_med->state; + info->media[info->media_cnt].dir = call_med->dir; + info->media[info->media_cnt].type = call_med->type; + + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + info->media[info->media_cnt].stream.aud.conf_slot = + call_med->strm.a.conf_slot; + } else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + pjmedia_vid_dev_index cap_dev = PJMEDIA_VID_INVALID_DEV; + + info->media[info->media_cnt].stream.vid.win_in = + call_med->strm.v.rdr_win_id; + + if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) { + cap_dev = call_med->strm.v.cap_dev; + } + info->media[info->media_cnt].stream.vid.cap_dev = cap_dev; + } else { + continue; + } + ++info->media_cnt; + } + + if (call->audio_idx != -1) { + info->media_status = call->media[call->audio_idx].state; + info->media_dir = call->media[call->audio_idx].dir; + info->conf_slot = call->media[call->audio_idx].strm.a.conf_slot; + } + + /* Build array of provisional media info */ + info->prov_media_cnt = 0; + for (mi=0; mi < call->med_prov_cnt && + info->prov_media_cnt < PJ_ARRAY_SIZE(info->prov_media); ++mi) + { + pjsua_call_media *call_med = &call->media_prov[mi]; + + info->prov_media[info->prov_media_cnt].index = mi; + info->prov_media[info->prov_media_cnt].status = call_med->state; + info->prov_media[info->prov_media_cnt].dir = call_med->dir; + info->prov_media[info->prov_media_cnt].type = call_med->type; + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + info->prov_media[info->prov_media_cnt].stream.aud.conf_slot = + call_med->strm.a.conf_slot; + } else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + pjmedia_vid_dev_index cap_dev = PJMEDIA_VID_INVALID_DEV; + + info->prov_media[info->prov_media_cnt].stream.vid.win_in = + call_med->strm.v.rdr_win_id; + + if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) { + cap_dev = call_med->strm.v.cap_dev; + } + info->prov_media[info->prov_media_cnt].stream.vid.cap_dev=cap_dev; + } else { + continue; + } + ++info->prov_media_cnt; + } + + /* calculate duration */ + if (info->state >= PJSIP_INV_STATE_DISCONNECTED) { + + info->total_duration = call->dis_time; + PJ_TIME_VAL_SUB(info->total_duration, call->start_time); + + if (call->conn_time.sec) { + info->connect_duration = call->dis_time; + PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time); + } + + } else if (info->state == PJSIP_INV_STATE_CONFIRMED) { + + pj_gettimeofday(&info->total_duration); + PJ_TIME_VAL_SUB(info->total_duration, call->start_time); + + pj_gettimeofday(&info->connect_duration); + PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time); + + } else { + pj_gettimeofday(&info->total_duration); + PJ_TIME_VAL_SUB(info->total_duration, call->start_time); + } + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + +/* + * Check if call remote peer support the specified capability. + */ +PJ_DEF(pjsip_dialog_cap_status) pjsua_call_remote_has_cap( + pjsua_call_id call_id, + int htype, + const pj_str_t *hname, + const pj_str_t *token) +{ + pjsua_call *call; + pjsip_dialog *dlg; + pj_status_t status; + pjsip_dialog_cap_status cap_status; + + status = acquire_call("pjsua_call_peer_has_cap()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + return PJSIP_DIALOG_CAP_UNKNOWN; + + cap_status = pjsip_dlg_remote_has_cap(dlg, htype, hname, token); + + pjsip_dlg_dec_lock(dlg); + + return cap_status; +} + + +/* + * Attach application specific data to the call. + */ +PJ_DEF(pj_status_t) pjsua_call_set_user_data( pjsua_call_id call_id, + void *user_data) +{ + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + pjsua_var.calls[call_id].user_data = user_data; + + return PJ_SUCCESS; +} + + +/* + * Get user data attached to the call. + */ +PJ_DEF(void*) pjsua_call_get_user_data(pjsua_call_id call_id) +{ + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + NULL); + return pjsua_var.calls[call_id].user_data; +} + + +/* + * Get remote's NAT type. + */ +PJ_DEF(pj_status_t) pjsua_call_get_rem_nat_type(pjsua_call_id call_id, + pj_stun_nat_type *p_type) +{ + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + PJ_ASSERT_RETURN(p_type != NULL, PJ_EINVAL); + + *p_type = pjsua_var.calls[call_id].rem_nat_type; + return PJ_SUCCESS; +} + + +/* + * Get media transport info for the specified media index. + */ +PJ_DEF(pj_status_t) +pjsua_call_get_med_transport_info(pjsua_call_id call_id, + unsigned med_idx, + pjmedia_transport_info *t) +{ + pjsua_call *call; + pjsua_call_media *call_med; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + PJ_ASSERT_RETURN(t, PJ_EINVAL); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (med_idx >= call->med_cnt) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + call_med = &call->media[med_idx]; + + pjmedia_transport_info_init(t); + status = pjmedia_transport_get_info(call_med->tp, t); + + PJSUA_UNLOCK(); + return status; +} + + +/* Media channel init callback for pjsua_call_answer(). */ +static pj_status_t +on_answer_call_med_tp_complete(pjsua_call_id call_id, + const pjsua_med_tp_state_info *info) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + pjmedia_sdp_session *sdp; + int sip_err_code = (info? info->sip_err_code: 0); + pj_status_t status = (info? info->status: PJ_SUCCESS); + + PJSUA_LOCK(); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + goto on_return; + } + + /* pjsua_media_channel_deinit() has been called. */ + if (call->async_call.med_ch_deinit) { + pjsua_media_channel_deinit(call->index); + call->med_ch_cb = NULL; + PJSUA_UNLOCK(); + return PJ_SUCCESS; + } + + status = pjsua_media_channel_create_sdp(call_id, + call->async_call.dlg->pool, + NULL, &sdp, &sip_err_code); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating SDP answer", status); + goto on_return; + } + + status = pjsip_inv_set_local_sdp(call->inv, sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error setting local SDP", status); + sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE; + goto on_return; + } + +on_return: + if (status != PJ_SUCCESS) { + /* If the callback is called from pjsua_call_on_incoming(), the + * invite's state is PJSIP_INV_STATE_NULL, so the invite session + * will be terminated later, otherwise we end the session here. + */ + if (call->inv->state > PJSIP_INV_STATE_NULL) { + pjsip_tx_data *tdata; + pj_status_t status_; + + status_ = pjsip_inv_end_session(call->inv, sip_err_code, NULL, + &tdata); + if (status_ == PJ_SUCCESS && tdata) + status_ = pjsip_inv_send_msg(call->inv, tdata); + } + + pjsua_media_channel_deinit(call->index); + } + + /* Set the callback to NULL to indicate that the async operation + * has completed. + */ + call->med_ch_cb = NULL; + + /* Finish any pending process */ + if (status == PJ_SUCCESS) { + /* Process pending call answers */ + process_pending_call_answer(call); + } + + PJSUA_UNLOCK(); + return status; +} + + +/* + * Send response to incoming INVITE request. + */ +PJ_DEF(pj_status_t) pjsua_call_answer( pjsua_call_id call_id, + unsigned code, + const pj_str_t *reason, + const pjsua_msg_data *msg_data) +{ + return pjsua_call_answer2(call_id, NULL, code, reason, msg_data); +} + + +/* + * Send response to incoming INVITE request. + */ +PJ_DEF(pj_status_t) pjsua_call_answer2(pjsua_call_id call_id, + const pjsua_call_setting *opt, + unsigned code, + const pj_str_t *reason, + const pjsua_msg_data *msg_data) +{ + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Answering call %d: code=%d", call_id, code)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_answer()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + /* Apply call setting, only if status code is 1xx or 2xx. */ + if (opt && code < 300) { + /* Check if it has not been set previously or it is different to + * the previous one. + */ + if (!call->opt_inited) { + call->opt_inited = PJ_TRUE; + apply_call_setting(call, opt, NULL); + } else if (pj_memcmp(opt, &call->opt, sizeof(*opt)) != 0) { + /* Warn application about call setting inconsistency */ + PJ_LOG(2,(THIS_FILE, "The call setting changes is ignored.")); + } + } + + PJSUA_LOCK(); + + /* Ticket #1526: When the incoming call contains no SDP offer, the media + * channel may have not been initialized at this stage. The media channel + * will be initialized here (along with SDP local offer generation) when + * the following conditions are met: + * - no pending media channel init + * - local SDP has not been generated + * - call setting has just been set, or SDP offer needs to be sent, i.e: + * answer code 183 or 2xx is issued + */ + if (!call->med_ch_cb && + (call->opt_inited || (code==183 && code/100==2)) && + (!call->inv->neg || + pjmedia_sdp_neg_get_state(call->inv->neg) == + PJMEDIA_SDP_NEG_STATE_NULL)) + { + /* Mark call setting as initialized as it is just about to be used + * for initializing the media channel. + */ + call->opt_inited = PJ_TRUE; + + status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC, + call->secure_level, + dlg->pool, + NULL, NULL, PJ_TRUE, + &on_answer_call_med_tp_complete); + if (status == PJ_SUCCESS) { + status = on_answer_call_med_tp_complete(call->index, NULL); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + goto on_return; + } + } else if (status != PJ_EPENDING) { + PJSUA_UNLOCK(); + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + goto on_return; + } + } + + /* If media transport creation is not yet completed, we will answer + * the call in the media transport creation callback instead. + */ + if (call->med_ch_cb) { + struct call_answer *answer; + + PJ_LOG(4,(THIS_FILE, "Pending answering call %d upon completion " + "of media transport", call_id)); + + answer = PJ_POOL_ZALLOC_T(call->inv->pool_prov, struct call_answer); + answer->code = code; + if (opt) { + answer->opt = PJ_POOL_ZALLOC_T(call->inv->pool_prov, + pjsua_call_setting); + *answer->opt = *opt; + } + if (reason) { + pj_strdup(call->inv->pool_prov, answer->reason, reason); + } + if (msg_data) { + answer->msg_data = pjsua_msg_data_clone(call->inv->pool_prov, + msg_data); + } + pj_list_push_back(&call->async_call.call_var.inc_call.answers, + answer); + + PJSUA_UNLOCK(); + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; + } + + PJSUA_UNLOCK(); + + if (call->res_time.sec == 0) + pj_gettimeofday(&call->res_time); + + if (reason && reason->slen == 0) + reason = NULL; + + /* Create response message */ + status = pjsip_inv_answer(call->inv, code, reason, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating response", + status); + goto on_return; + } + + /* Call might have been disconnected if application is answering with + * 200/OK and the media failed to start. + */ + if (call->inv == NULL) + goto on_return; + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + + /* Send the message */ + status = pjsip_inv_send_msg(call->inv, tdata); + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error sending response", + status); + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; +} + + +/* + * Hangup call by using method that is appropriate according to the + * call state. + */ +PJ_DEF(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id, + unsigned code, + const pj_str_t *reason, + const pjsua_msg_data *msg_data) +{ + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pj_status_t status; + pjsip_tx_data *tdata; + + + if (call_id<0 || call_id>=(int)pjsua_var.ua_cfg.max_calls) { + PJ_LOG(1,(THIS_FILE, "pjsua_call_hangup(): invalid call id %d", + call_id)); + } + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Call %d hanging up: code=%d..", call_id, code)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_hangup()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + if (code==0) { + if (call->inv->state == PJSIP_INV_STATE_CONFIRMED) + code = PJSIP_SC_OK; + else if (call->inv->role == PJSIP_ROLE_UAS) + code = PJSIP_SC_DECLINE; + else + code = PJSIP_SC_REQUEST_TERMINATED; + } + + status = pjsip_inv_end_session(call->inv, code, reason, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Failed to create end session message", + status); + goto on_return; + } + + /* pjsip_inv_end_session may return PJ_SUCCESS with NULL + * as p_tdata when INVITE transaction has not been answered + * with any provisional responses. + */ + if (tdata == NULL) + goto on_return; + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + + /* Send the message */ + status = pjsip_inv_send_msg(call->inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Failed to send end session message", + status); + goto on_return; + } + + /* Stop lock codec timer, if it is active */ + if (call->lock_codec.reinv_timer.id) { + pjsip_endpt_cancel_timer(pjsua_var.endpt, + &call->lock_codec.reinv_timer); + call->lock_codec.reinv_timer.id = PJ_FALSE; + } + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; +} + + +/* + * Accept or reject redirection. + */ +PJ_DEF(pj_status_t) pjsua_call_process_redirect( pjsua_call_id call_id, + pjsip_redirect_op cmd) +{ + pjsua_call *call; + pjsip_dialog *dlg; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + status = acquire_call("pjsua_call_process_redirect()", call_id, + &call, &dlg); + if (status != PJ_SUCCESS) + return status; + + status = pjsip_inv_process_redirect(call->inv, cmd, NULL); + + pjsip_dlg_dec_lock(dlg); + + return status; +} + + +/* + * Put the specified call on hold. + */ +PJ_DEF(pj_status_t) pjsua_call_set_hold(pjsua_call_id call_id, + const pjsua_msg_data *msg_data) +{ + pjmedia_sdp_session *sdp; + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Putting call %d on hold", call_id)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_set_hold()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed")); + status = PJSIP_ESESSIONSTATE; + goto on_return; + } + + status = create_sdp_of_call_hold(call, &sdp); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + goto on_return; + } + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + + /* Record the tx_data to keep track the operation */ + call->hold_msg = (void*) tdata; + + /* Send the request */ + status = pjsip_inv_send_msg( call->inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + call->hold_msg = NULL; + goto on_return; + } + + /* Set flag that local put the call on hold */ + call->local_hold = PJ_TRUE; + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; +} + + +/* + * Send re-INVITE (to release hold). + */ +PJ_DEF(pj_status_t) pjsua_call_reinvite( pjsua_call_id call_id, + unsigned options, + const pjsua_msg_data *msg_data) +{ + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pj_status_t status; + + status = acquire_call("pjsua_call_reinvite()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + if (options != call->opt.flag) + call->opt.flag = options; + + status = pjsua_call_reinvite2(call_id, NULL, msg_data); + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Send re-INVITE (to release hold). + */ +PJ_DEF(pj_status_t) pjsua_call_reinvite2(pjsua_call_id call_id, + const pjsua_call_setting *opt, + const pjsua_msg_data *msg_data) +{ + pjmedia_sdp_session *sdp; + pj_str_t *new_contact = NULL; + pjsip_tx_data *tdata; + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pj_status_t status; + + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Sending re-INVITE on call %d", call_id)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_reinvite2()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed")); + status = PJSIP_ESESSIONSTATE; + goto on_return; + } + + status = apply_call_setting(call, opt, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Failed to apply call setting", status); + goto on_return; + } + + /* Create SDP */ + if (call->local_hold && (call->opt.flag & PJSUA_CALL_UNHOLD)==0) { + status = create_sdp_of_call_hold(call, &sdp); + } else { + status = pjsua_media_channel_create_sdp(call->index, + call->inv->pool_prov, + NULL, &sdp, NULL); + call->local_hold = PJ_FALSE; + } + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", + status); + goto on_return; + } + + if ((call->opt.flag & PJSUA_CALL_UPDATE_CONTACT) & + pjsua_acc_is_valid(call->acc_id)) + { + new_contact = &pjsua_var.acc[call->acc_id].contact; + } + + /* Create re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, new_contact, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + goto on_return; + } + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + + /* Send the request */ + status = pjsip_inv_send_msg( call->inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + goto on_return; + } + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; +} + + +/* + * Send UPDATE request. + */ +PJ_DEF(pj_status_t) pjsua_call_update( pjsua_call_id call_id, + unsigned options, + const pjsua_msg_data *msg_data) +{ + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pj_status_t status; + + status = acquire_call("pjsua_call_update()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + if (options != call->opt.flag) + call->opt.flag = options; + + status = pjsua_call_update2(call_id, NULL, msg_data); + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + return status; +} + + +/* + * Send UPDATE request. + */ +PJ_DEF(pj_status_t) pjsua_call_update2(pjsua_call_id call_id, + const pjsua_call_setting *opt, + const pjsua_msg_data *msg_data) +{ + pjmedia_sdp_session *sdp; + pj_str_t *new_contact = NULL; + pjsip_tx_data *tdata; + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Sending UPDATE on call %d", call_id)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_update2()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + status = apply_call_setting(call, opt, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Failed to apply call setting", status); + goto on_return; + } + + /* Create SDP */ + status = pjsua_media_channel_create_sdp(call->index, + call->inv->pool_prov, + NULL, &sdp, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", + status); + goto on_return; + } + + if ((call->opt.flag & PJSUA_CALL_UPDATE_CONTACT) & + pjsua_acc_is_valid(call->acc_id)) + { + new_contact = &pjsua_var.acc[call->acc_id].contact; + } + + /* Create UPDATE with new offer */ + status = pjsip_inv_update(call->inv, new_contact, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create UPDATE request", status); + goto on_return; + } + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + + /* Send the request */ + status = pjsip_inv_send_msg( call->inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send UPDATE request", status); + goto on_return; + } + + call->local_hold = PJ_FALSE; + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; +} + + +/* + * Initiate call transfer to the specified address. + */ +PJ_DEF(pj_status_t) pjsua_call_xfer( pjsua_call_id call_id, + const pj_str_t *dest, + const pjsua_msg_data *msg_data) +{ + pjsip_evsub *sub; + pjsip_tx_data *tdata; + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pjsip_generic_string_hdr *gs_hdr; + const pj_str_t str_ref_by = { "Referred-By", 11 }; + struct pjsip_evsub_user xfer_cb; + pj_status_t status; + + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls && + dest, PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Transfering call %d to %.*s", call_id, + (int)dest->slen, dest->ptr)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_xfer()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create xfer client subscription. */ + pj_bzero(&xfer_cb, sizeof(xfer_cb)); + xfer_cb.on_evsub_state = &xfer_client_on_evsub_state; + + status = pjsip_xfer_create_uac(call->inv->dlg, &xfer_cb, &sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create xfer", status); + goto on_return; + } + + /* Associate this call with the client subscription */ + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, call); + + /* + * Create REFER request. + */ + status = pjsip_xfer_initiate(sub, dest, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create REFER request", status); + goto on_return; + } + + /* Add Referred-By header */ + gs_hdr = pjsip_generic_string_hdr_create(tdata->pool, &str_ref_by, + &dlg->local.info_str); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)gs_hdr); + + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + + /* Send. */ + status = pjsip_xfer_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send REFER request", status); + goto on_return; + } + + /* For simplicity (that's what this program is intended to be!), + * leave the original invite session as it is. More advanced application + * may want to hold the INVITE, or terminate the invite, or whatever. + */ +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; + +} + + +/* + * Initiate attended call transfer to the specified address. + */ +PJ_DEF(pj_status_t) pjsua_call_xfer_replaces( pjsua_call_id call_id, + pjsua_call_id dest_call_id, + unsigned options, + const pjsua_msg_data *msg_data) +{ + pjsua_call *dest_call; + pjsip_dialog *dest_dlg; + char str_dest_buf[PJSIP_MAX_URL_SIZE*2]; + pj_str_t str_dest; + int len; + pjsip_uri *uri; + pj_status_t status; + + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + PJ_ASSERT_RETURN(dest_call_id>=0 && + dest_call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Transfering call %d replacing with call %d", + call_id, dest_call_id)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_xfer_replaces()", dest_call_id, + &dest_call, &dest_dlg); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + return status; + } + + /* + * Create REFER destination URI with Replaces field. + */ + + /* Make sure we have sufficient buffer's length */ + PJ_ASSERT_ON_FAIL(dest_dlg->remote.info_str.slen + + dest_dlg->call_id->id.slen + + dest_dlg->remote.info->tag.slen + + dest_dlg->local.info->tag.slen + 32 + < (long)sizeof(str_dest_buf), + { status=PJSIP_EURITOOLONG; goto on_error; }); + + /* Print URI */ + str_dest_buf[0] = '<'; + str_dest.slen = 1; + + uri = (pjsip_uri*) pjsip_uri_get_uri(dest_dlg->remote.info->uri); + len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, + str_dest_buf+1, sizeof(str_dest_buf)-1); + if (len < 0) { + status = PJSIP_EURITOOLONG; + goto on_error; + } + + str_dest.slen += len; + + + /* Build the URI */ + len = pj_ansi_snprintf(str_dest_buf + str_dest.slen, + sizeof(str_dest_buf) - str_dest.slen, + "?%s" + "Replaces=%.*s" + "%%3Bto-tag%%3D%.*s" + "%%3Bfrom-tag%%3D%.*s>", + ((options&PJSUA_XFER_NO_REQUIRE_REPLACES) ? + "" : "Require=replaces&"), + (int)dest_dlg->call_id->id.slen, + dest_dlg->call_id->id.ptr, + (int)dest_dlg->remote.info->tag.slen, + dest_dlg->remote.info->tag.ptr, + (int)dest_dlg->local.info->tag.slen, + dest_dlg->local.info->tag.ptr); + + PJ_ASSERT_ON_FAIL(len > 0 && len <= (int)sizeof(str_dest_buf)-str_dest.slen, + { status=PJSIP_EURITOOLONG; goto on_error; }); + + str_dest.ptr = str_dest_buf; + str_dest.slen += len; + + pjsip_dlg_dec_lock(dest_dlg); + + status = pjsua_call_xfer(call_id, &str_dest, msg_data); + + pj_log_pop_indent(); + return status; + +on_error: + if (dest_dlg) pjsip_dlg_dec_lock(dest_dlg); + pj_log_pop_indent(); + return status; +} + + +/** + * Send instant messaging inside INVITE session. + */ +PJ_DEF(pj_status_t) pjsua_call_send_im( pjsua_call_id call_id, + const pj_str_t *mime_type, + const pj_str_t *content, + const pjsua_msg_data *msg_data, + void *user_data) +{ + pjsua_call *call; + pjsip_dialog *dlg = NULL; + const pj_str_t mime_text_plain = pj_str("text/plain"); + pjsip_media_type ctype; + pjsua_im_data *im_data; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Call %d sending %d bytes MESSAGE..", + call_id, (int)content->slen)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_send_im()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + /* Set default media type if none is specified */ + if (mime_type == NULL) { + mime_type = &mime_text_plain; + } + + /* Create request message. */ + status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method, + -1, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status); + goto on_return; + } + + /* Add accept header. */ + pjsip_msg_add_hdr( tdata->msg, + (pjsip_hdr*)pjsua_im_create_accept(tdata->pool)); + + /* Parse MIME type */ + pjsua_parse_media_type(tdata->pool, mime_type, &ctype); + + /* Create "text/plain" message body. */ + tdata->msg->body = pjsip_msg_body_create( tdata->pool, &ctype.type, + &ctype.subtype, content); + if (tdata->msg->body == NULL) { + pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM); + pjsip_tx_data_dec_ref(tdata); + goto on_return; + } + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + + /* Create IM data and attach to the request. */ + im_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsua_im_data); + im_data->acc_id = call->acc_id; + im_data->call_id = call_id; + im_data->to = call->inv->dlg->remote.info_str; + pj_strdup_with_null(tdata->pool, &im_data->body, content); + im_data->user_data = user_data; + + + /* Send the request. */ + status = pjsip_dlg_send_request( call->inv->dlg, tdata, + pjsua_var.mod.id, im_data); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status); + goto on_return; + } + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; +} + + +/* + * Send IM typing indication inside INVITE session. + */ +PJ_DEF(pj_status_t) pjsua_call_send_typing_ind( pjsua_call_id call_id, + pj_bool_t is_typing, + const pjsua_msg_data*msg_data) +{ + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Call %d sending typing indication..", + call_id)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_send_typing_ind", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create request message. */ + status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method, + -1, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status); + goto on_return; + } + + /* Create "application/im-iscomposing+xml" msg body. */ + tdata->msg->body = pjsip_iscomposing_create_body(tdata->pool, is_typing, + NULL, NULL, -1); + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + + /* Send the request. */ + status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status); + goto on_return; + } + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; +} + + +/* + * Send arbitrary request. + */ +PJ_DEF(pj_status_t) pjsua_call_send_request(pjsua_call_id call_id, + const pj_str_t *method_str, + const pjsua_msg_data *msg_data) +{ + pjsua_call *call; + pjsip_dialog *dlg = NULL; + pjsip_method method; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Call %d sending %.*s request..", + call_id, (int)method_str->slen, method_str->ptr)); + pj_log_push_indent(); + + status = acquire_call("pjsua_call_send_request", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + goto on_return; + + /* Init method */ + pjsip_method_init_np(&method, (pj_str_t*)method_str); + + /* Create request message. */ + status = pjsip_dlg_create_request( call->inv->dlg, &method, -1, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create request", status); + goto on_return; + } + + /* Add additional headers etc */ + pjsua_process_msg_data( tdata, msg_data); + + /* Send the request. */ + status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send request", status); + goto on_return; + } + +on_return: + if (dlg) pjsip_dlg_dec_lock(dlg); + pj_log_pop_indent(); + return status; +} + + +/* + * Terminate all calls. + */ +PJ_DEF(void) pjsua_call_hangup_all(void) +{ + unsigned i; + + PJ_LOG(4,(THIS_FILE, "Hangup all calls..")); + pj_log_push_indent(); + + // This may deadlock, see https://trac.pjsip.org/repos/ticket/1305 + //PJSUA_LOCK(); + + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + if (pjsua_var.calls[i].inv) + pjsua_call_hangup(i, 0, NULL, NULL); + } + + //PJSUA_UNLOCK(); + pj_log_pop_indent(); +} + + +/* Proto */ +static pj_status_t perform_lock_codec(pjsua_call *call); + +/* Timer callback to send re-INVITE or UPDATE to lock codec */ +static void reinv_timer_cb(pj_timer_heap_t *th, + pj_timer_entry *entry) +{ + pjsua_call_id call_id = (pjsua_call_id)(pj_size_t)entry->user_data; + pjsip_dialog *dlg; + pjsua_call *call; + pj_status_t status; + + PJ_UNUSED_ARG(th); + + pjsua_var.calls[call_id].lock_codec.reinv_timer.id = PJ_FALSE; + + status = acquire_call("reinv_timer_cb()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + return; + + status = perform_lock_codec(call); + + pjsip_dlg_dec_lock(dlg); +} + + +/* Check if the specified format can be skipped in counting codecs */ +static pj_bool_t is_non_av_fmt(const pjmedia_sdp_media *m, + const pj_str_t *fmt) +{ + const pj_str_t STR_TEL = {"telephone-event", 15}; + unsigned pt; + + pt = pj_strtoul(fmt); + + /* Check for comfort noise */ + if (pt == PJMEDIA_RTP_PT_CN) + return PJ_TRUE; + + /* Dynamic PT, check the format name */ + if (pt >= 96) { + pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap rtpmap; + + /* Get the format name */ + a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); + if (a && pjmedia_sdp_attr_get_rtpmap(a, &rtpmap)==PJ_SUCCESS) { + /* Check for telephone-event */ + if (pj_stricmp(&rtpmap.enc_name, &STR_TEL)==0) + return PJ_TRUE; + } else { + /* Invalid SDP, should not reach here */ + pj_assert(!"SDP should have been validated!"); + return PJ_TRUE; + } + } + + return PJ_FALSE; +} + + +/* Send re-INVITE or UPDATE with new SDP offer to select only one codec + * out of several codecs presented by callee in his answer. + */ +static pj_status_t perform_lock_codec(pjsua_call *call) +{ + const pj_str_t STR_UPDATE = {"UPDATE", 6}; + const pjmedia_sdp_session *local_sdp = NULL, *new_sdp; + unsigned i; + pj_bool_t rem_can_update; + pj_bool_t need_lock_codec = PJ_FALSE; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(call->lock_codec.reinv_timer.id==PJ_FALSE, + PJ_EINVALIDOP); + + /* Verify if another SDP negotiation is in progress, e.g: session timer + * or another re-INVITE. + */ + if (call->inv==NULL || call->inv->neg==NULL || + pjmedia_sdp_neg_get_state(call->inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) + { + return PJMEDIA_SDPNEG_EINSTATE; + } + + /* Don't do this if call is disconnecting! */ + if (call->inv->state > PJSIP_INV_STATE_CONFIRMED || + call->inv->cause >= 200) + { + return PJ_EINVALIDOP; + } + + /* Verify if another SDP negotiation has been completed by comparing + * the SDP version. + */ + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); + if (status != PJ_SUCCESS) + return status; + if (local_sdp->origin.version > call->lock_codec.sdp_ver) + return PJMEDIA_SDP_EINVER; + + PJ_LOG(3, (THIS_FILE, "Updating media session to use only one codec..")); + + /* Update the new offer so it contains only a codec. Note that formats + * order in the offer should have been matched to the answer, so we can + * just directly update the offer without looking-up the answer. + */ + new_sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, local_sdp); + + for (i = 0; i < call->med_cnt; ++i) { + unsigned j = 0, codec_cnt = 0; + const pjmedia_sdp_media *ref_m; + pjmedia_sdp_media *m; + pjsua_call_media *call_med = &call->media[i]; + + /* Verify if media is deactivated */ + if (call_med->state == PJSUA_CALL_MEDIA_NONE || + call_med->state == PJSUA_CALL_MEDIA_ERROR || + call_med->dir == PJMEDIA_DIR_NONE) + { + continue; + } + + ref_m = local_sdp->media[i]; + m = new_sdp->media[i]; + + /* Verify that media must be active. */ + pj_assert(ref_m->desc.port); + + while (j < m->desc.fmt_count) { + pjmedia_sdp_attr *a; + pj_str_t *fmt = &m->desc.fmt[j]; + + if (is_non_av_fmt(m, fmt) || (++codec_cnt == 1)) { + ++j; + continue; + } + + /* Remove format */ + a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "rtpmap", fmt); + if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); + a = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "fmtp", fmt); + if (a) pjmedia_sdp_attr_remove(&m->attr_count, m->attr, a); + pj_array_erase(m->desc.fmt, sizeof(m->desc.fmt[0]), + m->desc.fmt_count, j); + --m->desc.fmt_count; + } + + need_lock_codec |= (ref_m->desc.fmt_count > m->desc.fmt_count); + } + + /* Last check if SDP trully needs to be updated. It is possible that OA + * negotiations have completed and SDP has changed but we didn't + * increase the SDP version (should not happen!). + */ + if (!need_lock_codec) + return PJ_SUCCESS; + + /* Send UPDATE or re-INVITE */ + rem_can_update = pjsip_dlg_remote_has_cap(call->inv->dlg, + PJSIP_H_ALLOW, + NULL, &STR_UPDATE) == + PJSIP_DIALOG_CAP_SUPPORTED; + if (rem_can_update) { + status = pjsip_inv_update(call->inv, NULL, new_sdp, &tdata); + } else { + status = pjsip_inv_reinvite(call->inv, NULL, new_sdp, &tdata); + } + + if (status==PJ_EINVALIDOP && + ++call->lock_codec.retry_cnt <= LOCK_CODEC_MAX_RETRY) + { + /* Ups, let's reschedule again */ + pj_time_val delay = {0, LOCK_CODEC_RETRY_INTERVAL}; + pj_time_val_normalize(&delay); + call->lock_codec.reinv_timer.id = PJ_TRUE; + pjsip_endpt_schedule_timer(pjsua_var.endpt, + &call->lock_codec.reinv_timer, &delay); + return status; + } else if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating UPDATE/re-INVITE to lock codec", + status); + return status; + } + + /* Send the UPDATE/re-INVITE request */ + status = pjsip_inv_send_msg(call->inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error sending UPDATE/re-INVITE in lock codec", + status); + return status; + } + + return status; +} + +/* Check if remote answerer has given us more than one codecs. If so, + * create another offer with one codec only to lock down the codec. + */ +static pj_status_t lock_codec(pjsua_call *call) +{ + pjsip_inv_session *inv = call->inv; + const pjmedia_sdp_session *local_sdp, *remote_sdp; + pj_time_val delay = {0, 0}; + const pj_str_t st_update = {"UPDATE", 6}; + unsigned i; + pj_bool_t has_mult_fmt = PJ_FALSE; + pj_status_t status; + + /* Stop lock codec timer, if it is active */ + if (call->lock_codec.reinv_timer.id) { + pjsip_endpt_cancel_timer(pjsua_var.endpt, + &call->lock_codec.reinv_timer); + call->lock_codec.reinv_timer.id = PJ_FALSE; + } + + /* Skip this if we are the answerer */ + if (!inv->neg || !pjmedia_sdp_neg_was_answer_remote(inv->neg)) { + return PJ_SUCCESS; + } + + /* Delay this when the SDP negotiation done in call state EARLY and + * remote does not support UPDATE method. + */ + if (inv->state == PJSIP_INV_STATE_EARLY && + pjsip_dlg_remote_has_cap(inv->dlg, PJSIP_H_ALLOW, NULL, &st_update)!= + PJSIP_DIALOG_CAP_SUPPORTED) + { + call->lock_codec.pending = PJ_TRUE; + return PJ_SUCCESS; + } + + status = pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp); + if (status != PJ_SUCCESS) + return status; + status = pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp); + if (status != PJ_SUCCESS) + return status; + + /* Find multiple codecs answer in all media */ + for (i = 0; i < call->med_cnt; ++i) { + pjsua_call_media *call_med = &call->media[i]; + const pjmedia_sdp_media *rem_m, *loc_m; + unsigned codec_cnt = 0; + + /* Skip this if the media is inactive or error */ + if (call_med->state == PJSUA_CALL_MEDIA_NONE || + call_med->state == PJSUA_CALL_MEDIA_ERROR || + call_med->dir == PJMEDIA_DIR_NONE) + { + continue; + } + + /* Remote may answer with less media lines. */ + if (i >= remote_sdp->media_count) + continue; + + rem_m = remote_sdp->media[i]; + loc_m = local_sdp->media[i]; + + /* Verify that media must be active. */ + pj_assert(loc_m->desc.port && rem_m->desc.port); + + /* Count the formats in the answer. */ + if (rem_m->desc.fmt_count==1) { + codec_cnt = 1; + } else { + unsigned j; + for (j=0; j<rem_m->desc.fmt_count && codec_cnt <= 1; ++j) { + if (!is_non_av_fmt(rem_m, &rem_m->desc.fmt[j])) + ++codec_cnt; + } + } + + if (codec_cnt > 1) { + has_mult_fmt = PJ_TRUE; + break; + } + } + + /* Each media in the answer already contains single codec. */ + if (!has_mult_fmt) { + call->lock_codec.retry_cnt = 0; + return PJ_SUCCESS; + } + + /* Remote keeps answering with multiple codecs, let's just give up + * locking codec to avoid infinite retry loop. + */ + if (++call->lock_codec.retry_cnt > LOCK_CODEC_MAX_RETRY) + return PJ_SUCCESS; + + PJ_LOG(4, (THIS_FILE, "Got answer with multiple codecs, scheduling " + "updating media session to use only one codec..")); + + call->lock_codec.sdp_ver = local_sdp->origin.version; + + /* Can't send UPDATE or re-INVITE now, so just schedule it immediately. + * See: https://trac.pjsip.org/repos/ticket/1149 + */ + pj_timer_entry_init(&call->lock_codec.reinv_timer, PJ_TRUE, + (void*)(pj_size_t)call->index, + &reinv_timer_cb); + pjsip_endpt_schedule_timer(pjsua_var.endpt, + &call->lock_codec.reinv_timer, &delay); + + return PJ_SUCCESS; +} + +/* + * This callback receives notification from invite session when the + * session state has changed. + */ +static void pjsua_call_on_state_changed(pjsip_inv_session *inv, + pjsip_event *e) +{ + pjsua_call *call; + + pj_log_push_indent(); + + call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id]; + + if (!call) { + pj_log_pop_indent(); + return; + } + + + /* Get call times */ + switch (inv->state) { + case PJSIP_INV_STATE_EARLY: + case PJSIP_INV_STATE_CONNECTING: + if (call->res_time.sec == 0) + pj_gettimeofday(&call->res_time); + call->last_code = (pjsip_status_code) + e->body.tsx_state.tsx->status_code; + pj_strncpy(&call->last_text, + &e->body.tsx_state.tsx->status_text, + sizeof(call->last_text_buf_)); + break; + case PJSIP_INV_STATE_CONFIRMED: + pj_gettimeofday(&call->conn_time); + + /* See if lock codec was pended as media update was done in the + * EARLY state and remote does not support UPDATE. + */ + if (call->lock_codec.pending) { + pj_status_t status; + status = lock_codec(call); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to lock codec", status); + } + call->lock_codec.pending = PJ_FALSE; + } + break; + case PJSIP_INV_STATE_DISCONNECTED: + pj_gettimeofday(&call->dis_time); + if (call->res_time.sec == 0) + pj_gettimeofday(&call->res_time); + if (e->type == PJSIP_EVENT_TSX_STATE && + e->body.tsx_state.tsx->status_code > call->last_code) + { + call->last_code = (pjsip_status_code) + e->body.tsx_state.tsx->status_code; + pj_strncpy(&call->last_text, + &e->body.tsx_state.tsx->status_text, + sizeof(call->last_text_buf_)); + } else { + call->last_code = PJSIP_SC_REQUEST_TERMINATED; + pj_strncpy(&call->last_text, + pjsip_get_status_text(call->last_code), + sizeof(call->last_text_buf_)); + } + + /* Stop lock codec timer, if it is active */ + if (call->lock_codec.reinv_timer.id) { + pjsip_endpt_cancel_timer(pjsua_var.endpt, + &call->lock_codec.reinv_timer); + call->lock_codec.reinv_timer.id = PJ_FALSE; + } + break; + default: + call->last_code = (pjsip_status_code) + e->body.tsx_state.tsx->status_code; + pj_strncpy(&call->last_text, + &e->body.tsx_state.tsx->status_text, + sizeof(call->last_text_buf_)); + break; + } + + /* If this is an outgoing INVITE that was created because of + * REFER/transfer, send NOTIFY to transferer. + */ + if (call->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) { + int st_code = -1; + pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE; + + + switch (call->inv->state) { + case PJSIP_INV_STATE_NULL: + case PJSIP_INV_STATE_CALLING: + /* Do nothing */ + break; + + case PJSIP_INV_STATE_EARLY: + case PJSIP_INV_STATE_CONNECTING: + st_code = e->body.tsx_state.tsx->status_code; + if (call->inv->state == PJSIP_INV_STATE_CONNECTING) + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + else + ev_state = PJSIP_EVSUB_STATE_ACTIVE; + break; + + case PJSIP_INV_STATE_CONFIRMED: +#if 0 +/* We don't need this, as we've terminated the subscription in + * CONNECTING state. + */ + /* When state is confirmed, send the final 200/OK and terminate + * subscription. + */ + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_TERMINATED; +#endif + break; + + case PJSIP_INV_STATE_DISCONNECTED: + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + break; + + case PJSIP_INV_STATE_INCOMING: + /* Nothing to do. Just to keep gcc from complaining about + * unused enums. + */ + break; + } + + if (st_code != -1) { + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_xfer_notify( call->xfer_sub, + ev_state, st_code, + NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status); + } else { + status = pjsip_xfer_send_request(call->xfer_sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status); + } + } + } + } + + + if (pjsua_var.ua_cfg.cb.on_call_state) + (*pjsua_var.ua_cfg.cb.on_call_state)(call->index, e); + + /* call->inv may be NULL now */ + + /* Destroy media session when invite session is disconnected. */ + if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { + + PJSUA_LOCK(); + + pjsua_media_channel_deinit(call->index); + + /* Free call */ + call->inv = NULL; + + pj_assert(pjsua_var.call_cnt > 0); + --pjsua_var.call_cnt; + + /* Reset call */ + reset_call(call->index); + + pjsua_check_snd_dev_idle(); + + PJSUA_UNLOCK(); + } + pj_log_pop_indent(); +} + +/* + * This callback is called by invite session framework when UAC session + * has forked. + */ +static void pjsua_call_on_forked( pjsip_inv_session *inv, + pjsip_event *e) +{ + PJ_UNUSED_ARG(inv); + PJ_UNUSED_ARG(e); + + PJ_TODO(HANDLE_FORKED_DIALOG); +} + + +/* + * Callback from UA layer when forked dialog response is received. + */ +pjsip_dialog* on_dlg_forked(pjsip_dialog *dlg, pjsip_rx_data *res) +{ + if (dlg->uac_has_2xx && + res->msg_info.cseq->method.id == PJSIP_INVITE_METHOD && + pjsip_rdata_get_tsx(res) == NULL && + res->msg_info.msg->line.status.code/100 == 2) + { + pjsip_dialog *forked_dlg; + pjsip_tx_data *bye; + pj_status_t status; + + /* Create forked dialog */ + status = pjsip_dlg_fork(dlg, res, &forked_dlg); + if (status != PJ_SUCCESS) + return NULL; + + pjsip_dlg_inc_lock(forked_dlg); + + /* Disconnect the call */ + status = pjsip_dlg_create_request(forked_dlg, &pjsip_bye_method, + -1, &bye); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_request(forked_dlg, bye, -1, NULL); + } + + pjsip_dlg_dec_lock(forked_dlg); + + if (status != PJ_SUCCESS) { + return NULL; + } + + return forked_dlg; + + } else { + return dlg; + } +} + +/* + * Disconnect call upon error. + */ +static void call_disconnect( pjsip_inv_session *inv, + int code ) +{ + pjsua_call *call; + pjsip_tx_data *tdata; + pj_status_t status; + + call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id]; + + status = pjsip_inv_end_session(inv, code, NULL, &tdata); + if (status != PJ_SUCCESS) + return; + + /* Add SDP in 488 status */ +#if DISABLED_FOR_TICKET_1185 + if (call && call->tp && tdata->msg->type==PJSIP_RESPONSE_MSG && + code==PJSIP_SC_NOT_ACCEPTABLE_HERE) + { + pjmedia_sdp_session *local_sdp; + pjmedia_transport_info ti; + + pjmedia_transport_info_init(&ti); + pjmedia_transport_get_info(call->med_tp, &ti); + status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, tdata->pool, + 1, &ti.sock_info, &local_sdp); + if (status == PJ_SUCCESS) { + pjsip_create_sdp_body(tdata->pool, local_sdp, + &tdata->msg->body); + } + } +#endif + + pjsip_inv_send_msg(inv, tdata); +} + +/* + * Callback to be called when SDP offer/answer negotiation has just completed + * in the session. This function will start/update media if negotiation + * has succeeded. + */ +static void pjsua_call_on_media_update(pjsip_inv_session *inv, + pj_status_t status) +{ + pjsua_call *call; + const pjmedia_sdp_session *local_sdp; + const pjmedia_sdp_session *remote_sdp; + //const pj_str_t st_update = {"UPDATE", 6}; + + pj_log_push_indent(); + + call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id]; + + if (status != PJ_SUCCESS) { + + pjsua_perror(THIS_FILE, "SDP negotiation has failed", status); + + /* Clean up provisional media */ + pjsua_media_prov_clean_up(call->index); + + /* Do not deinitialize media since this may be a re-INVITE or + * UPDATE (which in this case the media should not get affected + * by the failed re-INVITE/UPDATE). The media will be shutdown + * when call is disconnected anyway. + */ + /* Stop/destroy media, if any */ + /*pjsua_media_channel_deinit(call->index);*/ + + /* Disconnect call if we're not in the middle of initializing an + * UAS dialog and if this is not a re-INVITE + */ + if (inv->state != PJSIP_INV_STATE_NULL && + inv->state != PJSIP_INV_STATE_CONFIRMED) + { + call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + } + + goto on_return; + } + + + /* Get local and remote SDP */ + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to retrieve currently active local SDP", + status); + //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + goto on_return; + } + + status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to retrieve currently active remote SDP", + status); + //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + goto on_return; + } + + /* Update remote's NAT type */ + if (pjsua_var.ua_cfg.nat_type_in_sdp) { + update_remote_nat_type(call, remote_sdp); + } + + /* Update media channel with the new SDP */ + status = pjsua_media_channel_update(call->index, local_sdp, remote_sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create media session", + status); + call_disconnect(inv, PJSIP_SC_NOT_ACCEPTABLE_HERE); + /* No need to deinitialize; media will be shutdown when call + * state is disconnected anyway. + */ + /*pjsua_media_channel_deinit(call->index);*/ + goto on_return; + } + + /* Ticket #476: make sure only one codec is specified in the answer. */ + status = lock_codec(call); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to lock codec", status); + } + + /* Call application callback, if any */ + if (pjsua_var.ua_cfg.cb.on_call_media_state) + pjsua_var.ua_cfg.cb.on_call_media_state(call->index); + +on_return: + pj_log_pop_indent(); +} + + +/* Modify SDP for call hold. */ +static pj_status_t modify_sdp_of_call_hold(pjsua_call *call, + pj_pool_t *pool, + pjmedia_sdp_session *sdp) +{ + unsigned mi; + + /* Call-hold is done by set the media direction to 'sendonly' + * (PJMEDIA_DIR_ENCODING), except when current media direction is + * 'inactive' (PJMEDIA_DIR_NONE). + * (See RFC 3264 Section 8.4 and RFC 4317 Section 3.1) + */ + /* http://trac.pjsip.org/repos/ticket/880 + if (call->dir != PJMEDIA_DIR_ENCODING) { + */ + /* https://trac.pjsip.org/repos/ticket/1142: + * configuration to use c=0.0.0.0 for call hold. + */ + + for (mi=0; mi<sdp->media_count; ++mi) { + pjmedia_sdp_media *m = sdp->media[mi]; + + if (call->call_hold_type == PJSUA_CALL_HOLD_TYPE_RFC2543) { + pjmedia_sdp_conn *conn; + pjmedia_sdp_attr *attr; + + /* Get SDP media connection line */ + conn = m->conn; + if (!conn) + conn = sdp->conn; + + /* Modify address */ + conn->addr = pj_str("0.0.0.0"); + + /* Remove existing directions attributes */ + pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(m, "recvonly"); + pjmedia_sdp_media_remove_all_attr(m, "inactive"); + + /* Add inactive attribute */ + attr = pjmedia_sdp_attr_create(pool, "inactive", NULL); + pjmedia_sdp_media_add_attr(m, attr); + + + } else { + pjmedia_sdp_attr *attr; + + /* Remove existing directions attributes */ + pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(m, "recvonly"); + pjmedia_sdp_media_remove_all_attr(m, "inactive"); + + if (call->media[mi].dir & PJMEDIA_DIR_ENCODING) { + /* Add sendonly attribute */ + attr = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + pjmedia_sdp_media_add_attr(m, attr); + } else { + /* Add inactive attribute */ + attr = pjmedia_sdp_attr_create(pool, "inactive", NULL); + pjmedia_sdp_media_add_attr(m, attr); + } + } + } + + return PJ_SUCCESS; +} + +/* Create SDP for call hold. */ +static pj_status_t create_sdp_of_call_hold(pjsua_call *call, + pjmedia_sdp_session **p_sdp) +{ + pj_status_t status; + pj_pool_t *pool; + pjmedia_sdp_session *sdp; + + /* Use call's provisional pool */ + pool = call->inv->pool_prov; + + /* Create new offer */ + status = pjsua_media_channel_create_sdp(call->index, pool, NULL, &sdp, + NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + return status; + } + + status = modify_sdp_of_call_hold(call, pool, sdp); + if (status != PJ_SUCCESS) + return status; + + *p_sdp = sdp; + + return PJ_SUCCESS; +} + +/* + * Called when session received new offer. + */ +static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer) +{ + pjsua_call *call; + pjmedia_sdp_session *answer; + unsigned i; + pj_status_t status; + + call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id]; + + /* Supply candidate answer */ + PJ_LOG(4,(THIS_FILE, "Call %d: received updated media offer", + call->index)); + pj_log_push_indent(); + + if (pjsua_var.ua_cfg.cb.on_call_rx_offer) { + pjsip_status_code code = PJSIP_SC_OK; + pjsua_call_setting opt = call->opt; + + (*pjsua_var.ua_cfg.cb.on_call_rx_offer)(call->index, offer, NULL, + &code, &opt); + + if (code != PJSIP_SC_OK) { + PJ_LOG(4,(THIS_FILE, "Rejecting updated media offer on call %d", + call->index)); + goto on_return; + } + + call->opt = opt; + } + + /* Re-init media for the new remote offer before creating SDP */ + status = apply_call_setting(call, &call->opt, offer); + if (status != PJ_SUCCESS) + goto on_return; + + status = pjsua_media_channel_create_sdp(call->index, + call->inv->pool_prov, + offer, &answer, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + goto on_return; + } + + /* Validate media count in the generated answer */ + pj_assert(answer->media_count == offer->media_count); + + /* Check if offer's conn address is zero */ + for (i = 0; i < answer->media_count; ++i) { + pjmedia_sdp_conn *conn; + + conn = offer->media[i]->conn; + if (!conn) + conn = offer->conn; + + if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 || + pj_strcmp2(&conn->addr, "0")==0) + { + pjmedia_sdp_conn *a_conn = answer->media[i]->conn; + + /* Modify answer address */ + if (a_conn) { + a_conn->addr = pj_str("0.0.0.0"); + } else if (answer->conn == NULL || + pj_strcmp2(&answer->conn->addr, "0.0.0.0") != 0) + { + a_conn = PJ_POOL_ZALLOC_T(call->inv->pool_prov, + pjmedia_sdp_conn); + a_conn->net_type = pj_str("IN"); + a_conn->addr_type = pj_str("IP4"); + a_conn->addr = pj_str("0.0.0.0"); + answer->media[i]->conn = a_conn; + } + } + } + + /* Check if call is on-hold */ + if (call->local_hold) { + modify_sdp_of_call_hold(call, call->inv->pool_prov, answer); + } + + status = pjsip_inv_set_sdp_answer(call->inv, answer); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to set answer", status); + goto on_return; + } + +on_return: + pj_log_pop_indent(); +} + + +/* + * Called to generate new offer. + */ +static void pjsua_call_on_create_offer(pjsip_inv_session *inv, + pjmedia_sdp_session **offer) +{ + pjsua_call *call; + pj_status_t status; + + pj_log_push_indent(); + + call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id]; + + /* See if we've put call on hold. */ + if (call->local_hold) { + PJ_LOG(4,(THIS_FILE, + "Call %d: call is on-hold locally, creating call-hold SDP ", + call->index)); + status = create_sdp_of_call_hold( call, offer ); + } else { + PJ_LOG(4,(THIS_FILE, "Call %d: asked to send a new offer", + call->index)); + + status = pjsua_media_channel_create_sdp(call->index, + call->inv->pool_prov, + NULL, offer, NULL); + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + goto on_return; + } + +on_return: + pj_log_pop_indent(); +} + + +/* + * Callback called by event framework when the xfer subscription state + * has changed. + */ +static void xfer_client_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + + PJ_UNUSED_ARG(event); + + pj_log_push_indent(); + + /* + * When subscription is accepted (got 200/OK to REFER), check if + * subscription suppressed. + */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACCEPTED) { + + pjsip_rx_data *rdata; + pjsip_generic_string_hdr *refer_sub; + const pj_str_t REFER_SUB = { "Refer-Sub", 9 }; + pjsua_call *call; + + call = (pjsua_call*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + + /* Must be receipt of response message */ + pj_assert(event->type == PJSIP_EVENT_TSX_STATE && + event->body.tsx_state.type == PJSIP_EVENT_RX_MSG); + rdata = event->body.tsx_state.src.rdata; + + /* Find Refer-Sub header */ + refer_sub = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, + &REFER_SUB, NULL); + + /* Check if subscription is suppressed */ + if (refer_sub && pj_stricmp2(&refer_sub->hvalue, "false")==0) { + /* Since no subscription is desired, assume that call has been + * transfered successfully. + */ + if (call && pjsua_var.ua_cfg.cb.on_call_transfer_status) { + const pj_str_t ACCEPTED = { "Accepted", 8 }; + pj_bool_t cont = PJ_FALSE; + (*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index, + 200, + &ACCEPTED, + PJ_TRUE, + &cont); + } + + /* Yes, subscription is suppressed. + * Terminate our subscription now. + */ + PJ_LOG(4,(THIS_FILE, "Xfer subscription suppressed, terminating " + "event subcription...")); + pjsip_evsub_terminate(sub, PJ_TRUE); + + } else { + /* Notify application about call transfer progress. + * Initially notify with 100/Accepted status. + */ + if (call && pjsua_var.ua_cfg.cb.on_call_transfer_status) { + const pj_str_t ACCEPTED = { "Accepted", 8 }; + pj_bool_t cont = PJ_FALSE; + (*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index, + 100, + &ACCEPTED, + PJ_FALSE, + &cont); + } + } + } + /* + * On incoming NOTIFY, notify application about call transfer progress. + */ + else if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACTIVE || + pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) + { + pjsua_call *call; + pjsip_msg *msg; + pjsip_msg_body *body; + pjsip_status_line status_line; + pj_bool_t is_last; + pj_bool_t cont; + pj_status_t status; + + call = (pjsua_call*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + + /* When subscription is terminated, clear the xfer_sub member of + * the inv_data. + */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + PJ_LOG(4,(THIS_FILE, "Xfer client subscription terminated")); + + } + + if (!call || !event || !pjsua_var.ua_cfg.cb.on_call_transfer_status) { + /* Application is not interested with call progress status */ + goto on_return; + } + + /* This better be a NOTIFY request */ + if (event->type == PJSIP_EVENT_TSX_STATE && + event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + { + pjsip_rx_data *rdata; + + rdata = event->body.tsx_state.src.rdata; + + /* Check if there's body */ + msg = rdata->msg_info.msg; + body = msg->body; + if (!body) { + PJ_LOG(2,(THIS_FILE, + "Warning: received NOTIFY without message body")); + goto on_return; + } + + /* Check for appropriate content */ + if (pj_stricmp2(&body->content_type.type, "message") != 0 || + pj_stricmp2(&body->content_type.subtype, "sipfrag") != 0) + { + PJ_LOG(2,(THIS_FILE, + "Warning: received NOTIFY with non message/sipfrag " + "content")); + goto on_return; + } + + /* Try to parse the content */ + status = pjsip_parse_status_line((char*)body->data, body->len, + &status_line); + if (status != PJ_SUCCESS) { + PJ_LOG(2,(THIS_FILE, + "Warning: received NOTIFY with invalid " + "message/sipfrag content")); + goto on_return; + } + + } else { + status_line.code = 500; + status_line.reason = *pjsip_get_status_text(500); + } + + /* Notify application */ + is_last = (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED); + cont = !is_last; + (*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index, + status_line.code, + &status_line.reason, + is_last, &cont); + + if (!cont) { + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + } + + /* If the call transfer has completed but the subscription is + * not terminated, terminate it now. + */ + if (status_line.code/100 == 2 && !is_last) { + pjsip_tx_data *tdata; + + status = pjsip_evsub_initiate(sub, &pjsip_subscribe_method, + 0, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_evsub_send_request(sub, tdata); + } + } + +on_return: + pj_log_pop_indent(); +} + + +/* + * Callback called by event framework when the xfer subscription state + * has changed. + */ +static void xfer_server_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + PJ_UNUSED_ARG(event); + + pj_log_push_indent(); + + /* + * When subscription is terminated, clear the xfer_sub member of + * the inv_data. + */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsua_call *call; + + call = (pjsua_call*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!call) + goto on_return; + + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + call->xfer_sub = NULL; + + PJ_LOG(4,(THIS_FILE, "Xfer server subscription terminated")); + } + +on_return: + pj_log_pop_indent(); +} + + +/* + * Follow transfer (REFER) request. + */ +static void on_call_transfered( pjsip_inv_session *inv, + pjsip_rx_data *rdata ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + pjsua_call *existing_call; + int new_call; + const pj_str_t str_refer_to = { "Refer-To", 8}; + const pj_str_t str_refer_sub = { "Refer-Sub", 9 }; + const pj_str_t str_ref_by = { "Referred-By", 11 }; + pjsip_generic_string_hdr *refer_to; + pjsip_generic_string_hdr *refer_sub; + pjsip_hdr *ref_by_hdr; + pj_bool_t no_refer_sub = PJ_FALSE; + char *uri; + pjsua_msg_data msg_data; + pj_str_t tmp; + pjsip_status_code code; + pjsip_evsub *sub; + pjsua_call_setting call_opt; + + pj_log_push_indent(); + + existing_call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id]; + + /* Find the Refer-To header */ + refer_to = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL); + + if (refer_to == NULL) { + /* Invalid Request. + * No Refer-To header! + */ + PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!")); + pjsip_dlg_respond( inv->dlg, rdata, 400, NULL, NULL, NULL); + goto on_return; + } + + /* Find optional Refer-Sub header */ + refer_sub = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_sub, NULL); + + if (refer_sub) { + if (!pj_strnicmp2(&refer_sub->hvalue, "true", 4)==0) + no_refer_sub = PJ_TRUE; + } + + /* Find optional Referred-By header (to be copied onto outgoing INVITE + * request. + */ + ref_by_hdr = (pjsip_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_ref_by, + NULL); + + /* Notify callback */ + code = PJSIP_SC_ACCEPTED; + if (pjsua_var.ua_cfg.cb.on_call_transfer_request) { + (*pjsua_var.ua_cfg.cb.on_call_transfer_request)(existing_call->index, + &refer_to->hvalue, + &code); + } + + call_opt = existing_call->opt; + if (pjsua_var.ua_cfg.cb.on_call_transfer_request2) { + (*pjsua_var.ua_cfg.cb.on_call_transfer_request2)(existing_call->index, + &refer_to->hvalue, + &code, + &call_opt); + } + + if (code < 200) + code = PJSIP_SC_ACCEPTED; + if (code >= 300) { + /* Application rejects call transfer request */ + pjsip_dlg_respond( inv->dlg, rdata, code, NULL, NULL, NULL); + goto on_return; + } + + PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s", + (int)inv->dlg->remote.info_str.slen, + inv->dlg->remote.info_str.ptr, + (int)refer_to->hvalue.slen, + refer_to->hvalue.ptr)); + + if (no_refer_sub) { + /* + * Always answer with 2xx. + */ + pjsip_tx_data *tdata; + const pj_str_t str_false = { "false", 5}; + pjsip_hdr *hdr; + + status = pjsip_dlg_create_response(inv->dlg, rdata, code, NULL, + &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create 2xx response to REFER", + status); + goto on_return; + } + + /* Add Refer-Sub header */ + hdr = (pjsip_hdr*) + pjsip_generic_string_hdr_create(tdata->pool, &str_refer_sub, + &str_false); + pjsip_msg_add_hdr(tdata->msg, hdr); + + + /* Send answer */ + status = pjsip_dlg_send_response(inv->dlg, pjsip_rdata_get_tsx(rdata), + tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create 2xx response to REFER", + status); + goto on_return; + } + + /* Don't have subscription */ + sub = NULL; + + } else { + struct pjsip_evsub_user xfer_cb; + pjsip_hdr hdr_list; + + /* Init callback */ + pj_bzero(&xfer_cb, sizeof(xfer_cb)); + xfer_cb.on_evsub_state = &xfer_server_on_evsub_state; + + /* Init additional header list to be sent with REFER response */ + pj_list_init(&hdr_list); + + /* Create transferee event subscription */ + status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create xfer uas", status); + pjsip_dlg_respond( inv->dlg, rdata, 500, NULL, NULL, NULL); + goto on_return; + } + + /* If there's Refer-Sub header and the value is "true", send back + * Refer-Sub in the response with value "true" too. + */ + if (refer_sub) { + const pj_str_t str_true = { "true", 4 }; + pjsip_hdr *hdr; + + hdr = (pjsip_hdr*) + pjsip_generic_string_hdr_create(inv->dlg->pool, + &str_refer_sub, + &str_true); + pj_list_push_back(&hdr_list, hdr); + + } + + /* Accept the REFER request, send 2xx. */ + pjsip_xfer_accept(sub, rdata, code, &hdr_list); + + /* Create initial NOTIFY request */ + status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, + 100, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", + status); + goto on_return; + } + + /* Send initial NOTIFY request */ + status = pjsip_xfer_send_request( sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status); + goto on_return; + } + } + + /* We're cheating here. + * We need to get a null terminated string from a pj_str_t. + * So grab the pointer from the hvalue and NULL terminate it, knowing + * that the NULL position will be occupied by a newline. + */ + uri = refer_to->hvalue.ptr; + uri[refer_to->hvalue.slen] = '\0'; + + /* Init msg_data */ + pjsua_msg_data_init(&msg_data); + + /* If Referred-By header is present in the REFER request, copy this + * to the outgoing INVITE request. + */ + if (ref_by_hdr != NULL) { + pjsip_hdr *dup = (pjsip_hdr*) + pjsip_hdr_clone(rdata->tp_info.pool, ref_by_hdr); + pj_list_push_back(&msg_data.hdr_list, dup); + } + + /* Now make the outgoing call. */ + tmp = pj_str(uri); + status = pjsua_call_make_call(existing_call->acc_id, &tmp, &call_opt, + existing_call->user_data, &msg_data, + &new_call); + if (status != PJ_SUCCESS) { + + /* Notify xferer about the error (if we have subscription) */ + if (sub) { + status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + 500, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", + status); + goto on_return; + } + status = pjsip_xfer_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", + status); + goto on_return; + } + } + goto on_return; + } + + if (sub) { + /* Put the server subscription in inv_data. + * Subsequent state changed in pjsua_inv_on_state_changed() will be + * reported back to the server subscription. + */ + pjsua_var.calls[new_call].xfer_sub = sub; + + /* Put the invite_data in the subscription. */ + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, + &pjsua_var.calls[new_call]); + } + +on_return: + pj_log_pop_indent(); +} + + + +/* + * This callback is called when transaction state has changed in INVITE + * session. We use this to trap: + * - incoming REFER request. + * - incoming MESSAGE request. + */ +static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_event *e) +{ + pjsua_call *call; + + pj_log_push_indent(); + + call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id]; + + if (call == NULL) + goto on_return; + + if (call->inv == NULL) { + /* Shouldn't happen. It happens only when we don't terminate the + * server subscription caused by REFER after the call has been + * transfered (and this call has been disconnected), and we + * receive another REFER for this call. + */ + goto on_return; + } + + /* https://trac.pjsip.org/repos/ticket/1452: + * If a request is retried due to 401/407 challenge, don't process the + * transaction first but wait until we've retried it. + */ + if (tsx->role == PJSIP_ROLE_UAC && + (tsx->status_code==401 || tsx->status_code==407) && + tsx->last_tx && tsx->last_tx->auth_retry) + { + goto on_return; + } + + /* Notify application callback first */ + if (pjsua_var.ua_cfg.cb.on_call_tsx_state) { + (*pjsua_var.ua_cfg.cb.on_call_tsx_state)(call->index, tsx, e); + } + + if (tsx->role==PJSIP_ROLE_UAS && + tsx->state==PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, pjsip_get_refer_method())==0) + { + /* + * Incoming REFER request. + */ + on_call_transfered(call->inv, e->body.tsx_state.src.rdata); + + } + else if (tsx->role==PJSIP_ROLE_UAS && + tsx->state==PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0) + { + /* + * Incoming MESSAGE request! + */ + pjsip_rx_data *rdata; + pjsip_msg *msg; + pjsip_accept_hdr *accept_hdr; + pj_status_t status; + + rdata = e->body.tsx_state.src.rdata; + msg = rdata->msg_info.msg; + + /* Request MUST have message body, with Content-Type equal to + * "text/plain". + */ + if (pjsua_im_accept_pager(rdata, &accept_hdr) == PJ_FALSE) { + + pjsip_hdr hdr_list; + + pj_list_init(&hdr_list); + pj_list_push_back(&hdr_list, accept_hdr); + + pjsip_dlg_respond( inv->dlg, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, + NULL, &hdr_list, NULL ); + goto on_return; + } + + /* Respond with 200 first, so that remote doesn't retransmit in case + * the UI takes too long to process the message. + */ + status = pjsip_dlg_respond( inv->dlg, rdata, 200, NULL, NULL, NULL); + + /* Process MESSAGE request */ + pjsua_im_process_pager(call->index, &inv->dlg->remote.info_str, + &inv->dlg->local.info_str, rdata); + + } + else if (tsx->role == PJSIP_ROLE_UAC && + pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0) + { + /* Handle outgoing pager status */ + if (tsx->status_code >= 200) { + pjsua_im_data *im_data; + + im_data = (pjsua_im_data*) tsx->mod_data[pjsua_var.mod.id]; + /* im_data can be NULL if this is typing indication */ + + if (im_data && pjsua_var.ua_cfg.cb.on_pager_status) { + pjsua_var.ua_cfg.cb.on_pager_status(im_data->call_id, + &im_data->to, + &im_data->body, + im_data->user_data, + (pjsip_status_code) + tsx->status_code, + &tsx->status_text); + } + } + } else if (tsx->role == PJSIP_ROLE_UAC && + tsx->last_tx == (pjsip_tx_data*)call->hold_msg && + tsx->state >= PJSIP_TSX_STATE_COMPLETED) + { + /* Monitor the status of call hold request */ + call->hold_msg = NULL; + if (tsx->status_code/100 != 2) { + /* Outgoing call hold failed */ + call->local_hold = PJ_FALSE; + PJ_LOG(3,(THIS_FILE, "Error putting call %d on hold (reason=%d)", + call->index, tsx->status_code)); + } + } else if (tsx->role==PJSIP_ROLE_UAS && + tsx->state==PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_info_method)==0) + { + /* + * Incoming INFO request for media control. + */ + const pj_str_t STR_APPLICATION = { "application", 11}; + const pj_str_t STR_MEDIA_CONTROL_XML = { "media_control+xml", 17 }; + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_msg_body *body = rdata->msg_info.msg->body; + + if (body && body->len && + pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 && + pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML)==0) + { + pjsip_tx_data *tdata; + pj_str_t control_st; + pj_status_t status; + + /* Apply and answer the INFO request */ + pj_strset(&control_st, (char*)body->data, body->len); + status = pjsua_media_apply_xml_control(call->index, &control_st); + if (status == PJ_SUCCESS) { + status = pjsip_endpt_create_response(tsx->endpt, rdata, + 200, NULL, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_tsx_send_msg(tsx, tdata); + } else { + status = pjsip_endpt_create_response(tsx->endpt, rdata, + 400, NULL, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_tsx_send_msg(tsx, tdata); + } + } + } + +on_return: + pj_log_pop_indent(); +} + + +/* Redirection handler */ +static pjsip_redirect_op pjsua_call_on_redirected(pjsip_inv_session *inv, + const pjsip_uri *target, + const pjsip_event *e) +{ + pjsua_call *call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id]; + pjsip_redirect_op op; + + pj_log_push_indent(); + + if (pjsua_var.ua_cfg.cb.on_call_redirected) { + op = (*pjsua_var.ua_cfg.cb.on_call_redirected)(call->index, + target, e); + } else { + PJ_LOG(4,(THIS_FILE, "Unhandled redirection for call %d " + "(callback not implemented by application). Disconnecting " + "call.", + call->index)); + op = PJSIP_REDIRECT_STOP; + } + + pj_log_pop_indent(); + + return op; +} + diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c new file mode 100644 index 0000000..f4f9508 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -0,0 +1,2909 @@ +/* $Id: pjsua_core.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + + +#define THIS_FILE "pjsua_core.c" + + +/* Internal prototypes */ +static void resolve_stun_entry(pjsua_stun_resolve *sess); + + +/* PJSUA application instance. */ +struct pjsua_data pjsua_var; + + +PJ_DEF(struct pjsua_data*) pjsua_get_var(void) +{ + return &pjsua_var; +} + + +/* Display error */ +PJ_DEF(void) pjsua_perror( const char *sender, const char *title, + pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(1,(sender, "%s: %s [status=%d]", title, errmsg, status)); +} + + +static void init_data() +{ + unsigned i; + + pj_bzero(&pjsua_var, sizeof(pjsua_var)); + + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) + pjsua_var.acc[i].index = i; + + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.tpdata); ++i) + pjsua_var.tpdata[i].index = i; + + pjsua_var.stun_status = PJ_EUNKNOWN; + pjsua_var.nat_status = PJ_EPENDING; + pj_list_init(&pjsua_var.stun_res); + pj_list_init(&pjsua_var.outbound_proxy); + + pjsua_config_default(&pjsua_var.ua_cfg); + + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + pjsua_vid_win_reset(i); + } +} + + +PJ_DEF(void) pjsua_logging_config_default(pjsua_logging_config *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->msg_logging = PJ_TRUE; + cfg->level = 5; + cfg->console_level = 4; + cfg->decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | + PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE | + PJ_LOG_HAS_SPACE | PJ_LOG_HAS_THREAD_SWC | + PJ_LOG_HAS_INDENT; +#if defined(PJ_WIN32) && PJ_WIN32 != 0 + cfg->decor |= PJ_LOG_HAS_COLOR; +#endif +} + +PJ_DEF(void) pjsua_logging_config_dup(pj_pool_t *pool, + pjsua_logging_config *dst, + const pjsua_logging_config *src) +{ + pj_memcpy(dst, src, sizeof(*src)); + pj_strdup_with_null(pool, &dst->log_filename, &src->log_filename); +} + +PJ_DEF(void) pjsua_config_default(pjsua_config *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->max_calls = ((PJSUA_MAX_CALLS) < 4) ? (PJSUA_MAX_CALLS) : 4; + cfg->thread_cnt = 1; + cfg->nat_type_in_sdp = 1; + cfg->stun_ignore_failure = PJ_TRUE; + cfg->force_lr = PJ_TRUE; + cfg->enable_unsolicited_mwi = PJ_TRUE; + cfg->use_srtp = PJSUA_DEFAULT_USE_SRTP; + cfg->srtp_secure_signaling = PJSUA_DEFAULT_SRTP_SECURE_SIGNALING; + cfg->hangup_forked_call = PJ_TRUE; + + cfg->use_timer = PJSUA_SIP_TIMER_OPTIONAL; + pjsip_timer_setting_default(&cfg->timer_setting); +} + +PJ_DEF(void) pjsua_config_dup(pj_pool_t *pool, + pjsua_config *dst, + const pjsua_config *src) +{ + unsigned i; + + pj_memcpy(dst, src, sizeof(*src)); + + for (i=0; i<src->outbound_proxy_cnt; ++i) { + pj_strdup_with_null(pool, &dst->outbound_proxy[i], + &src->outbound_proxy[i]); + } + + for (i=0; i<src->cred_count; ++i) { + pjsip_cred_dup(pool, &dst->cred_info[i], &src->cred_info[i]); + } + + pj_strdup_with_null(pool, &dst->user_agent, &src->user_agent); + pj_strdup_with_null(pool, &dst->stun_domain, &src->stun_domain); + pj_strdup_with_null(pool, &dst->stun_host, &src->stun_host); + + for (i=0; i<src->stun_srv_cnt; ++i) { + pj_strdup_with_null(pool, &dst->stun_srv[i], &src->stun_srv[i]); + } +} + +PJ_DEF(void) pjsua_msg_data_init(pjsua_msg_data *msg_data) +{ + pj_bzero(msg_data, sizeof(*msg_data)); + pj_list_init(&msg_data->hdr_list); + pjsip_media_type_init(&msg_data->multipart_ctype, NULL, NULL); + pj_list_init(&msg_data->multipart_parts); +} + +PJ_DEF(pjsua_msg_data*) pjsua_msg_data_clone(pj_pool_t *pool, + const pjsua_msg_data *rhs) +{ + pjsua_msg_data *msg_data; + const pjsip_hdr *hdr; + const pjsip_multipart_part *mpart; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + msg_data = PJ_POOL_ZALLOC_T(pool, pjsua_msg_data); + PJ_ASSERT_RETURN(msg_data != NULL, NULL); + + pj_list_init(&msg_data->hdr_list); + hdr = rhs->hdr_list.next; + while (hdr != &rhs->hdr_list) { + pj_list_push_back(&msg_data->hdr_list, pjsip_hdr_clone(pool, hdr)); + hdr = hdr->next; + } + + pj_strdup(pool, &msg_data->content_type, &rhs->content_type); + pj_strdup(pool, &msg_data->msg_body, &rhs->msg_body); + + pjsip_media_type_cp(pool, &msg_data->multipart_ctype, + &rhs->multipart_ctype); + + pj_list_init(&msg_data->multipart_parts); + mpart = rhs->multipart_parts.next; + while (mpart != &rhs->multipart_parts) { + pj_list_push_back(&msg_data->multipart_parts, + pjsip_multipart_clone_part(pool, mpart)); + mpart = mpart->next; + } + + return msg_data; +} + +PJ_DEF(void) pjsua_transport_config_default(pjsua_transport_config *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + pjsip_tls_setting_default(&cfg->tls_setting); +} + +PJ_DEF(void) pjsua_transport_config_dup(pj_pool_t *pool, + pjsua_transport_config *dst, + const pjsua_transport_config *src) +{ + PJ_UNUSED_ARG(pool); + pj_memcpy(dst, src, sizeof(*src)); +} + +PJ_DEF(void) pjsua_acc_config_default(pjsua_acc_config *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->reg_timeout = PJSUA_REG_INTERVAL; + cfg->reg_delay_before_refresh = PJSIP_REGISTER_CLIENT_DELAY_BEFORE_REFRESH; + cfg->unreg_timeout = PJSUA_UNREG_TIMEOUT; + pjsip_publishc_opt_default(&cfg->publish_opt); + cfg->unpublish_max_wait_time_msec = PJSUA_UNPUBLISH_MAX_WAIT_TIME_MSEC; + cfg->transport_id = PJSUA_INVALID_ID; + cfg->allow_contact_rewrite = PJ_TRUE; + cfg->allow_via_rewrite = PJ_TRUE; + cfg->require_100rel = pjsua_var.ua_cfg.require_100rel; + cfg->use_timer = pjsua_var.ua_cfg.use_timer; + cfg->timer_setting = pjsua_var.ua_cfg.timer_setting; + cfg->ka_interval = 15; + cfg->ka_data = pj_str("\r\n"); + cfg->vid_cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; + cfg->vid_rend_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV; +#if PJMEDIA_HAS_VIDEO + pjmedia_vid_stream_rc_config_default(&cfg->vid_stream_rc_cfg); +#endif + pjsua_transport_config_default(&cfg->rtp_cfg); + cfg->use_srtp = pjsua_var.ua_cfg.use_srtp; + cfg->srtp_secure_signaling = pjsua_var.ua_cfg.srtp_secure_signaling; + cfg->srtp_optional_dup_offer = pjsua_var.ua_cfg.srtp_optional_dup_offer; + cfg->reg_retry_interval = PJSUA_REG_RETRY_INTERVAL; + cfg->contact_rewrite_method = PJSUA_CONTACT_REWRITE_METHOD; + cfg->use_rfc5626 = PJ_TRUE; + cfg->reg_use_proxy = PJSUA_REG_USE_OUTBOUND_PROXY | + PJSUA_REG_USE_ACC_PROXY; +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 + cfg->use_stream_ka = (PJMEDIA_STREAM_ENABLE_KA != 0); +#endif + pj_list_init(&cfg->reg_hdr_list); + pj_list_init(&cfg->sub_hdr_list); + cfg->call_hold_type = PJSUA_CALL_HOLD_TYPE_DEFAULT; + cfg->register_on_acc_add = PJ_TRUE; + cfg->mwi_expires = PJSIP_MWI_DEFAULT_EXPIRES; +} + +PJ_DEF(void) pjsua_buddy_config_default(pjsua_buddy_config *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); +} + +PJ_DEF(void) pjsua_media_config_default(pjsua_media_config *cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + + cfg->clock_rate = PJSUA_DEFAULT_CLOCK_RATE; + cfg->snd_clock_rate = 0; + cfg->channel_count = 1; + cfg->audio_frame_ptime = PJSUA_DEFAULT_AUDIO_FRAME_PTIME; + cfg->max_media_ports = PJSUA_MAX_CONF_PORTS; + cfg->has_ioqueue = PJ_TRUE; + cfg->thread_cnt = 1; + cfg->quality = PJSUA_DEFAULT_CODEC_QUALITY; + cfg->ilbc_mode = PJSUA_DEFAULT_ILBC_MODE; + cfg->ec_tail_len = PJSUA_DEFAULT_EC_TAIL_LEN; + cfg->snd_rec_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY; + cfg->snd_play_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; + cfg->jb_init = cfg->jb_min_pre = cfg->jb_max_pre = cfg->jb_max = -1; + cfg->snd_auto_close_time = 1; + + cfg->ice_max_host_cands = -1; + pj_ice_sess_options_default(&cfg->ice_opt); + + cfg->turn_conn_type = PJ_TURN_TP_UDP; + cfg->vid_preview_enable_native = PJ_TRUE; +} + +/***************************************************************************** + * This is a very simple PJSIP module, whose sole purpose is to display + * incoming and outgoing messages to log. This module will have priority + * higher than transport layer, which means: + * + * - incoming messages will come to this module first before reaching + * transaction layer. + * + * - outgoing messages will come to this module last, after the message + * has been 'printed' to contiguous buffer by transport layer and + * appropriate transport instance has been decided for this message. + * + */ + +/* Notification on incoming messages */ +static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata) +{ + PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n" + "%.*s\n" + "--end msg--", + rdata->msg_info.len, + pjsip_rx_data_get_info(rdata), + rdata->tp_info.transport->type_name, + rdata->pkt_info.src_name, + rdata->pkt_info.src_port, + (int)rdata->msg_info.len, + rdata->msg_info.msg_buf)); + + /* Always return false, otherwise messages will not get processed! */ + return PJ_FALSE; +} + +/* Notification on outgoing messages */ +static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata) +{ + + /* Important note: + * tp_info field is only valid after outgoing messages has passed + * transport layer. So don't try to access tp_info when the module + * has lower priority than transport layer. + */ + + PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n" + "%.*s\n" + "--end msg--", + (tdata->buf.cur - tdata->buf.start), + pjsip_tx_data_get_info(tdata), + tdata->tp_info.transport->type_name, + tdata->tp_info.dst_name, + tdata->tp_info.dst_port, + (int)(tdata->buf.cur - tdata->buf.start), + tdata->buf.start)); + + /* Always return success, otherwise message will not get sent! */ + return PJ_SUCCESS; +} + +/* The module instance. */ +static pjsip_module pjsua_msg_logger = +{ + NULL, NULL, /* prev, next. */ + { "mod-pjsua-log", 13 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &logging_on_rx_msg, /* on_rx_request() */ + &logging_on_rx_msg, /* on_rx_response() */ + &logging_on_tx_msg, /* on_tx_request. */ + &logging_on_tx_msg, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + +/***************************************************************************** + * Another simple module to handle incoming OPTIONS request + */ + +/* Notification on incoming request */ +static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata) +{ + pjsip_tx_data *tdata; + pjsip_response_addr res_addr; + const pjsip_hdr *cap_hdr; + pj_status_t status; + + /* Only want to handle OPTIONS requests */ + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, + pjsip_get_options_method()) != 0) + { + return PJ_FALSE; + } + + /* Don't want to handle if shutdown is in progress */ + if (pjsua_var.thread_quit_flag) { + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, + PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL, + NULL, NULL); + return PJ_TRUE; + } + + /* Create basic response. */ + status = pjsip_endpt_create_response(pjsua_var.endpt, rdata, 200, NULL, + &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create OPTIONS response", status); + return PJ_TRUE; + } + + /* Add Allow header */ + cap_hdr = pjsip_endpt_get_capability(pjsua_var.endpt, PJSIP_H_ALLOW, NULL); + if (cap_hdr) { + pjsip_msg_add_hdr(tdata->msg, + (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr)); + } + + /* Add Accept header */ + cap_hdr = pjsip_endpt_get_capability(pjsua_var.endpt, PJSIP_H_ACCEPT, NULL); + if (cap_hdr) { + pjsip_msg_add_hdr(tdata->msg, + (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr)); + } + + /* Add Supported header */ + cap_hdr = pjsip_endpt_get_capability(pjsua_var.endpt, PJSIP_H_SUPPORTED, NULL); + if (cap_hdr) { + pjsip_msg_add_hdr(tdata->msg, + (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr)); + } + + /* Add Allow-Events header from the evsub module */ + cap_hdr = pjsip_evsub_get_allow_events_hdr(NULL); + if (cap_hdr) { + pjsip_msg_add_hdr(tdata->msg, + (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr)); + } + + /* Add User-Agent header */ + if (pjsua_var.ua_cfg.user_agent.slen) { + const pj_str_t USER_AGENT = { "User-Agent", 10}; + pjsip_hdr *h; + + h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, + &USER_AGENT, + &pjsua_var.ua_cfg.user_agent); + pjsip_msg_add_hdr(tdata->msg, h); + } + + /* Get media socket info, make sure transport is ready */ +#if DISABLED_FOR_TICKET_1185 + if (pjsua_var.calls[0].med_tp) { + pjmedia_transport_info tpinfo; + pjmedia_sdp_session *sdp; + + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(pjsua_var.calls[0].med_tp, &tpinfo); + + /* Add SDP body, using call0's RTP address */ + status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, tdata->pool, 1, + &tpinfo.sock_info, &sdp); + if (status == PJ_SUCCESS) { + pjsip_create_sdp_body(tdata->pool, sdp, &tdata->msg->body); + } + } +#endif + + /* Send response */ + pjsip_get_response_addr(tdata->pool, rdata, &res_addr); + status = pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, tdata, NULL, NULL); + if (status != PJ_SUCCESS) + pjsip_tx_data_dec_ref(tdata); + + return PJ_TRUE; +} + + +/* The module instance. */ +static pjsip_module pjsua_options_handler = +{ + NULL, NULL, /* prev, next. */ + { "mod-pjsua-options", 17 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &options_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + +/***************************************************************************** + * These two functions are the main callbacks registered to PJSIP stack + * to receive SIP request and response messages that are outside any + * dialogs and any transactions. + */ + +/* + * Handler for receiving incoming requests. + * + * This handler serves multiple purposes: + * - it receives requests outside dialogs. + * - it receives requests inside dialogs, when the requests are + * unhandled by other dialog usages. Example of these + * requests are: MESSAGE. + */ +static pj_bool_t mod_pjsua_on_rx_request(pjsip_rx_data *rdata) +{ + pj_bool_t processed = PJ_FALSE; + + PJSUA_LOCK(); + + if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) { + + processed = pjsua_call_on_incoming(rdata); + } + + PJSUA_UNLOCK(); + + return processed; +} + + +/* + * Handler for receiving incoming responses. + * + * This handler serves multiple purposes: + * - it receives strayed responses (i.e. outside any dialog and + * outside any transactions). + * - it receives responses coming to a transaction, when pjsua + * module is set as transaction user for the transaction. + * - it receives responses inside a dialog, when these responses + * are unhandled by other dialog usages. + */ +static pj_bool_t mod_pjsua_on_rx_response(pjsip_rx_data *rdata) +{ + PJ_UNUSED_ARG(rdata); + return PJ_FALSE; +} + + +/***************************************************************************** + * Logging. + */ + +/* Log callback */ +static void log_writer(int level, const char *buffer, int len) +{ + /* Write to file, stdout or application callback. */ + + if (pjsua_var.log_file) { + pj_ssize_t size = len; + pj_file_write(pjsua_var.log_file, buffer, &size); + /* This will slow things down considerably! Don't do it! + pj_file_flush(pjsua_var.log_file); + */ + } + + if (level <= (int)pjsua_var.log_cfg.console_level) { + if (pjsua_var.log_cfg.cb) + (*pjsua_var.log_cfg.cb)(level, buffer, len); + else + pj_log_write(level, buffer, len); + } +} + + +/* + * Application can call this function at any time (after pjsua_create(), of + * course) to change logging settings. + */ +PJ_DEF(pj_status_t) pjsua_reconfigure_logging(const pjsua_logging_config *cfg) +{ + pj_status_t status; + + /* Save config. */ + pjsua_logging_config_dup(pjsua_var.pool, &pjsua_var.log_cfg, cfg); + + /* Redirect log function to ours */ + pj_log_set_log_func( &log_writer ); + + /* Set decor */ + pj_log_set_decor(pjsua_var.log_cfg.decor); + + /* Set log level */ + pj_log_set_level(pjsua_var.log_cfg.level); + + /* Close existing file, if any */ + if (pjsua_var.log_file) { + pj_file_close(pjsua_var.log_file); + pjsua_var.log_file = NULL; + } + + /* If output log file is desired, create the file: */ + if (pjsua_var.log_cfg.log_filename.slen) { + unsigned flags = PJ_O_WRONLY; + flags |= pjsua_var.log_cfg.log_file_flags; + status = pj_file_open(pjsua_var.pool, + pjsua_var.log_cfg.log_filename.ptr, + flags, + &pjsua_var.log_file); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating log file", status); + return status; + } + } + + /* Unregister msg logging if it's previously registered */ + if (pjsua_msg_logger.id >= 0) { + pjsip_endpt_unregister_module(pjsua_var.endpt, &pjsua_msg_logger); + pjsua_msg_logger.id = -1; + } + + /* Enable SIP message logging */ + if (pjsua_var.log_cfg.msg_logging) + pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_msg_logger); + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * PJSUA Base API. + */ + +/* Worker thread function. */ +static int worker_thread(void *arg) +{ + enum { TIMEOUT = 10 }; + + PJ_UNUSED_ARG(arg); + + while (!pjsua_var.thread_quit_flag) { + int count; + + count = pjsua_handle_events(TIMEOUT); + if (count < 0) + pj_thread_sleep(TIMEOUT); + } + + return 0; +} + + +/* Init random seed */ +static void init_random_seed(void) +{ + pj_sockaddr addr; + const pj_str_t *hostname; + pj_uint32_t pid; + pj_time_val t; + unsigned seed=0; + + /* Add hostname */ + hostname = pj_gethostname(); + seed = pj_hash_calc(seed, hostname->ptr, (int)hostname->slen); + + /* Add primary IP address */ + if (pj_gethostip(pj_AF_INET(), &addr)==PJ_SUCCESS) + seed = pj_hash_calc(seed, &addr.ipv4.sin_addr, 4); + + /* Get timeofday */ + pj_gettimeofday(&t); + seed = pj_hash_calc(seed, &t, sizeof(t)); + + /* Add PID */ + pid = pj_getpid(); + seed = pj_hash_calc(seed, &pid, sizeof(pid)); + + /* Init random seed */ + pj_srand(seed); +} + +/* + * Instantiate pjsua application. + */ +PJ_DEF(pj_status_t) pjsua_create(void) +{ + pj_status_t status; + + /* Init pjsua data */ + init_data(); + + /* Set default logging settings */ + pjsua_logging_config_default(&pjsua_var.log_cfg); + + /* Init PJLIB: */ + status = pj_init(); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + pj_log_push_indent(); + + /* Init random seed */ + init_random_seed(); + + /* Init PJLIB-UTIL: */ + status = pjlib_util_init(); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Init PJNATH */ + status = pjnath_init(); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Set default sound device ID */ + pjsua_var.cap_dev = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV; + pjsua_var.play_dev = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; + + /* Set default video device ID */ + pjsua_var.vcap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; + pjsua_var.vrdr_dev = PJMEDIA_VID_DEFAULT_RENDER_DEV; + + /* Init caching pool. */ + pj_caching_pool_init(&pjsua_var.cp, NULL, 0); + + /* Create memory pool for application. */ + pjsua_var.pool = pjsua_pool_create("pjsua", 1000, 1000); + + PJ_ASSERT_RETURN(pjsua_var.pool, PJ_ENOMEM); + + /* Create mutex */ + status = pj_mutex_create_recursive(pjsua_var.pool, "pjsua", + &pjsua_var.mutex); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + pjsua_perror(THIS_FILE, "Unable to create mutex", status); + return status; + } + + /* Must create SIP endpoint to initialize SIP parser. The parser + * is needed for example when application needs to call pjsua_verify_url(). + */ + status = pjsip_endpt_create(&pjsua_var.cp.factory, + pj_gethostname()->ptr, + &pjsua_var.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Init timer entry list */ + pj_list_init(&pjsua_var.timer_list); + + /* Create timer mutex */ + status = pj_mutex_create_recursive(pjsua_var.pool, "pjsua_timer", + &pjsua_var.timer_mutex); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + pjsua_perror(THIS_FILE, "Unable to create mutex", status); + return status; + } + + pjsua_set_state(PJSUA_STATE_CREATED); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Initialize pjsua with the specified settings. All the settings are + * optional, and the default values will be used when the config is not + * specified. + */ +PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, + const pjsua_logging_config *log_cfg, + const pjsua_media_config *media_cfg) +{ + pjsua_config default_cfg; + pjsua_media_config default_media_cfg; + const pj_str_t STR_OPTIONS = { "OPTIONS", 7 }; + pjsip_ua_init_param ua_init_param; + unsigned i; + pj_status_t status; + + pj_log_push_indent(); + + /* Create default configurations when the config is not supplied */ + + if (ua_cfg == NULL) { + pjsua_config_default(&default_cfg); + ua_cfg = &default_cfg; + } + + if (media_cfg == NULL) { + pjsua_media_config_default(&default_media_cfg); + media_cfg = &default_media_cfg; + } + + /* Initialize logging first so that info/errors can be captured */ + if (log_cfg) { + status = pjsua_reconfigure_logging(log_cfg); + if (status != PJ_SUCCESS) + goto on_error; + } + +#if defined(PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT) && \ + PJ_IPHONE_OS_HAS_MULTITASKING_SUPPORT != 0 + if (!(pj_get_sys_info()->flags & PJ_SYS_HAS_IOS_BG)) { + PJ_LOG(5, (THIS_FILE, "Device does not support " + "background mode")); + pj_activesock_enable_iphone_os_bg(PJ_FALSE); + } +#endif + + /* If nameserver is configured, create DNS resolver instance and + * set it to be used by SIP resolver. + */ + if (ua_cfg->nameserver_count) { +#if PJSIP_HAS_RESOLVER + unsigned i; + + /* Create DNS resolver */ + status = pjsip_endpt_create_resolver(pjsua_var.endpt, + &pjsua_var.resolver); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating resolver", status); + goto on_error; + } + + /* Configure nameserver for the DNS resolver */ + status = pj_dns_resolver_set_ns(pjsua_var.resolver, + ua_cfg->nameserver_count, + ua_cfg->nameserver, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error setting nameserver", status); + goto on_error; + } + + /* Set this DNS resolver to be used by the SIP resolver */ + status = pjsip_endpt_set_resolver(pjsua_var.endpt, pjsua_var.resolver); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error setting DNS resolver", status); + goto on_error; + } + + /* Print nameservers */ + for (i=0; i<ua_cfg->nameserver_count; ++i) { + PJ_LOG(4,(THIS_FILE, "Nameserver %.*s added", + (int)ua_cfg->nameserver[i].slen, + ua_cfg->nameserver[i].ptr)); + } +#else + PJ_LOG(2,(THIS_FILE, + "DNS resolver is disabled (PJSIP_HAS_RESOLVER==0)")); +#endif + } + + /* Init SIP UA: */ + + /* Initialize transaction layer: */ + status = pjsip_tsx_layer_init_module(pjsua_var.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + + /* Initialize UA layer module: */ + pj_bzero(&ua_init_param, sizeof(ua_init_param)); + if (ua_cfg->hangup_forked_call) { + ua_init_param.on_dlg_forked = &on_dlg_forked; + } + status = pjsip_ua_init_module( pjsua_var.endpt, &ua_init_param); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + + /* Initialize Replaces support. */ + status = pjsip_replaces_init_module( pjsua_var.endpt ); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Initialize 100rel support */ + status = pjsip_100rel_init_module(pjsua_var.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Initialize session timer support */ + status = pjsip_timer_init_module(pjsua_var.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Initialize and register PJSUA application module. */ + { + const pjsip_module mod_initializer = + { + NULL, NULL, /* prev, next. */ + { "mod-pjsua", 9 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &mod_pjsua_on_rx_request, /* on_rx_request() */ + &mod_pjsua_on_rx_response, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + }; + + pjsua_var.mod = mod_initializer; + + status = pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_var.mod); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + } + + /* Parse outbound proxies */ + for (i=0; i<ua_cfg->outbound_proxy_cnt; ++i) { + pj_str_t tmp; + pj_str_t hname = { "Route", 5}; + pjsip_route_hdr *r; + + pj_strdup_with_null(pjsua_var.pool, &tmp, &ua_cfg->outbound_proxy[i]); + + r = (pjsip_route_hdr*) + pjsip_parse_hdr(pjsua_var.pool, &hname, tmp.ptr, + (unsigned)tmp.slen, NULL); + if (r == NULL) { + pjsua_perror(THIS_FILE, "Invalid outbound proxy URI", + PJSIP_EINVALIDURI); + status = PJSIP_EINVALIDURI; + goto on_error; + } + + if (pjsua_var.ua_cfg.force_lr) { + pjsip_sip_uri *sip_url; + if (!PJSIP_URI_SCHEME_IS_SIP(r->name_addr.uri) && + !PJSIP_URI_SCHEME_IS_SIP(r->name_addr.uri)) + { + status = PJSIP_EINVALIDSCHEME; + goto on_error; + } + sip_url = (pjsip_sip_uri*)r->name_addr.uri; + sip_url->lr_param = 1; + } + + pj_list_push_back(&pjsua_var.outbound_proxy, r); + } + + + /* Initialize PJSUA call subsystem: */ + status = pjsua_call_subsys_init(ua_cfg); + if (status != PJ_SUCCESS) + goto on_error; + + /* Convert deprecated STUN settings */ + if (pjsua_var.ua_cfg.stun_srv_cnt==0) { + if (pjsua_var.ua_cfg.stun_domain.slen) { + pjsua_var.ua_cfg.stun_srv[pjsua_var.ua_cfg.stun_srv_cnt++] = + pjsua_var.ua_cfg.stun_domain; + } + if (pjsua_var.ua_cfg.stun_host.slen) { + pjsua_var.ua_cfg.stun_srv[pjsua_var.ua_cfg.stun_srv_cnt++] = + pjsua_var.ua_cfg.stun_host; + } + } + + /* Start resolving STUN server */ + status = resolve_stun_server(PJ_FALSE); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + pjsua_perror(THIS_FILE, "Error resolving STUN server", status); + goto on_error; + } + + /* Initialize PJSUA media subsystem */ + status = pjsua_media_subsys_init(media_cfg); + if (status != PJ_SUCCESS) + goto on_error; + + + /* Init core SIMPLE module : */ + status = pjsip_evsub_init_module(pjsua_var.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + + /* Init presence module: */ + status = pjsip_pres_init_module( pjsua_var.endpt, pjsip_evsub_instance()); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Initialize MWI support */ + status = pjsip_mwi_init_module(pjsua_var.endpt, pjsip_evsub_instance()); + + /* Init PUBLISH module */ + pjsip_publishc_init_module(pjsua_var.endpt); + + /* Init xfer/REFER module */ + status = pjsip_xfer_init_module( pjsua_var.endpt ); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Init pjsua presence handler: */ + status = pjsua_pres_init(); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init out-of-dialog MESSAGE request handler. */ + status = pjsua_im_init(); + if (status != PJ_SUCCESS) + goto on_error; + + /* Register OPTIONS handler */ + pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_options_handler); + + /* Add OPTIONS in Allow header */ + pjsip_endpt_add_capability(pjsua_var.endpt, NULL, PJSIP_H_ALLOW, + NULL, 1, &STR_OPTIONS); + + /* Start worker thread if needed. */ + if (pjsua_var.ua_cfg.thread_cnt) { + unsigned i; + + if (pjsua_var.ua_cfg.thread_cnt > PJ_ARRAY_SIZE(pjsua_var.thread)) + pjsua_var.ua_cfg.thread_cnt = PJ_ARRAY_SIZE(pjsua_var.thread); + + for (i=0; i<pjsua_var.ua_cfg.thread_cnt; ++i) { + status = pj_thread_create(pjsua_var.pool, "pjsua", &worker_thread, + NULL, 0, 0, &pjsua_var.thread[i]); + if (status != PJ_SUCCESS) + goto on_error; + } + PJ_LOG(4,(THIS_FILE, "%d SIP worker threads created", + pjsua_var.ua_cfg.thread_cnt)); + } else { + PJ_LOG(4,(THIS_FILE, "No SIP worker threads created")); + } + + /* Done! */ + + PJ_LOG(3,(THIS_FILE, "pjsua version %s for %s initialized", + pj_get_version(), pj_get_sys_info()->info.ptr)); + + pjsua_set_state(PJSUA_STATE_INIT); + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + pjsua_destroy(); + pj_log_pop_indent(); + return status; +} + + +/* Sleep with polling */ +static void busy_sleep(unsigned msec) +{ + pj_time_val timeout, now; + + pj_gettimeofday(&timeout); + timeout.msec += msec; + pj_time_val_normalize(&timeout); + + do { + int i; + i = msec / 10; + while (pjsua_handle_events(10) > 0 && i > 0) + --i; + pj_gettimeofday(&now); + } while (PJ_TIME_VAL_LT(now, timeout)); +} + +/* Internal function to destroy STUN resolution session + * (pj_stun_resolve). + */ +static void destroy_stun_resolve(pjsua_stun_resolve *sess) +{ + PJSUA_LOCK(); + pj_list_erase(sess); + PJSUA_UNLOCK(); + + pj_assert(sess->stun_sock==NULL); + pj_pool_release(sess->pool); +} + +/* This is the internal function to be called when STUN resolution + * session (pj_stun_resolve) has completed. + */ +static void stun_resolve_complete(pjsua_stun_resolve *sess) +{ + pj_stun_resolve_result result; + + pj_bzero(&result, sizeof(result)); + result.token = sess->token; + result.status = sess->status; + result.name = sess->srv[sess->idx]; + pj_memcpy(&result.addr, &sess->addr, sizeof(result.addr)); + + if (result.status == PJ_SUCCESS) { + char addr[PJ_INET6_ADDRSTRLEN+10]; + pj_sockaddr_print(&result.addr, addr, sizeof(addr), 3); + PJ_LOG(4,(THIS_FILE, + "STUN resolution success, using %.*s, address is %s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr, + addr)); + } else { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(result.status, errmsg, sizeof(errmsg)); + PJ_LOG(1,(THIS_FILE, "STUN resolution failed: %s", errmsg)); + } + + sess->cb(&result); + + if (!sess->blocking) { + destroy_stun_resolve(sess); + } +} + +/* This is the callback called by the STUN socket (pj_stun_sock) + * to report it's state. We use this as part of testing the + * STUN server. + */ +static pj_bool_t test_stun_on_status(pj_stun_sock *stun_sock, + pj_stun_sock_op op, + pj_status_t status) +{ + pjsua_stun_resolve *sess; + + sess = (pjsua_stun_resolve*) pj_stun_sock_get_user_data(stun_sock); + pj_assert(stun_sock == sess->stun_sock); + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + + PJ_LOG(4,(THIS_FILE, "STUN resolution for %.*s failed: %s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr, errmsg)); + + sess->status = status; + + pj_stun_sock_destroy(stun_sock); + sess->stun_sock = NULL; + + ++sess->idx; + resolve_stun_entry(sess); + + return PJ_FALSE; + + } else if (op == PJ_STUN_SOCK_BINDING_OP) { + pj_stun_sock_info ssi; + + pj_stun_sock_get_info(stun_sock, &ssi); + pj_memcpy(&sess->addr, &ssi.srv_addr, sizeof(sess->addr)); + + sess->status = PJ_SUCCESS; + pj_stun_sock_destroy(stun_sock); + sess->stun_sock = NULL; + + stun_resolve_complete(sess); + + return PJ_FALSE; + + } else + return PJ_TRUE; + +} + +/* This is an internal function to resolve and test current + * server entry in pj_stun_resolve session. It is called by + * pjsua_resolve_stun_servers() and test_stun_on_status() above + */ +static void resolve_stun_entry(pjsua_stun_resolve *sess) +{ + /* Loop while we have entry to try */ + for (; sess->idx < sess->count; ++sess->idx) { + const int af = pj_AF_INET(); + pj_str_t hostpart; + pj_uint16_t port; + pj_stun_sock_cb stun_sock_cb; + + pj_assert(sess->idx < sess->count); + + /* Parse the server entry into host:port */ + sess->status = pj_sockaddr_parse2(af, 0, &sess->srv[sess->idx], + &hostpart, &port, NULL); + if (sess->status != PJ_SUCCESS) { + PJ_LOG(2,(THIS_FILE, "Invalid STUN server entry %.*s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr)); + continue; + } + + /* Use default port if not specified */ + if (port == 0) + port = PJ_STUN_PORT; + + pj_assert(sess->stun_sock == NULL); + + PJ_LOG(4,(THIS_FILE, "Trying STUN server %.*s (%d of %d)..", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr, + sess->idx+1, sess->count)); + + /* Use STUN_sock to test this entry */ + pj_bzero(&stun_sock_cb, sizeof(stun_sock_cb)); + stun_sock_cb.on_status = &test_stun_on_status; + sess->status = pj_stun_sock_create(&pjsua_var.stun_cfg, "stunresolve", + pj_AF_INET(), &stun_sock_cb, + NULL, sess, &sess->stun_sock); + if (sess->status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(sess->status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(THIS_FILE, + "Error creating STUN socket for %.*s: %s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr, errmsg)); + + continue; + } + + sess->status = pj_stun_sock_start(sess->stun_sock, &hostpart, + port, pjsua_var.resolver); + if (sess->status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(sess->status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(THIS_FILE, + "Error starting STUN socket for %.*s: %s", + (int)sess->srv[sess->idx].slen, + sess->srv[sess->idx].ptr, errmsg)); + + pj_stun_sock_destroy(sess->stun_sock); + sess->stun_sock = NULL; + continue; + } + + /* Done for now, testing will resume/complete asynchronously in + * stun_sock_cb() + */ + return; + } + + if (sess->idx >= sess->count) { + /* No more entries to try */ + PJ_ASSERT_ON_FAIL(sess->status != PJ_SUCCESS, + sess->status = PJ_EUNKNOWN); + stun_resolve_complete(sess); + } +} + + +/* + * Resolve STUN server. + */ +PJ_DEF(pj_status_t) pjsua_resolve_stun_servers( unsigned count, + pj_str_t srv[], + pj_bool_t wait, + void *token, + pj_stun_resolve_cb cb) +{ + pj_pool_t *pool; + pjsua_stun_resolve *sess; + pj_status_t status; + unsigned i; + + PJ_ASSERT_RETURN(count && srv && cb, PJ_EINVAL); + + pool = pjsua_pool_create("stunres", 256, 256); + if (!pool) + return PJ_ENOMEM; + + sess = PJ_POOL_ZALLOC_T(pool, pjsua_stun_resolve); + sess->pool = pool; + sess->token = token; + sess->cb = cb; + sess->count = count; + sess->blocking = wait; + sess->status = PJ_EPENDING; + sess->srv = (pj_str_t*) pj_pool_calloc(pool, count, sizeof(pj_str_t)); + for (i=0; i<count; ++i) { + pj_strdup(pool, &sess->srv[i], &srv[i]); + } + + PJSUA_LOCK(); + pj_list_push_back(&pjsua_var.stun_res, sess); + PJSUA_UNLOCK(); + + resolve_stun_entry(sess); + + if (!wait) + return PJ_SUCCESS; + + while (sess->status == PJ_EPENDING) { + pjsua_handle_events(50); + } + + status = sess->status; + destroy_stun_resolve(sess); + + return status; +} + +/* + * Cancel pending STUN resolution. + */ +PJ_DEF(pj_status_t) pjsua_cancel_stun_resolution( void *token, + pj_bool_t notify_cb) +{ + pjsua_stun_resolve *sess; + unsigned cancelled_count = 0; + + PJSUA_LOCK(); + sess = pjsua_var.stun_res.next; + while (sess != &pjsua_var.stun_res) { + pjsua_stun_resolve *next = sess->next; + + if (sess->token == token) { + if (notify_cb) { + pj_stun_resolve_result result; + + pj_bzero(&result, sizeof(result)); + result.token = token; + result.status = PJ_ECANCELLED; + + sess->cb(&result); + } + + destroy_stun_resolve(sess); + ++cancelled_count; + } + + sess = next; + } + PJSUA_UNLOCK(); + + return cancelled_count ? PJ_SUCCESS : PJ_ENOTFOUND; +} + +static void internal_stun_resolve_cb(const pj_stun_resolve_result *result) +{ + pjsua_var.stun_status = result->status; + if (result->status == PJ_SUCCESS) { + pj_memcpy(&pjsua_var.stun_srv, &result->addr, sizeof(result->addr)); + } +} + +/* + * Resolve STUN server. + */ +pj_status_t resolve_stun_server(pj_bool_t wait) +{ + if (pjsua_var.stun_status == PJ_EUNKNOWN) { + pj_status_t status; + + /* Initialize STUN configuration */ + pj_stun_config_init(&pjsua_var.stun_cfg, &pjsua_var.cp.factory, 0, + pjsip_endpt_get_ioqueue(pjsua_var.endpt), + pjsip_endpt_get_timer_heap(pjsua_var.endpt)); + + /* Start STUN server resolution */ + if (pjsua_var.ua_cfg.stun_srv_cnt) { + pjsua_var.stun_status = PJ_EPENDING; + status = pjsua_resolve_stun_servers(pjsua_var.ua_cfg.stun_srv_cnt, + pjsua_var.ua_cfg.stun_srv, + wait, NULL, + &internal_stun_resolve_cb); + if (wait || status != PJ_SUCCESS) { + pjsua_var.stun_status = status; + } + } else { + pjsua_var.stun_status = PJ_SUCCESS; + } + + } else if (pjsua_var.stun_status == PJ_EPENDING) { + /* STUN server resolution has been started, wait for the + * result. + */ + if (wait) { + while (pjsua_var.stun_status == PJ_EPENDING) { + if (pjsua_var.thread[0] == NULL) + pjsua_handle_events(10); + else + pj_thread_sleep(10); + } + } + } + + if (pjsua_var.stun_status != PJ_EPENDING && + pjsua_var.stun_status != PJ_SUCCESS && + pjsua_var.ua_cfg.stun_ignore_failure) + { + PJ_LOG(2,(THIS_FILE, + "Ignoring STUN resolution failure (by setting)")); + pjsua_var.stun_status = PJ_SUCCESS; + } + + return pjsua_var.stun_status; +} + +/* + * Destroy pjsua. + */ +PJ_DEF(pj_status_t) pjsua_destroy2(unsigned flags) +{ + int i; /* Must be signed */ + + if (pjsua_var.endpt) { + PJ_LOG(4,(THIS_FILE, "Shutting down, flags=%d...", flags)); + } + + if (pjsua_var.state > PJSUA_STATE_NULL && + pjsua_var.state < PJSUA_STATE_CLOSING) + { + pjsua_set_state(PJSUA_STATE_CLOSING); + } + + /* Signal threads to quit: */ + pjsua_var.thread_quit_flag = 1; + + /* Wait worker threads to quit: */ + for (i=0; i<(int)pjsua_var.ua_cfg.thread_cnt; ++i) { + if (pjsua_var.thread[i]) { + pj_status_t status; + status = pj_thread_join(pjsua_var.thread[i]); + if (status != PJ_SUCCESS) { + PJ_PERROR(4,(THIS_FILE, status, "Error joining worker thread")); + pj_thread_sleep(1000); + } + pj_thread_destroy(pjsua_var.thread[i]); + pjsua_var.thread[i] = NULL; + } + } + + if (pjsua_var.endpt) { + unsigned max_wait; + + pj_log_push_indent(); + + /* Terminate all calls. */ + if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) { + pjsua_call_hangup_all(); + } + + /* Set all accounts to offline */ + for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + pjsua_var.acc[i].online_status = PJ_FALSE; + pj_bzero(&pjsua_var.acc[i].rpid, sizeof(pjrpid_element)); + } + + /* Terminate all presence subscriptions. */ + pjsua_pres_shutdown(flags); + + /* Destroy media (to shutdown media transports etc) */ + pjsua_media_subsys_destroy(flags); + + /* Wait for sometime until all publish client sessions are done + * (ticket #364) + */ + /* First stage, get the maximum wait time */ + max_wait = 100; + for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + if (pjsua_var.acc[i].cfg.unpublish_max_wait_time_msec > max_wait) + max_wait = pjsua_var.acc[i].cfg.unpublish_max_wait_time_msec; + } + + /* No waiting if RX is disabled */ + if (flags & PJSUA_DESTROY_NO_RX_MSG) { + max_wait = 0; + } + + /* Second stage, wait for unpublications to complete */ + for (i=0; i<(int)(max_wait/50); ++i) { + unsigned j; + for (j=0; j<PJ_ARRAY_SIZE(pjsua_var.acc); ++j) { + if (!pjsua_var.acc[j].valid) + continue; + + if (pjsua_var.acc[j].publish_sess) + break; + } + if (j != PJ_ARRAY_SIZE(pjsua_var.acc)) + busy_sleep(50); + else + break; + } + + /* Third stage, forcefully destroy unfinished unpublications */ + for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (pjsua_var.acc[i].publish_sess) { + pjsip_publishc_destroy(pjsua_var.acc[i].publish_sess); + pjsua_var.acc[i].publish_sess = NULL; + } + } + + /* Unregister all accounts */ + for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + + if (pjsua_var.acc[i].regc && (flags & PJSUA_DESTROY_NO_TX_MSG)==0) + { + pjsua_acc_set_registration(i, PJ_FALSE); + } + } + + /* Terminate any pending STUN resolution */ + if (!pj_list_empty(&pjsua_var.stun_res)) { + pjsua_stun_resolve *sess = pjsua_var.stun_res.next; + while (sess != &pjsua_var.stun_res) { + pjsua_stun_resolve *next = sess->next; + destroy_stun_resolve(sess); + sess = next; + } + } + + /* Wait until all unregistrations are done (ticket #364) */ + /* First stage, get the maximum wait time */ + max_wait = 100; + for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + if (pjsua_var.acc[i].cfg.unreg_timeout > max_wait) + max_wait = pjsua_var.acc[i].cfg.unreg_timeout; + } + + /* No waiting if RX is disabled */ + if (flags & PJSUA_DESTROY_NO_RX_MSG) { + max_wait = 0; + } + + /* Second stage, wait for unregistrations to complete */ + for (i=0; i<(int)(max_wait/50); ++i) { + unsigned j; + for (j=0; j<PJ_ARRAY_SIZE(pjsua_var.acc); ++j) { + if (!pjsua_var.acc[j].valid) + continue; + + if (pjsua_var.acc[j].regc) + break; + } + if (j != PJ_ARRAY_SIZE(pjsua_var.acc)) + busy_sleep(50); + else + break; + } + /* Note variable 'i' is used below */ + + /* Wait for some time to allow unregistration and ICE/TURN + * transports shutdown to complete: + */ + if (i < 20 && (flags & PJSUA_DESTROY_NO_RX_MSG) == 0) { + busy_sleep(1000 - i*50); + } + + PJ_LOG(4,(THIS_FILE, "Destroying...")); + + /* Must destroy endpoint first before destroying pools in + * buddies or accounts, since shutting down transaction layer + * may emit events which trigger some buddy or account callbacks + * to be called. + */ + pjsip_endpt_destroy(pjsua_var.endpt); + pjsua_var.endpt = NULL; + + /* Destroy pool in the buddy object */ + for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + if (pjsua_var.buddy[i].pool) { + pj_pool_release(pjsua_var.buddy[i].pool); + pjsua_var.buddy[i].pool = NULL; + } + } + + /* Destroy accounts */ + for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (pjsua_var.acc[i].pool) { + pj_pool_release(pjsua_var.acc[i].pool); + pjsua_var.acc[i].pool = NULL; + } + } + } + + /* Destroy mutex */ + if (pjsua_var.mutex) { + pj_mutex_destroy(pjsua_var.mutex); + pjsua_var.mutex = NULL; + } + + /* Destroy pool and pool factory. */ + if (pjsua_var.pool) { + pj_pool_release(pjsua_var.pool); + pjsua_var.pool = NULL; + pj_caching_pool_destroy(&pjsua_var.cp); + + pjsua_set_state(PJSUA_STATE_NULL); + + PJ_LOG(4,(THIS_FILE, "PJSUA destroyed...")); + + /* End logging */ + if (pjsua_var.log_file) { + pj_file_close(pjsua_var.log_file); + pjsua_var.log_file = NULL; + } + + pj_log_pop_indent(); + + /* Shutdown PJLIB */ + pj_shutdown(); + } + + /* Clear pjsua_var */ + pj_bzero(&pjsua_var, sizeof(pjsua_var)); + + /* Done. */ + return PJ_SUCCESS; +} + +void pjsua_set_state(pjsua_state new_state) +{ + const char *state_name[] = { + "NULL", + "CREATED", + "INIT", + "STARTING", + "RUNNING", + "CLOSING" + }; + pjsua_state old_state = pjsua_var.state; + + pjsua_var.state = new_state; + PJ_LOG(4,(THIS_FILE, "PJSUA state changed: %s --> %s", + state_name[old_state], state_name[new_state])); +} + +/* Get state */ +PJ_DEF(pjsua_state) pjsua_get_state(void) +{ + return pjsua_var.state; +} + +PJ_DEF(pj_status_t) pjsua_destroy(void) +{ + return pjsua_destroy2(0); +} + + +/** + * Application is recommended to call this function after all initialization + * is done, so that the library can do additional checking set up + * additional + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DEF(pj_status_t) pjsua_start(void) +{ + pj_status_t status; + + pjsua_set_state(PJSUA_STATE_STARTING); + pj_log_push_indent(); + + status = pjsua_call_subsys_start(); + if (status != PJ_SUCCESS) + goto on_return; + + status = pjsua_media_subsys_start(); + if (status != PJ_SUCCESS) + goto on_return; + + status = pjsua_pres_start(); + if (status != PJ_SUCCESS) + goto on_return; + + pjsua_set_state(PJSUA_STATE_RUNNING); + +on_return: + pj_log_pop_indent(); + return status; +} + + +/** + * Poll pjsua for events, and if necessary block the caller thread for + * the specified maximum interval (in miliseconds). + */ +PJ_DEF(int) pjsua_handle_events(unsigned msec_timeout) +{ +#if defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 + + return pj_symbianos_poll(-1, msec_timeout); + +#else + + unsigned count = 0; + pj_time_val tv; + pj_status_t status; + + tv.sec = 0; + tv.msec = msec_timeout; + pj_time_val_normalize(&tv); + + status = pjsip_endpt_handle_events2(pjsua_var.endpt, &tv, &count); + + if (status != PJ_SUCCESS) + return -status; + + return count; + +#endif +} + + +/* + * Create memory pool. + */ +PJ_DEF(pj_pool_t*) pjsua_pool_create( const char *name, pj_size_t init_size, + pj_size_t increment) +{ + /* Pool factory is thread safe, no need to lock */ + return pj_pool_create(&pjsua_var.cp.factory, name, init_size, increment, + NULL); +} + + +/* + * Internal function to get SIP endpoint instance of pjsua, which is + * needed for example to register module, create transports, etc. + * Probably is only valid after #pjsua_init() is called. + */ +PJ_DEF(pjsip_endpoint*) pjsua_get_pjsip_endpt(void) +{ + return pjsua_var.endpt; +} + +/* + * Internal function to get media endpoint instance. + * Only valid after #pjsua_init() is called. + */ +PJ_DEF(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void) +{ + return pjsua_var.med_endpt; +} + +/* + * Internal function to get PJSUA pool factory. + */ +PJ_DEF(pj_pool_factory*) pjsua_get_pool_factory(void) +{ + return &pjsua_var.cp.factory; +} + +/***************************************************************************** + * PJSUA SIP Transport API. + */ + +/* + * Tools to get address string. + */ +static const char *addr_string(const pj_sockaddr_t *addr) +{ + static char str[128]; + str[0] = '\0'; + pj_inet_ntop(((const pj_sockaddr*)addr)->addr.sa_family, + pj_sockaddr_get_addr(addr), + str, sizeof(str)); + return str; +} + +void pjsua_acc_on_tp_state_changed(pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info); + +/* Callback to receive transport state notifications */ +static void on_tp_state_callback(pjsip_transport *tp, + pjsip_transport_state state, + const pjsip_transport_state_info *info) +{ + if (pjsua_var.ua_cfg.cb.on_transport_state) { + (*pjsua_var.ua_cfg.cb.on_transport_state)(tp, state, info); + } + if (pjsua_var.old_tp_cb) { + (*pjsua_var.old_tp_cb)(tp, state, info); + } + pjsua_acc_on_tp_state_changed(tp, state, info); +} + +/* + * Create and initialize SIP socket (and possibly resolve public + * address via STUN, depending on config). + */ +static pj_status_t create_sip_udp_sock(int af, + const pjsua_transport_config *cfg, + pj_sock_t *p_sock, + pj_sockaddr *p_pub_addr) +{ + char stun_ip_addr[PJ_INET6_ADDRSTRLEN]; + unsigned port = cfg->port; + pj_str_t stun_srv; + pj_sock_t sock; + pj_sockaddr bind_addr; + pj_status_t status; + + /* Make sure STUN server resolution has completed */ + status = resolve_stun_server(PJ_TRUE); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error resolving STUN server", status); + return status; + } + + /* Initialize bound address */ + if (cfg->bound_addr.slen) { + status = pj_sockaddr_init(af, &bind_addr, &cfg->bound_addr, + (pj_uint16_t)port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to resolve transport bound address", + status); + return status; + } + } else { + pj_sockaddr_init(af, &bind_addr, NULL, (pj_uint16_t)port); + } + + /* Create socket */ + status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &sock); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "socket() error", status); + return status; + } + + /* Apply QoS, if specified */ + status = pj_sock_apply_qos2(sock, cfg->qos_type, + &cfg->qos_params, + 2, THIS_FILE, "SIP UDP socket"); + + /* Bind socket */ + status = pj_sock_bind(sock, &bind_addr, pj_sockaddr_get_len(&bind_addr)); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "bind() error", status); + pj_sock_close(sock); + return status; + } + + /* If port is zero, get the bound port */ + if (port == 0) { + pj_sockaddr bound_addr; + int namelen = sizeof(bound_addr); + status = pj_sock_getsockname(sock, &bound_addr, &namelen); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "getsockname() error", status); + pj_sock_close(sock); + return status; + } + + port = pj_sockaddr_get_port(&bound_addr); + } + + if (pjsua_var.stun_srv.addr.sa_family != 0) { + pj_ansi_strcpy(stun_ip_addr,pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr)); + stun_srv = pj_str(stun_ip_addr); + } else { + stun_srv.slen = 0; + } + + /* Get the published address, either by STUN or by resolving + * the name of local host. + */ + if (pj_sockaddr_has_addr(p_pub_addr)) { + /* + * Public address is already specified, no need to resolve the + * address, only set the port. + */ + if (pj_sockaddr_get_port(p_pub_addr) == 0) + pj_sockaddr_set_port(p_pub_addr, (pj_uint16_t)port); + + } else if (stun_srv.slen) { + /* + * STUN is specified, resolve the address with STUN. + */ + if (af != pj_AF_INET()) { + pjsua_perror(THIS_FILE, "Cannot use STUN", PJ_EAFNOTSUP); + pj_sock_close(sock); + return PJ_EAFNOTSUP; + } + + status = pjstun_get_mapped_addr(&pjsua_var.cp.factory, 1, &sock, + &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port), + &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port), + &p_pub_addr->ipv4); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error contacting STUN server", status); + pj_sock_close(sock); + return status; + } + + } else { + pj_bzero(p_pub_addr, sizeof(pj_sockaddr)); + + if (pj_sockaddr_has_addr(&bind_addr)) { + pj_sockaddr_copy_addr(p_pub_addr, &bind_addr); + } else { + status = pj_gethostip(af, p_pub_addr); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to get local host IP", status); + pj_sock_close(sock); + return status; + } + } + + p_pub_addr->addr.sa_family = (pj_uint16_t)af; + pj_sockaddr_set_port(p_pub_addr, (pj_uint16_t)port); + } + + *p_sock = sock; + + PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d", + addr_string(p_pub_addr), + (int)pj_sockaddr_get_port(p_pub_addr))); + + return PJ_SUCCESS; +} + + +/* + * Create SIP transport. + */ +PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, + const pjsua_transport_config *cfg, + pjsua_transport_id *p_id) +{ + pjsip_transport *tp; + unsigned id; + pj_status_t status; + + PJSUA_LOCK(); + + /* Find empty transport slot */ + for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) { + if (pjsua_var.tpdata[id].data.ptr == NULL) + break; + } + + if (id == PJ_ARRAY_SIZE(pjsua_var.tpdata)) { + status = PJ_ETOOMANY; + pjsua_perror(THIS_FILE, "Error creating transport", status); + goto on_return; + } + + /* Create the transport */ + if (type==PJSIP_TRANSPORT_UDP || type==PJSIP_TRANSPORT_UDP6) { + /* + * Create UDP transport (IPv4 or IPv6). + */ + pjsua_transport_config config; + char hostbuf[PJ_INET6_ADDRSTRLEN]; + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_sockaddr pub_addr; + pjsip_host_port addr_name; + + /* Supply default config if it's not specified */ + if (cfg == NULL) { + pjsua_transport_config_default(&config); + cfg = &config; + } + + /* Initialize the public address from the config, if any */ + pj_sockaddr_init(pjsip_transport_type_get_af(type), &pub_addr, + NULL, (pj_uint16_t)cfg->port); + if (cfg->public_addr.slen) { + status = pj_sockaddr_set_str_addr(pjsip_transport_type_get_af(type), + &pub_addr, &cfg->public_addr); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to resolve transport public address", + status); + goto on_return; + } + } + + /* Create the socket and possibly resolve the address with STUN + * (only when public address is not specified). + */ + status = create_sip_udp_sock(pjsip_transport_type_get_af(type), + cfg, &sock, &pub_addr); + if (status != PJ_SUCCESS) + goto on_return; + + pj_ansi_strcpy(hostbuf, addr_string(&pub_addr)); + addr_name.host = pj_str(hostbuf); + addr_name.port = pj_sockaddr_get_port(&pub_addr); + + /* Create UDP transport */ + status = pjsip_udp_transport_attach2(pjsua_var.endpt, type, sock, + &addr_name, 1, &tp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating SIP UDP transport", + status); + pj_sock_close(sock); + goto on_return; + } + + + /* Save the transport */ + pjsua_var.tpdata[id].type = type; + pjsua_var.tpdata[id].local_name = tp->local_name; + pjsua_var.tpdata[id].data.tp = tp; + +#if defined(PJ_HAS_TCP) && PJ_HAS_TCP!=0 + + } else if (type == PJSIP_TRANSPORT_TCP || type == PJSIP_TRANSPORT_TCP6) { + /* + * Create TCP transport. + */ + pjsua_transport_config config; + pjsip_tpfactory *tcp; + pjsip_tcp_transport_cfg tcp_cfg; + + pjsip_tcp_transport_cfg_default(&tcp_cfg, pj_AF_INET()); + + /* Supply default config if it's not specified */ + if (cfg == NULL) { + pjsua_transport_config_default(&config); + cfg = &config; + } + + /* Configure bind address */ + if (cfg->port) + pj_sockaddr_set_port(&tcp_cfg.bind_addr, (pj_uint16_t)cfg->port); + + if (cfg->bound_addr.slen) { + status = pj_sockaddr_set_str_addr(tcp_cfg.af, + &tcp_cfg.bind_addr, + &cfg->bound_addr); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to resolve transport bound address", + status); + goto on_return; + } + } + + /* Set published name */ + if (cfg->public_addr.slen) + tcp_cfg.addr_name.host = cfg->public_addr; + + /* Copy the QoS settings */ + tcp_cfg.qos_type = cfg->qos_type; + pj_memcpy(&tcp_cfg.qos_params, &cfg->qos_params, + sizeof(cfg->qos_params)); + + /* Create the TCP transport */ + status = pjsip_tcp_transport_start3(pjsua_var.endpt, &tcp_cfg, &tcp); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating SIP TCP listener", + status); + goto on_return; + } + + /* Save the transport */ + pjsua_var.tpdata[id].type = type; + pjsua_var.tpdata[id].local_name = tcp->addr_name; + pjsua_var.tpdata[id].data.factory = tcp; + +#endif /* PJ_HAS_TCP */ + +#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0 + } else if (type == PJSIP_TRANSPORT_TLS) { + /* + * Create TLS transport. + */ + pjsua_transport_config config; + pjsip_host_port a_name; + pjsip_tpfactory *tls; + pj_sockaddr_in local_addr; + + /* Supply default config if it's not specified */ + if (cfg == NULL) { + pjsua_transport_config_default(&config); + config.port = 5061; + cfg = &config; + } + + /* Init local address */ + pj_sockaddr_in_init(&local_addr, 0, 0); + + if (cfg->port) + local_addr.sin_port = pj_htons((pj_uint16_t)cfg->port); + + if (cfg->bound_addr.slen) { + status = pj_sockaddr_in_set_str_addr(&local_addr,&cfg->bound_addr); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to resolve transport bound address", + status); + goto on_return; + } + } + + /* Init published name */ + pj_bzero(&a_name, sizeof(pjsip_host_port)); + if (cfg->public_addr.slen) + a_name.host = cfg->public_addr; + + status = pjsip_tls_transport_start(pjsua_var.endpt, + &cfg->tls_setting, + &local_addr, &a_name, 1, &tls); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating SIP TLS listener", + status); + goto on_return; + } + + /* Save the transport */ + pjsua_var.tpdata[id].type = type; + pjsua_var.tpdata[id].local_name = tls->addr_name; + pjsua_var.tpdata[id].data.factory = tls; +#endif + + } else { + status = PJSIP_EUNSUPTRANSPORT; + pjsua_perror(THIS_FILE, "Error creating transport", status); + goto on_return; + } + + /* Set transport state callback */ + if (pjsua_var.ua_cfg.cb.on_transport_state) { + pjsip_tp_state_callback tpcb; + pjsip_tpmgr *tpmgr; + + tpmgr = pjsip_endpt_get_tpmgr(pjsua_var.endpt); + tpcb = pjsip_tpmgr_get_state_cb(tpmgr); + + if (tpcb != &on_tp_state_callback) { + pjsua_var.old_tp_cb = tpcb; + pjsip_tpmgr_set_state_cb(tpmgr, &on_tp_state_callback); + } + } + + /* Return the ID */ + if (p_id) *p_id = id; + + status = PJ_SUCCESS; + +on_return: + + PJSUA_UNLOCK(); + + return status; +} + + +/* + * Register transport that has been created by application. + */ +PJ_DEF(pj_status_t) pjsua_transport_register( pjsip_transport *tp, + pjsua_transport_id *p_id) +{ + unsigned id; + + PJSUA_LOCK(); + + /* Find empty transport slot */ + for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) { + if (pjsua_var.tpdata[id].data.ptr == NULL) + break; + } + + if (id == PJ_ARRAY_SIZE(pjsua_var.tpdata)) { + pjsua_perror(THIS_FILE, "Error creating transport", PJ_ETOOMANY); + PJSUA_UNLOCK(); + return PJ_ETOOMANY; + } + + /* Save the transport */ + pjsua_var.tpdata[id].type = (pjsip_transport_type_e) tp->key.type; + pjsua_var.tpdata[id].local_name = tp->local_name; + pjsua_var.tpdata[id].data.tp = tp; + + /* Return the ID */ + if (p_id) *p_id = id; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * Enumerate all transports currently created in the system. + */ +PJ_DEF(pj_status_t) pjsua_enum_transports( pjsua_transport_id id[], + unsigned *p_count ) +{ + unsigned i, count; + + PJSUA_LOCK(); + + for (i=0, count=0; i<PJ_ARRAY_SIZE(pjsua_var.tpdata) && count<*p_count; + ++i) + { + if (!pjsua_var.tpdata[i].data.ptr) + continue; + + id[count++] = i; + } + + *p_count = count; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * Get information about transports. + */ +PJ_DEF(pj_status_t) pjsua_transport_get_info( pjsua_transport_id id, + pjsua_transport_info *info) +{ + pjsua_transport_data *t = &pjsua_var.tpdata[id]; + pj_status_t status; + + pj_bzero(info, sizeof(*info)); + + /* Make sure id is in range. */ + PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.tpdata), + PJ_EINVAL); + + /* Make sure that transport exists */ + PJ_ASSERT_RETURN(pjsua_var.tpdata[id].data.ptr != NULL, PJ_EINVAL); + + PJSUA_LOCK(); + + if (t->type == PJSIP_TRANSPORT_UDP) { + + pjsip_transport *tp = t->data.tp; + + if (tp == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } + + info->id = id; + info->type = (pjsip_transport_type_e) tp->key.type; + info->type_name = pj_str(tp->type_name); + info->info = pj_str(tp->info); + info->flag = tp->flag; + info->addr_len = tp->addr_len; + info->local_addr = tp->local_addr; + info->local_name = tp->local_name; + info->usage_count = pj_atomic_get(tp->ref_cnt); + + status = PJ_SUCCESS; + + } else if (t->type == PJSIP_TRANSPORT_TCP || + t->type == PJSIP_TRANSPORT_TLS) + { + + pjsip_tpfactory *factory = t->data.factory; + + if (factory == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } + + info->id = id; + info->type = t->type; + info->type_name = (t->type==PJSIP_TRANSPORT_TCP)? pj_str("TCP"): + pj_str("TLS"); + info->info = (t->type==PJSIP_TRANSPORT_TCP)? pj_str("TCP transport"): + pj_str("TLS transport"); + info->flag = factory->flag; + info->addr_len = sizeof(factory->local_addr); + info->local_addr = factory->local_addr; + info->local_name = factory->addr_name; + info->usage_count = 0; + + status = PJ_SUCCESS; + + } else { + pj_assert(!"Unsupported transport"); + status = PJ_EINVALIDOP; + } + + + PJSUA_UNLOCK(); + + return status; +} + + +/* + * Disable a transport or re-enable it. + */ +PJ_DEF(pj_status_t) pjsua_transport_set_enable( pjsua_transport_id id, + pj_bool_t enabled) +{ + /* Make sure id is in range. */ + PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.tpdata), + PJ_EINVAL); + + /* Make sure that transport exists */ + PJ_ASSERT_RETURN(pjsua_var.tpdata[id].data.ptr != NULL, PJ_EINVAL); + + + /* To be done!! */ + PJ_TODO(pjsua_transport_set_enable); + PJ_UNUSED_ARG(enabled); + + return PJ_EINVALIDOP; +} + + +/* + * Close the transport. + */ +PJ_DEF(pj_status_t) pjsua_transport_close( pjsua_transport_id id, + pj_bool_t force ) +{ + pj_status_t status; + + /* Make sure id is in range. */ + PJ_ASSERT_RETURN(id>=0 && id<(int)PJ_ARRAY_SIZE(pjsua_var.tpdata), + PJ_EINVAL); + + /* Make sure that transport exists */ + PJ_ASSERT_RETURN(pjsua_var.tpdata[id].data.ptr != NULL, PJ_EINVAL); + + /* Note: destroy() may not work if there are objects still referencing + * the transport. + */ + if (force) { + switch (pjsua_var.tpdata[id].type) { + case PJSIP_TRANSPORT_UDP: + status = pjsip_transport_shutdown(pjsua_var.tpdata[id].data.tp); + if (status != PJ_SUCCESS) + return status; + status = pjsip_transport_destroy(pjsua_var.tpdata[id].data.tp); + if (status != PJ_SUCCESS) + return status; + break; + + case PJSIP_TRANSPORT_TLS: + case PJSIP_TRANSPORT_TCP: + /* This will close the TCP listener, but existing TCP/TLS + * connections (if any) will still linger + */ + status = (*pjsua_var.tpdata[id].data.factory->destroy) + (pjsua_var.tpdata[id].data.factory); + if (status != PJ_SUCCESS) + return status; + + break; + + default: + return PJ_EINVAL; + } + + } else { + /* If force is not specified, transports will be closed at their + * convenient time. However this will leak PJSUA-API transport + * descriptors as PJSUA-API wouldn't know when exactly the + * transport is closed thus it can't cleanup PJSUA transport + * descriptor. + */ + switch (pjsua_var.tpdata[id].type) { + case PJSIP_TRANSPORT_UDP: + return pjsip_transport_shutdown(pjsua_var.tpdata[id].data.tp); + case PJSIP_TRANSPORT_TLS: + case PJSIP_TRANSPORT_TCP: + return (*pjsua_var.tpdata[id].data.factory->destroy) + (pjsua_var.tpdata[id].data.factory); + default: + return PJ_EINVAL; + } + } + + /* Cleanup pjsua data when force is applied */ + if (force) { + pjsua_var.tpdata[id].type = PJSIP_TRANSPORT_UNSPECIFIED; + pjsua_var.tpdata[id].data.ptr = NULL; + } + + return PJ_SUCCESS; +} + + +/* + * Add additional headers etc in msg_data specified by application + * when sending requests. + */ +void pjsua_process_msg_data(pjsip_tx_data *tdata, + const pjsua_msg_data *msg_data) +{ + pj_bool_t allow_body; + const pjsip_hdr *hdr; + + /* Always add User-Agent */ + if (pjsua_var.ua_cfg.user_agent.slen && + tdata->msg->type == PJSIP_REQUEST_MSG) + { + const pj_str_t STR_USER_AGENT = { "User-Agent", 10 }; + pjsip_hdr *h; + h = (pjsip_hdr*)pjsip_generic_string_hdr_create(tdata->pool, + &STR_USER_AGENT, + &pjsua_var.ua_cfg.user_agent); + pjsip_msg_add_hdr(tdata->msg, h); + } + + if (!msg_data) + return; + + hdr = msg_data->hdr_list.next; + while (hdr && hdr != &msg_data->hdr_list) { + pjsip_hdr *new_hdr; + + new_hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr); + pjsip_msg_add_hdr(tdata->msg, new_hdr); + + hdr = hdr->next; + } + + allow_body = (tdata->msg->body == NULL); + + if (allow_body && msg_data->content_type.slen && msg_data->msg_body.slen) { + pjsip_media_type ctype; + pjsip_msg_body *body; + + pjsua_parse_media_type(tdata->pool, &msg_data->content_type, &ctype); + body = pjsip_msg_body_create(tdata->pool, &ctype.type, &ctype.subtype, + &msg_data->msg_body); + tdata->msg->body = body; + } + + /* Multipart */ + if (!pj_list_empty(&msg_data->multipart_parts) && + msg_data->multipart_ctype.type.slen) + { + pjsip_msg_body *bodies; + pjsip_multipart_part *part; + pj_str_t *boundary = NULL; + + bodies = pjsip_multipart_create(tdata->pool, + &msg_data->multipart_ctype, + boundary); + part = msg_data->multipart_parts.next; + while (part != &msg_data->multipart_parts) { + pjsip_multipart_part *part_copy; + + part_copy = pjsip_multipart_clone_part(tdata->pool, part); + pjsip_multipart_add_part(tdata->pool, bodies, part_copy); + part = part->next; + } + + if (tdata->msg->body) { + part = pjsip_multipart_create_part(tdata->pool); + part->body = tdata->msg->body; + pjsip_multipart_add_part(tdata->pool, bodies, part); + + tdata->msg->body = NULL; + } + + tdata->msg->body = bodies; + } +} + + +/* + * Add route_set to outgoing requests + */ +void pjsua_set_msg_route_set( pjsip_tx_data *tdata, + const pjsip_route_hdr *route_set ) +{ + const pjsip_route_hdr *r; + + r = route_set->next; + while (r != route_set) { + pjsip_route_hdr *new_r; + + new_r = (pjsip_route_hdr*) pjsip_hdr_clone(tdata->pool, r); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)new_r); + + r = r->next; + } +} + + +/* + * Simple version of MIME type parsing (it doesn't support parameters) + */ +void pjsua_parse_media_type( pj_pool_t *pool, + const pj_str_t *mime, + pjsip_media_type *media_type) +{ + pj_str_t tmp; + char *pos; + + pj_bzero(media_type, sizeof(*media_type)); + + pj_strdup_with_null(pool, &tmp, mime); + + pos = pj_strchr(&tmp, '/'); + if (pos) { + media_type->type.ptr = tmp.ptr; + media_type->type.slen = (pos-tmp.ptr); + media_type->subtype.ptr = pos+1; + media_type->subtype.slen = tmp.ptr+tmp.slen-pos-1; + } else { + media_type->type = tmp; + } +} + + +/* + * Internal function to init transport selector from transport id. + */ +void pjsua_init_tpselector(pjsua_transport_id tp_id, + pjsip_tpselector *sel) +{ + pjsua_transport_data *tpdata; + unsigned flag; + + pj_bzero(sel, sizeof(*sel)); + if (tp_id == PJSUA_INVALID_ID) + return; + + pj_assert(tp_id >= 0 && tp_id < (int)PJ_ARRAY_SIZE(pjsua_var.tpdata)); + tpdata = &pjsua_var.tpdata[tp_id]; + + flag = pjsip_transport_get_flag_from_type(tpdata->type); + + if (flag & PJSIP_TRANSPORT_DATAGRAM) { + sel->type = PJSIP_TPSELECTOR_TRANSPORT; + sel->u.transport = tpdata->data.tp; + } else { + sel->type = PJSIP_TPSELECTOR_LISTENER; + sel->u.listener = tpdata->data.factory; + } +} + + +/* Callback upon NAT detection completion */ +static void nat_detect_cb(void *user_data, + const pj_stun_nat_detect_result *res) +{ + PJ_UNUSED_ARG(user_data); + + pjsua_var.nat_in_progress = PJ_FALSE; + pjsua_var.nat_status = res->status; + pjsua_var.nat_type = res->nat_type; + + if (pjsua_var.ua_cfg.cb.on_nat_detect) { + (*pjsua_var.ua_cfg.cb.on_nat_detect)(res); + } +} + + +/* + * Detect NAT type. + */ +PJ_DEF(pj_status_t) pjsua_detect_nat_type() +{ + pj_status_t status; + + if (pjsua_var.nat_in_progress) + return PJ_SUCCESS; + + /* Make sure STUN server resolution has completed */ + status = resolve_stun_server(PJ_TRUE); + if (status != PJ_SUCCESS) { + pjsua_var.nat_status = status; + pjsua_var.nat_type = PJ_STUN_NAT_TYPE_ERR_UNKNOWN; + return status; + } + + /* Make sure we have STUN */ + if (pjsua_var.stun_srv.ipv4.sin_family == 0) { + pjsua_var.nat_status = PJNATH_ESTUNINSERVER; + return PJNATH_ESTUNINSERVER; + } + + status = pj_stun_detect_nat_type(&pjsua_var.stun_srv.ipv4, + &pjsua_var.stun_cfg, + NULL, &nat_detect_cb); + + if (status != PJ_SUCCESS) { + pjsua_var.nat_status = status; + pjsua_var.nat_type = PJ_STUN_NAT_TYPE_ERR_UNKNOWN; + return status; + } + + pjsua_var.nat_in_progress = PJ_TRUE; + + return PJ_SUCCESS; +} + + +/* + * Get NAT type. + */ +PJ_DEF(pj_status_t) pjsua_get_nat_type(pj_stun_nat_type *type) +{ + *type = pjsua_var.nat_type; + return pjsua_var.nat_status; +} + +/* + * Verify that valid url is given. + */ +PJ_DEF(pj_status_t) pjsua_verify_url(const char *c_url) +{ + pjsip_uri *p; + pj_pool_t *pool; + char *url; + int len = (c_url ? pj_ansi_strlen(c_url) : 0); + + if (!len) return PJSIP_EINVALIDURI; + + pool = pj_pool_create(&pjsua_var.cp.factory, "check%p", 1024, 0, NULL); + if (!pool) return PJ_ENOMEM; + + url = (char*) pj_pool_alloc(pool, len+1); + pj_ansi_strcpy(url, c_url); + + p = pjsip_parse_uri(pool, url, len, 0); + + pj_pool_release(pool); + return p ? 0 : PJSIP_EINVALIDURI; +} + +/* + * Verify that valid SIP url is given. + */ +PJ_DEF(pj_status_t) pjsua_verify_sip_url(const char *c_url) +{ + pjsip_uri *p; + pj_pool_t *pool; + char *url; + int len = (c_url ? pj_ansi_strlen(c_url) : 0); + + if (!len) return PJSIP_EINVALIDURI; + + pool = pj_pool_create(&pjsua_var.cp.factory, "check%p", 1024, 0, NULL); + if (!pool) return PJ_ENOMEM; + + url = (char*) pj_pool_alloc(pool, len+1); + pj_ansi_strcpy(url, c_url); + + p = pjsip_parse_uri(pool, url, len, 0); + if (!p || (pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0 && + pj_stricmp2(pjsip_uri_get_scheme(p), "sips") != 0)) + { + p = NULL; + } + + pj_pool_release(pool); + return p ? 0 : PJSIP_EINVALIDURI; +} + +/* + * Schedule a timer entry. + */ +#if PJ_TIMER_DEBUG +PJ_DEF(pj_status_t) pjsua_schedule_timer_dbg( pj_timer_entry *entry, + const pj_time_val *delay, + const char *src_file, + int src_line) +{ + return pjsip_endpt_schedule_timer_dbg(pjsua_var.endpt, entry, delay, + src_file, src_line); +} +#else +PJ_DEF(pj_status_t) pjsua_schedule_timer( pj_timer_entry *entry, + const pj_time_val *delay) +{ + return pjsip_endpt_schedule_timer(pjsua_var.endpt, entry, delay); +} +#endif + +/* Timer callback */ +static void timer_cb( pj_timer_heap_t *th, + pj_timer_entry *entry) +{ + pjsua_timer_list *tmr = (pjsua_timer_list *)entry->user_data; + void (*cb)(void *user_data) = tmr->cb; + void *user_data = tmr->user_data; + + PJ_UNUSED_ARG(th); + + pj_mutex_lock(pjsua_var.timer_mutex); + pj_list_push_back(&pjsua_var.timer_list, tmr); + pj_mutex_unlock(pjsua_var.timer_mutex); + + if (cb) + (*cb)(user_data); +} + +/* + * Schedule a timer callback. + */ +#if PJ_TIMER_DEBUG +PJ_DEF(pj_status_t) pjsua_schedule_timer2_dbg( void (*cb)(void *user_data), + void *user_data, + unsigned msec_delay, + const char *src_file, + int src_line) +#else +PJ_DEF(pj_status_t) pjsua_schedule_timer2( void (*cb)(void *user_data), + void *user_data, + unsigned msec_delay) +#endif +{ + pjsua_timer_list *tmr = NULL; + pj_status_t status; + pj_time_val delay; + + pj_mutex_lock(pjsua_var.timer_mutex); + + if (pj_list_empty(&pjsua_var.timer_list)) { + tmr = PJ_POOL_ALLOC_T(pjsua_var.pool, pjsua_timer_list); + } else { + tmr = pjsua_var.timer_list.next; + pj_list_erase(tmr); + } + pj_timer_entry_init(&tmr->entry, 0, tmr, timer_cb); + tmr->cb = cb; + tmr->user_data = user_data; + delay.sec = 0; + delay.msec = msec_delay; + +#if PJ_TIMER_DEBUG + status = pjsip_endpt_schedule_timer_dbg(pjsua_var.endpt, &tmr->entry, + &delay, src_file, src_line); +#else + status = pjsip_endpt_schedule_timer(pjsua_var.endpt, &tmr->entry, &delay); +#endif + if (status != PJ_SUCCESS) { + pj_list_push_back(&pjsua_var.timer_list, tmr); + } + + pj_mutex_unlock(pjsua_var.timer_mutex); + + return status; +} + +/* + * Cancel the previously scheduled timer. + * + */ +PJ_DEF(void) pjsua_cancel_timer(pj_timer_entry *entry) +{ + pjsip_endpt_cancel_timer(pjsua_var.endpt, entry); +} + +/** + * Normalize route URI (check for ";lr" and append one if it doesn't + * exist and pjsua_config.force_lr is set. + */ +pj_status_t normalize_route_uri(pj_pool_t *pool, pj_str_t *uri) +{ + pj_str_t tmp_uri; + pj_pool_t *tmp_pool; + pjsip_uri *uri_obj; + pjsip_sip_uri *sip_uri; + + tmp_pool = pjsua_pool_create("tmplr%p", 512, 512); + if (!tmp_pool) + return PJ_ENOMEM; + + pj_strdup_with_null(tmp_pool, &tmp_uri, uri); + + uri_obj = pjsip_parse_uri(tmp_pool, tmp_uri.ptr, tmp_uri.slen, 0); + if (!uri_obj) { + PJ_LOG(1,(THIS_FILE, "Invalid route URI: %.*s", + (int)uri->slen, uri->ptr)); + pj_pool_release(tmp_pool); + return PJSIP_EINVALIDURI; + } + + if (!PJSIP_URI_SCHEME_IS_SIP(uri_obj) && + !PJSIP_URI_SCHEME_IS_SIP(uri_obj)) + { + PJ_LOG(1,(THIS_FILE, "Route URI must be SIP URI: %.*s", + (int)uri->slen, uri->ptr)); + pj_pool_release(tmp_pool); + return PJSIP_EINVALIDSCHEME; + } + + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri_obj); + + /* Done if force_lr is disabled or if lr parameter is present */ + if (!pjsua_var.ua_cfg.force_lr || sip_uri->lr_param) { + pj_pool_release(tmp_pool); + return PJ_SUCCESS; + } + + /* Set lr param */ + sip_uri->lr_param = 1; + + /* Print the URI */ + tmp_uri.ptr = (char*) pj_pool_alloc(tmp_pool, PJSIP_MAX_URL_SIZE); + tmp_uri.slen = pjsip_uri_print(PJSIP_URI_IN_ROUTING_HDR, uri_obj, + tmp_uri.ptr, PJSIP_MAX_URL_SIZE); + if (tmp_uri.slen < 1) { + PJ_LOG(1,(THIS_FILE, "Route URI is too long: %.*s", + (int)uri->slen, uri->ptr)); + pj_pool_release(tmp_pool); + return PJSIP_EURITOOLONG; + } + + /* Clone the URI */ + pj_strdup_with_null(pool, uri, &tmp_uri); + + pj_pool_release(tmp_pool); + return PJ_SUCCESS; +} + +/* + * This is a utility function to dump the stack states to log, using + * verbosity level 3. + */ +PJ_DEF(void) pjsua_dump(pj_bool_t detail) +{ + unsigned old_decor; + unsigned i; + + PJ_LOG(3,(THIS_FILE, "Start dumping application states:")); + + old_decor = pj_log_get_decor(); + pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR)); + + if (detail) + pj_dump_config(); + + pjsip_endpt_dump(pjsua_get_pjsip_endpt(), detail); + + pjmedia_endpt_dump(pjsua_get_pjmedia_endpt()); + + PJ_LOG(3,(THIS_FILE, "Dumping media transports:")); + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + pjmedia_transport *tp[PJSUA_MAX_CALL_MEDIA*2]; + unsigned tp_cnt = 0; + unsigned j; + + /* Collect media transports in this call */ + for (j = 0; j < call->med_cnt; ++j) { + if (call->media[j].tp != NULL) + tp[tp_cnt++] = call->media[j].tp; + } + for (j = 0; j < call->med_prov_cnt; ++j) { + pjmedia_transport *med_tp = call->media_prov[j].tp; + if (med_tp) { + unsigned k; + pj_bool_t used = PJ_FALSE; + for (k = 0; k < tp_cnt; ++k) { + if (med_tp == tp[k]) { + used = PJ_TRUE; + break; + } + } + if (!used) + tp[tp_cnt++] = med_tp; + } + } + + /* Dump the media transports in this call */ + for (j = 0; j < tp_cnt; ++j) { + pjmedia_transport_info tpinfo; + char addr_buf[80]; + + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(tp[j], &tpinfo); + PJ_LOG(3,(THIS_FILE, " %s: %s", + (pjsua_var.media_cfg.enable_ice ? "ICE" : "UDP"), + pj_sockaddr_print(&tpinfo.sock_info.rtp_addr_name, + addr_buf, + sizeof(addr_buf), 3))); + } + } + + pjsip_tsx_layer_dump(detail); + pjsip_ua_dump(detail); + +// Dumping complete call states may require a 'large' buffer +// (about 3KB per call session, including RTCP XR). +#if 0 + /* Dump all invite sessions: */ + PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:")); + + if (pjsua_call_get_count() == 0) { + + PJ_LOG(3,(THIS_FILE, " - no sessions -")); + + } else { + unsigned i; + + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + if (pjsua_call_is_active(i)) { + /* Tricky logging, since call states log string tends to be + * longer than PJ_LOG_MAX_SIZE. + */ + char buf[1024 * 3]; + unsigned call_dump_len; + unsigned part_len; + unsigned part_idx; + unsigned log_decor; + + pjsua_call_dump(i, detail, buf, sizeof(buf), " "); + call_dump_len = strlen(buf); + + log_decor = pj_log_get_decor(); + pj_log_set_decor(log_decor & ~(PJ_LOG_HAS_NEWLINE | + PJ_LOG_HAS_CR)); + PJ_LOG(3,(THIS_FILE, "\n")); + pj_log_set_decor(0); + + part_idx = 0; + part_len = PJ_LOG_MAX_SIZE-80; + while (part_idx < call_dump_len) { + char p_orig, *p; + + p = &buf[part_idx]; + if (part_idx + part_len > call_dump_len) + part_len = call_dump_len - part_idx; + p_orig = p[part_len]; + p[part_len] = '\0'; + PJ_LOG(3,(THIS_FILE, "%s", p)); + p[part_len] = p_orig; + part_idx += part_len; + } + pj_log_set_decor(log_decor); + } + } + } +#endif + + /* Dump presence status */ + pjsua_pres_dump(detail); + + pj_log_set_decor(old_decor); + PJ_LOG(3,(THIS_FILE, "Dump complete")); +} + diff --git a/pjsip/src/pjsua-lib/pjsua_dump.c b/pjsip/src/pjsua-lib/pjsua_dump.c new file mode 100644 index 0000000..0b23a97 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_dump.c @@ -0,0 +1,974 @@ +/* $Id: pjsua_dump.c 4085 2012-04-25 07:45:22Z nanang $ */ +/* + * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + +const char *good_number(char *buf, pj_int32_t val) +{ + if (val < 1000) { + pj_ansi_sprintf(buf, "%d", val); + } else if (val < 1000000) { + pj_ansi_sprintf(buf, "%d.%dK", + val / 1000, + (val % 1000) / 100); + } else { + pj_ansi_sprintf(buf, "%d.%02dM", + val / 1000000, + (val % 1000000) / 10000); + } + + return buf; +} + +static unsigned dump_media_stat(const char *indent, + char *buf, unsigned maxlen, + const pjmedia_rtcp_stat *stat, + const char *rx_info, const char *tx_info) +{ + char last_update[64]; + char packets[32], bytes[32], ipbytes[32], avg_bps[32], avg_ipbps[32]; + pj_time_val media_duration, now; + char *p = buf, *end = buf+maxlen; + int len; + + if (stat->rx.update_cnt == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, stat->rx.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + pj_gettimeofday(&media_duration); + PJ_TIME_VAL_SUB(media_duration, stat->start); + if (PJ_TIME_VAL_MSEC(media_duration) == 0) + media_duration.msec = 1; + + len = pj_ansi_snprintf(p, end-p, + "%s RX %s last update:%s\n" + "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n" + "%s pkt loss=%d (%3.1f%%), discrd=%d (%3.1f%%), dup=%d (%2.1f%%), reord=%d (%3.1f%%)\n" + "%s (msec) min avg max last dev\n" + "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n" + "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n" +#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0 + "%s raw jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n" +#endif +#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0 + "%s IPDV : %7.3f %7.3f %7.3f %7.3f %7.3f\n" +#endif + "%s", + indent, + rx_info? rx_info : "", + last_update, + + indent, + good_number(packets, stat->rx.pkt), + good_number(bytes, stat->rx.bytes), + good_number(ipbytes, stat->rx.bytes + stat->rx.pkt * 40), + good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->rx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), + good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->rx.bytes + stat->rx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), + indent, + stat->rx.loss, + (stat->rx.loss? stat->rx.loss * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0), + stat->rx.discard, + (stat->rx.discard? stat->rx.discard * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0), + stat->rx.dup, + (stat->rx.dup? stat->rx.dup * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0), + stat->rx.reorder, + (stat->rx.reorder? stat->rx.reorder * 100.0 / (stat->rx.pkt + stat->rx.loss) : 0), + indent, indent, + stat->rx.loss_period.min / 1000.0, + stat->rx.loss_period.mean / 1000.0, + stat->rx.loss_period.max / 1000.0, + stat->rx.loss_period.last / 1000.0, + pj_math_stat_get_stddev(&stat->rx.loss_period) / 1000.0, + indent, + stat->rx.jitter.min / 1000.0, + stat->rx.jitter.mean / 1000.0, + stat->rx.jitter.max / 1000.0, + stat->rx.jitter.last / 1000.0, + pj_math_stat_get_stddev(&stat->rx.jitter) / 1000.0, +#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0 + indent, + stat->rx_raw_jitter.min / 1000.0, + stat->rx_raw_jitter.mean / 1000.0, + stat->rx_raw_jitter.max / 1000.0, + stat->rx_raw_jitter.last / 1000.0, + pj_math_stat_get_stddev(&stat->rx_raw_jitter) / 1000.0, +#endif +#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0 + indent, + stat->rx_ipdv.min / 1000.0, + stat->rx_ipdv.mean / 1000.0, + stat->rx_ipdv.max / 1000.0, + stat->rx_ipdv.last / 1000.0, + pj_math_stat_get_stddev(&stat->rx_ipdv) / 1000.0, +#endif + "" + ); + + if (len < 1 || len > end-p) { + *p = '\0'; + return (p-buf); + } + p += len; + + if (stat->tx.update_cnt == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, stat->tx.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + len = pj_ansi_snprintf(p, end-p, + "%s TX %s last update:%s\n" + "%s total %spkt %sB (%sB +IP hdr) @avg=%sbps/%sbps\n" + "%s pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n" + "%s (msec) min avg max last dev \n" + "%s loss period: %7.3f %7.3f %7.3f %7.3f %7.3f\n" + "%s jitter : %7.3f %7.3f %7.3f %7.3f %7.3f\n", + indent, + tx_info, + last_update, + + indent, + good_number(packets, stat->tx.pkt), + good_number(bytes, stat->tx.bytes), + good_number(ipbytes, stat->tx.bytes + stat->tx.pkt * 40), + good_number(avg_bps, (pj_int32_t)((pj_int64_t)stat->tx.bytes * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), + good_number(avg_ipbps, (pj_int32_t)(((pj_int64_t)stat->tx.bytes + stat->tx.pkt * 40) * 8 * 1000 / PJ_TIME_VAL_MSEC(media_duration))), + + indent, + stat->tx.loss, + (stat->tx.loss? stat->tx.loss * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0), + stat->tx.dup, + (stat->tx.dup? stat->tx.dup * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0), + stat->tx.reorder, + (stat->tx.reorder? stat->tx.reorder * 100.0 / (stat->tx.pkt + stat->tx.loss) : 0), + + indent, indent, + stat->tx.loss_period.min / 1000.0, + stat->tx.loss_period.mean / 1000.0, + stat->tx.loss_period.max / 1000.0, + stat->tx.loss_period.last / 1000.0, + pj_math_stat_get_stddev(&stat->tx.loss_period) / 1000.0, + indent, + stat->tx.jitter.min / 1000.0, + stat->tx.jitter.mean / 1000.0, + stat->tx.jitter.max / 1000.0, + stat->tx.jitter.last / 1000.0, + pj_math_stat_get_stddev(&stat->tx.jitter) / 1000.0 + ); + + if (len < 1 || len > end-p) { + *p = '\0'; + return (p-buf); + } + p += len; + + len = pj_ansi_snprintf(p, end-p, + "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f\n", + indent, + stat->rtt.min / 1000.0, + stat->rtt.mean / 1000.0, + stat->rtt.max / 1000.0, + stat->rtt.last / 1000.0, + pj_math_stat_get_stddev(&stat->rtt) / 1000.0 + ); + if (len < 1 || len > end-p) { + *p = '\0'; + return (p-buf); + } + p += len; + + return (p-buf); +} + + +/* Dump media session */ +static void dump_media_session(const char *indent, + char *buf, unsigned maxlen, + pjsua_call *call) +{ + unsigned i; + char *p = buf, *end = buf+maxlen; + int len; + + for (i=0; i<call->med_cnt; ++i) { + pjsua_call_media *call_med = &call->media[i]; + pjmedia_rtcp_stat stat; + pj_bool_t has_stat; + pjmedia_transport_info tp_info; + char rem_addr_buf[80]; + char codec_info[32] = {'0'}; + char rx_info[80] = {'\0'}; + char tx_info[80] = {'\0'}; + const char *rem_addr; + const char *dir_str; + const char *media_type_str; + + switch (call_med->type) { + case PJMEDIA_TYPE_AUDIO: + media_type_str = "audio"; + break; + case PJMEDIA_TYPE_VIDEO: + media_type_str = "video"; + break; + case PJMEDIA_TYPE_APPLICATION: + media_type_str = "application"; + break; + default: + media_type_str = "unknown"; + break; + } + + /* Check if the stream is deactivated */ + if (call_med->tp == NULL || + (!call_med->strm.a.stream && !call_med->strm.v.stream)) + { + len = pj_ansi_snprintf(p, end-p, + "%s #%d %s deactivated\n", + indent, i, media_type_str); + if (len < 1 || len > end-p) { + *p = '\0'; + return; + } + + p += len; + continue; + } + + pjmedia_transport_info_init(&tp_info); + pjmedia_transport_get_info(call_med->tp, &tp_info); + + // rem_addr will contain actual address of RTP originator, instead of + // remote RTP address specified by stream which is fetched from the SDP. + // Please note that we are assuming only one stream per call. + //rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr, + // rem_addr_buf, sizeof(rem_addr_buf), 3); + if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) { + rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf, + sizeof(rem_addr_buf), 3); + } else { + pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-"); + rem_addr = rem_addr_buf; + } + + if (call_med->dir == PJMEDIA_DIR_NONE) { + /* To handle when the stream that is currently being paused + * (http://trac.pjsip.org/repos/ticket/1079) + */ + dir_str = "inactive"; + } else if (call_med->dir == PJMEDIA_DIR_ENCODING) + dir_str = "sendonly"; + else if (call_med->dir == PJMEDIA_DIR_DECODING) + dir_str = "recvonly"; + else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING) + dir_str = "sendrecv"; + else + dir_str = "inactive"; + + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + pjmedia_stream *stream = call_med->strm.a.stream; + pjmedia_stream_info info; + + pjmedia_stream_get_stat(stream, &stat); + has_stat = PJ_TRUE; + + pjmedia_stream_get_info(stream, &info); + pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz", + (int)info.fmt.encoding_name.slen, + info.fmt.encoding_name.ptr, + info.fmt.clock_rate / 1000); + pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,", + info.rx_pt); + pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,", + info.tx_pt, + info.param->setting.frm_per_pkt* + info.param->info.frm_ptime); + +#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + } else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + pjmedia_vid_stream *stream = call_med->strm.v.stream; + pjmedia_vid_stream_info info; + + pjmedia_vid_stream_get_stat(stream, &stat); + has_stat = PJ_TRUE; + + pjmedia_vid_stream_get_info(stream, &info); + pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s", + (int)info.codec_info.encoding_name.slen, + info.codec_info.encoding_name.ptr); + if (call_med->dir & PJMEDIA_DIR_DECODING) { + pjmedia_video_format_detail *vfd; + vfd = pjmedia_format_get_video_format_detail( + &info.codec_param->dec_fmt, PJ_TRUE); + pj_ansi_snprintf(rx_info, sizeof(rx_info), + "pt=%d, size=%dx%d, fps=%.2f,", + info.rx_pt, + vfd->size.w, vfd->size.h, + vfd->fps.num*1.0/vfd->fps.denum); + } + if (call_med->dir & PJMEDIA_DIR_ENCODING) { + pjmedia_video_format_detail *vfd; + vfd = pjmedia_format_get_video_format_detail( + &info.codec_param->enc_fmt, PJ_TRUE); + pj_ansi_snprintf(tx_info, sizeof(tx_info), + "pt=%d, size=%dx%d, fps=%.2f,", + info.tx_pt, + vfd->size.w, vfd->size.h, + vfd->fps.num*1.0/vfd->fps.denum); + } +#endif /* PJMEDIA_HAS_VIDEO */ + + } else { + has_stat = PJ_FALSE; + } + + len = pj_ansi_snprintf(p, end-p, + "%s #%d %s%s, %s, peer=%s\n", + indent, + call_med->idx, + media_type_str, + codec_info, + dir_str, + rem_addr); + if (len < 1 || len > end-p) { + *p = '\0'; + return; + } + p += len; + + /* Get and ICE SRTP status */ + if (call_med->tp) { + pjmedia_transport_info tp_info; + + pjmedia_transport_info_init(&tp_info); + pjmedia_transport_get_info(call_med->tp, &tp_info); + if (tp_info.specific_info_cnt > 0) { + unsigned j; + for (j = 0; j < tp_info.specific_info_cnt; ++j) { + if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP) + { + pjmedia_srtp_info *srtp_info = + (pjmedia_srtp_info*) tp_info.spc_info[j].buffer; + + len = pj_ansi_snprintf(p, end-p, + " %s SRTP status: %s Crypto-suite: %s", + indent, + (srtp_info->active?"Active":"Not active"), + srtp_info->tx_policy.name.ptr); + if (len > 0 && len < end-p) { + p += len; + *p++ = '\n'; + *p = '\0'; + } + } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) { + const pjmedia_ice_transport_info *ii; + unsigned jj; + + ii = (const pjmedia_ice_transport_info*) + tp_info.spc_info[j].buffer; + + len = pj_ansi_snprintf(p, end-p, + " %s ICE role: %s, state: %s, comp_cnt: %u", + indent, + pj_ice_sess_role_name(ii->role), + pj_ice_strans_state_name(ii->sess_state), + ii->comp_cnt); + if (len > 0 && len < end-p) { + p += len; + *p++ = '\n'; + *p = '\0'; + } + + for (jj=0; ii->sess_state==PJ_ICE_STRANS_STATE_RUNNING && jj<2; ++jj) { + const char *type1 = pj_ice_get_cand_type_name(ii->comp[jj].lcand_type); + const char *type2 = pj_ice_get_cand_type_name(ii->comp[jj].rcand_type); + char addr1[PJ_INET6_ADDRSTRLEN+10]; + char addr2[PJ_INET6_ADDRSTRLEN+10]; + + if (pj_sockaddr_has_addr(&ii->comp[jj].lcand_addr)) + pj_sockaddr_print(&ii->comp[jj].lcand_addr, addr1, sizeof(addr1), 3); + else + strcpy(addr1, "0.0.0.0:0"); + if (pj_sockaddr_has_addr(&ii->comp[jj].rcand_addr)) + pj_sockaddr_print(&ii->comp[jj].rcand_addr, addr2, sizeof(addr2), 3); + else + strcpy(addr2, "0.0.0.0:0"); + len = pj_ansi_snprintf(p, end-p, + " %s [%d]: L:%s (%c) --> R:%s (%c)\n", + indent, jj, + addr1, type1[0], + addr2, type2[0]); + if (len > 0 && len < end-p) { + p += len; + *p = '\0'; + } + } + } + } + } + } + + + if (has_stat) { + len = dump_media_stat(indent, p, end-p, &stat, + rx_info, tx_info); + p += len; + } + +#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) +# define SAMPLES_TO_USEC(usec, samples, clock_rate) \ + do { \ + if (samples <= 4294) \ + usec = samples * 1000000 / clock_rate; \ + else { \ + usec = samples * 1000 / clock_rate; \ + usec *= 1000; \ + } \ + } while(0) + +# define PRINT_VOIP_MTC_VAL(s, v) \ + if (v == 127) \ + sprintf(s, "(na)"); \ + else \ + sprintf(s, "%d", v) + +# define VALIDATE_PRINT_BUF() \ + if (len < 1 || len > end-p) { *p = '\0'; return; } \ + p += len; *p++ = '\n'; *p = '\0' + + + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + pjmedia_stream_info info; + char last_update[64]; + char loss[16], dup[16]; + char jitter[80]; + char toh[80]; + char plc[16], jba[16], jbr[16]; + char signal_lvl[16], noise_lvl[16], rerl[16]; + char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16]; + pjmedia_rtcp_xr_stat xr_stat; + unsigned clock_rate; + pj_time_val now; + + if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream, + &xr_stat) != PJ_SUCCESS) + { + continue; + } + + if (pjmedia_stream_get_info(call_med->strm.a.stream, &info) + != PJ_SUCCESS) + { + continue; + } + + clock_rate = info.fmt.clock_rate; + pj_gettimeofday(&now); + + len = pj_ansi_snprintf(p, end-p, "\n%s Extended reports:", indent); + VALIDATE_PRINT_BUF(); + + /* Statistics Summary */ + len = pj_ansi_snprintf(p, end-p, "%s Statistics Summary", indent); + VALIDATE_PRINT_BUF(); + + if (xr_stat.rx.stat_sum.l) + sprintf(loss, "%d", xr_stat.rx.stat_sum.lost); + else + sprintf(loss, "(na)"); + + if (xr_stat.rx.stat_sum.d) + sprintf(dup, "%d", xr_stat.rx.stat_sum.dup); + else + sprintf(dup, "(na)"); + + if (xr_stat.rx.stat_sum.j) { + unsigned jmin, jmax, jmean, jdev; + + SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min, + clock_rate); + SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max, + clock_rate); + SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean, + clock_rate); + SAMPLES_TO_USEC(jdev, + pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter), + clock_rate); + sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", + jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0); + } else + sprintf(jitter, "(report not available)"); + + if (xr_stat.rx.stat_sum.t) { + sprintf(toh, "%11d %11d %11d %11d", + xr_stat.rx.stat_sum.toh.min, + xr_stat.rx.stat_sum.toh.mean, + xr_stat.rx.stat_sum.toh.max, + pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh)); + } else + sprintf(toh, "(report not available)"); + + if (xr_stat.rx.stat_sum.update.sec == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + len = pj_ansi_snprintf(p, end-p, + "%s RX last update: %s\n" + "%s begin seq=%d, end seq=%d\n" + "%s pkt loss=%s, dup=%s\n" + "%s (msec) min avg max dev\n" + "%s jitter : %s\n" + "%s toh : %s", + indent, last_update, + indent, + xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq, + indent, loss, dup, + indent, + indent, jitter, + indent, toh + ); + VALIDATE_PRINT_BUF(); + + if (xr_stat.tx.stat_sum.l) + sprintf(loss, "%d", xr_stat.tx.stat_sum.lost); + else + sprintf(loss, "(na)"); + + if (xr_stat.tx.stat_sum.d) + sprintf(dup, "%d", xr_stat.tx.stat_sum.dup); + else + sprintf(dup, "(na)"); + + if (xr_stat.tx.stat_sum.j) { + unsigned jmin, jmax, jmean, jdev; + + SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min, + clock_rate); + SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max, + clock_rate); + SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean, + clock_rate); + SAMPLES_TO_USEC(jdev, + pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter), + clock_rate); + sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", + jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0); + } else + sprintf(jitter, "(report not available)"); + + if (xr_stat.tx.stat_sum.t) { + sprintf(toh, "%11d %11d %11d %11d", + xr_stat.tx.stat_sum.toh.min, + xr_stat.tx.stat_sum.toh.mean, + xr_stat.tx.stat_sum.toh.max, + pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh)); + } else + sprintf(toh, "(report not available)"); + + if (xr_stat.tx.stat_sum.update.sec == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + len = pj_ansi_snprintf(p, end-p, + "%s TX last update: %s\n" + "%s begin seq=%d, end seq=%d\n" + "%s pkt loss=%s, dup=%s\n" + "%s (msec) min avg max dev\n" + "%s jitter : %s\n" + "%s toh : %s", + indent, last_update, + indent, + xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq, + indent, loss, dup, + indent, + indent, jitter, + indent, toh + ); + VALIDATE_PRINT_BUF(); + + + /* VoIP Metrics */ + len = pj_ansi_snprintf(p, end-p, "%s VoIP Metrics", indent); + VALIDATE_PRINT_BUF(); + + PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl); + PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl); + PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl); + PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor); + PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor); + PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq); + PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq); + + switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) { + case PJMEDIA_RTCP_XR_PLC_DIS: + sprintf(plc, "DISABLED"); + break; + case PJMEDIA_RTCP_XR_PLC_ENH: + sprintf(plc, "ENHANCED"); + break; + case PJMEDIA_RTCP_XR_PLC_STD: + sprintf(plc, "STANDARD"); + break; + case PJMEDIA_RTCP_XR_PLC_UNK: + default: + sprintf(plc, "UNKNOWN"); + break; + } + + switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) { + case PJMEDIA_RTCP_XR_JB_FIXED: + sprintf(jba, "FIXED"); + break; + case PJMEDIA_RTCP_XR_JB_ADAPTIVE: + sprintf(jba, "ADAPTIVE"); + break; + default: + sprintf(jba, "UNKNOWN"); + break; + } + + sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F); + + if (xr_stat.rx.voip_mtc.update.sec == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + len = pj_ansi_snprintf(p, end-p, + "%s RX last update: %s\n" + "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n" + "%s burst : density=%d (%.2f%%), duration=%d%s\n" + "%s gap : density=%d (%.2f%%), duration=%d%s\n" + "%s delay : round trip=%d%s, end system=%d%s\n" + "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n" + "%s quality : R factor=%s, ext R factor=%s\n" + "%s MOS LQ=%s, MOS CQ=%s\n" + "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n" + "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s", + indent, + last_update, + /* packets */ + indent, + xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256, + xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256, + /* burst */ + indent, + xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256, + xr_stat.rx.voip_mtc.burst_dur, "ms", + /* gap */ + indent, + xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256, + xr_stat.rx.voip_mtc.gap_dur, "ms", + /* delay */ + indent, + xr_stat.rx.voip_mtc.rnd_trip_delay, "ms", + xr_stat.rx.voip_mtc.end_sys_delay, "ms", + /* level */ + indent, + signal_lvl, "dB", + noise_lvl, "dB", + rerl, "", + /* quality */ + indent, + r_factor, ext_r_factor, + indent, + mos_lq, mos_cq, + /* config */ + indent, + plc, jba, jbr, xr_stat.rx.voip_mtc.gmin, + /* JB delay */ + indent, + xr_stat.rx.voip_mtc.jb_nom, "ms", + xr_stat.rx.voip_mtc.jb_max, "ms", + xr_stat.rx.voip_mtc.jb_abs_max, "ms" + ); + VALIDATE_PRINT_BUF(); + + PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl); + PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl); + PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl); + PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor); + PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor); + PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq); + PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq); + + switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) { + case PJMEDIA_RTCP_XR_PLC_DIS: + sprintf(plc, "DISABLED"); + break; + case PJMEDIA_RTCP_XR_PLC_ENH: + sprintf(plc, "ENHANCED"); + break; + case PJMEDIA_RTCP_XR_PLC_STD: + sprintf(plc, "STANDARD"); + break; + case PJMEDIA_RTCP_XR_PLC_UNK: + default: + sprintf(plc, "unknown"); + break; + } + + switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) { + case PJMEDIA_RTCP_XR_JB_FIXED: + sprintf(jba, "FIXED"); + break; + case PJMEDIA_RTCP_XR_JB_ADAPTIVE: + sprintf(jba, "ADAPTIVE"); + break; + default: + sprintf(jba, "unknown"); + break; + } + + sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F); + + if (xr_stat.tx.voip_mtc.update.sec == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update); + sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + len = pj_ansi_snprintf(p, end-p, + "%s TX last update: %s\n" + "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n" + "%s burst : density=%d (%.2f%%), duration=%d%s\n" + "%s gap : density=%d (%.2f%%), duration=%d%s\n" + "%s delay : round trip=%d%s, end system=%d%s\n" + "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n" + "%s quality : R factor=%s, ext R factor=%s\n" + "%s MOS LQ=%s, MOS CQ=%s\n" + "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n" + "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s", + indent, + last_update, + /* pakcets */ + indent, + xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256, + xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256, + /* burst */ + indent, + xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256, + xr_stat.tx.voip_mtc.burst_dur, "ms", + /* gap */ + indent, + xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256, + xr_stat.tx.voip_mtc.gap_dur, "ms", + /* delay */ + indent, + xr_stat.tx.voip_mtc.rnd_trip_delay, "ms", + xr_stat.tx.voip_mtc.end_sys_delay, "ms", + /* level */ + indent, + signal_lvl, "dB", + noise_lvl, "dB", + rerl, "", + /* quality */ + indent, + r_factor, ext_r_factor, + indent, + mos_lq, mos_cq, + /* config */ + indent, + plc, jba, jbr, xr_stat.tx.voip_mtc.gmin, + /* JB delay */ + indent, + xr_stat.tx.voip_mtc.jb_nom, "ms", + xr_stat.tx.voip_mtc.jb_max, "ms", + xr_stat.tx.voip_mtc.jb_abs_max, "ms" + ); + VALIDATE_PRINT_BUF(); + + + /* RTT delay (by receiver side) */ + len = pj_ansi_snprintf(p, end-p, + "%s RTT (from recv) min avg max last dev", + indent); + VALIDATE_PRINT_BUF(); + len = pj_ansi_snprintf(p, end-p, + "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f", + indent, + xr_stat.rtt.min / 1000.0, + xr_stat.rtt.mean / 1000.0, + xr_stat.rtt.max / 1000.0, + xr_stat.rtt.last / 1000.0, + pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0 + ); + VALIDATE_PRINT_BUF(); + } /* if audio */; +#endif + + } +} + + +/* Print call info */ +void print_call(const char *title, + int call_id, + char *buf, pj_size_t size) +{ + int len; + pjsip_inv_session *inv = pjsua_var.calls[call_id].inv; + pjsip_dialog *dlg = inv->dlg; + char userinfo[128]; + + /* Dump invite sesion info. */ + + len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); + if (len < 0) + pj_ansi_strcpy(userinfo, "<--uri too long-->"); + else + userinfo[len] = '\0'; + + len = pj_ansi_snprintf(buf, size, "%s[%s] %s", + title, + pjsip_inv_state_name(inv->state), + userinfo); + if (len < 1 || len >= (int)size) { + pj_ansi_strcpy(buf, "<--uri too long-->"); + len = 18; + } else + buf[len] = '\0'; +} + + +/* + * Dump call and media statistics to string. + */ +PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id, + pj_bool_t with_media, + char *buffer, + unsigned maxlen, + const char *indent) +{ + pjsua_call *call; + pjsip_dialog *dlg; + pj_time_val duration, res_delay, con_delay; + char tmp[128]; + char *p, *end; + pj_status_t status; + int len; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + status = acquire_call("pjsua_call_dump()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + return status; + + *buffer = '\0'; + p = buffer; + end = buffer + maxlen; + len = 0; + + print_call(indent, call_id, tmp, sizeof(tmp)); + + len = pj_ansi_strlen(tmp); + pj_ansi_strcpy(buffer, tmp); + + p += len; + *p++ = '\r'; + *p++ = '\n'; + + /* Calculate call duration */ + if (call->conn_time.sec != 0) { + pj_gettimeofday(&duration); + PJ_TIME_VAL_SUB(duration, call->conn_time); + con_delay = call->conn_time; + PJ_TIME_VAL_SUB(con_delay, call->start_time); + } else { + duration.sec = duration.msec = 0; + con_delay.sec = con_delay.msec = 0; + } + + /* Calculate first response delay */ + if (call->res_time.sec != 0) { + res_delay = call->res_time; + PJ_TIME_VAL_SUB(res_delay, call->start_time); + } else { + res_delay.sec = res_delay.msec = 0; + } + + /* Print duration */ + len = pj_ansi_snprintf(p, end-p, + "%s Call time: %02dh:%02dm:%02ds, " + "1st res in %d ms, conn in %dms", + indent, + (int)(duration.sec / 3600), + (int)((duration.sec % 3600)/60), + (int)(duration.sec % 60), + (int)PJ_TIME_VAL_MSEC(res_delay), + (int)PJ_TIME_VAL_MSEC(con_delay)); + + if (len > 0 && len < end-p) { + p += len; + *p++ = '\n'; + *p = '\0'; + } + + /* Dump session statistics */ + if (with_media) + dump_media_session(indent, p, end-p, call); + + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsua-lib/pjsua_im.c b/pjsip/src/pjsua-lib/pjsua_im.c new file mode 100644 index 0000000..fa11282 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_im.c @@ -0,0 +1,745 @@ +/* $Id: pjsua_im.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + + +#define THIS_FILE "pjsua_im.h" + + +/* Declare MESSAGE method */ +/* We put PJSIP_MESSAGE_METHOD as the enum here, so that when + * somebody add that method into pjsip_method_e in sip_msg.h we + * will get an error here. + */ +enum +{ + PJSIP_MESSAGE_METHOD = PJSIP_OTHER_METHOD +}; + +const pjsip_method pjsip_message_method = +{ + (pjsip_method_e) PJSIP_MESSAGE_METHOD, + { "MESSAGE", 7 } +}; + + +/* Proto */ +static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata); + + +/* The module instance. */ +static pjsip_module mod_pjsua_im = +{ + NULL, NULL, /* prev, next. */ + { "mod-pjsua-im", 12 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &im_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + +/* MIME constants. */ +static const pj_str_t STR_MIME_APP = { "application", 11 }; +static const pj_str_t STR_MIME_ISCOMPOSING = { "im-iscomposing+xml", 18 }; +static const pj_str_t STR_MIME_TEXT = { "text", 4 }; +static const pj_str_t STR_MIME_PLAIN = { "plain", 5 }; + + +/* Check if content type is acceptable */ +#if 0 +static pj_bool_t acceptable_message(const pjsip_media_type *mime) +{ + return (pj_stricmp(&mime->type, &STR_MIME_TEXT)==0 && + pj_stricmp(&mime->subtype, &STR_MIME_PLAIN)==0) + || + (pj_stricmp(&mime->type, &STR_MIME_APP)==0 && + pj_stricmp(&mime->subtype, &STR_MIME_ISCOMPOSING)==0); +} +#endif + +/** + * Create Accept header for MESSAGE. + */ +pjsip_accept_hdr* pjsua_im_create_accept(pj_pool_t *pool) +{ + /* Create Accept header. */ + pjsip_accept_hdr *accept; + + accept = pjsip_accept_hdr_create(pool); + accept->values[0] = pj_str("text/plain"); + accept->values[1] = pj_str("application/im-iscomposing+xml"); + accept->count = 2; + + return accept; +} + +/** + * Private: check if we can accept the message. + */ +pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata, + pjsip_accept_hdr **p_accept_hdr) +{ + /* Some UA sends text/html, so this check will break */ +#if 0 + pjsip_ctype_hdr *ctype; + pjsip_msg *msg; + + msg = rdata->msg_info.msg; + + /* Request MUST have message body, with Content-Type equal to + * "text/plain". + */ + ctype = (pjsip_ctype_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_CONTENT_TYPE, NULL); + if (msg->body == NULL || ctype == NULL || + !acceptable_message(&ctype->media)) + { + /* Create Accept header. */ + if (p_accept_hdr) + *p_accept_hdr = pjsua_im_create_accept(rdata->tp_info.pool); + + return PJ_FALSE; + } +#elif 0 + pjsip_msg *msg; + + msg = rdata->msg_info.msg; + if (msg->body == NULL) { + /* Create Accept header. */ + if (p_accept_hdr) + *p_accept_hdr = pjsua_im_create_accept(rdata->tp_info.pool); + + return PJ_FALSE; + } +#else + /* Ticket #693: allow incoming MESSAGE without message body */ + PJ_UNUSED_ARG(rdata); + PJ_UNUSED_ARG(p_accept_hdr); +#endif + + return PJ_TRUE; +} + +/** + * Private: process pager message. + * This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing(). + */ +void pjsua_im_process_pager(int call_id, const pj_str_t *from, + const pj_str_t *to, pjsip_rx_data *rdata) +{ + pjsip_contact_hdr *contact_hdr; + pj_str_t contact; + pjsip_msg_body *body = rdata->msg_info.msg->body; + +#if 0 + /* Ticket #693: allow incoming MESSAGE without message body */ + /* Body MUST have been checked before */ + pj_assert(body != NULL); +#endif + + + /* Build remote contact */ + contact_hdr = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, + NULL); + if (contact_hdr && contact_hdr->uri) { + contact.ptr = (char*) pj_pool_alloc(rdata->tp_info.pool, + PJSIP_MAX_URL_SIZE); + contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, + contact_hdr->uri, contact.ptr, + PJSIP_MAX_URL_SIZE); + } else { + contact.slen = 0; + } + + if (body && pj_stricmp(&body->content_type.type, &STR_MIME_APP)==0 && + pj_stricmp(&body->content_type.subtype, &STR_MIME_ISCOMPOSING)==0) + { + /* Expecting typing indication */ + pj_status_t status; + pj_bool_t is_typing; + + status = pjsip_iscomposing_parse(rdata->tp_info.pool, (char*)body->data, + body->len, &is_typing, NULL, NULL, + NULL ); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Invalid MESSAGE body", status); + return; + } + + if (pjsua_var.ua_cfg.cb.on_typing) { + (*pjsua_var.ua_cfg.cb.on_typing)(call_id, from, to, &contact, + is_typing); + } + + if (pjsua_var.ua_cfg.cb.on_typing2) { + pjsua_acc_id acc_id; + + if (call_id == PJSUA_INVALID_ID) { + acc_id = pjsua_acc_find_for_incoming(rdata); + } else { + pjsua_call *call = &pjsua_var.calls[call_id]; + acc_id = call->acc_id; + } + + + (*pjsua_var.ua_cfg.cb.on_typing2)(call_id, from, to, &contact, + is_typing, rdata, acc_id); + } + + } else { + pj_str_t mime_type; + char buf[256]; + pjsip_media_type *m; + pj_str_t text_body; + + /* Save text body */ + if (body) { + text_body.ptr = (char*)rdata->msg_info.msg->body->data; + text_body.slen = rdata->msg_info.msg->body->len; + + /* Get mime type */ + m = &rdata->msg_info.msg->body->content_type; + mime_type.ptr = buf; + mime_type.slen = pj_ansi_snprintf(buf, sizeof(buf), + "%.*s/%.*s", + (int)m->type.slen, + m->type.ptr, + (int)m->subtype.slen, + m->subtype.ptr); + if (mime_type.slen < 1) + mime_type.slen = 0; + + + } else { + text_body.ptr = mime_type.ptr = ""; + text_body.slen = mime_type.slen = 0; + } + + if (pjsua_var.ua_cfg.cb.on_pager) { + (*pjsua_var.ua_cfg.cb.on_pager)(call_id, from, to, &contact, + &mime_type, &text_body); + } + + if (pjsua_var.ua_cfg.cb.on_pager2) { + pjsua_acc_id acc_id; + + if (call_id == PJSUA_INVALID_ID) { + acc_id = pjsua_acc_find_for_incoming(rdata); + } else { + pjsua_call *call = &pjsua_var.calls[call_id]; + acc_id = call->acc_id; + } + + (*pjsua_var.ua_cfg.cb.on_pager2)(call_id, from, to, &contact, + &mime_type, &text_body, rdata, + acc_id); + } + } +} + + +/* + * Handler to receive incoming MESSAGE + */ +static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata) +{ + pj_str_t from, to; + pjsip_accept_hdr *accept_hdr; + pjsip_msg *msg; + pj_status_t status; + + msg = rdata->msg_info.msg; + + /* Only want to handle MESSAGE requests. */ + if (pjsip_method_cmp(&msg->line.req.method, &pjsip_message_method) != 0) { + return PJ_FALSE; + } + + + /* Should not have any transaction attached to rdata. */ + PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata)==NULL, PJ_FALSE); + + /* Should not have any dialog attached to rdata. */ + PJ_ASSERT_RETURN(pjsip_rdata_get_dlg(rdata)==NULL, PJ_FALSE); + + /* Check if we can accept the message. */ + if (!pjsua_im_accept_pager(rdata, &accept_hdr)) { + pjsip_hdr hdr_list; + + pj_list_init(&hdr_list); + pj_list_push_back(&hdr_list, accept_hdr); + + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, + PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL, + &hdr_list, NULL); + return PJ_TRUE; + } + + /* Respond with 200 first, so that remote doesn't retransmit in case + * the UI takes too long to process the message. + */ + status = pjsip_endpt_respond( pjsua_var.endpt, NULL, rdata, 200, NULL, + NULL, NULL, NULL); + + /* For the source URI, we use Contact header if present, since + * Contact header contains the port number information. If this is + * not available, then use From header. + */ + from.ptr = (char*)pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_URL_SIZE); + from.slen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, + rdata->msg_info.from->uri, + from.ptr, PJSIP_MAX_URL_SIZE); + + if (from.slen < 1) + from = pj_str("<--URI is too long-->"); + + /* Build the To text. */ + to.ptr = (char*) pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_URL_SIZE); + to.slen = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, + rdata->msg_info.to->uri, + to.ptr, PJSIP_MAX_URL_SIZE); + if (to.slen < 1) + to = pj_str("<--URI is too long-->"); + + /* Process pager. */ + pjsua_im_process_pager(-1, &from, &to, rdata); + + /* Done. */ + return PJ_TRUE; +} + + +/* Outgoing IM callback. */ +static void im_callback(void *token, pjsip_event *e) +{ + pjsua_im_data *im_data = (pjsua_im_data*) token; + + if (e->type == PJSIP_EVENT_TSX_STATE) { + + pjsip_transaction *tsx = e->body.tsx_state.tsx; + + /* Ignore provisional response, if any */ + if (tsx->status_code < 200) + return; + + + /* Handle authentication challenges */ + if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG && + (tsx->status_code == 401 || tsx->status_code == 407)) + { + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pjsip_auth_clt_sess auth; + pj_status_t status; + + PJ_LOG(4,(THIS_FILE, "Resending IM with authentication")); + + /* Create temporary authentication session */ + pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0); + + pjsip_auth_clt_set_credentials(&auth, + pjsua_var.acc[im_data->acc_id].cred_cnt, + pjsua_var.acc[im_data->acc_id].cred); + + pjsip_auth_clt_set_prefs(&auth, + &pjsua_var.acc[im_data->acc_id].cfg.auth_pref); + + status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx, + &tdata); + if (status == PJ_SUCCESS) { + pjsua_im_data *im_data2; + + /* Must duplicate im_data */ + im_data2 = pjsua_im_data_dup(tdata->pool, im_data); + + /* Increment CSeq */ + PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq++; + + /* Re-send request */ + status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1, + im_data2, &im_callback); + if (status == PJ_SUCCESS) { + /* Done */ + return; + } + } + } + + if (tsx->status_code/100 == 2) { + PJ_LOG(4,(THIS_FILE, + "Message \'%s\' delivered successfully", + im_data->body.ptr)); + } else { + PJ_LOG(3,(THIS_FILE, + "Failed to deliver message \'%s\': %d/%.*s", + im_data->body.ptr, + tsx->status_code, + (int)tsx->status_text.slen, + tsx->status_text.ptr)); + } + + if (pjsua_var.ua_cfg.cb.on_pager_status) { + pjsua_var.ua_cfg.cb.on_pager_status(im_data->call_id, + &im_data->to, + &im_data->body, + im_data->user_data, + (pjsip_status_code) + tsx->status_code, + &tsx->status_text); + } + + if (pjsua_var.ua_cfg.cb.on_pager_status2) { + pjsip_rx_data *rdata; + + if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + rdata = e->body.tsx_state.src.rdata; + else + rdata = NULL; + + pjsua_var.ua_cfg.cb.on_pager_status2(im_data->call_id, + &im_data->to, + &im_data->body, + im_data->user_data, + (pjsip_status_code) + tsx->status_code, + &tsx->status_text, + tsx->last_tx, + rdata, im_data->acc_id); + } + } +} + + +/* Outgoing typing indication callback. + * (used to reauthenticate request) + */ +static void typing_callback(void *token, pjsip_event *e) +{ + pjsua_im_data *im_data = (pjsua_im_data*) token; + + if (e->type == PJSIP_EVENT_TSX_STATE) { + + pjsip_transaction *tsx = e->body.tsx_state.tsx; + + /* Ignore provisional response, if any */ + if (tsx->status_code < 200) + return; + + /* Handle authentication challenges */ + if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG && + (tsx->status_code == 401 || tsx->status_code == 407)) + { + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pjsip_auth_clt_sess auth; + pj_status_t status; + + PJ_LOG(4,(THIS_FILE, "Resending IM with authentication")); + + /* Create temporary authentication session */ + pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0); + + pjsip_auth_clt_set_credentials(&auth, + pjsua_var.acc[im_data->acc_id].cred_cnt, + pjsua_var.acc[im_data->acc_id].cred); + + pjsip_auth_clt_set_prefs(&auth, + &pjsua_var.acc[im_data->acc_id].cfg.auth_pref); + + status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx, + &tdata); + if (status == PJ_SUCCESS) { + pjsua_im_data *im_data2; + + /* Must duplicate im_data */ + im_data2 = pjsua_im_data_dup(tdata->pool, im_data); + + /* Increment CSeq */ + PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq++; + + /* Re-send request */ + status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1, + im_data2, &typing_callback); + if (status == PJ_SUCCESS) { + /* Done */ + return; + } + } + } + + } +} + + +/* + * Send instant messaging outside dialog, using the specified account for + * route set and authentication. + */ +PJ_DEF(pj_status_t) pjsua_im_send( pjsua_acc_id acc_id, + const pj_str_t *to, + const pj_str_t *mime_type, + const pj_str_t *content, + const pjsua_msg_data *msg_data, + void *user_data) +{ + pjsip_tx_data *tdata; + const pj_str_t mime_text_plain = pj_str("text/plain"); + const pj_str_t STR_CONTACT = { "Contact", 7 }; + pjsip_media_type media_type; + pjsua_im_data *im_data; + pjsua_acc *acc; + pj_str_t contact; + pj_status_t status; + + /* To and message body must be specified. */ + PJ_ASSERT_RETURN(to && content, PJ_EINVAL); + + acc = &pjsua_var.acc[acc_id]; + + /* Create request. */ + status = pjsip_endpt_create_request(pjsua_var.endpt, + &pjsip_message_method, to, + &acc->cfg.id, + to, NULL, NULL, -1, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create request", status); + return status; + } + + /* If account is locked to specific transport, then set transport to + * the request. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_tx_data_set_transport(tdata, &tp_sel); + } + + /* Add accept header. */ + pjsip_msg_add_hdr( tdata->msg, + (pjsip_hdr*)pjsua_im_create_accept(tdata->pool)); + + /* Create suitable Contact header unless a Contact header has been + * set in the account. + */ + if (acc->contact.slen) { + contact = acc->contact; + } else { + status = pjsua_acc_create_uac_contact(tdata->pool, &contact, acc_id, to); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", status); + pjsip_tx_data_dec_ref(tdata); + return status; + } + } + + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_generic_string_hdr_create(tdata->pool, + &STR_CONTACT, &contact)); + + /* Create IM data to keep message details and give it back to + * application on the callback + */ + im_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsua_im_data); + im_data->acc_id = acc_id; + im_data->call_id = PJSUA_INVALID_ID; + pj_strdup_with_null(tdata->pool, &im_data->to, to); + pj_strdup_with_null(tdata->pool, &im_data->body, content); + im_data->user_data = user_data; + + + /* Set default media type if none is specified */ + if (mime_type == NULL) { + mime_type = &mime_text_plain; + } + + /* Parse MIME type */ + pjsua_parse_media_type(tdata->pool, mime_type, &media_type); + + /* Add message body */ + tdata->msg->body = pjsip_msg_body_create( tdata->pool, &media_type.type, + &media_type.subtype, + &im_data->body); + if (tdata->msg->body == NULL) { + pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM); + pjsip_tx_data_dec_ref(tdata); + return PJ_ENOMEM; + } + + /* Add additional headers etc. */ + pjsua_process_msg_data(tdata, msg_data); + + /* Add route set */ + pjsua_set_msg_route_set(tdata, &acc->route_set); + + /* If via_addr is set, use this address for the Via header. */ + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) { + tdata->via_addr = acc->via_addr; + tdata->via_tp = acc->via_tp; + } + + /* Send request (statefully) */ + status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1, + im_data, &im_callback); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send request", status); + return status; + } + + return PJ_SUCCESS; +} + + +/* + * Send typing indication outside dialog. + */ +PJ_DEF(pj_status_t) pjsua_im_typing( pjsua_acc_id acc_id, + const pj_str_t *to, + pj_bool_t is_typing, + const pjsua_msg_data *msg_data) +{ + const pj_str_t STR_CONTACT = { "Contact", 7 }; + pjsua_im_data *im_data; + pjsip_tx_data *tdata; + pjsua_acc *acc; + pj_str_t contact; + pj_status_t status; + + acc = &pjsua_var.acc[acc_id]; + + /* Create request. */ + status = pjsip_endpt_create_request( pjsua_var.endpt, &pjsip_message_method, + to, &acc->cfg.id, + to, NULL, NULL, -1, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create request", status); + return status; + } + + + /* If account is locked to specific transport, then set transport to + * the request. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_tx_data_set_transport(tdata, &tp_sel); + } + + /* Add accept header. */ + pjsip_msg_add_hdr( tdata->msg, + (pjsip_hdr*)pjsua_im_create_accept(tdata->pool)); + + + /* Create suitable Contact header unless a Contact header has been + * set in the account. + */ + if (acc->contact.slen) { + contact = acc->contact; + } else { + status = pjsua_acc_create_uac_contact(tdata->pool, &contact, acc_id, to); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", status); + pjsip_tx_data_dec_ref(tdata); + return status; + } + } + + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) + pjsip_generic_string_hdr_create(tdata->pool, + &STR_CONTACT, &contact)); + + + /* Create "application/im-iscomposing+xml" msg body. */ + tdata->msg->body = pjsip_iscomposing_create_body( tdata->pool, is_typing, + NULL, NULL, -1); + + /* Add additional headers etc. */ + pjsua_process_msg_data(tdata, msg_data); + + /* Add route set */ + pjsua_set_msg_route_set(tdata, &acc->route_set); + + /* If via_addr is set, use this address for the Via header. */ + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) { + tdata->via_addr = acc->via_addr; + tdata->via_tp = acc->via_tp; + } + + /* Create data to reauthenticate */ + im_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsua_im_data); + im_data->acc_id = acc_id; + + /* Send request (statefully) */ + status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1, + im_data, &typing_callback); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send request", status); + return status; + } + + return PJ_SUCCESS; +} + + +/* + * Init pjsua IM module. + */ +pj_status_t pjsua_im_init(void) +{ + const pj_str_t msg_tag = { "MESSAGE", 7 }; + const pj_str_t STR_MIME_TEXT_PLAIN = { "text/plain", 10 }; + const pj_str_t STR_MIME_APP_ISCOMPOSING = + { "application/im-iscomposing+xml", 30 }; + pj_status_t status; + + /* Register module */ + status = pjsip_endpt_register_module(pjsua_var.endpt, &mod_pjsua_im); + if (status != PJ_SUCCESS) + return status; + + /* Register support for MESSAGE method. */ + pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ALLOW, + NULL, 1, &msg_tag); + + /* Register support for "application/im-iscomposing+xml" content */ + pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ACCEPT, + NULL, 1, &STR_MIME_APP_ISCOMPOSING); + + /* Register support for "text/plain" content */ + pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ACCEPT, + NULL, 1, &STR_MIME_TEXT_PLAIN); + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c new file mode 100644 index 0000000..8bcb0da --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -0,0 +1,2695 @@ +/* $Id: pjsua_media.c 4182 2012-06-27 07:12:23Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + + +#define THIS_FILE "pjsua_media.c" + +#define DEFAULT_RTP_PORT 4000 + +#ifndef PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT +# define PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT 0 +#endif + +/* Next RTP port to be used */ +static pj_uint16_t next_rtp_port; + +static void pjsua_media_config_dup(pj_pool_t *pool, + pjsua_media_config *dst, + const pjsua_media_config *src) +{ + pj_memcpy(dst, src, sizeof(*src)); + pj_strdup(pool, &dst->turn_server, &src->turn_server); + pj_stun_auth_cred_dup(pool, &dst->turn_auth_cred, &src->turn_auth_cred); +} + + +/** + * Init media subsystems. + */ +pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg) +{ + pj_status_t status; + + pj_log_push_indent(); + + /* Specify which audio device settings are save-able */ + pjsua_var.aud_svmask = 0xFFFFFFFF; + /* These are not-settable */ + pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EXT_FORMAT | + PJMEDIA_AUD_DEV_CAP_INPUT_SIGNAL_METER | + PJMEDIA_AUD_DEV_CAP_OUTPUT_SIGNAL_METER); + /* EC settings use different API */ + pjsua_var.aud_svmask &= ~(PJMEDIA_AUD_DEV_CAP_EC | + PJMEDIA_AUD_DEV_CAP_EC_TAIL); + + /* Copy configuration */ + pjsua_media_config_dup(pjsua_var.pool, &pjsua_var.media_cfg, cfg); + + /* Normalize configuration */ + if (pjsua_var.media_cfg.snd_clock_rate == 0) { + pjsua_var.media_cfg.snd_clock_rate = pjsua_var.media_cfg.clock_rate; + } + + if (pjsua_var.media_cfg.has_ioqueue && + pjsua_var.media_cfg.thread_cnt == 0) + { + pjsua_var.media_cfg.thread_cnt = 1; + } + + if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) { + pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2; + } + + /* Create media endpoint. */ + status = pjmedia_endpt_create(&pjsua_var.cp.factory, + pjsua_var.media_cfg.has_ioqueue? NULL : + pjsip_endpt_get_ioqueue(pjsua_var.endpt), + pjsua_var.media_cfg.thread_cnt, + &pjsua_var.med_endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Media stack initialization has returned error", + status); + goto on_error; + } + + status = pjsua_aud_subsys_init(); + if (status != PJ_SUCCESS) + goto on_error; + +#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) + /* Initialize SRTP library (ticket #788). */ + status = pjmedia_srtp_init_lib(pjsua_var.med_endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing SRTP library", + status); + goto on_error; + } +#endif + + /* Video */ +#if PJMEDIA_HAS_VIDEO + status = pjsua_vid_subsys_init(); + if (status != PJ_SUCCESS) + goto on_error; +#endif + + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + pj_log_pop_indent(); + return status; +} + +/* + * Start pjsua media subsystem. + */ +pj_status_t pjsua_media_subsys_start(void) +{ + pj_status_t status; + + pj_log_push_indent(); + +#if DISABLED_FOR_TICKET_1185 + /* Create media for calls, if none is specified */ + if (pjsua_var.calls[0].media[0].tp == NULL) { + pjsua_transport_config transport_cfg; + + /* Create default transport config */ + pjsua_transport_config_default(&transport_cfg); + transport_cfg.port = DEFAULT_RTP_PORT; + + status = pjsua_media_transports_create(&transport_cfg); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + return status; + } + } +#endif + + /* Audio */ + status = pjsua_aud_subsys_start(); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + return status; + } + + /* Video */ +#if PJMEDIA_HAS_VIDEO + status = pjsua_vid_subsys_start(); + if (status != PJ_SUCCESS) { + pjsua_aud_subsys_destroy(); + pj_log_pop_indent(); + return status; + } +#endif + + /* Perform NAT detection */ + status = pjsua_detect_nat_type(); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, "NAT type detection failed")); + } + + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Destroy pjsua media subsystem. + */ +pj_status_t pjsua_media_subsys_destroy(unsigned flags) +{ + unsigned i; + + PJ_LOG(4,(THIS_FILE, "Shutting down media..")); + pj_log_push_indent(); + + if (pjsua_var.med_endpt) { + pjsua_aud_subsys_destroy(); + } + + /* Close media transports */ + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + /* TODO: check if we're not allowed to send to network in the + * "flags", and if so do not do TURN allocation... + */ + PJ_UNUSED_ARG(flags); + pjsua_media_channel_deinit(i); + } + + /* Destroy media endpoint. */ + if (pjsua_var.med_endpt) { + +# if PJMEDIA_HAS_VIDEO + pjsua_vid_subsys_destroy(); +# endif + + pjmedia_endpt_destroy(pjsua_var.med_endpt); + pjsua_var.med_endpt = NULL; + + /* Deinitialize sound subsystem */ + // Not necessary, as pjmedia_snd_deinit() should have been called + // in pjmedia_endpt_destroy(). + //pjmedia_snd_deinit(); + } + + /* Reset RTP port */ + next_rtp_port = 0; + + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + +/* + * Create RTP and RTCP socket pair, and possibly resolve their public + * address via STUN. + */ +static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, + pjmedia_sock_info *skinfo) +{ + enum { + RTP_RETRY = 100 + }; + int i; + pj_sockaddr_in bound_addr; + pj_sockaddr_in mapped_addr[2]; + pj_status_t status = PJ_SUCCESS; + char addr_buf[PJ_INET6_ADDRSTRLEN+2]; + pj_sock_t sock[2]; + + /* Make sure STUN server resolution has completed */ + status = resolve_stun_server(PJ_TRUE); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error resolving STUN server", status); + return status; + } + + if (next_rtp_port == 0) + next_rtp_port = (pj_uint16_t)cfg->port; + + if (next_rtp_port == 0) + next_rtp_port = (pj_uint16_t)40000; + + for (i=0; i<2; ++i) + sock[i] = PJ_INVALID_SOCKET; + + bound_addr.sin_addr.s_addr = PJ_INADDR_ANY; + if (cfg->bound_addr.slen) { + status = pj_sockaddr_in_set_str_addr(&bound_addr, &cfg->bound_addr); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to resolve transport bind address", + status); + return status; + } + } + + /* Loop retry to bind RTP and RTCP sockets. */ + for (i=0; i<RTP_RETRY; ++i, next_rtp_port += 2) { + + /* Create RTP socket. */ + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[0]); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "socket() error", status); + return status; + } + + /* Apply QoS to RTP socket, if specified */ + status = pj_sock_apply_qos2(sock[0], cfg->qos_type, + &cfg->qos_params, + 2, THIS_FILE, "RTP socket"); + + /* Bind RTP socket */ + status=pj_sock_bind_in(sock[0], pj_ntohl(bound_addr.sin_addr.s_addr), + next_rtp_port); + if (status != PJ_SUCCESS) { + pj_sock_close(sock[0]); + sock[0] = PJ_INVALID_SOCKET; + continue; + } + + /* Create RTCP socket. */ + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[1]); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "socket() error", status); + pj_sock_close(sock[0]); + return status; + } + + /* Apply QoS to RTCP socket, if specified */ + status = pj_sock_apply_qos2(sock[1], cfg->qos_type, + &cfg->qos_params, + 2, THIS_FILE, "RTCP socket"); + + /* Bind RTCP socket */ + status=pj_sock_bind_in(sock[1], pj_ntohl(bound_addr.sin_addr.s_addr), + (pj_uint16_t)(next_rtp_port+1)); + if (status != PJ_SUCCESS) { + pj_sock_close(sock[0]); + sock[0] = PJ_INVALID_SOCKET; + + pj_sock_close(sock[1]); + sock[1] = PJ_INVALID_SOCKET; + continue; + } + + /* + * If we're configured to use STUN, then find out the mapped address, + * and make sure that the mapped RTCP port is adjacent with the RTP. + */ + if (pjsua_var.stun_srv.addr.sa_family != 0) { + char ip_addr[32]; + pj_str_t stun_srv; + + pj_ansi_strcpy(ip_addr, + pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr)); + stun_srv = pj_str(ip_addr); + + status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock, + &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port), + &stun_srv, pj_ntohs(pjsua_var.stun_srv.ipv4.sin_port), + mapped_addr); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "STUN resolve error", status); + goto on_error; + } + +#if PJSUA_REQUIRE_CONSECUTIVE_RTCP_PORT + if (pj_ntohs(mapped_addr[1].sin_port) == + pj_ntohs(mapped_addr[0].sin_port)+1) + { + /* Success! */ + break; + } + + pj_sock_close(sock[0]); + sock[0] = PJ_INVALID_SOCKET; + + pj_sock_close(sock[1]); + sock[1] = PJ_INVALID_SOCKET; +#else + if (pj_ntohs(mapped_addr[1].sin_port) != + pj_ntohs(mapped_addr[0].sin_port)+1) + { + PJ_LOG(4,(THIS_FILE, + "Note: STUN mapped RTCP port %d is not adjacent" + " to RTP port %d", + pj_ntohs(mapped_addr[1].sin_port), + pj_ntohs(mapped_addr[0].sin_port))); + } + /* Success! */ + break; +#endif + + } else if (cfg->public_addr.slen) { + + status = pj_sockaddr_in_init(&mapped_addr[0], &cfg->public_addr, + (pj_uint16_t)next_rtp_port); + if (status != PJ_SUCCESS) + goto on_error; + + status = pj_sockaddr_in_init(&mapped_addr[1], &cfg->public_addr, + (pj_uint16_t)(next_rtp_port+1)); + if (status != PJ_SUCCESS) + goto on_error; + + break; + + } else { + + if (bound_addr.sin_addr.s_addr == 0) { + pj_sockaddr addr; + + /* Get local IP address. */ + status = pj_gethostip(pj_AF_INET(), &addr); + if (status != PJ_SUCCESS) + goto on_error; + + bound_addr.sin_addr.s_addr = addr.ipv4.sin_addr.s_addr; + } + + for (i=0; i<2; ++i) { + pj_sockaddr_in_init(&mapped_addr[i], NULL, 0); + mapped_addr[i].sin_addr.s_addr = bound_addr.sin_addr.s_addr; + } + + mapped_addr[0].sin_port=pj_htons((pj_uint16_t)next_rtp_port); + mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(next_rtp_port+1)); + break; + } + } + + if (sock[0] == PJ_INVALID_SOCKET) { + PJ_LOG(1,(THIS_FILE, + "Unable to find appropriate RTP/RTCP ports combination")); + goto on_error; + } + + + skinfo->rtp_sock = sock[0]; + pj_memcpy(&skinfo->rtp_addr_name, + &mapped_addr[0], sizeof(pj_sockaddr_in)); + + skinfo->rtcp_sock = sock[1]; + pj_memcpy(&skinfo->rtcp_addr_name, + &mapped_addr[1], sizeof(pj_sockaddr_in)); + + PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s", + pj_sockaddr_print(&skinfo->rtp_addr_name, addr_buf, + sizeof(addr_buf), 3))); + PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s", + pj_sockaddr_print(&skinfo->rtcp_addr_name, addr_buf, + sizeof(addr_buf), 3))); + + next_rtp_port += 2; + return PJ_SUCCESS; + +on_error: + for (i=0; i<2; ++i) { + if (sock[i] != PJ_INVALID_SOCKET) + pj_sock_close(sock[i]); + } + return status; +} + +/* Create normal UDP media transports */ +static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg, + pjsua_call_media *call_med) +{ + pjmedia_sock_info skinfo; + pj_status_t status; + + status = create_rtp_rtcp_sock(cfg, &skinfo); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket", + status); + goto on_error; + } + + status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL, + &skinfo, 0, &call_med->tp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create media transport", + status); + goto on_error; + } + + pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING, + pjsua_var.media_cfg.tx_drop_pct); + + pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING, + pjsua_var.media_cfg.rx_drop_pct); + + call_med->tp_ready = PJ_SUCCESS; + + return PJ_SUCCESS; + +on_error: + if (call_med->tp) + pjmedia_transport_close(call_med->tp); + + return status; +} + +#if DISABLED_FOR_TICKET_1185 +/* Create normal UDP media transports */ +static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg) +{ + unsigned i; + pj_status_t status; + + for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + status = create_udp_media_transport(cfg, &call_med->tp); + if (status != PJ_SUCCESS) + goto on_error; + } + } + + return PJ_SUCCESS; + +on_error: + for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + if (call_med->tp) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + } + } + } + return status; +} +#endif + +static void med_tp_timer_cb(void *user_data) +{ + pjsua_call_media *call_med = (pjsua_call_media*)user_data; + pjsua_call *call = NULL; + pjsip_dialog *dlg = NULL; + + acquire_call("med_tp_timer_cb", call_med->call->index, &call, &dlg); + + call_med->tp_ready = call_med->tp_result; + if (call_med->med_create_cb) + (*call_med->med_create_cb)(call_med, call_med->tp_ready, + call_med->call->secure_level, NULL); + + if (dlg) + pjsip_dlg_dec_lock(dlg); +} + +/* This callback is called when ICE negotiation completes */ +static void on_ice_complete(pjmedia_transport *tp, + pj_ice_strans_op op, + pj_status_t result) +{ + pjsua_call_media *call_med = (pjsua_call_media*)tp->user_data; + + if (!call_med) + return; + + switch (op) { + case PJ_ICE_STRANS_OP_INIT: + call_med->tp_result = result; + pjsua_schedule_timer2(&med_tp_timer_cb, call_med, 1); + break; + case PJ_ICE_STRANS_OP_NEGOTIATION: + if (result != PJ_SUCCESS) { + call_med->state = PJSUA_CALL_MEDIA_ERROR; + call_med->dir = PJMEDIA_DIR_NONE; + + if (call_med->call && pjsua_var.ua_cfg.cb.on_call_media_state) { + pjsua_var.ua_cfg.cb.on_call_media_state(call_med->call->index); + } + } else if (call_med->call) { + /* Send UPDATE if default transport address is different than + * what was advertised (ticket #881) + */ + pjmedia_transport_info tpinfo; + pjmedia_ice_transport_info *ii = NULL; + unsigned i; + + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(tp, &tpinfo); + for (i=0; i<tpinfo.specific_info_cnt; ++i) { + if (tpinfo.spc_info[i].type==PJMEDIA_TRANSPORT_TYPE_ICE) { + ii = (pjmedia_ice_transport_info*) + tpinfo.spc_info[i].buffer; + break; + } + } + + if (ii && ii->role==PJ_ICE_SESS_ROLE_CONTROLLING && + pj_sockaddr_cmp(&tpinfo.sock_info.rtp_addr_name, + &call_med->rtp_addr)) + { + pj_bool_t use_update; + const pj_str_t STR_UPDATE = { "UPDATE", 6 }; + pjsip_dialog_cap_status support_update; + pjsip_dialog *dlg; + + dlg = call_med->call->inv->dlg; + support_update = pjsip_dlg_remote_has_cap(dlg, PJSIP_H_ALLOW, + NULL, &STR_UPDATE); + use_update = (support_update == PJSIP_DIALOG_CAP_SUPPORTED); + + PJ_LOG(4,(THIS_FILE, + "ICE default transport address has changed for " + "call %d, sending %s", + call_med->call->index, + (use_update ? "UPDATE" : "re-INVITE"))); + + if (use_update) + pjsua_call_update(call_med->call->index, 0, NULL); + else + pjsua_call_reinvite(call_med->call->index, 0, NULL); + } + } + break; + case PJ_ICE_STRANS_OP_KEEP_ALIVE: + if (result != PJ_SUCCESS) { + PJ_PERROR(4,(THIS_FILE, result, + "ICE keep alive failure for transport %d:%d", + call_med->call->index, call_med->idx)); + } + if (pjsua_var.ua_cfg.cb.on_call_media_transport_state) { + pjsua_med_tp_state_info info; + + pj_bzero(&info, sizeof(info)); + info.med_idx = call_med->idx; + info.state = call_med->tp_st; + info.status = result; + info.ext_info = &op; + (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)( + call_med->call->index, &info); + } + if (pjsua_var.ua_cfg.cb.on_ice_transport_error) { + pjsua_call_id id = call_med->call->index; + (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result, + NULL); + } + break; + } +} + + +/* Parse "HOST:PORT" format */ +static pj_status_t parse_host_port(const pj_str_t *host_port, + pj_str_t *host, pj_uint16_t *port) +{ + pj_str_t str_port; + + str_port.ptr = pj_strchr(host_port, ':'); + if (str_port.ptr != NULL) { + int iport; + + host->ptr = host_port->ptr; + host->slen = (str_port.ptr - host->ptr); + str_port.ptr++; + str_port.slen = host_port->slen - host->slen - 1; + iport = (int)pj_strtoul(&str_port); + if (iport < 1 || iport > 65535) + return PJ_EINVAL; + *port = (pj_uint16_t)iport; + } else { + *host = *host_port; + *port = 0; + } + + return PJ_SUCCESS; +} + +/* Create ICE media transports (when ice is enabled) */ +static pj_status_t create_ice_media_transport( + const pjsua_transport_config *cfg, + pjsua_call_media *call_med, + pj_bool_t async) +{ + char stunip[PJ_INET6_ADDRSTRLEN]; + pj_ice_strans_cfg ice_cfg; + pjmedia_ice_cb ice_cb; + char name[32]; + unsigned comp_cnt; + pj_status_t status; + + /* Make sure STUN server resolution has completed */ + status = resolve_stun_server(PJ_TRUE); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error resolving STUN server", status); + return status; + } + + /* Create ICE stream transport configuration */ + pj_ice_strans_cfg_default(&ice_cfg); + pj_stun_config_init(&ice_cfg.stun_cfg, &pjsua_var.cp.factory, 0, + pjsip_endpt_get_ioqueue(pjsua_var.endpt), + pjsip_endpt_get_timer_heap(pjsua_var.endpt)); + + ice_cfg.af = pj_AF_INET(); + ice_cfg.resolver = pjsua_var.resolver; + + ice_cfg.opt = pjsua_var.media_cfg.ice_opt; + + /* Configure STUN settings */ + if (pj_sockaddr_has_addr(&pjsua_var.stun_srv)) { + pj_sockaddr_print(&pjsua_var.stun_srv, stunip, sizeof(stunip), 0); + ice_cfg.stun.server = pj_str(stunip); + ice_cfg.stun.port = pj_sockaddr_get_port(&pjsua_var.stun_srv); + } + if (pjsua_var.media_cfg.ice_max_host_cands >= 0) + ice_cfg.stun.max_host_cands = pjsua_var.media_cfg.ice_max_host_cands; + + /* Copy QoS setting to STUN setting */ + ice_cfg.stun.cfg.qos_type = cfg->qos_type; + pj_memcpy(&ice_cfg.stun.cfg.qos_params, &cfg->qos_params, + sizeof(cfg->qos_params)); + + /* Configure TURN settings */ + if (pjsua_var.media_cfg.enable_turn) { + status = parse_host_port(&pjsua_var.media_cfg.turn_server, + &ice_cfg.turn.server, + &ice_cfg.turn.port); + if (status != PJ_SUCCESS || ice_cfg.turn.server.slen == 0) { + PJ_LOG(1,(THIS_FILE, "Invalid TURN server setting")); + return PJ_EINVAL; + } + if (ice_cfg.turn.port == 0) + ice_cfg.turn.port = 3479; + ice_cfg.turn.conn_type = pjsua_var.media_cfg.turn_conn_type; + pj_memcpy(&ice_cfg.turn.auth_cred, + &pjsua_var.media_cfg.turn_auth_cred, + sizeof(ice_cfg.turn.auth_cred)); + + /* Copy QoS setting to TURN setting */ + ice_cfg.turn.cfg.qos_type = cfg->qos_type; + pj_memcpy(&ice_cfg.turn.cfg.qos_params, &cfg->qos_params, + sizeof(cfg->qos_params)); + } + + pj_bzero(&ice_cb, sizeof(pjmedia_ice_cb)); + ice_cb.on_ice_complete = &on_ice_complete; + pj_ansi_snprintf(name, sizeof(name), "icetp%02d", call_med->idx); + call_med->tp_ready = PJ_EPENDING; + + comp_cnt = 1; + if (PJMEDIA_ADVERTISE_RTCP && !pjsua_var.media_cfg.ice_no_rtcp) + ++comp_cnt; + + status = pjmedia_ice_create3(pjsua_var.med_endpt, name, comp_cnt, + &ice_cfg, &ice_cb, 0, call_med, + &call_med->tp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create ICE media transport", + status); + goto on_error; + } + + /* Wait until transport is initialized, or time out */ + if (!async) { + pj_bool_t has_pjsua_lock = PJSUA_LOCK_IS_LOCKED(); + if (has_pjsua_lock) + PJSUA_UNLOCK(); + while (call_med->tp_ready == PJ_EPENDING) { + pjsua_handle_events(100); + } + if (has_pjsua_lock) + PJSUA_LOCK(); + } + + if (async && call_med->tp_ready == PJ_EPENDING) { + return PJ_EPENDING; + } else if (call_med->tp_ready != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing ICE media transport", + call_med->tp_ready); + status = call_med->tp_ready; + goto on_error; + } + + pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING, + pjsua_var.media_cfg.tx_drop_pct); + + pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING, + pjsua_var.media_cfg.rx_drop_pct); + + return PJ_SUCCESS; + +on_error: + if (call_med->tp != NULL) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + } + + return status; +} + +#if DISABLED_FOR_TICKET_1185 +/* Create ICE media transports (when ice is enabled) */ +static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg) +{ + unsigned i; + pj_status_t status; + + for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + status = create_ice_media_transport(cfg, call_med); + if (status != PJ_SUCCESS) + goto on_error; + } + } + + return PJ_SUCCESS; + +on_error: + for (i=0; i < pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + if (call_med->tp) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + } + } + } + return status; +} +#endif + +#if DISABLED_FOR_TICKET_1185 +/* + * Create media transports for all the calls. This function creates + * one UDP media transport for each call. + */ +PJ_DEF(pj_status_t) pjsua_media_transports_create( + const pjsua_transport_config *app_cfg) +{ + pjsua_transport_config cfg; + unsigned i; + pj_status_t status; + + + /* Make sure pjsua_init() has been called */ + PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP); + + PJSUA_LOCK(); + + /* Delete existing media transports */ + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + if (call_med->tp && call_med->tp_auto_del) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + call_med->tp_orig = NULL; + } + } + } + + /* Copy config */ + pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg); + + /* Create the transports */ + if (pjsua_var.media_cfg.enable_ice) { + status = create_ice_media_transports(&cfg); + } else { + status = create_udp_media_transports(&cfg); + } + + /* Set media transport auto_delete to True */ + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + call_med->tp_auto_del = PJ_TRUE; + } + } + + PJSUA_UNLOCK(); + + return status; +} + +/* + * Attach application's created media transports. + */ +PJ_DEF(pj_status_t) pjsua_media_transports_attach(pjsua_media_transport tp[], + unsigned count, + pj_bool_t auto_delete) +{ + unsigned i; + + PJ_ASSERT_RETURN(tp && count==pjsua_var.ua_cfg.max_calls, PJ_EINVAL); + + /* Assign the media transports */ + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + pjsua_call *call = &pjsua_var.calls[i]; + unsigned strm_idx; + + for (strm_idx=0; strm_idx < call->med_cnt; ++strm_idx) { + pjsua_call_media *call_med = &call->media[strm_idx]; + + if (call_med->tp && call_med->tp_auto_del) { + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + call_med->tp_orig = NULL; + } + } + + PJ_TODO(remove_pjsua_media_transports_attach); + + call->media[0].tp = tp[i].transport; + call->media[0].tp_auto_del = auto_delete; + } + + return PJ_SUCCESS; +} +#endif + +/* Go through the list of media in the SDP, find acceptable media, and + * sort them based on the "quality" of the media, and store the indexes + * in the specified array. Media with the best quality will be listed + * first in the array. The quality factors considered currently is + * encryption. + */ +static void sort_media(const pjmedia_sdp_session *sdp, + const pj_str_t *type, + pjmedia_srtp_use use_srtp, + pj_uint8_t midx[], + unsigned *p_count, + unsigned *p_total_count) +{ + unsigned i; + unsigned count = 0; + int score[PJSUA_MAX_CALL_MEDIA]; + + pj_assert(*p_count >= PJSUA_MAX_CALL_MEDIA); + pj_assert(*p_total_count >= PJSUA_MAX_CALL_MEDIA); + + *p_count = 0; + *p_total_count = 0; + for (i=0; i<PJSUA_MAX_CALL_MEDIA; ++i) + score[i] = 1; + + /* Score each media */ + for (i=0; i<sdp->media_count && count<PJSUA_MAX_CALL_MEDIA; ++i) { + const pjmedia_sdp_media *m = sdp->media[i]; + const pjmedia_sdp_conn *c; + + /* Skip different media */ + if (pj_stricmp(&m->desc.media, type) != 0) { + score[count++] = -22000; + continue; + } + + c = m->conn? m->conn : sdp->conn; + + /* Supported transports */ + if (pj_stricmp2(&m->desc.transport, "RTP/SAVP")==0) { + switch (use_srtp) { + case PJMEDIA_SRTP_MANDATORY: + case PJMEDIA_SRTP_OPTIONAL: + ++score[i]; + break; + case PJMEDIA_SRTP_DISABLED: + //--score[i]; + score[i] -= 5; + break; + } + } else if (pj_stricmp2(&m->desc.transport, "RTP/AVP")==0) { + switch (use_srtp) { + case PJMEDIA_SRTP_MANDATORY: + //--score[i]; + score[i] -= 5; + break; + case PJMEDIA_SRTP_OPTIONAL: + /* No change in score */ + break; + case PJMEDIA_SRTP_DISABLED: + ++score[i]; + break; + } + } else { + score[i] -= 10; + } + + /* Is media disabled? */ + if (m->desc.port == 0) + score[i] -= 10; + + /* Is media inactive? */ + if (pjmedia_sdp_media_find_attr2(m, "inactive", NULL) || + pj_strcmp2(&c->addr, "0.0.0.0") == 0) + { + //score[i] -= 10; + score[i] -= 1; + } + + ++count; + } + + /* Created sorted list based on quality */ + for (i=0; i<count; ++i) { + unsigned j; + int best = 0; + + for (j=1; j<count; ++j) { + if (score[j] > score[best]) + best = j; + } + /* Don't put media with negative score, that media is unacceptable + * for us. + */ + midx[i] = (pj_uint8_t)best; + if (score[best] >= 0) + (*p_count)++; + if (score[best] > -22000) + (*p_total_count)++; + + score[best] = -22000; + + } +} + +/* Callback to receive media events */ +pj_status_t call_media_on_event(pjmedia_event *event, + void *user_data) +{ + pjsua_call_media *call_med = (pjsua_call_media*)user_data; + pjsua_call *call = call_med->call; + pj_status_t status = PJ_SUCCESS; + + switch(event->type) { + case PJMEDIA_EVENT_KEYFRAME_MISSING: + if (call->opt.req_keyframe_method & PJSUA_VID_REQ_KEYFRAME_SIP_INFO) + { + pj_timestamp now; + + pj_get_timestamp(&now); + if (pj_elapsed_msec(&call_med->last_req_keyframe, &now) >= + PJSUA_VID_REQ_KEYFRAME_INTERVAL) + { + pjsua_msg_data msg_data; + const pj_str_t SIP_INFO = {"INFO", 4}; + const char *BODY_TYPE = "application/media_control+xml"; + const char *BODY = + "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<media_control><vc_primitive><to_encoder>" + "<picture_fast_update/>" + "</to_encoder></vc_primitive></media_control>"; + + PJ_LOG(4,(THIS_FILE, + "Sending video keyframe request via SIP INFO")); + + pjsua_msg_data_init(&msg_data); + pj_cstr(&msg_data.content_type, BODY_TYPE); + pj_cstr(&msg_data.msg_body, BODY); + status = pjsua_call_send_request(call->index, &SIP_INFO, + &msg_data); + if (status != PJ_SUCCESS) { + pj_perror(3, THIS_FILE, status, + "Failed requesting keyframe via SIP INFO"); + } else { + call_med->last_req_keyframe = now; + } + } + } + break; + + default: + break; + } + + if (pjsua_var.ua_cfg.cb.on_call_media_event && call) { + (*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index, + call_med->idx, event); + } + + return status; +} + +/* Set media transport state and notify the application via the callback. */ +void pjsua_set_media_tp_state(pjsua_call_media *call_med, + pjsua_med_tp_st tp_st) +{ + if (pjsua_var.ua_cfg.cb.on_call_media_transport_state && + call_med->tp_st != tp_st) + { + pjsua_med_tp_state_info info; + + pj_bzero(&info, sizeof(info)); + info.med_idx = call_med->idx; + info.state = tp_st; + info.status = call_med->tp_ready; + (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)( + call_med->call->index, &info); + } + + call_med->tp_st = tp_st; +} + +/* Callback to resume pjsua_call_media_init() after media transport + * creation is completed. + */ +static pj_status_t call_media_init_cb(pjsua_call_media *call_med, + pj_status_t status, + int security_level, + int *sip_err_code) +{ + pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; + pjmedia_transport_info tpinfo; + int err_code = 0; + + if (status != PJ_SUCCESS) + goto on_return; + + pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_ENCODING, + pjsua_var.media_cfg.tx_drop_pct); + + pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING, + pjsua_var.media_cfg.rx_drop_pct); + + if (call_med->tp_st == PJSUA_MED_TP_CREATING) + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE); + + if (!call_med->tp_orig && + pjsua_var.ua_cfg.cb.on_create_media_transport) + { + call_med->use_custom_med_tp = PJ_TRUE; + } else + call_med->use_custom_med_tp = PJ_FALSE; + +#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) + /* This function may be called when SRTP transport already exists + * (e.g: in re-invite, update), don't need to destroy/re-create. + */ + if (!call_med->tp_orig) { + pjmedia_srtp_setting srtp_opt; + pjmedia_transport *srtp = NULL; + + /* Check if SRTP requires secure signaling */ + if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) { + if (security_level < acc->cfg.srtp_secure_signaling) { + err_code = PJSIP_SC_NOT_ACCEPTABLE; + status = PJSIP_ESESSIONINSECURE; + goto on_return; + } + } + + /* Always create SRTP adapter */ + pjmedia_srtp_setting_default(&srtp_opt); + srtp_opt.close_member_tp = PJ_TRUE; + + /* If media session has been ever established, let's use remote's + * preference in SRTP usage policy, especially when it is stricter. + */ + if (call_med->rem_srtp_use > acc->cfg.use_srtp) + srtp_opt.use = call_med->rem_srtp_use; + else + srtp_opt.use = acc->cfg.use_srtp; + + status = pjmedia_transport_srtp_create(pjsua_var.med_endpt, + call_med->tp, + &srtp_opt, &srtp); + if (status != PJ_SUCCESS) { + err_code = PJSIP_SC_INTERNAL_SERVER_ERROR; + goto on_return; + } + + /* Set SRTP as current media transport */ + call_med->tp_orig = call_med->tp; + call_med->tp = srtp; + } +#else + call_med->tp_orig = call_med->tp; + PJ_UNUSED_ARG(security_level); +#endif + + + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); + + pj_sockaddr_cp(&call_med->rtp_addr, &tpinfo.sock_info.rtp_addr_name); + + +on_return: + if (status != PJ_SUCCESS && call_med->tp) { + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL); + pjmedia_transport_close(call_med->tp); + call_med->tp = NULL; + } + + if (sip_err_code) + *sip_err_code = err_code; + + if (call_med->med_init_cb) { + pjsua_med_tp_state_info info; + + pj_bzero(&info, sizeof(info)); + info.status = status; + info.state = call_med->tp_st; + info.med_idx = call_med->idx; + info.sip_err_code = err_code; + (*call_med->med_init_cb)(call_med->call->index, &info); + } + + return status; +} + +/* Initialize the media line */ +pj_status_t pjsua_call_media_init(pjsua_call_media *call_med, + pjmedia_type type, + const pjsua_transport_config *tcfg, + int security_level, + int *sip_err_code, + pj_bool_t async, + pjsua_med_tp_state_cb cb) +{ + pj_status_t status = PJ_SUCCESS; + + /* + * Note: this function may be called when the media already exists + * (e.g. in reinvites, updates, etc.) + */ + call_med->type = type; + + /* Create the media transport for initial call. Here are the possible + * media transport state and the action needed: + * - PJSUA_MED_TP_NULL or call_med->tp==NULL, create one. + * - PJSUA_MED_TP_RUNNING, do nothing. + * - PJSUA_MED_TP_DISABLED, re-init (media_create(), etc). Currently, + * this won't happen as media_channel_update() will always clean up + * the unused transport of a disabled media. + */ + if (call_med->tp == NULL) { +#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + /* While in initial call, set default video devices */ + if (type == PJMEDIA_TYPE_VIDEO) { + status = pjsua_vid_channel_init(call_med); + if (status != PJ_SUCCESS) + return status; + } +#endif + + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_CREATING); + + if (pjsua_var.media_cfg.enable_ice) { + status = create_ice_media_transport(tcfg, call_med, async); + if (async && status == PJ_EPENDING) { + /* We will resume call media initialization in the + * on_ice_complete() callback. + */ + call_med->med_create_cb = &call_media_init_cb; + call_med->med_init_cb = cb; + + return PJ_EPENDING; + } + } else { + status = create_udp_media_transport(tcfg, call_med); + } + + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport")); + return status; + } + + /* Media transport creation completed immediately, so + * we don't need to call the callback. + */ + call_med->med_init_cb = NULL; + + } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) { + /* Media is being reenabled. */ + //pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE); + + pj_assert(!"Currently no media transport reuse"); + } + + return call_media_init_cb(call_med, status, security_level, + sip_err_code); +} + +/* Callback to resume pjsua_media_channel_init() after media transport + * initialization is completed. + */ +static pj_status_t media_channel_init_cb(pjsua_call_id call_id, + const pjsua_med_tp_state_info *info) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + pj_status_t status = (info? info->status : PJ_SUCCESS); + unsigned mi; + + if (info) { + pj_mutex_lock(call->med_ch_mutex); + + /* Set the callback to NULL to indicate that the async operation + * has completed. + */ + call->media_prov[info->med_idx].med_init_cb = NULL; + + /* In case of failure, save the information to be returned + * by the last media transport to finish. + */ + if (info->status != PJ_SUCCESS) + pj_memcpy(&call->med_ch_info, info, sizeof(info)); + + /* Check whether all the call's medias have finished calling their + * callbacks. + */ + for (mi=0; mi < call->med_prov_cnt; ++mi) { + pjsua_call_media *call_med = &call->media_prov[mi]; + + if (call_med->med_init_cb) { + pj_mutex_unlock(call->med_ch_mutex); + return PJ_SUCCESS; + } + + if (call_med->tp_ready != PJ_SUCCESS) + status = call_med->tp_ready; + } + + /* OK, we are called by the last media transport finished. */ + pj_mutex_unlock(call->med_ch_mutex); + } + + if (call->med_ch_mutex) { + pj_mutex_destroy(call->med_ch_mutex); + call->med_ch_mutex = NULL; + } + + if (status != PJ_SUCCESS) { + if (call->med_ch_info.status == PJ_SUCCESS) { + call->med_ch_info.status = status; + call->med_ch_info.sip_err_code = PJSIP_SC_TEMPORARILY_UNAVAILABLE; + } + pjsua_media_prov_clean_up(call_id); + goto on_return; + } + + /* Tell the media transport of a new offer/answer session */ + for (mi=0; mi < call->med_prov_cnt; ++mi) { + pjsua_call_media *call_med = &call->media_prov[mi]; + + /* Note: tp may be NULL if this media line is disabled */ + if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) { + pj_pool_t *tmp_pool = call->async_call.pool_prov; + + if (!tmp_pool) { + tmp_pool = (call->inv? call->inv->pool_prov: + call->async_call.dlg->pool); + } + + if (call_med->use_custom_med_tp) { + unsigned custom_med_tp_flags = 0; + + /* Use custom media transport returned by the application */ + call_med->tp = + (*pjsua_var.ua_cfg.cb.on_create_media_transport) + (call_id, mi, call_med->tp, + custom_med_tp_flags); + if (!call_med->tp) { + status = + PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_TEMPORARILY_UNAVAILABLE); + } + } + + if (call_med->tp) { + status = pjmedia_transport_media_create( + call_med->tp, tmp_pool, + 0, call->async_call.rem_sdp, mi); + } + if (status != PJ_SUCCESS) { + call->med_ch_info.status = status; + call->med_ch_info.med_idx = mi; + call->med_ch_info.state = call_med->tp_st; + call->med_ch_info.sip_err_code = PJSIP_SC_TEMPORARILY_UNAVAILABLE; + pjsua_media_prov_clean_up(call_id); + goto on_return; + } + + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT); + } + } + + call->med_ch_info.status = PJ_SUCCESS; + +on_return: + if (call->med_ch_cb) + (*call->med_ch_cb)(call->index, &call->med_ch_info); + + return status; +} + + +/* Clean up media transports in provisional media that is not used + * by call media. + */ +void pjsua_media_prov_clean_up(pjsua_call_id call_id) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + unsigned i; + + for (i = 0; i < call->med_prov_cnt; ++i) { + pjsua_call_media *call_med = &call->media_prov[i]; + unsigned j; + pj_bool_t used = PJ_FALSE; + + if (call_med->tp == NULL) + continue; + + for (j = 0; j < call->med_cnt; ++j) { + if (call->media[j].tp == call_med->tp) { + used = PJ_TRUE; + break; + } + } + + if (!used) { + if (call_med->tp_st > PJSUA_MED_TP_IDLE) { + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE); + pjmedia_transport_media_stop(call_med->tp); + } + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL); + pjmedia_transport_close(call_med->tp); + call_med->tp = call_med->tp_orig = NULL; + } + } +} + + +pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, + pjsip_role_e role, + int security_level, + pj_pool_t *tmp_pool, + const pjmedia_sdp_session *rem_sdp, + int *sip_err_code, + pj_bool_t async, + pjsua_med_tp_state_cb cb) +{ + const pj_str_t STR_AUDIO = { "audio", 5 }; + const pj_str_t STR_VIDEO = { "video", 5 }; + pjsua_call *call = &pjsua_var.calls[call_id]; + pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; + pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA]; + unsigned maudcnt = PJ_ARRAY_SIZE(maudidx); + unsigned mtotaudcnt = PJ_ARRAY_SIZE(maudidx); + pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA]; + unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx); + unsigned mtotvidcnt = PJ_ARRAY_SIZE(mvididx); + unsigned mi; + pj_bool_t pending_med_tp = PJ_FALSE; + pj_bool_t reinit = PJ_FALSE; + pj_status_t status; + + PJ_UNUSED_ARG(role); + + /* + * Note: this function may be called when the media already exists + * (e.g. in reinvites, updates, etc). + */ + + if (pjsua_get_state() != PJSUA_STATE_RUNNING) + return PJ_EBUSY; + + if (async) { + pj_pool_t *tmppool = (call->inv? call->inv->pool_prov: + call->async_call.dlg->pool); + + status = pj_mutex_create_simple(tmppool, NULL, &call->med_ch_mutex); + if (status != PJ_SUCCESS) + return status; + } + + if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) + reinit = PJ_TRUE; + + PJ_LOG(4,(THIS_FILE, "Call %d: %sinitializing media..", + call_id, (reinit?"re-":"") )); + + pj_log_push_indent(); + + /* Init provisional media state */ + if (call->med_cnt == 0) { + /* New media session, just copy whole from call media state. */ + pj_memcpy(call->media_prov, call->media, sizeof(call->media)); + } else { + /* Clean up any unused transports. Note that when local SDP reoffer + * is rejected by remote, there may be any initialized transports that + * are not used by call media and currently there is no notification + * from PJSIP level regarding the reoffer rejection. + */ + pjsua_media_prov_clean_up(call_id); + + /* Updating media session, copy from call media state. */ + pj_memcpy(call->media_prov, call->media, + sizeof(call->media[0]) * call->med_cnt); + } + call->med_prov_cnt = call->med_cnt; + +#if DISABLED_FOR_TICKET_1185 + /* Return error if media transport has not been created yet + * (e.g. application is starting) + */ + for (i=0; i<call->med_cnt; ++i) { + if (call->media[i].tp == NULL) { + status = PJ_EBUSY; + goto on_error; + } + } +#endif + + /* Get media count for each media type */ + if (rem_sdp) { + sort_media(rem_sdp, &STR_AUDIO, acc->cfg.use_srtp, + maudidx, &maudcnt, &mtotaudcnt); + if (maudcnt==0) { + /* Expecting audio in the offer */ + if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE; + status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE); + goto on_error; + } + +#if PJMEDIA_HAS_VIDEO + sort_media(rem_sdp, &STR_VIDEO, acc->cfg.use_srtp, + mvididx, &mvidcnt, &mtotvidcnt); +#else + mvidcnt = mtotvidcnt = 0; + PJ_UNUSED_ARG(STR_VIDEO); +#endif + + /* Update media count only when remote add any media, this media count + * must never decrease. Also note that we shouldn't apply the media + * count setting (of the call setting) before the SDP negotiation. + */ + if (call->med_prov_cnt < rem_sdp->media_count) + call->med_prov_cnt = PJ_MIN(rem_sdp->media_count, + PJSUA_MAX_CALL_MEDIA); + + call->rem_offerer = PJ_TRUE; + call->rem_aud_cnt = maudcnt; + call->rem_vid_cnt = mvidcnt; + + } else { + + /* If call already established, calculate media count from current + * local active SDP and call setting. Otherwise, calculate media + * count from the call setting only. + */ + if (reinit) { + const pjmedia_sdp_session *sdp; + + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &sdp); + pj_assert(status == PJ_SUCCESS); + + sort_media(sdp, &STR_AUDIO, acc->cfg.use_srtp, + maudidx, &maudcnt, &mtotaudcnt); + pj_assert(maudcnt > 0); + + sort_media(sdp, &STR_VIDEO, acc->cfg.use_srtp, + mvididx, &mvidcnt, &mtotvidcnt); + + /* Call setting may add or remove media. Adding media is done by + * enabling any disabled/port-zeroed media first, then adding new + * media whenever needed. Removing media is done by disabling + * media with the lowest 'quality'. + */ + + /* Check if we need to add new audio */ + if (maudcnt < call->opt.aud_cnt && + mtotaudcnt < call->opt.aud_cnt) + { + for (mi = 0; mi < call->opt.aud_cnt - mtotaudcnt; ++mi) + maudidx[maudcnt++] = (pj_uint8_t)call->med_prov_cnt++; + + mtotaudcnt = call->opt.aud_cnt; + } + maudcnt = call->opt.aud_cnt; + + /* Check if we need to add new video */ + if (mvidcnt < call->opt.vid_cnt && + mtotvidcnt < call->opt.vid_cnt) + { + for (mi = 0; mi < call->opt.vid_cnt - mtotvidcnt; ++mi) + mvididx[mvidcnt++] = (pj_uint8_t)call->med_prov_cnt++; + + mtotvidcnt = call->opt.vid_cnt; + } + mvidcnt = call->opt.vid_cnt; + + } else { + + maudcnt = mtotaudcnt = call->opt.aud_cnt; + for (mi=0; mi<maudcnt; ++mi) { + maudidx[mi] = (pj_uint8_t)mi; + } + mvidcnt = mtotvidcnt = call->opt.vid_cnt; + for (mi=0; mi<mvidcnt; ++mi) { + mvididx[mi] = (pj_uint8_t)(maudcnt + mi); + } + call->med_prov_cnt = maudcnt + mvidcnt; + + /* Need to publish supported media? */ + if (call->opt.flag & PJSUA_CALL_INCLUDE_DISABLED_MEDIA) { + if (mtotaudcnt == 0) { + mtotaudcnt = 1; + maudidx[0] = (pj_uint8_t)call->med_prov_cnt++; + } +#if PJMEDIA_HAS_VIDEO + if (mtotvidcnt == 0) { + mtotvidcnt = 1; + mvididx[0] = (pj_uint8_t)call->med_prov_cnt++; + } +#endif + } + } + + call->rem_offerer = PJ_FALSE; + } + + if (call->med_prov_cnt == 0) { + /* Expecting at least one media */ + if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE; + status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE_HERE); + goto on_error; + } + + if (async) { + call->med_ch_cb = cb; + } + + if (rem_sdp) { + call->async_call.rem_sdp = + pjmedia_sdp_session_clone(call->inv->pool_prov, rem_sdp); + } else { + call->async_call.rem_sdp = NULL; + } + + call->async_call.pool_prov = tmp_pool; + + /* Initialize each media line */ + for (mi=0; mi < call->med_prov_cnt; ++mi) { + pjsua_call_media *call_med = &call->media_prov[mi]; + pj_bool_t enabled = PJ_FALSE; + pjmedia_type media_type = PJMEDIA_TYPE_UNKNOWN; + + if (pj_memchr(maudidx, mi, mtotaudcnt * sizeof(maudidx[0]))) { + media_type = PJMEDIA_TYPE_AUDIO; + if (call->opt.aud_cnt && + pj_memchr(maudidx, mi, maudcnt * sizeof(maudidx[0]))) + { + enabled = PJ_TRUE; + } + } else if (pj_memchr(mvididx, mi, mtotvidcnt * sizeof(mvididx[0]))) { + media_type = PJMEDIA_TYPE_VIDEO; + if (call->opt.vid_cnt && + pj_memchr(mvididx, mi, mvidcnt * sizeof(mvididx[0]))) + { + enabled = PJ_TRUE; + } + } + + if (enabled) { + status = pjsua_call_media_init(call_med, media_type, + &acc->cfg.rtp_cfg, + security_level, sip_err_code, + async, + (async? &media_channel_init_cb: + NULL)); + if (status == PJ_EPENDING) { + pending_med_tp = PJ_TRUE; + } else if (status != PJ_SUCCESS) { + if (pending_med_tp) { + /* Save failure information. */ + call_med->tp_ready = status; + pj_bzero(&call->med_ch_info, sizeof(call->med_ch_info)); + call->med_ch_info.status = status; + call->med_ch_info.state = call_med->tp_st; + call->med_ch_info.med_idx = call_med->idx; + if (sip_err_code) + call->med_ch_info.sip_err_code = *sip_err_code; + + /* We will return failure in the callback later. */ + return PJ_EPENDING; + } + + pjsua_media_prov_clean_up(call_id); + goto on_error; + } + } else { + /* By convention, the media is disabled if transport is NULL + * or transport state is PJSUA_MED_TP_DISABLED. + */ + if (call_med->tp) { + // Don't close transport here, as SDP negotiation has not been + // done and stream may be still active. Once SDP negotiation + // is done (channel_update() invoked), this transport will be + // closed there. + //pjmedia_transport_close(call_med->tp); + //call_med->tp = NULL; + pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT || + call_med->tp_st == PJSUA_MED_TP_RUNNING); + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED); + } + + /* Put media type just for info */ + call_med->type = media_type; + } + } + + call->audio_idx = maudidx[0]; + + PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d", + call->audio_idx, call->index)); + + if (pending_med_tp) { + /* We shouldn't use temporary pool anymore. */ + call->async_call.pool_prov = NULL; + /* We have a pending media transport initialization. */ + pj_log_pop_indent(); + return PJ_EPENDING; + } + + /* Media transport initialization completed immediately, so + * we don't need to call the callback. + */ + call->med_ch_cb = NULL; + + status = media_channel_init_cb(call_id, NULL); + if (status != PJ_SUCCESS && sip_err_code) + *sip_err_code = call->med_ch_info.sip_err_code; + + pj_log_pop_indent(); + return status; + +on_error: + if (call->med_ch_mutex) { + pj_mutex_destroy(call->med_ch_mutex); + call->med_ch_mutex = NULL; + } + + pj_log_pop_indent(); + return status; +} + + +/* Create SDP based on the current media channel. Note that, this function + * will not modify the media channel, so when receiving new offer or + * updating media count (via call setting), media channel must be reinit'd + * (using pjsua_media_channel_init()) first before calling this function. + */ +pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, + pj_pool_t *pool, + const pjmedia_sdp_session *rem_sdp, + pjmedia_sdp_session **p_sdp, + int *sip_err_code) +{ + enum { MAX_MEDIA = PJSUA_MAX_CALL_MEDIA }; + pjmedia_sdp_session *sdp; + pj_sockaddr origin; + pjsua_call *call = &pjsua_var.calls[call_id]; + pjmedia_sdp_neg_state sdp_neg_state = PJMEDIA_SDP_NEG_STATE_NULL; + unsigned mi; + unsigned tot_bandw_tias = 0; + pj_status_t status; + + if (pjsua_get_state() != PJSUA_STATE_RUNNING) + return PJ_EBUSY; + +#if 0 + // This function should not really change the media channel. + if (rem_sdp) { + /* If this is a re-offer, let's re-initialize media as remote may + * add or remove media + */ + if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) { + status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS, + call->secure_level, pool, + rem_sdp, sip_err_code, + PJ_FALSE, NULL); + if (status != PJ_SUCCESS) + return status; + } + } else { + /* Audio is first in our offer, by convention */ + // The audio_idx should not be changed here, as this function may be + // called in generating re-offer and the current active audio index + // can be anywhere. + //call->audio_idx = 0; + } +#endif + +#if 0 + // Since r3512, old-style hold should have got transport, created by + // pjsua_media_channel_init() in initial offer/answer or remote reoffer. + /* Create media if it's not created. This could happen when call is + * currently on-hold (with the old style hold) + */ + if (call->media[call->audio_idx].tp == NULL) { + pjsip_role_e role; + role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC); + status = pjsua_media_channel_init(call_id, role, call->secure_level, + pool, rem_sdp, sip_err_code); + if (status != PJ_SUCCESS) + return status; + } +#endif + + /* Get SDP negotiator state */ + if (call->inv && call->inv->neg) + sdp_neg_state = pjmedia_sdp_neg_get_state(call->inv->neg); + + /* Get one address to use in the origin field */ + pj_bzero(&origin, sizeof(origin)); + for (mi=0; mi<call->med_prov_cnt; ++mi) { + pjmedia_transport_info tpinfo; + + if (call->media_prov[mi].tp == NULL) + continue; + + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call->media_prov[mi].tp, &tpinfo); + pj_sockaddr_cp(&origin, &tpinfo.sock_info.rtp_addr_name); + break; + } + + /* Create the base (blank) SDP */ + status = pjmedia_endpt_create_base_sdp(pjsua_var.med_endpt, pool, NULL, + &origin, &sdp); + if (status != PJ_SUCCESS) + return status; + + /* Process each media line */ + for (mi=0; mi<call->med_prov_cnt; ++mi) { + pjsua_call_media *call_med = &call->media_prov[mi]; + pjmedia_sdp_media *m = NULL; + pjmedia_transport_info tpinfo; + unsigned i; + + if (rem_sdp && mi >= rem_sdp->media_count) { + /* Remote might have removed some media lines. */ + break; + } + + if (call_med->tp == NULL || call_med->tp_st == PJSUA_MED_TP_DISABLED) + { + /* + * This media is disabled. Just create a valid SDP with zero + * port. + */ + if (rem_sdp) { + /* Just clone the remote media and deactivate it */ + m = pjmedia_sdp_media_clone_deactivate(pool, + rem_sdp->media[mi]); + } else { + m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); + m->desc.transport = pj_str("RTP/AVP"); + m->desc.fmt_count = 1; + m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); + m->conn->net_type = pj_str("IN"); + m->conn->addr_type = pj_str("IP4"); + m->conn->addr = pj_str("127.0.0.1"); + + switch (call_med->type) { + case PJMEDIA_TYPE_AUDIO: + m->desc.media = pj_str("audio"); + m->desc.fmt[0] = pj_str("0"); + break; + case PJMEDIA_TYPE_VIDEO: + m->desc.media = pj_str("video"); + m->desc.fmt[0] = pj_str("31"); + break; + default: + /* This must be us generating re-offer, and some unknown + * media may exist, so just clone from active local SDP + * (and it should have been deactivated already). + */ + pj_assert(call->inv && call->inv->neg && + sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE); + { + const pjmedia_sdp_session *s_; + pjmedia_sdp_neg_get_active_local(call->inv->neg, &s_); + + pj_assert(mi < s_->media_count); + m = pjmedia_sdp_media_clone(pool, s_->media[mi]); + m->desc.port = 0; + } + break; + } + } + + sdp->media[sdp->media_count++] = m; + continue; + } + + /* Get transport address info */ + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); + + /* Ask pjmedia endpoint to create SDP media line */ + switch (call_med->type) { + case PJMEDIA_TYPE_AUDIO: + status = pjmedia_endpt_create_audio_sdp(pjsua_var.med_endpt, pool, + &tpinfo.sock_info, 0, &m); + break; +#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + case PJMEDIA_TYPE_VIDEO: + status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool, + &tpinfo.sock_info, 0, &m); + break; +#endif + default: + pj_assert(!"Invalid call_med media type"); + return PJ_EBUG; + } + + if (status != PJ_SUCCESS) + return status; + + sdp->media[sdp->media_count++] = m; + + /* Give to transport */ + status = pjmedia_transport_encode_sdp(call_med->tp, pool, + sdp, rem_sdp, mi); + if (status != PJ_SUCCESS) { + if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE; + return status; + } + + /* Copy c= line of the first media to session level, + * if there's none. + */ + if (sdp->conn == NULL) { + sdp->conn = pjmedia_sdp_conn_clone(pool, m->conn); + } + + + /* Find media bandwidth info */ + for (i = 0; i < m->bandw_count; ++i) { + const pj_str_t STR_BANDW_MODIFIER_TIAS = { "TIAS", 4 }; + if (!pj_stricmp(&m->bandw[i]->modifier, &STR_BANDW_MODIFIER_TIAS)) + { + tot_bandw_tias += m->bandw[i]->value; + break; + } + } + } + + /* Add NAT info in the SDP */ + if (pjsua_var.ua_cfg.nat_type_in_sdp) { + pjmedia_sdp_attr *a; + pj_str_t value; + char nat_info[80]; + + value.ptr = nat_info; + if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) { + value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info), + "%d", pjsua_var.nat_type); + } else { + const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type); + value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info), + "%d %s", + pjsua_var.nat_type, + type_name); + } + + a = pjmedia_sdp_attr_create(pool, "X-nat", &value); + + pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a); + + } + + + /* Add bandwidth info in session level using bandwidth modifier "AS". */ + if (tot_bandw_tias) { + unsigned bandw; + const pj_str_t STR_BANDW_MODIFIER_AS = { "AS", 2 }; + pjmedia_sdp_bandw *b; + + /* AS bandwidth = RTP bitrate + RTCP bitrate. + * RTP bitrate = payload bitrate (total TIAS) + overheads (~16kbps). + * RTCP bitrate = est. 5% of RTP bitrate. + * Note that AS bandwidth is in kbps. + */ + bandw = tot_bandw_tias + 16000; + bandw += bandw * 5 / 100; + b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw); + b->modifier = STR_BANDW_MODIFIER_AS; + b->value = bandw / 1000; + sdp->bandw[sdp->bandw_count++] = b; + } + + +#if DISABLED_FOR_TICKET_1185 && defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) + /* Check if SRTP is in optional mode and configured to use duplicated + * media, i.e: secured and unsecured version, in the SDP offer. + */ + if (!rem_sdp && + pjsua_var.acc[call->acc_id].cfg.use_srtp == PJMEDIA_SRTP_OPTIONAL && + pjsua_var.acc[call->acc_id].cfg.srtp_optional_dup_offer) + { + unsigned i; + + for (i = 0; i < sdp->media_count; ++i) { + pjmedia_sdp_media *m = sdp->media[i]; + + /* Check if this media is unsecured but has SDP "crypto" + * attribute. + */ + if (pj_stricmp2(&m->desc.transport, "RTP/AVP") == 0 && + pjmedia_sdp_media_find_attr2(m, "crypto", NULL) != NULL) + { + if (i == (unsigned)call->audio_idx && + sdp_neg_state == PJMEDIA_SDP_NEG_STATE_DONE) + { + /* This is a session update, and peer has chosen the + * unsecured version, so let's make this unsecured too. + */ + pjmedia_sdp_media_remove_all_attr(m, "crypto"); + } else { + /* This is new offer, duplicate media so we'll have + * secured (with "RTP/SAVP" transport) and and unsecured + * versions. + */ + pjmedia_sdp_media *new_m; + + /* Duplicate this media and apply secured transport */ + new_m = pjmedia_sdp_media_clone(pool, m); + pj_strdup2(pool, &new_m->desc.transport, "RTP/SAVP"); + + /* Remove the "crypto" attribute in the unsecured media */ + pjmedia_sdp_media_remove_all_attr(m, "crypto"); + + /* Insert the new media before the unsecured media */ + if (sdp->media_count < PJMEDIA_MAX_SDP_MEDIA) { + pj_array_insert(sdp->media, sizeof(new_m), + sdp->media_count, i, &new_m); + ++sdp->media_count; + ++i; + } + } + } + } + } +#endif + + call->rem_offerer = (rem_sdp != NULL); + + /* Notify application */ + if (pjsua_var.ua_cfg.cb.on_call_sdp_created) { + (*pjsua_var.ua_cfg.cb.on_call_sdp_created)(call_id, sdp, + pool, rem_sdp); + } + + *p_sdp = sdp; + return PJ_SUCCESS; +} + + +static void stop_media_session(pjsua_call_id call_id) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + unsigned mi; + + pj_log_push_indent(); + + for (mi=0; mi<call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; + + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + pjsua_aud_stop_stream(call_med); + } + +#if PJMEDIA_HAS_VIDEO + else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + pjsua_vid_stop_stream(call_med); + } +#endif + + PJ_LOG(4,(THIS_FILE, "Media session call%02d:%d is destroyed", + call_id, mi)); + call_med->prev_state = call_med->state; + call_med->state = PJSUA_CALL_MEDIA_NONE; + + /* Try to sync recent changes to provisional media */ + if (mi<call->med_prov_cnt && call->media_prov[mi].tp==call_med->tp) + { + pjsua_call_media *prov_med = &call->media_prov[mi]; + + /* Media state */ + prov_med->prev_state = call_med->prev_state; + prov_med->state = call_med->state; + + /* RTP seq/ts */ + prov_med->rtp_tx_seq_ts_set = call_med->rtp_tx_seq_ts_set; + prov_med->rtp_tx_seq = call_med->rtp_tx_seq; + prov_med->rtp_tx_ts = call_med->rtp_tx_ts; + + /* Stream */ + if (call_med->type == PJMEDIA_TYPE_AUDIO) { + prov_med->strm.a.conf_slot = call_med->strm.a.conf_slot; + prov_med->strm.a.stream = call_med->strm.a.stream; + } +#if PJMEDIA_HAS_VIDEO + else if (call_med->type == PJMEDIA_TYPE_VIDEO) { + prov_med->strm.v.cap_win_id = call_med->strm.v.cap_win_id; + prov_med->strm.v.rdr_win_id = call_med->strm.v.rdr_win_id; + prov_med->strm.v.stream = call_med->strm.v.stream; + } +#endif + } + } + + pj_log_pop_indent(); +} + +pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + unsigned mi; + + for (mi=0; mi<call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; + + if (call_med->tp_st == PJSUA_MED_TP_CREATING) { + /* We will do the deinitialization after media transport + * creation is completed. + */ + call->async_call.med_ch_deinit = PJ_TRUE; + return PJ_SUCCESS; + } + } + + PJ_LOG(4,(THIS_FILE, "Call %d: deinitializing media..", call_id)); + pj_log_push_indent(); + + stop_media_session(call_id); + + /* Clean up media transports */ + pjsua_media_prov_clean_up(call_id); + call->med_prov_cnt = 0; + for (mi=0; mi<call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; + + if (call_med->tp_st > PJSUA_MED_TP_IDLE) { + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_IDLE); + pjmedia_transport_media_stop(call_med->tp); + } + + if (call_med->tp) { + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL); + pjmedia_transport_close(call_med->tp); + call_med->tp = call_med->tp_orig = NULL; + } + call_med->tp_orig = NULL; + } + + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + + +pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *remote_sdp) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; + pj_pool_t *tmp_pool = call->inv->pool_prov; + unsigned mi; + pj_bool_t got_media = PJ_FALSE; + pj_status_t status = PJ_SUCCESS; + + const pj_str_t STR_AUDIO = { "audio", 5 }; + const pj_str_t STR_VIDEO = { "video", 5 }; + pj_uint8_t maudidx[PJSUA_MAX_CALL_MEDIA]; + unsigned maudcnt = PJ_ARRAY_SIZE(maudidx); + unsigned mtotaudcnt = PJ_ARRAY_SIZE(maudidx); + pj_uint8_t mvididx[PJSUA_MAX_CALL_MEDIA]; + unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx); + unsigned mtotvidcnt = PJ_ARRAY_SIZE(mvididx); + pj_bool_t need_renego_sdp = PJ_FALSE; + + if (pjsua_get_state() != PJSUA_STATE_RUNNING) + return PJ_EBUSY; + + PJ_LOG(4,(THIS_FILE, "Call %d: updating media..", call_id)); + pj_log_push_indent(); + + /* Destroy existing media session, if any. */ + stop_media_session(call->index); + + /* Call media count must be at least equal to SDP media. Note that + * it may not be equal when remote removed any SDP media line. + */ + pj_assert(call->med_prov_cnt >= local_sdp->media_count); + + /* Reset audio_idx first */ + call->audio_idx = -1; + + /* Sort audio/video based on "quality" */ + sort_media(local_sdp, &STR_AUDIO, acc->cfg.use_srtp, + maudidx, &maudcnt, &mtotaudcnt); +#if PJMEDIA_HAS_VIDEO + sort_media(local_sdp, &STR_VIDEO, acc->cfg.use_srtp, + mvididx, &mvidcnt, &mtotvidcnt); +#else + PJ_UNUSED_ARG(STR_VIDEO); + mvidcnt = mtotvidcnt = 0; +#endif + + /* Applying media count limitation. Note that in generating SDP answer, + * no media count limitation applied, as we didn't know yet which media + * would pass the SDP negotiation. + */ + if (maudcnt > call->opt.aud_cnt || mvidcnt > call->opt.vid_cnt) + { + pjmedia_sdp_session *local_sdp2; + + maudcnt = PJ_MIN(maudcnt, call->opt.aud_cnt); + mvidcnt = PJ_MIN(mvidcnt, call->opt.vid_cnt); + local_sdp2 = pjmedia_sdp_session_clone(tmp_pool, local_sdp); + + for (mi=0; mi < local_sdp2->media_count; ++mi) { + pjmedia_sdp_media *m = local_sdp2->media[mi]; + + if (m->desc.port == 0 || + pj_memchr(maudidx, mi, maudcnt*sizeof(maudidx[0])) || + pj_memchr(mvididx, mi, mvidcnt*sizeof(mvididx[0]))) + { + continue; + } + + /* Deactivate this media */ + pjmedia_sdp_media_deactivate(tmp_pool, m); + } + + local_sdp = local_sdp2; + need_renego_sdp = PJ_TRUE; + } + + /* Process each media stream */ + for (mi=0; mi < call->med_prov_cnt; ++mi) { + pjsua_call_media *call_med = &call->media_prov[mi]; + + if (mi >= local_sdp->media_count || + mi >= remote_sdp->media_count) + { + /* This may happen when remote removed any SDP media lines in + * its re-offer. + */ + if (call_med->tp) { + /* Close the media transport */ + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL); + pjmedia_transport_close(call_med->tp); + call_med->tp = call_med->tp_orig = NULL; + } + continue; +#if 0 + /* Something is wrong */ + PJ_LOG(1,(THIS_FILE, "Error updating media for call %d: " + "invalid media index %d in SDP", call_id, mi)); + status = PJMEDIA_SDP_EINSDP; + goto on_error; +#endif + } + + if (call_med->type==PJMEDIA_TYPE_AUDIO) { + pjmedia_stream_info the_si, *si = &the_si; + + status = pjmedia_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt, + local_sdp, remote_sdp, mi); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "pjmedia_stream_info_from_sdp() failed " + "for call_id %d media %d", + call_id, mi)); + continue; + } + + /* Check if no media is active */ + if (si->dir == PJMEDIA_DIR_NONE) { + /* Update call media state and direction */ + call_med->state = PJSUA_CALL_MEDIA_NONE; + call_med->dir = PJMEDIA_DIR_NONE; + + } else { + pjmedia_transport_info tp_info; + + /* Start/restart media transport based on info in SDP */ + status = pjmedia_transport_media_start(call_med->tp, + tmp_pool, local_sdp, + remote_sdp, mi); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "pjmedia_transport_media_start() failed " + "for call_id %d media %d", + call_id, mi)); + continue; + } + + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING); + + /* Get remote SRTP usage policy */ + pjmedia_transport_info_init(&tp_info); + pjmedia_transport_get_info(call_med->tp, &tp_info); + if (tp_info.specific_info_cnt > 0) { + unsigned i; + for (i = 0; i < tp_info.specific_info_cnt; ++i) { + if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP) + { + pjmedia_srtp_info *srtp_info = + (pjmedia_srtp_info*) tp_info.spc_info[i].buffer; + + call_med->rem_srtp_use = srtp_info->peer_use; + break; + } + } + } + + /* Call media direction */ + call_med->dir = si->dir; + + /* Call media state */ + if (call->local_hold) + call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD; + else if (call_med->dir == PJMEDIA_DIR_DECODING) + call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD; + else + call_med->state = PJSUA_CALL_MEDIA_ACTIVE; + } + + /* Call implementation */ + status = pjsua_aud_channel_update(call_med, tmp_pool, si, + local_sdp, remote_sdp); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "pjsua_aud_channel_update() failed " + "for call_id %d media %d", + call_id, mi)); + continue; + } + + /* Print info. */ + if (status == PJ_SUCCESS) { + char info[80]; + int info_len = 0; + int len; + const char *dir; + + switch (si->dir) { + case PJMEDIA_DIR_NONE: + dir = "inactive"; + break; + case PJMEDIA_DIR_ENCODING: + dir = "sendonly"; + break; + case PJMEDIA_DIR_DECODING: + dir = "recvonly"; + break; + case PJMEDIA_DIR_ENCODING_DECODING: + dir = "sendrecv"; + break; + default: + dir = "unknown"; + break; + } + len = pj_ansi_sprintf( info+info_len, + ", stream #%d: %.*s (%s)", mi, + (int)si->fmt.encoding_name.slen, + si->fmt.encoding_name.ptr, + dir); + if (len > 0) + info_len += len; + PJ_LOG(4,(THIS_FILE,"Audio updated%s", info)); + } + + + if (call->audio_idx==-1 && status==PJ_SUCCESS && + si->dir != PJMEDIA_DIR_NONE) + { + call->audio_idx = mi; + } + +#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + } else if (call_med->type==PJMEDIA_TYPE_VIDEO) { + pjmedia_vid_stream_info the_si, *si = &the_si; + + status = pjmedia_vid_stream_info_from_sdp(si, tmp_pool, pjsua_var.med_endpt, + local_sdp, remote_sdp, mi); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "pjmedia_vid_stream_info_from_sdp() failed " + "for call_id %d media %d", + call_id, mi)); + continue; + } + + /* Check if no media is active */ + if (si->dir == PJMEDIA_DIR_NONE) { + /* Update call media state and direction */ + call_med->state = PJSUA_CALL_MEDIA_NONE; + call_med->dir = PJMEDIA_DIR_NONE; + + } else { + pjmedia_transport_info tp_info; + + /* Start/restart media transport */ + status = pjmedia_transport_media_start(call_med->tp, + tmp_pool, local_sdp, + remote_sdp, mi); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "pjmedia_transport_media_start() failed " + "for call_id %d media %d", + call_id, mi)); + continue; + } + + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING); + + /* Get remote SRTP usage policy */ + pjmedia_transport_info_init(&tp_info); + pjmedia_transport_get_info(call_med->tp, &tp_info); + if (tp_info.specific_info_cnt > 0) { + unsigned i; + for (i = 0; i < tp_info.specific_info_cnt; ++i) { + if (tp_info.spc_info[i].type == + PJMEDIA_TRANSPORT_TYPE_SRTP) + { + pjmedia_srtp_info *sri; + sri=(pjmedia_srtp_info*)tp_info.spc_info[i].buffer; + call_med->rem_srtp_use = sri->peer_use; + break; + } + } + } + + /* Call media direction */ + call_med->dir = si->dir; + + /* Call media state */ + if (call->local_hold) + call_med->state = PJSUA_CALL_MEDIA_LOCAL_HOLD; + else if (call_med->dir == PJMEDIA_DIR_DECODING) + call_med->state = PJSUA_CALL_MEDIA_REMOTE_HOLD; + else + call_med->state = PJSUA_CALL_MEDIA_ACTIVE; + } + + status = pjsua_vid_channel_update(call_med, tmp_pool, si, + local_sdp, remote_sdp); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "pjsua_vid_channel_update() failed " + "for call_id %d media %d", + call_id, mi)); + continue; + } + + /* Print info. */ + { + char info[80]; + int info_len = 0; + int len; + const char *dir; + + switch (si->dir) { + case PJMEDIA_DIR_NONE: + dir = "inactive"; + break; + case PJMEDIA_DIR_ENCODING: + dir = "sendonly"; + break; + case PJMEDIA_DIR_DECODING: + dir = "recvonly"; + break; + case PJMEDIA_DIR_ENCODING_DECODING: + dir = "sendrecv"; + break; + default: + dir = "unknown"; + break; + } + len = pj_ansi_sprintf( info+info_len, + ", stream #%d: %.*s (%s)", mi, + (int)si->codec_info.encoding_name.slen, + si->codec_info.encoding_name.ptr, + dir); + if (len > 0) + info_len += len; + PJ_LOG(4,(THIS_FILE,"Video updated%s", info)); + } + +#endif + } else { + status = PJMEDIA_EINVALIMEDIATYPE; + } + + /* Close the transport of deactivated media, need this here as media + * can be deactivated by the SDP negotiation and the max media count + * (account) setting. + */ + if (local_sdp->media[mi]->desc.port==0 && call_med->tp) { + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL); + pjmedia_transport_close(call_med->tp); + call_med->tp = call_med->tp_orig = NULL; + } + + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, "Error updating media call%02d:%d", + call_id, mi)); + } else { + got_media = PJ_TRUE; + } + } + + /* Update call media from provisional media */ + call->med_cnt = call->med_prov_cnt; + pj_memcpy(call->media, call->media_prov, + sizeof(call->media_prov[0]) * call->med_prov_cnt); + + /* Perform SDP re-negotiation if needed. */ + if (got_media && need_renego_sdp) { + pjmedia_sdp_neg *neg = call->inv->neg; + + /* This should only happen when we are the answerer. */ + PJ_ASSERT_RETURN(neg && !pjmedia_sdp_neg_was_answer_remote(neg), + PJMEDIA_SDPNEG_EINSTATE); + + status = pjmedia_sdp_neg_set_remote_offer(tmp_pool, neg, remote_sdp); + if (status != PJ_SUCCESS) + goto on_error; + + status = pjmedia_sdp_neg_set_local_answer(tmp_pool, neg, local_sdp); + if (status != PJ_SUCCESS) + goto on_error; + + status = pjmedia_sdp_neg_negotiate(tmp_pool, neg, 0); + if (status != PJ_SUCCESS) + goto on_error; + } + + pj_log_pop_indent(); + return (got_media? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA); + +on_error: + pj_log_pop_indent(); + return status; +} + +/***************************************************************************** + * Codecs. + */ + +/* + * Enum all supported codecs in the system. + */ +PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[], + unsigned *p_count ) +{ + pjmedia_codec_mgr *codec_mgr; + pjmedia_codec_info info[32]; + unsigned i, count, prio[32]; + pj_status_t status; + + codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt); + count = PJ_ARRAY_SIZE(info); + status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio); + if (status != PJ_SUCCESS) { + *p_count = 0; + return status; + } + + if (count > *p_count) count = *p_count; + + for (i=0; i<count; ++i) { + pj_bzero(&id[i], sizeof(pjsua_codec_info)); + + pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_)); + id[i].codec_id = pj_str(id[i].buf_); + id[i].priority = (pj_uint8_t) prio[i]; + } + + *p_count = count; + + return PJ_SUCCESS; +} + + +/* + * Change codec priority. + */ +PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id, + pj_uint8_t priority ) +{ + const pj_str_t all = { NULL, 0 }; + pjmedia_codec_mgr *codec_mgr; + + codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt); + + if (codec_id->slen==1 && *codec_id->ptr=='*') + codec_id = &all; + + return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id, + priority); +} + + +/* + * Get codec parameters. + */ +PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id, + pjmedia_codec_param *param ) +{ + const pj_str_t all = { NULL, 0 }; + const pjmedia_codec_info *info; + pjmedia_codec_mgr *codec_mgr; + unsigned count = 1; + pj_status_t status; + + codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt); + + if (codec_id->slen==1 && *codec_id->ptr=='*') + codec_id = &all; + + status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id, + &count, &info, NULL); + if (status != PJ_SUCCESS) + return status; + + if (count != 1) + return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND); + + status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param); + return status; +} + + +/* + * Set codec parameters. + */ +PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id, + const pjmedia_codec_param *param) +{ + const pjmedia_codec_info *info[2]; + pjmedia_codec_mgr *codec_mgr; + unsigned count = 2; + pj_status_t status; + + codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt); + + status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id, + &count, info, NULL); + if (status != PJ_SUCCESS) + return status; + + /* Codec ID should be specific, except for G.722.1 */ + if (count > 1 && + pj_strnicmp2(codec_id, "G7221/16", 8) != 0 && + pj_strnicmp2(codec_id, "G7221/32", 8) != 0) + { + pj_assert(!"Codec ID is not specific"); + return PJ_ETOOMANY; + } + + status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param); + return status; +} + + +pj_status_t pjsua_media_apply_xml_control(pjsua_call_id call_id, + const pj_str_t *xml_st) +{ +#if PJMEDIA_HAS_VIDEO + pjsua_call *call = &pjsua_var.calls[call_id]; + const pj_str_t PICT_FAST_UPDATE = {"picture_fast_update", 19}; + + if (pj_strstr(xml_st, &PICT_FAST_UPDATE)) { + unsigned i; + + PJ_LOG(4,(THIS_FILE, "Received keyframe request via SIP INFO")); + + for (i = 0; i < call->med_cnt; ++i) { + pjsua_call_media *cm = &call->media[i]; + if (cm->type != PJMEDIA_TYPE_VIDEO || !cm->strm.v.stream) + continue; + + pjmedia_vid_stream_send_keyframe(cm->strm.v.stream); + } + + return PJ_SUCCESS; + } +#endif + + /* Just to avoid compiler warning of unused var */ + PJ_UNUSED_ARG(call_id); + PJ_UNUSED_ARG(xml_st); + + return PJ_ENOTSUP; +} + diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c new file mode 100644 index 0000000..4551c91 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_pres.c @@ -0,0 +1,2402 @@ +/* $Id: pjsua_pres.c 4186 2012-06-29 01:37:50Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + + +#define THIS_FILE "pjsua_pres.c" + + +static void subscribe_buddy_presence(pjsua_buddy_id buddy_id); +static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id); + + +/* + * Find buddy. + */ +static pjsua_buddy_id find_buddy(const pjsip_uri *uri) +{ + const pjsip_sip_uri *sip_uri; + unsigned i; + + uri = (const pjsip_uri*) pjsip_uri_get_uri((pjsip_uri*)uri); + + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) + return PJSUA_INVALID_ID; + + sip_uri = (const pjsip_sip_uri*) uri; + + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + const pjsua_buddy *b = &pjsua_var.buddy[i]; + + if (!pjsua_buddy_is_valid(i)) + continue; + + if (pj_stricmp(&sip_uri->user, &b->name)==0 && + pj_stricmp(&sip_uri->host, &b->host)==0 && + (sip_uri->port==(int)b->port || (sip_uri->port==0 && b->port==5060))) + { + /* Match */ + return i; + } + } + + return PJSUA_INVALID_ID; +} + +#define LOCK_DIALOG 1 +#define LOCK_PJSUA 2 +#define LOCK_ALL (LOCK_DIALOG | LOCK_PJSUA) + +/* Buddy lock object */ +struct buddy_lock +{ + pjsua_buddy *buddy; + pjsip_dialog *dlg; + pj_uint8_t flag; +}; + +/* Acquire lock to the specified buddy_id */ +pj_status_t lock_buddy(const char *title, + pjsua_buddy_id buddy_id, + struct buddy_lock *lck, + unsigned _unused_) +{ + enum { MAX_RETRY=50 }; + pj_bool_t has_pjsua_lock = PJ_FALSE; + unsigned retry; + + PJ_UNUSED_ARG(_unused_); + + pj_bzero(lck, sizeof(*lck)); + + for (retry=0; retry<MAX_RETRY; ++retry) { + + if (PJSUA_TRY_LOCK() != PJ_SUCCESS) { + pj_thread_sleep(retry/10); + continue; + } + + has_pjsua_lock = PJ_TRUE; + lck->flag = LOCK_PJSUA; + lck->buddy = &pjsua_var.buddy[buddy_id]; + + if (lck->buddy->dlg == NULL) + return PJ_SUCCESS; + + if (pjsip_dlg_try_inc_lock(lck->buddy->dlg) != PJ_SUCCESS) { + lck->flag = 0; + lck->buddy = NULL; + has_pjsua_lock = PJ_FALSE; + PJSUA_UNLOCK(); + pj_thread_sleep(retry/10); + continue; + } + + lck->dlg = lck->buddy->dlg; + lck->flag = LOCK_DIALOG; + PJSUA_UNLOCK(); + + break; + } + + if (lck->flag == 0) { + if (has_pjsua_lock == PJ_FALSE) + PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire PJSUA mutex " + "(possibly system has deadlocked) in %s", + title)); + else + PJ_LOG(1,(THIS_FILE, "Timed-out trying to acquire dialog mutex " + "(possibly system has deadlocked) in %s", + title)); + return PJ_ETIMEDOUT; + } + + return PJ_SUCCESS; +} + +/* Release buddy lock */ +static void unlock_buddy(struct buddy_lock *lck) +{ + if (lck->flag & LOCK_DIALOG) + pjsip_dlg_dec_lock(lck->dlg); + + if (lck->flag & LOCK_PJSUA) + PJSUA_UNLOCK(); +} + + +/* + * Get total number of buddies. + */ +PJ_DEF(unsigned) pjsua_get_buddy_count(void) +{ + return pjsua_var.buddy_cnt; +} + + +/* + * Find buddy. + */ +PJ_DEF(pjsua_buddy_id) pjsua_buddy_find(const pj_str_t *uri_str) +{ + pj_str_t input; + pj_pool_t *pool; + pjsip_uri *uri; + pjsua_buddy_id buddy_id; + + pool = pjsua_pool_create("buddyfind", 512, 512); + pj_strdup_with_null(pool, &input, uri_str); + + uri = pjsip_parse_uri(pool, input.ptr, input.slen, 0); + if (!uri) + buddy_id = PJSUA_INVALID_ID; + else { + PJSUA_LOCK(); + buddy_id = find_buddy(uri); + PJSUA_UNLOCK(); + } + + pj_pool_release(pool); + + return buddy_id; +} + + +/* + * Check if buddy ID is valid. + */ +PJ_DEF(pj_bool_t) pjsua_buddy_is_valid(pjsua_buddy_id buddy_id) +{ + return buddy_id>=0 && buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy) && + pjsua_var.buddy[buddy_id].uri.slen != 0; +} + + +/* + * Enum buddy IDs. + */ +PJ_DEF(pj_status_t) pjsua_enum_buddies( pjsua_buddy_id ids[], + unsigned *count) +{ + unsigned i, c; + + PJ_ASSERT_RETURN(ids && count, PJ_EINVAL); + + PJSUA_LOCK(); + + for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + if (!pjsua_var.buddy[i].uri.slen) + continue; + ids[c] = i; + ++c; + } + + *count = c; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + + +/* + * Get detailed buddy info. + */ +PJ_DEF(pj_status_t) pjsua_buddy_get_info( pjsua_buddy_id buddy_id, + pjsua_buddy_info *info) +{ + unsigned total=0; + struct buddy_lock lck; + pjsua_buddy *buddy; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL); + + pj_bzero(info, sizeof(pjsua_buddy_info)); + + status = lock_buddy("pjsua_buddy_get_info()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + buddy = lck.buddy; + info->id = buddy->index; + if (pjsua_var.buddy[buddy_id].uri.slen == 0) { + unlock_buddy(&lck); + return PJ_SUCCESS; + } + + /* uri */ + info->uri.ptr = info->buf_ + total; + pj_strncpy(&info->uri, &buddy->uri, sizeof(info->buf_)-total); + total += info->uri.slen; + + /* contact */ + info->contact.ptr = info->buf_ + total; + pj_strncpy(&info->contact, &buddy->contact, sizeof(info->buf_)-total); + total += info->contact.slen; + + /* Presence status */ + pj_memcpy(&info->pres_status, &buddy->status, sizeof(pjsip_pres_status)); + + /* status and status text */ + if (buddy->sub == NULL || buddy->status.info_cnt==0) { + info->status = PJSUA_BUDDY_STATUS_UNKNOWN; + info->status_text = pj_str("?"); + } else if (pjsua_var.buddy[buddy_id].status.info[0].basic_open) { + info->status = PJSUA_BUDDY_STATUS_ONLINE; + + /* copy RPID information */ + info->rpid = buddy->status.info[0].rpid; + + if (info->rpid.note.slen) + info->status_text = info->rpid.note; + else + info->status_text = pj_str("Online"); + + } else { + info->status = PJSUA_BUDDY_STATUS_OFFLINE; + info->rpid = buddy->status.info[0].rpid; + + if (info->rpid.note.slen) + info->status_text = info->rpid.note; + else + info->status_text = pj_str("Offline"); + } + + /* monitor pres */ + info->monitor_pres = buddy->monitor; + + /* subscription state and termination reason */ + info->sub_term_code = buddy->term_code; + if (buddy->sub) { + info->sub_state = pjsip_evsub_get_state(buddy->sub); + info->sub_state_name = pjsip_evsub_get_state_name(buddy->sub); + if (info->sub_state == PJSIP_EVSUB_STATE_TERMINATED && + total < sizeof(info->buf_)) + { + info->sub_term_reason.ptr = info->buf_ + total; + pj_strncpy(&info->sub_term_reason, + pjsip_evsub_get_termination_reason(buddy->sub), + sizeof(info->buf_) - total); + total += info->sub_term_reason.slen; + } else { + info->sub_term_reason = pj_str(""); + } + } else if (total < sizeof(info->buf_)) { + info->sub_state_name = "NULL"; + info->sub_term_reason.ptr = info->buf_ + total; + pj_strncpy(&info->sub_term_reason, &buddy->term_reason, + sizeof(info->buf_) - total); + total += info->sub_term_reason.slen; + } else { + info->sub_state_name = "NULL"; + info->sub_term_reason = pj_str(""); + } + + unlock_buddy(&lck); + return PJ_SUCCESS; +} + +/* + * Set the user data associated with the buddy object. + */ +PJ_DEF(pj_status_t) pjsua_buddy_set_user_data( pjsua_buddy_id buddy_id, + void *user_data) +{ + struct buddy_lock lck; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL); + + status = lock_buddy("pjsua_buddy_set_user_data()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + pjsua_var.buddy[buddy_id].user_data = user_data; + + unlock_buddy(&lck); + + return PJ_SUCCESS; +} + + +/* + * Get the user data associated with the budy object. + */ +PJ_DEF(void*) pjsua_buddy_get_user_data(pjsua_buddy_id buddy_id) +{ + struct buddy_lock lck; + pj_status_t status; + void *user_data; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), NULL); + + status = lock_buddy("pjsua_buddy_get_user_data()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return NULL; + + user_data = pjsua_var.buddy[buddy_id].user_data; + + unlock_buddy(&lck); + + return user_data; +} + + +/* + * Reset buddy descriptor. + */ +static void reset_buddy(pjsua_buddy_id id) +{ + pj_pool_t *pool = pjsua_var.buddy[id].pool; + pj_bzero(&pjsua_var.buddy[id], sizeof(pjsua_var.buddy[id])); + pjsua_var.buddy[id].pool = pool; + pjsua_var.buddy[id].index = id; +} + + +/* + * Add new buddy. + */ +PJ_DEF(pj_status_t) pjsua_buddy_add( const pjsua_buddy_config *cfg, + pjsua_buddy_id *p_buddy_id) +{ + pjsip_name_addr *url; + pjsua_buddy *buddy; + pjsip_sip_uri *sip_uri; + int index; + pj_str_t tmp; + + PJ_ASSERT_RETURN(pjsua_var.buddy_cnt <= + PJ_ARRAY_SIZE(pjsua_var.buddy), + PJ_ETOOMANY); + + PJ_LOG(4,(THIS_FILE, "Adding buddy: %.*s", + (int)cfg->uri.slen, cfg->uri.ptr)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + /* Find empty slot */ + for (index=0; index<(int)PJ_ARRAY_SIZE(pjsua_var.buddy); ++index) { + if (pjsua_var.buddy[index].uri.slen == 0) + break; + } + + /* Expect to find an empty slot */ + if (index == PJ_ARRAY_SIZE(pjsua_var.buddy)) { + PJSUA_UNLOCK(); + /* This shouldn't happen */ + pj_assert(!"index < PJ_ARRAY_SIZE(pjsua_var.buddy)"); + pj_log_pop_indent(); + return PJ_ETOOMANY; + } + + buddy = &pjsua_var.buddy[index]; + + /* Create pool for this buddy */ + if (buddy->pool) { + pj_pool_reset(buddy->pool); + } else { + char name[PJ_MAX_OBJ_NAME]; + pj_ansi_snprintf(name, sizeof(name), "buddy%03d", index); + buddy->pool = pjsua_pool_create(name, 512, 256); + } + + /* Init buffers for presence subscription status */ + buddy->term_reason.ptr = (char*) + pj_pool_alloc(buddy->pool, + PJSUA_BUDDY_SUB_TERM_REASON_LEN); + + /* Get name and display name for buddy */ + pj_strdup_with_null(buddy->pool, &tmp, &cfg->uri); + url = (pjsip_name_addr*)pjsip_parse_uri(buddy->pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + + if (url == NULL) { + pjsua_perror(THIS_FILE, "Unable to add buddy", PJSIP_EINVALIDURI); + pj_pool_release(buddy->pool); + buddy->pool = NULL; + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJSIP_EINVALIDURI; + } + + /* Only support SIP schemes */ + if (!PJSIP_URI_SCHEME_IS_SIP(url) && !PJSIP_URI_SCHEME_IS_SIPS(url)) { + pj_pool_release(buddy->pool); + buddy->pool = NULL; + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJSIP_EINVALIDSCHEME; + } + + /* Reset buddy, to make sure everything is cleared with default + * values + */ + reset_buddy(index); + + /* Save URI */ + pjsua_var.buddy[index].uri = tmp; + + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(url->uri); + pjsua_var.buddy[index].name = sip_uri->user; + pjsua_var.buddy[index].display = url->display; + pjsua_var.buddy[index].host = sip_uri->host; + pjsua_var.buddy[index].port = sip_uri->port; + pjsua_var.buddy[index].monitor = cfg->subscribe; + if (pjsua_var.buddy[index].port == 0) + pjsua_var.buddy[index].port = 5060; + + /* Save user data */ + pjsua_var.buddy[index].user_data = (void*)cfg->user_data; + + if (p_buddy_id) + *p_buddy_id = index; + + pjsua_var.buddy_cnt++; + + PJSUA_UNLOCK(); + + PJ_LOG(4,(THIS_FILE, "Buddy %d added.", index)); + + pjsua_buddy_subscribe_pres(index, cfg->subscribe); + + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Delete buddy. + */ +PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id) +{ + struct buddy_lock lck; + pj_status_t status; + + PJ_ASSERT_RETURN(buddy_id>=0 && + buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy), + PJ_EINVAL); + + if (pjsua_var.buddy[buddy_id].uri.slen == 0) { + return PJ_SUCCESS; + } + + status = lock_buddy("pjsua_buddy_del()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + PJ_LOG(4,(THIS_FILE, "Buddy %d: deleting..", buddy_id)); + pj_log_push_indent(); + + /* Unsubscribe presence */ + pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE); + + /* Not interested with further events for this buddy */ + if (pjsua_var.buddy[buddy_id].sub) { + pjsip_evsub_set_mod_data(pjsua_var.buddy[buddy_id].sub, + pjsua_var.mod.id, NULL); + } + + /* Remove buddy */ + pjsua_var.buddy[buddy_id].uri.slen = 0; + pjsua_var.buddy_cnt--; + + /* Clear timer */ + if (pjsua_var.buddy[buddy_id].timer.id) { + pjsua_cancel_timer(&pjsua_var.buddy[buddy_id].timer); + pjsua_var.buddy[buddy_id].timer.id = PJ_FALSE; + } + + /* Reset buddy struct */ + reset_buddy(buddy_id); + + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Enable/disable buddy's presence monitoring. + */ +PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id buddy_id, + pj_bool_t subscribe) +{ + struct buddy_lock lck; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL); + + status = lock_buddy("pjsua_buddy_subscribe_pres()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + PJ_LOG(4,(THIS_FILE, "Buddy %d: unsubscribing presence..", buddy_id)); + pj_log_push_indent(); + + lck.buddy->monitor = subscribe; + + pjsua_buddy_update_pres(buddy_id); + + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Update buddy's presence. + */ +PJ_DEF(pj_status_t) pjsua_buddy_update_pres(pjsua_buddy_id buddy_id) +{ + struct buddy_lock lck; + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua_buddy_is_valid(buddy_id), PJ_EINVAL); + + status = lock_buddy("pjsua_buddy_update_pres()", buddy_id, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + PJ_LOG(4,(THIS_FILE, "Buddy %d: updating presence..", buddy_id)); + pj_log_push_indent(); + + /* Is this an unsubscribe request? */ + if (!lck.buddy->monitor) { + unsubscribe_buddy_presence(buddy_id); + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; + } + + /* Ignore if presence is already active for the buddy */ + if (lck.buddy->sub) { + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; + } + + /* Initiate presence subscription */ + subscribe_buddy_presence(buddy_id); + + unlock_buddy(&lck); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Dump presence subscriptions to log file. + */ +PJ_DEF(void) pjsua_pres_dump(pj_bool_t verbose) +{ + unsigned acc_id; + unsigned i; + + + PJSUA_LOCK(); + + /* + * When no detail is required, just dump number of server and client + * subscriptions. + */ + if (verbose == PJ_FALSE) { + + int count = 0; + + for (acc_id=0; acc_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) { + + if (!pjsua_var.acc[acc_id].valid) + continue; + + if (!pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) { + struct pjsua_srv_pres *uapres; + + uapres = pjsua_var.acc[acc_id].pres_srv_list.next; + while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) { + ++count; + uapres = uapres->next; + } + } + } + + PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d", + count)); + + count = 0; + + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + if (pjsua_var.buddy[i].uri.slen == 0) + continue; + if (pjsua_var.buddy[i].sub) { + ++count; + } + } + + PJ_LOG(3,(THIS_FILE, "Number of client/UAC subscriptions: %d", + count)); + PJSUA_UNLOCK(); + return; + } + + + /* + * Dumping all server (UAS) subscriptions + */ + PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:")); + + for (acc_id=0; acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) { + + if (!pjsua_var.acc[acc_id].valid) + continue; + + PJ_LOG(3,(THIS_FILE, " %.*s", + (int)pjsua_var.acc[acc_id].cfg.id.slen, + pjsua_var.acc[acc_id].cfg.id.ptr)); + + if (pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) { + + PJ_LOG(3,(THIS_FILE, " - none - ")); + + } else { + struct pjsua_srv_pres *uapres; + + uapres = pjsua_var.acc[acc_id].pres_srv_list.next; + while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) { + + PJ_LOG(3,(THIS_FILE, " %10s %s", + pjsip_evsub_get_state_name(uapres->sub), + uapres->remote)); + + uapres = uapres->next; + } + } + } + + /* + * Dumping all client (UAC) subscriptions + */ + PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:")); + + if (pjsua_var.buddy_cnt == 0) { + + PJ_LOG(3,(THIS_FILE, " - no buddy list - ")); + + } else { + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + + if (pjsua_var.buddy[i].uri.slen == 0) + continue; + + if (pjsua_var.buddy[i].sub) { + PJ_LOG(3,(THIS_FILE, " %10s %.*s", + pjsip_evsub_get_state_name(pjsua_var.buddy[i].sub), + (int)pjsua_var.buddy[i].uri.slen, + pjsua_var.buddy[i].uri.ptr)); + } else { + PJ_LOG(3,(THIS_FILE, " %10s %.*s", + "(null)", + (int)pjsua_var.buddy[i].uri.slen, + pjsua_var.buddy[i].uri.ptr)); + } + } + } + + PJSUA_UNLOCK(); +} + + +/*************************************************************************** + * Server subscription. + */ + +/* Proto */ +static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata); + +/* The module instance. */ +static pjsip_module mod_pjsua_pres = +{ + NULL, NULL, /* prev, next. */ + { "mod-pjsua-pres", 14 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &pres_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + +/* Callback called when *server* subscription state has changed. */ +static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsua_srv_pres *uapres; + + PJ_UNUSED_ARG(event); + + PJSUA_LOCK(); + + uapres = (pjsua_srv_pres*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (uapres) { + pjsip_evsub_state state; + + PJ_LOG(4,(THIS_FILE, "Server subscription to %s is %s", + uapres->remote, pjsip_evsub_get_state_name(sub))); + pj_log_push_indent(); + + state = pjsip_evsub_get_state(sub); + + if (pjsua_var.ua_cfg.cb.on_srv_subscribe_state) { + pj_str_t from; + + from = uapres->dlg->remote.info_str; + (*pjsua_var.ua_cfg.cb.on_srv_subscribe_state)(uapres->acc_id, + uapres, &from, + state, event); + } + + if (state == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + pj_list_erase(uapres); + } + pj_log_pop_indent(); + } + + PJSUA_UNLOCK(); +} + +/* This is called when request is received. + * We need to check for incoming SUBSCRIBE request. + */ +static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) +{ + int acc_id; + pjsua_acc *acc; + pj_str_t contact; + pjsip_method *req_method = &rdata->msg_info.msg->line.req.method; + pjsua_srv_pres *uapres; + pjsip_evsub *sub; + pjsip_evsub_user pres_cb; + pjsip_dialog *dlg; + pjsip_status_code st_code; + pj_str_t reason; + pjsip_expires_hdr *expires_hdr; + pjsua_msg_data msg_data; + pj_status_t status; + + if (pjsip_method_cmp(req_method, pjsip_get_subscribe_method()) != 0) + return PJ_FALSE; + + /* Incoming SUBSCRIBE: */ + + /* Don't want to accept the request if shutdown is in progress */ + if (pjsua_var.thread_quit_flag) { + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, + PJSIP_SC_TEMPORARILY_UNAVAILABLE, NULL, + NULL, NULL); + return PJ_TRUE; + } + + PJSUA_LOCK(); + + /* Find which account for the incoming request. */ + acc_id = pjsua_acc_find_for_incoming(rdata); + acc = &pjsua_var.acc[acc_id]; + + PJ_LOG(4,(THIS_FILE, "Creating server subscription, using account %d", + acc_id)); + pj_log_push_indent(); + + /* Create suitable Contact header */ + if (acc->contact.slen) { + contact = acc->contact; + } else { + status = pjsua_acc_create_uas_contact(rdata->tp_info.pool, &contact, + acc_id, rdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + PJSUA_UNLOCK(); + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL, + NULL, NULL); + pj_log_pop_indent(); + return PJ_TRUE; + } + } + + /* Create UAS dialog: */ + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, + &contact, &dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to create UAS dialog for subscription", + status); + PJSUA_UNLOCK(); + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 400, NULL, + NULL, NULL); + pj_log_pop_indent(); + return PJ_TRUE; + } + + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) + pjsip_dlg_set_via_sent_by(dlg, &acc->via_addr, acc->via_tp); + + /* Set credentials and preference. */ + pjsip_auth_clt_set_credentials(&dlg->auth_sess, acc->cred_cnt, acc->cred); + pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref); + + /* Init callback: */ + pj_bzero(&pres_cb, sizeof(pres_cb)); + pres_cb.on_evsub_state = &pres_evsub_on_srv_state; + + /* Create server presence subscription: */ + status = pjsip_pres_create_uas( dlg, &pres_cb, rdata, &sub); + if (status != PJ_SUCCESS) { + int code = PJSIP_ERRNO_TO_SIP_STATUS(status); + pjsip_tx_data *tdata; + + pjsua_perror(THIS_FILE, "Unable to create server subscription", + status); + + if (code==599 || code > 699 || code < 300) { + code = 400; + } + + status = pjsip_dlg_create_response(dlg, rdata, code, NULL, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), + tdata); + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_TRUE; + } + + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(dlg, &tp_sel); + } + + /* Attach our data to the subscription: */ + uapres = PJ_POOL_ALLOC_T(dlg->pool, pjsua_srv_pres); + uapres->sub = sub; + uapres->remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE); + uapres->acc_id = acc_id; + uapres->dlg = dlg; + status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri, + uapres->remote, PJSIP_MAX_URL_SIZE); + if (status < 1) + pj_ansi_strcpy(uapres->remote, "<-- url is too long-->"); + else + uapres->remote[status] = '\0'; + + pjsip_evsub_add_header(sub, &acc->cfg.sub_hdr_list); + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, uapres); + + /* Add server subscription to the list: */ + pj_list_push_back(&pjsua_var.acc[acc_id].pres_srv_list, uapres); + + + /* Capture the value of Expires header. */ + expires_hdr = (pjsip_expires_hdr*) + pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, + NULL); + if (expires_hdr) + uapres->expires = expires_hdr->ivalue; + else + uapres->expires = -1; + + st_code = (pjsip_status_code)200; + reason = pj_str("OK"); + pjsua_msg_data_init(&msg_data); + + /* Notify application callback, if any */ + if (pjsua_var.ua_cfg.cb.on_incoming_subscribe) { + pjsua_buddy_id buddy_id; + + buddy_id = find_buddy(rdata->msg_info.from->uri); + + (*pjsua_var.ua_cfg.cb.on_incoming_subscribe)(acc_id, uapres, buddy_id, + &dlg->remote.info_str, + rdata, &st_code, &reason, + &msg_data); + } + + /* Handle rejection case */ + if (st_code >= 300) { + pjsip_tx_data *tdata; + + /* Create response */ + status = pjsip_dlg_create_response(dlg, rdata, st_code, + &reason, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating response", status); + pj_list_erase(uapres); + pjsip_pres_terminate(sub, PJ_FALSE); + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_FALSE; + } + + /* Add header list, if any */ + pjsua_process_msg_data(tdata, &msg_data); + + /* Send the response */ + status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), + tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error sending response", status); + /* This is not fatal */ + } + + /* Terminate presence subscription */ + pj_list_erase(uapres); + pjsip_pres_terminate(sub, PJ_FALSE); + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_TRUE; + } + + /* Create and send 2xx response to the SUBSCRIBE request: */ + status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to accept presence subscription", + status); + pj_list_erase(uapres); + pjsip_pres_terminate(sub, PJ_FALSE); + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_FALSE; + } + + /* If code is 200, send NOTIFY now */ + if (st_code == 200) { + pjsua_pres_notify(acc_id, uapres, PJSIP_EVSUB_STATE_ACTIVE, + NULL, NULL, PJ_TRUE, &msg_data); + } + + /* Done: */ + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_TRUE; +} + + +/* + * Send NOTIFY. + */ +PJ_DEF(pj_status_t) pjsua_pres_notify( pjsua_acc_id acc_id, + pjsua_srv_pres *srv_pres, + pjsip_evsub_state ev_state, + const pj_str_t *state_str, + const pj_str_t *reason, + pj_bool_t with_body, + const pjsua_msg_data *msg_data) +{ + pjsua_acc *acc; + pjsip_pres_status pres_status; + pjsua_buddy_id buddy_id; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check parameters */ + PJ_ASSERT_RETURN(acc_id!=-1 && srv_pres, PJ_EINVAL); + + /* Check that account ID is valid */ + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), + PJ_EINVAL); + /* Check that account is valid */ + PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP); + + PJ_LOG(4,(THIS_FILE, "Acc %d: sending NOTIFY for srv_pres=0x%p..", + acc_id, (int)(long)srv_pres)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + acc = &pjsua_var.acc[acc_id]; + + /* Check that the server presence subscription is still valid */ + if (pj_list_find_node(&acc->pres_srv_list, srv_pres) == NULL) { + /* Subscription has been terminated */ + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_EINVALIDOP; + } + + /* Set our online status: */ + pj_bzero(&pres_status, sizeof(pres_status)); + pres_status.info_cnt = 1; + pres_status.info[0].basic_open = acc->online_status; + pres_status.info[0].id = acc->cfg.pidf_tuple_id; + //Both pjsua_var.local_uri and pjsua_var.contact_uri are enclosed in "<" and ">" + //causing XML parsing to fail. + //pres_status.info[0].contact = pjsua_var.local_uri; + /* add RPID information */ + pj_memcpy(&pres_status.info[0].rpid, &acc->rpid, + sizeof(pjrpid_element)); + + pjsip_pres_set_status(srv_pres->sub, &pres_status); + + /* Check expires value. If it's zero, send our presense state but + * set subscription state to TERMINATED. + */ + if (srv_pres->expires == 0) + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + + /* Create and send the NOTIFY to active subscription: */ + status = pjsip_pres_notify(srv_pres->sub, ev_state, state_str, + reason, &tdata); + if (status == PJ_SUCCESS) { + /* Force removal of message body if msg_body==FALSE */ + if (!with_body) { + tdata->msg->body = NULL; + } + pjsua_process_msg_data(tdata, msg_data); + status = pjsip_pres_send_request( srv_pres->sub, tdata); + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create/send NOTIFY", + status); + pj_list_erase(srv_pres); + pjsip_pres_terminate(srv_pres->sub, PJ_FALSE); + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; + } + + + /* Subscribe to buddy's presence if we're not subscribed */ + buddy_id = find_buddy(srv_pres->dlg->remote.info->uri); + if (buddy_id != PJSUA_INVALID_ID) { + pjsua_buddy *b = &pjsua_var.buddy[buddy_id]; + if (b->monitor && b->sub == NULL) { + PJ_LOG(4,(THIS_FILE, "Received SUBSCRIBE from buddy %d, " + "activating outgoing subscription", buddy_id)); + subscribe_buddy_presence(buddy_id); + } + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + + +/* + * Client presence publication callback. + */ +static void publish_cb(struct pjsip_publishc_cbparam *param) +{ + pjsua_acc *acc = (pjsua_acc*) param->token; + + if (param->code/100 != 2 || param->status != PJ_SUCCESS) { + + pjsip_publishc_destroy(param->pubc); + acc->publish_sess = NULL; + + if (param->status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(param->status, errmsg, sizeof(errmsg)); + PJ_LOG(1,(THIS_FILE, + "Client publication (PUBLISH) failed, status=%d, msg=%s", + param->status, errmsg)); + } else if (param->code == 412) { + /* 412 (Conditional Request Failed) + * The PUBLISH refresh has failed, retry with new one. + */ + pjsua_pres_init_publish_acc(acc->index); + + } else { + PJ_LOG(1,(THIS_FILE, + "Client publication (PUBLISH) failed (%d/%.*s)", + param->code, (int)param->reason.slen, + param->reason.ptr)); + } + + } else { + if (param->expiration < 1) { + /* Could happen if server "forgot" to include Expires header + * in the response. We will not renew, so destroy the pubc. + */ + pjsip_publishc_destroy(param->pubc); + acc->publish_sess = NULL; + } + } +} + + +/* + * Send PUBLISH request. + */ +static pj_status_t send_publish(int acc_id, pj_bool_t active) +{ + pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg; + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsip_pres_status pres_status; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(5,(THIS_FILE, "Acc %d: sending %sPUBLISH..", + acc_id, (active ? "" : "un-"))); + pj_log_push_indent(); + + /* Create PUBLISH request */ + if (active) { + char *bpos; + pj_str_t entity; + + status = pjsip_publishc_publish(acc->publish_sess, PJ_TRUE, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status); + goto on_error; + } + + /* Set our online status: */ + pj_bzero(&pres_status, sizeof(pres_status)); + pres_status.info_cnt = 1; + pres_status.info[0].basic_open = acc->online_status; + pres_status.info[0].id = acc->cfg.pidf_tuple_id; + /* .. including RPID information */ + pj_memcpy(&pres_status.info[0].rpid, &acc->rpid, + sizeof(pjrpid_element)); + + /* Be careful not to send PIDF with presence entity ID containing + * "<" character. + */ + if ((bpos=pj_strchr(&acc_cfg->id, '<')) != NULL) { + char *epos = pj_strchr(&acc_cfg->id, '>'); + if (epos - bpos < 2) { + pj_assert(!"Unexpected invalid URI"); + status = PJSIP_EINVALIDURI; + goto on_error; + } + entity.ptr = bpos+1; + entity.slen = epos - bpos - 1; + } else { + entity = acc_cfg->id; + } + + /* Create and add PIDF message body */ + status = pjsip_pres_create_pidf(tdata->pool, &pres_status, + &entity, &tdata->msg->body); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating PIDF for PUBLISH request", + status); + pjsip_tx_data_dec_ref(tdata); + goto on_error; + } + } else { + status = pjsip_publishc_unpublish(acc->publish_sess, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating PUBLISH request", status); + goto on_error; + } + } + + /* Add headers etc */ + pjsua_process_msg_data(tdata, NULL); + + /* Set Via sent-by */ + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) { + pjsip_publishc_set_via_sent_by(acc->publish_sess, &acc->via_addr, + acc->via_tp); + } + + /* Send the PUBLISH request */ + status = pjsip_publishc_send(acc->publish_sess, tdata); + if (status == PJ_EPENDING) { + PJ_LOG(3,(THIS_FILE, "Previous request is in progress, " + "PUBLISH request is queued")); + } else if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error sending PUBLISH request", status); + goto on_error; + } + + acc->publish_state = acc->online_status; + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + if (acc->publish_sess) { + pjsip_publishc_destroy(acc->publish_sess); + acc->publish_sess = NULL; + } + pj_log_pop_indent(); + return status; +} + + +/* Create client publish session */ +pj_status_t pjsua_pres_init_publish_acc(int acc_id) +{ + const pj_str_t STR_PRESENCE = { "presence", 8 }; + pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg; + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pj_status_t status; + + /* Create and init client publication session */ + if (acc_cfg->publish_enabled) { + + /* Create client publication */ + status = pjsip_publishc_create(pjsua_var.endpt, &acc_cfg->publish_opt, + acc, &publish_cb, + &acc->publish_sess); + if (status != PJ_SUCCESS) { + acc->publish_sess = NULL; + return status; + } + + /* Initialize client publication */ + status = pjsip_publishc_init(acc->publish_sess, &STR_PRESENCE, + &acc_cfg->id, &acc_cfg->id, + &acc_cfg->id, + PJSUA_PUBLISH_EXPIRATION); + if (status != PJ_SUCCESS) { + acc->publish_sess = NULL; + return status; + } + + /* Add credential for authentication */ + if (acc->cred_cnt) { + pjsip_publishc_set_credentials(acc->publish_sess, acc->cred_cnt, + acc->cred); + } + + /* Set route-set */ + pjsip_publishc_set_route_set(acc->publish_sess, &acc->route_set); + + /* Send initial PUBLISH request */ + if (acc->online_status != 0) { + status = send_publish(acc_id, PJ_TRUE); + if (status != PJ_SUCCESS) + return status; + } + + } else { + acc->publish_sess = NULL; + } + + return PJ_SUCCESS; +} + + +/* Init presence for account */ +pj_status_t pjsua_pres_init_acc(int acc_id) +{ + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + + /* Init presence subscription */ + pj_list_init(&acc->pres_srv_list); + + return PJ_SUCCESS; +} + + +/* Unpublish presence publication */ +void pjsua_pres_unpublish(pjsua_acc *acc, unsigned flags) +{ + if (acc->publish_sess) { + pjsua_acc_config *acc_cfg = &acc->cfg; + + acc->online_status = PJ_FALSE; + + if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) { + send_publish(acc->index, PJ_FALSE); + } + + /* By ticket #364, don't destroy the session yet (let the callback + destroy it) + if (acc->publish_sess) { + pjsip_publishc_destroy(acc->publish_sess); + acc->publish_sess = NULL; + } + */ + acc_cfg->publish_enabled = PJ_FALSE; + } +} + +/* Terminate server subscription for the account */ +void pjsua_pres_delete_acc(int acc_id, unsigned flags) +{ + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsua_srv_pres *uapres; + + uapres = pjsua_var.acc[acc_id].pres_srv_list.next; + + /* Notify all subscribers that we're no longer available */ + while (uapres != &acc->pres_srv_list) { + + pjsip_pres_status pres_status; + pj_str_t reason = { "noresource", 10 }; + pjsua_srv_pres *next; + pjsip_tx_data *tdata; + + next = uapres->next; + + pjsip_pres_get_status(uapres->sub, &pres_status); + + pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status; + pjsip_pres_set_status(uapres->sub, &pres_status); + + if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) { + if (pjsip_pres_notify(uapres->sub, + PJSIP_EVSUB_STATE_TERMINATED, NULL, + &reason, &tdata)==PJ_SUCCESS) + { + pjsip_pres_send_request(uapres->sub, tdata); + } + } else { + pjsip_pres_terminate(uapres->sub, PJ_FALSE); + } + + uapres = next; + } + + /* Clear server presence subscription list because account might be reused + * later. */ + pj_list_init(&acc->pres_srv_list); + + /* Terminate presence publication, if any */ + pjsua_pres_unpublish(acc, flags); +} + + +/* Update server subscription (e.g. when our online status has changed) */ +void pjsua_pres_update_acc(int acc_id, pj_bool_t force) +{ + pjsua_acc *acc = &pjsua_var.acc[acc_id]; + pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg; + pjsua_srv_pres *uapres; + + uapres = pjsua_var.acc[acc_id].pres_srv_list.next; + + while (uapres != &acc->pres_srv_list) { + + pjsip_pres_status pres_status; + pjsip_tx_data *tdata; + + pjsip_pres_get_status(uapres->sub, &pres_status); + + /* Only send NOTIFY once subscription is active. Some subscriptions + * may still be in NULL (when app is adding a new buddy while in the + * on_incoming_subscribe() callback) or PENDING (when user approval is + * being requested) state and we don't send NOTIFY to these subs until + * the user accepted the request. + */ + if (pjsip_evsub_get_state(uapres->sub)==PJSIP_EVSUB_STATE_ACTIVE && + (force || pres_status.info[0].basic_open != acc->online_status)) + { + + pres_status.info[0].basic_open = acc->online_status; + pj_memcpy(&pres_status.info[0].rpid, &acc->rpid, + sizeof(pjrpid_element)); + + pjsip_pres_set_status(uapres->sub, &pres_status); + + if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS) { + pjsua_process_msg_data(tdata, NULL); + pjsip_pres_send_request(uapres->sub, tdata); + } + } + + uapres = uapres->next; + } + + /* Send PUBLISH if required. We only do this when we have a PUBLISH + * session. If we don't have a PUBLISH session, then it could be + * that we're waiting until registration has completed before we + * send the first PUBLISH. + */ + if (acc_cfg->publish_enabled && acc->publish_sess) { + if (force || acc->publish_state != acc->online_status) { + send_publish(acc_id, PJ_TRUE); + } + } +} + + + +/*************************************************************************** + * Client subscription. + */ + +static void buddy_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry) +{ + pjsua_buddy *buddy = (pjsua_buddy*)entry->user_data; + + PJ_UNUSED_ARG(th); + + entry->id = PJ_FALSE; + pjsua_buddy_update_pres(buddy->index); +} + +/* Reschedule subscription refresh timer or terminate the subscription + * refresh timer for the specified buddy. + */ +static void buddy_resubscribe(pjsua_buddy *buddy, pj_bool_t resched, + unsigned msec_interval) +{ + if (buddy->timer.id) { + pjsua_cancel_timer(&buddy->timer); + buddy->timer.id = PJ_FALSE; + } + + if (resched) { + pj_time_val delay; + + PJ_LOG(4,(THIS_FILE, + "Resubscribing buddy id %u in %u ms (reason: %.*s)", + buddy->index, msec_interval, + (int)buddy->term_reason.slen, + buddy->term_reason.ptr)); + + pj_timer_entry_init(&buddy->timer, 0, buddy, &buddy_timer_cb); + delay.sec = 0; + delay.msec = msec_interval; + pj_time_val_normalize(&delay); + + if (pjsua_schedule_timer(&buddy->timer, &delay)==PJ_SUCCESS) + buddy->timer.id = PJ_TRUE; + } +} + +/* Callback called when *client* subscription state has changed. */ +static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsua_buddy *buddy; + + PJ_UNUSED_ARG(event); + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (buddy) { + PJ_LOG(4,(THIS_FILE, + "Presence subscription to %.*s is %s", + (int)pjsua_var.buddy[buddy->index].uri.slen, + pjsua_var.buddy[buddy->index].uri.ptr, + pjsip_evsub_get_state_name(sub))); + pj_log_push_indent(); + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + int resub_delay = -1; + + if (buddy->term_reason.ptr == NULL) { + buddy->term_reason.ptr = (char*) + pj_pool_alloc(buddy->pool, + PJSUA_BUDDY_SUB_TERM_REASON_LEN); + } + pj_strncpy(&buddy->term_reason, + pjsip_evsub_get_termination_reason(sub), + PJSUA_BUDDY_SUB_TERM_REASON_LEN); + + buddy->term_code = 200; + + /* Determine whether to resubscribe automatically */ + if (event && event->type==PJSIP_EVENT_TSX_STATE) { + const pjsip_transaction *tsx = event->body.tsx_state.tsx; + if (pjsip_method_cmp(&tsx->method, + &pjsip_subscribe_method)==0) + { + buddy->term_code = tsx->status_code; + switch (tsx->status_code) { + case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST: + /* 481: we refreshed too late? resubscribe + * immediately. + */ + /* But this must only happen when the 481 is received + * on subscription refresh request. We MUST NOT try to + * resubscribe automatically if the 481 is received + * on the initial SUBSCRIBE (if server returns this + * response for some reason). + */ + if (buddy->dlg->remote.contact) + resub_delay = 500; + break; + } + } else if (pjsip_method_cmp(&tsx->method, + &pjsip_notify_method)==0) + { + if (pj_stricmp2(&buddy->term_reason, "deactivated")==0 || + pj_stricmp2(&buddy->term_reason, "timeout")==0) { + /* deactivated: The subscription has been terminated, + * but the subscriber SHOULD retry immediately with + * a new subscription. + */ + /* timeout: The subscription has been terminated + * because it was not refreshed before it expired. + * Clients MAY re-subscribe immediately. The + * "retry-after" parameter has no semantics for + * "timeout". + */ + resub_delay = 500; + } + else if (pj_stricmp2(&buddy->term_reason, "probation")==0|| + pj_stricmp2(&buddy->term_reason, "giveup")==0) { + /* probation: The subscription has been terminated, + * but the client SHOULD retry at some later time. + * If a "retry-after" parameter is also present, the + * client SHOULD wait at least the number of seconds + * specified by that parameter before attempting to re- + * subscribe. + */ + /* giveup: The subscription has been terminated because + * the notifier could not obtain authorization in a + * timely fashion. If a "retry-after" parameter is + * also present, the client SHOULD wait at least the + * number of seconds specified by that parameter before + * attempting to re-subscribe; otherwise, the client + * MAY retry immediately, but will likely get put back + * into pending state. + */ + const pjsip_sub_state_hdr *sub_hdr; + pj_str_t sub_state = { "Subscription-State", 18 }; + const pjsip_msg *msg; + + msg = event->body.tsx_state.src.rdata->msg_info.msg; + sub_hdr = (const pjsip_sub_state_hdr*) + pjsip_msg_find_hdr_by_name(msg, &sub_state, + NULL); + if (sub_hdr && sub_hdr->retry_after > 0) + resub_delay = sub_hdr->retry_after * 1000; + } + + } + } + + /* For other cases of subscription termination, if resubscribe + * timer is not set, schedule with default expiration (plus minus + * some random value, to avoid sending SUBSCRIBEs all at once) + */ + if (resub_delay == -1) { + pj_assert(PJSUA_PRES_TIMER >= 3); + resub_delay = PJSUA_PRES_TIMER*1000 - 2500 + (pj_rand()%5000); + } + + buddy_resubscribe(buddy, PJ_TRUE, resub_delay); + + } else { + /* This will clear the last termination code/reason */ + buddy->term_code = 0; + buddy->term_reason.slen = 0; + } + + /* Call callbacks */ + if (pjsua_var.ua_cfg.cb.on_buddy_evsub_state) + (*pjsua_var.ua_cfg.cb.on_buddy_evsub_state)(buddy->index, sub, + event); + + if (pjsua_var.ua_cfg.cb.on_buddy_state) + (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index); + + /* Clear subscription */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + buddy->sub = NULL; + buddy->status.info_cnt = 0; + buddy->dlg = NULL; + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + } + + pj_log_pop_indent(); + } +} + + +/* Callback when transaction state has changed. */ +static void pjsua_evsub_on_tsx_state(pjsip_evsub *sub, + pjsip_transaction *tsx, + pjsip_event *event) +{ + pjsua_buddy *buddy; + pjsip_contact_hdr *contact_hdr; + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!buddy) { + return; + } + + /* We only use this to update buddy's Contact, when it's not + * set. + */ + if (buddy->contact.slen != 0) { + /* Contact already set */ + return; + } + + /* Only care about 2xx response to outgoing SUBSCRIBE */ + if (tsx->status_code/100 != 2 || + tsx->role != PJSIP_UAC_ROLE || + event->type != PJSIP_EVENT_RX_MSG || + pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method())!=0) + { + return; + } + + /* Find contact header. */ + contact_hdr = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg, + PJSIP_H_CONTACT, NULL); + if (!contact_hdr || !contact_hdr->uri) { + return; + } + + buddy->contact.ptr = (char*) + pj_pool_alloc(buddy->pool, PJSIP_MAX_URL_SIZE); + buddy->contact.slen = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, + contact_hdr->uri, + buddy->contact.ptr, + PJSIP_MAX_URL_SIZE); + if (buddy->contact.slen < 0) + buddy->contact.slen = 0; +} + + +/* Callback called when we receive NOTIFY */ +static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body) +{ + pjsua_buddy *buddy; + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + buddy = (pjsua_buddy*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (buddy) { + /* Update our info. */ + pjsip_pres_get_status(sub, &buddy->status); + } + + /* The default is to send 200 response to NOTIFY. + * Just leave it there.. + */ + PJ_UNUSED_ARG(rdata); + PJ_UNUSED_ARG(p_st_code); + PJ_UNUSED_ARG(p_st_text); + PJ_UNUSED_ARG(res_hdr); + PJ_UNUSED_ARG(p_body); +} + + +/* It does what it says.. */ +static void subscribe_buddy_presence(pjsua_buddy_id buddy_id) +{ + pjsip_evsub_user pres_callback; + pj_pool_t *tmp_pool = NULL; + pjsua_buddy *buddy; + int acc_id; + pjsua_acc *acc; + pj_str_t contact; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Event subscription callback. */ + pj_bzero(&pres_callback, sizeof(pres_callback)); + pres_callback.on_evsub_state = &pjsua_evsub_on_state; + pres_callback.on_tsx_state = &pjsua_evsub_on_tsx_state; + pres_callback.on_rx_notify = &pjsua_evsub_on_rx_notify; + + buddy = &pjsua_var.buddy[buddy_id]; + acc_id = pjsua_acc_find_for_outgoing(&buddy->uri); + + acc = &pjsua_var.acc[acc_id]; + + PJ_LOG(4,(THIS_FILE, "Buddy %d: subscribing presence,using account %d..", + buddy_id, acc_id)); + pj_log_push_indent(); + + /* Generate suitable Contact header unless one is already set in + * the account + */ + if (acc->contact.slen) { + contact = acc->contact; + } else { + tmp_pool = pjsua_pool_create("tmpbuddy", 512, 256); + + status = pjsua_acc_create_uac_contact(tmp_pool, &contact, + acc_id, &buddy->uri); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + } + + /* Create UAC dialog */ + status = pjsip_dlg_create_uac( pjsip_ua_instance(), + &acc->cfg.id, + &contact, + &buddy->uri, + NULL, &buddy->dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create dialog", + status); + if (tmp_pool) pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + + /* Increment the dialog's lock otherwise when presence session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(buddy->dlg); + + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) + pjsip_dlg_set_via_sent_by(buddy->dlg, &acc->via_addr, acc->via_tp); + + status = pjsip_pres_create_uac( buddy->dlg, &pres_callback, + PJSIP_EVSUB_NO_EVENT_ID, &buddy->sub); + if (status != PJ_SUCCESS) { + buddy->sub = NULL; + pjsua_perror(THIS_FILE, "Unable to create presence client", + status); + /* This should destroy the dialog since there's no session + * referencing it + */ + if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg); + if (tmp_pool) pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(buddy->dlg, &tp_sel); + } + + /* Set route-set */ + if (!pj_list_empty(&acc->route_set)) { + pjsip_dlg_set_route_set(buddy->dlg, &acc->route_set); + } + + /* Set credentials */ + if (acc->cred_cnt) { + pjsip_auth_clt_set_credentials( &buddy->dlg->auth_sess, + acc->cred_cnt, acc->cred); + } + + /* Set authentication preference */ + pjsip_auth_clt_set_prefs(&buddy->dlg->auth_sess, &acc->cfg.auth_pref); + + pjsip_evsub_set_mod_data(buddy->sub, pjsua_var.mod.id, buddy); + + status = pjsip_pres_initiate(buddy->sub, -1, &tdata); + if (status != PJ_SUCCESS) { + if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg); + if (buddy->sub) { + pjsip_pres_terminate(buddy->sub, PJ_FALSE); + } + buddy->sub = NULL; + pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE", + status); + if (tmp_pool) pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + + pjsua_process_msg_data(tdata, NULL); + + status = pjsip_pres_send_request(buddy->sub, tdata); + if (status != PJ_SUCCESS) { + if (buddy->dlg) pjsip_dlg_dec_lock(buddy->dlg); + if (buddy->sub) { + pjsip_pres_terminate(buddy->sub, PJ_FALSE); + } + buddy->sub = NULL; + pjsua_perror(THIS_FILE, "Unable to send initial SUBSCRIBE", + status); + if (tmp_pool) pj_pool_release(tmp_pool); + pj_log_pop_indent(); + return; + } + + pjsip_dlg_dec_lock(buddy->dlg); + if (tmp_pool) pj_pool_release(tmp_pool); + pj_log_pop_indent(); +} + + +/* It does what it says... */ +static void unsubscribe_buddy_presence(pjsua_buddy_id buddy_id) +{ + pjsua_buddy *buddy; + pjsip_tx_data *tdata; + pj_status_t status; + + buddy = &pjsua_var.buddy[buddy_id]; + + if (buddy->sub == NULL) + return; + + if (pjsip_evsub_get_state(buddy->sub) == PJSIP_EVSUB_STATE_TERMINATED) { + buddy->sub = NULL; + return; + } + + PJ_LOG(5,(THIS_FILE, "Buddy %d: unsubscribing..", buddy_id)); + pj_log_push_indent(); + + status = pjsip_pres_initiate( buddy->sub, 0, &tdata); + if (status == PJ_SUCCESS) { + pjsua_process_msg_data(tdata, NULL); + status = pjsip_pres_send_request( buddy->sub, tdata ); + } + + if (status != PJ_SUCCESS && buddy->sub) { + pjsip_pres_terminate(buddy->sub, PJ_FALSE); + buddy->sub = NULL; + pjsua_perror(THIS_FILE, "Unable to unsubscribe presence", + status); + } + + pj_log_pop_indent(); +} + +/* It does what it says.. */ +static pj_status_t refresh_client_subscriptions(void) +{ + unsigned i; + pj_status_t status; + + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + struct buddy_lock lck; + + if (!pjsua_buddy_is_valid(i)) + continue; + + status = lock_buddy("refresh_client_subscriptions()", i, &lck, 0); + if (status != PJ_SUCCESS) + return status; + + if (pjsua_var.buddy[i].monitor && !pjsua_var.buddy[i].sub) { + subscribe_buddy_presence(i); + + } else if (!pjsua_var.buddy[i].monitor && pjsua_var.buddy[i].sub) { + unsubscribe_buddy_presence(i); + + } + + unlock_buddy(&lck); + } + + return PJ_SUCCESS; +} + +/*************************************************************************** + * MWI + */ +/* Callback called when *client* subscription state has changed. */ +static void mwi_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsua_acc *acc; + + PJ_UNUSED_ARG(event); + + /* Note: #937: no need to acuire PJSUA_LOCK here. Since the buddy has + * a dialog attached to it, lock_buddy() will use the dialog + * lock, which we are currently holding! + */ + acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!acc) + return; + + PJ_LOG(4,(THIS_FILE, + "MWI subscription for %.*s is %s", + (int)acc->cfg.id.slen, acc->cfg.id.ptr, + pjsip_evsub_get_state_name(sub))); + + /* Call callback */ + if (pjsua_var.ua_cfg.cb.on_mwi_state) { + (*pjsua_var.ua_cfg.cb.on_mwi_state)(acc->index, sub); + } + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + /* Clear subscription */ + acc->mwi_dlg = NULL; + acc->mwi_sub = NULL; + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + + } +} + +/* Callback called when we receive NOTIFY */ +static void mwi_evsub_on_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body) +{ + pjsua_mwi_info mwi_info; + pjsua_acc *acc; + + PJ_UNUSED_ARG(p_st_code); + PJ_UNUSED_ARG(p_st_text); + PJ_UNUSED_ARG(res_hdr); + PJ_UNUSED_ARG(p_body); + + acc = (pjsua_acc*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id); + if (!acc) + return; + + /* Construct mwi_info */ + pj_bzero(&mwi_info, sizeof(mwi_info)); + mwi_info.evsub = sub; + mwi_info.rdata = rdata; + + PJ_LOG(4,(THIS_FILE, "MWI got NOTIFY..")); + pj_log_push_indent(); + + /* Call callback */ + if (pjsua_var.ua_cfg.cb.on_mwi_info) { + (*pjsua_var.ua_cfg.cb.on_mwi_info)(acc->index, &mwi_info); + } + + pj_log_pop_indent(); +} + + +/* Event subscription callback. */ +static pjsip_evsub_user mwi_cb = +{ + &mwi_evsub_on_state, + NULL, /* on_tsx_state: not interested */ + NULL, /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless + * we want to authenticate + */ + + &mwi_evsub_on_rx_notify, + + NULL, /* on_client_refresh: Use default behaviour, which is to + * refresh client subscription. */ + + NULL, /* on_server_timeout: Use default behaviour, which is to send + * NOTIFY to terminate. + */ +}; + +pj_status_t pjsua_start_mwi(pjsua_acc_id acc_id, pj_bool_t force_renew) +{ + pjsua_acc *acc; + pj_pool_t *tmp_pool = NULL; + pj_str_t contact; + pjsip_tx_data *tdata; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc) + && pjsua_var.acc[acc_id].valid, PJ_EINVAL); + + acc = &pjsua_var.acc[acc_id]; + + if (!acc->cfg.mwi_enabled) { + if (acc->mwi_sub) { + /* Terminate MWI subscription */ + pjsip_evsub *sub = acc->mwi_sub; + + /* Detach sub from this account */ + acc->mwi_sub = NULL; + acc->mwi_dlg = NULL; + pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL); + + /* Unsubscribe */ + status = pjsip_mwi_initiate(sub, 0, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_mwi_send_request(sub, tdata); + } + } + return status; + } + + /* Subscription is already active */ + if (acc->mwi_sub) { + if (!force_renew) + return PJ_SUCCESS; + + /* Update MWI subscription */ + pj_assert(acc->mwi_dlg); + pjsip_dlg_inc_lock(acc->mwi_dlg); + + status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata); + if (status == PJ_SUCCESS) { + pjsua_process_msg_data(tdata, NULL); + status = pjsip_pres_send_request(acc->mwi_sub, tdata); + } + + pjsip_dlg_dec_lock(acc->mwi_dlg); + return status; + } + + PJ_LOG(4,(THIS_FILE, "Starting MWI subscription..")); + pj_log_push_indent(); + + /* Generate suitable Contact header unless one is already set in + * the account + */ + if (acc->contact.slen) { + contact = acc->contact; + } else { + tmp_pool = pjsua_pool_create("tmpmwi", 512, 256); + status = pjsua_acc_create_uac_contact(tmp_pool, &contact, + acc->index, &acc->cfg.id); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to generate Contact header", + status); + goto on_return; + } + } + + /* Create UAC dialog */ + status = pjsip_dlg_create_uac( pjsip_ua_instance(), + &acc->cfg.id, + &contact, + &acc->cfg.id, + NULL, &acc->mwi_dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create dialog", status); + goto on_return; + } + + /* Increment the dialog's lock otherwise when presence session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(acc->mwi_dlg); + + if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) + pjsip_dlg_set_via_sent_by(acc->mwi_dlg, &acc->via_addr, acc->via_tp); + + /* Create UAC subscription */ + status = pjsip_mwi_create_uac(acc->mwi_dlg, &mwi_cb, + PJSIP_EVSUB_NO_EVENT_ID, &acc->mwi_sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating MWI subscription", status); + if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg); + goto on_return; + } + + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(acc->mwi_dlg, &tp_sel); + } + + /* Set route-set */ + if (!pj_list_empty(&acc->route_set)) { + pjsip_dlg_set_route_set(acc->mwi_dlg, &acc->route_set); + } + + /* Set credentials */ + if (acc->cred_cnt) { + pjsip_auth_clt_set_credentials( &acc->mwi_dlg->auth_sess, + acc->cred_cnt, acc->cred); + } + + /* Set authentication preference */ + pjsip_auth_clt_set_prefs(&acc->mwi_dlg->auth_sess, &acc->cfg.auth_pref); + + pjsip_evsub_set_mod_data(acc->mwi_sub, pjsua_var.mod.id, acc); + + status = pjsip_mwi_initiate(acc->mwi_sub, acc->cfg.mwi_expires, &tdata); + if (status != PJ_SUCCESS) { + if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg); + if (acc->mwi_sub) { + pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE); + } + acc->mwi_sub = NULL; + acc->mwi_dlg = NULL; + pjsua_perror(THIS_FILE, "Unable to create initial MWI SUBSCRIBE", + status); + goto on_return; + } + + pjsua_process_msg_data(tdata, NULL); + + status = pjsip_pres_send_request(acc->mwi_sub, tdata); + if (status != PJ_SUCCESS) { + if (acc->mwi_dlg) pjsip_dlg_dec_lock(acc->mwi_dlg); + if (acc->mwi_sub) { + pjsip_pres_terminate(acc->mwi_sub, PJ_FALSE); + } + acc->mwi_sub = NULL; + acc->mwi_dlg = NULL; + pjsua_perror(THIS_FILE, "Unable to send initial MWI SUBSCRIBE", + status); + goto on_return; + } + + pjsip_dlg_dec_lock(acc->mwi_dlg); + +on_return: + if (tmp_pool) pj_pool_release(tmp_pool); + + pj_log_pop_indent(); + return status; +} + + +/*************************************************************************** + * Unsolicited MWI + */ +static pj_bool_t unsolicited_mwi_on_rx_request(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + pj_str_t EVENT_HDR = { "Event", 5 }; + pj_str_t MWI = { "message-summary", 15 }; + pjsip_event_hdr *eh; + + if (pjsip_method_cmp(&msg->line.req.method, &pjsip_notify_method)!=0) { + /* Only interested with NOTIFY request */ + return PJ_FALSE; + } + + eh = (pjsip_event_hdr*) pjsip_msg_find_hdr_by_name(msg, &EVENT_HDR, NULL); + if (!eh) { + /* Something wrong with the request, it has no Event hdr */ + return PJ_FALSE; + } + + if (pj_stricmp(&eh->event_type, &MWI) != 0) { + /* Not MWI event */ + return PJ_FALSE; + } + + PJ_LOG(4,(THIS_FILE, "Got unsolicited NOTIFY from %s:%d..", + rdata->pkt_info.src_name, rdata->pkt_info.src_port)); + pj_log_push_indent(); + + /* Got unsolicited MWI request, respond with 200/OK first */ + pjsip_endpt_respond(pjsua_get_pjsip_endpt(), NULL, rdata, 200, NULL, + NULL, NULL, NULL); + + + /* Call callback */ + if (pjsua_var.ua_cfg.cb.on_mwi_info) { + pjsua_acc_id acc_id; + pjsua_mwi_info mwi_info; + + acc_id = pjsua_acc_find_for_incoming(rdata); + + pj_bzero(&mwi_info, sizeof(mwi_info)); + mwi_info.rdata = rdata; + + (*pjsua_var.ua_cfg.cb.on_mwi_info)(acc_id, &mwi_info); + } + + pj_log_pop_indent(); + return PJ_TRUE; +} + +/* The module instance. */ +static pjsip_module pjsua_unsolicited_mwi_mod = +{ + NULL, NULL, /* prev, next. */ + { "mod-unsolicited-mwi", 19 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &unsolicited_mwi_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +static pj_status_t enable_unsolicited_mwi(void) +{ + pj_status_t status; + + status = pjsip_endpt_register_module(pjsua_get_pjsip_endpt(), + &pjsua_unsolicited_mwi_mod); + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Error registering unsolicited MWI module", + status); + + return status; +} + + + +/***************************************************************************/ + +/* Timer callback to re-create client subscription */ +static void pres_timer_cb(pj_timer_heap_t *th, + pj_timer_entry *entry) +{ + unsigned i; + pj_time_val delay = { PJSUA_PRES_TIMER, 0 }; + + entry->id = PJ_FALSE; + + /* Retry failed PUBLISH and MWI SUBSCRIBE requests */ + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + pjsua_acc *acc = &pjsua_var.acc[i]; + + /* Acc may not be ready yet, otherwise assertion will happen */ + if (!pjsua_acc_is_valid(i)) + continue; + + /* Retry PUBLISH */ + if (acc->cfg.publish_enabled && acc->publish_sess==NULL) + pjsua_pres_init_publish_acc(acc->index); + + /* Re-subscribe MWI subscription if it's terminated prematurely */ + if (acc->cfg.mwi_enabled && !acc->mwi_sub) + pjsua_start_mwi(acc->index, PJ_FALSE); + } + + /* #937: No need to do bulk client refresh, as buddies have their + * own individual timer now. + */ + //refresh_client_subscriptions(); + + pjsip_endpt_schedule_timer(pjsua_var.endpt, entry, &delay); + entry->id = PJ_TRUE; + + PJ_UNUSED_ARG(th); +} + + +/* + * Init presence + */ +pj_status_t pjsua_pres_init() +{ + unsigned i; + pj_status_t status; + + status = pjsip_endpt_register_module( pjsua_var.endpt, &mod_pjsua_pres); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to register pjsua presence module", + status); + } + + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + reset_buddy(i); + } + + return status; +} + + +/* + * Start presence subsystem. + */ +pj_status_t pjsua_pres_start(void) +{ + /* Start presence timer to re-subscribe to buddy's presence when + * subscription has failed. + */ + if (pjsua_var.pres_timer.id == PJ_FALSE) { + pj_time_val pres_interval = {PJSUA_PRES_TIMER, 0}; + + pjsua_var.pres_timer.cb = &pres_timer_cb; + pjsip_endpt_schedule_timer(pjsua_var.endpt, &pjsua_var.pres_timer, + &pres_interval); + pjsua_var.pres_timer.id = PJ_TRUE; + } + + if (pjsua_var.ua_cfg.enable_unsolicited_mwi) { + pj_status_t status = enable_unsolicited_mwi(); + if (status != PJ_SUCCESS) + return status; + } + + return PJ_SUCCESS; +} + + +/* + * Shutdown presence. + */ +void pjsua_pres_shutdown(unsigned flags) +{ + unsigned i; + + PJ_LOG(4,(THIS_FILE, "Shutting down presence..")); + pj_log_push_indent(); + + if (pjsua_var.pres_timer.id != 0) { + pjsip_endpt_cancel_timer(pjsua_var.endpt, &pjsua_var.pres_timer); + pjsua_var.pres_timer.id = PJ_FALSE; + } + + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (!pjsua_var.acc[i].valid) + continue; + pjsua_pres_delete_acc(i, flags); + } + + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) { + pjsua_var.buddy[i].monitor = 0; + } + + if ((flags & PJSUA_DESTROY_NO_TX_MSG) == 0) { + refresh_client_subscriptions(); + + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) { + if (pjsua_var.acc[i].valid) + pjsua_pres_update_acc(i, PJ_FALSE); + } + } + + pj_log_pop_indent(); +} diff --git a/pjsip/src/pjsua-lib/pjsua_vid.c b/pjsip/src/pjsua-lib/pjsua_vid.c new file mode 100644 index 0000000..eb74ee1 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_vid.c @@ -0,0 +1,2166 @@ +/* $Id: pjsua_vid.c 4071 2012-04-24 05:40:32Z nanang $ */ +/* + * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/pjsua_internal.h> + +#if defined(PJSUA_MEDIA_HAS_PJMEDIA) && PJSUA_MEDIA_HAS_PJMEDIA != 0 + +#define THIS_FILE "pjsua_vid.c" + +#if PJSUA_HAS_VIDEO + +#define ENABLE_EVENT 1 +#define VID_TEE_MAX_PORT (PJSUA_MAX_CALLS + 1) + +#define PJSUA_SHOW_WINDOW 1 +#define PJSUA_HIDE_WINDOW 0 + + +static void free_vid_win(pjsua_vid_win_id wid); + +/***************************************************************************** + * pjsua video subsystem. + */ +pj_status_t pjsua_vid_subsys_init(void) +{ + unsigned i; + pj_status_t status; + + PJ_LOG(4,(THIS_FILE, "Initializing video subsystem..")); + pj_log_push_indent(); + + status = pjmedia_video_format_mgr_create(pjsua_var.pool, 64, 0, NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error creating PJMEDIA video format manager")); + goto on_error; + } + + status = pjmedia_converter_mgr_create(pjsua_var.pool, NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error creating PJMEDIA converter manager")); + goto on_error; + } + + status = pjmedia_event_mgr_create(pjsua_var.pool, 0, NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error creating PJMEDIA event manager")); + goto on_error; + } + + status = pjmedia_vid_codec_mgr_create(pjsua_var.pool, NULL); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error creating PJMEDIA video codec manager")); + goto on_error; + } + +#if PJMEDIA_HAS_VIDEO && PJMEDIA_HAS_FFMPEG_VID_CODEC + status = pjmedia_codec_ffmpeg_vid_init(NULL, &pjsua_var.cp.factory); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error initializing ffmpeg library")); + goto on_error; + } +#endif + + status = pjmedia_vid_dev_subsys_init(&pjsua_var.cp.factory); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error creating PJMEDIA video subsystem")); + goto on_error; + } + + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + if (pjsua_var.win[i].pool == NULL) { + pjsua_var.win[i].pool = pjsua_pool_create("win%p", 512, 512); + if (pjsua_var.win[i].pool == NULL) { + status = PJ_ENOMEM; + goto on_error; + } + } + } + + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + pj_log_pop_indent(); + return status; +} + +pj_status_t pjsua_vid_subsys_start(void) +{ + return PJ_SUCCESS; +} + +pj_status_t pjsua_vid_subsys_destroy(void) +{ + unsigned i; + + PJ_LOG(4,(THIS_FILE, "Destroying video subsystem..")); + pj_log_push_indent(); + + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + if (pjsua_var.win[i].pool) { + free_vid_win(i); + pj_pool_release(pjsua_var.win[i].pool); + pjsua_var.win[i].pool = NULL; + } + } + + pjmedia_vid_dev_subsys_shutdown(); + +#if PJMEDIA_HAS_FFMPEG_VID_CODEC + pjmedia_codec_ffmpeg_vid_deinit(); +#endif + + if (pjmedia_vid_codec_mgr_instance()) + pjmedia_vid_codec_mgr_destroy(NULL); + + if (pjmedia_converter_mgr_instance()) + pjmedia_converter_mgr_destroy(NULL); + + if (pjmedia_event_mgr_instance()) + pjmedia_event_mgr_destroy(NULL); + + if (pjmedia_video_format_mgr_instance()) + pjmedia_video_format_mgr_destroy(NULL); + + pj_log_pop_indent(); + return PJ_SUCCESS; +} + +PJ_DEF(const char*) pjsua_vid_win_type_name(pjsua_vid_win_type wt) +{ + const char *win_type_names[] = { + "none", + "preview", + "stream" + }; + + return (wt < PJ_ARRAY_SIZE(win_type_names)) ? win_type_names[wt] : "??"; +} + +PJ_DEF(void) +pjsua_call_vid_strm_op_param_default(pjsua_call_vid_strm_op_param *param) +{ + pj_bzero(param, sizeof(*param)); + param->med_idx = -1; + param->dir = PJMEDIA_DIR_ENCODING_DECODING; + param->cap_dev = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; +} + +PJ_DEF(void) pjsua_vid_preview_param_default(pjsua_vid_preview_param *p) +{ + p->rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV; + p->show = PJ_TRUE; + p->wnd_flags = 0; +} + + +/***************************************************************************** + * Devices. + */ + +/* + * Get the number of video devices installed in the system. + */ +PJ_DEF(unsigned) pjsua_vid_dev_count(void) +{ + return pjmedia_vid_dev_count(); +} + +/* + * Retrieve the video device info for the specified device index. + */ +PJ_DEF(pj_status_t) pjsua_vid_dev_get_info(pjmedia_vid_dev_index id, + pjmedia_vid_dev_info *vdi) +{ + return pjmedia_vid_dev_get_info(id, vdi); +} + +/* + * Enum all video devices installed in the system. + */ +PJ_DEF(pj_status_t) pjsua_vid_enum_devs(pjmedia_vid_dev_info info[], + unsigned *count) +{ + unsigned i, dev_count; + + dev_count = pjmedia_vid_dev_count(); + + if (dev_count > *count) dev_count = *count; + + for (i=0; i<dev_count; ++i) { + pj_status_t status; + + status = pjmedia_vid_dev_get_info(i, &info[i]); + if (status != PJ_SUCCESS) + return status; + } + + *count = dev_count; + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * Codecs. + */ + +static pj_status_t find_codecs_with_rtp_packing( + const pj_str_t *codec_id, + unsigned *count, + const pjmedia_vid_codec_info *p_info[]) +{ + const pjmedia_vid_codec_info *info[32]; + unsigned i, j, count_ = PJ_ARRAY_SIZE(info); + pj_status_t status; + + status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, codec_id, + &count_, info, NULL); + if (status != PJ_SUCCESS) + return status; + + for (i = 0, j = 0; i < count_ && j<*count; ++i) { + if ((info[i]->packings & PJMEDIA_VID_PACKING_PACKETS) == 0) + continue; + p_info[j++] = info[i]; + } + *count = j; + return PJ_SUCCESS; +} + +/* + * Enum all supported video codecs in the system. + */ +PJ_DEF(pj_status_t) pjsua_vid_enum_codecs( pjsua_codec_info id[], + unsigned *p_count ) +{ + pjmedia_vid_codec_info info[32]; + unsigned i, j, count, prio[32]; + pj_status_t status; + + count = PJ_ARRAY_SIZE(info); + status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio); + if (status != PJ_SUCCESS) { + *p_count = 0; + return status; + } + + for (i=0, j=0; i<count && j<*p_count; ++i) { + if (info[i].packings & PJMEDIA_VID_PACKING_PACKETS) { + pj_bzero(&id[j], sizeof(pjsua_codec_info)); + + pjmedia_vid_codec_info_to_id(&info[i], id[j].buf_, sizeof(id[j].buf_)); + id[j].codec_id = pj_str(id[j].buf_); + id[j].priority = (pj_uint8_t) prio[i]; + + if (id[j].codec_id.slen < sizeof(id[j].buf_)) { + id[j].desc.ptr = id[j].codec_id.ptr + id[j].codec_id.slen + 1; + pj_strncpy(&id[j].desc, &info[i].encoding_desc, + sizeof(id[j].buf_) - id[j].codec_id.slen - 1); + } + + ++j; + } + } + + *p_count = j; + + return PJ_SUCCESS; +} + + +/* + * Change video codec priority. + */ +PJ_DEF(pj_status_t) pjsua_vid_codec_set_priority( const pj_str_t *codec_id, + pj_uint8_t priority ) +{ + const pj_str_t all = { NULL, 0 }; + + if (codec_id->slen==1 && *codec_id->ptr=='*') + codec_id = &all; + + return pjmedia_vid_codec_mgr_set_codec_priority(NULL, codec_id, + priority); +} + + +/* + * Get video codec parameters. + */ +PJ_DEF(pj_status_t) pjsua_vid_codec_get_param( + const pj_str_t *codec_id, + pjmedia_vid_codec_param *param) +{ + const pjmedia_vid_codec_info *info[2]; + unsigned count = 2; + pj_status_t status; + + status = find_codecs_with_rtp_packing(codec_id, &count, info); + if (status != PJ_SUCCESS) + return status; + + if (count != 1) + return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND); + + status = pjmedia_vid_codec_mgr_get_default_param(NULL, info[0], param); + return status; +} + + +/* + * Set video codec parameters. + */ +PJ_DEF(pj_status_t) pjsua_vid_codec_set_param( + const pj_str_t *codec_id, + const pjmedia_vid_codec_param *param) +{ + const pjmedia_vid_codec_info *info[2]; + unsigned count = 2; + pj_status_t status; + + status = find_codecs_with_rtp_packing(codec_id, &count, info); + if (status != PJ_SUCCESS) + return status; + + if (count != 1) + return (count > 1? PJ_ETOOMANY : PJ_ENOTFOUND); + + status = pjmedia_vid_codec_mgr_set_default_param(NULL, info[0], param); + return status; +} + + +/***************************************************************************** + * Preview + */ + +static pjsua_vid_win_id vid_preview_get_win(pjmedia_vid_dev_index id, + pj_bool_t running_only) +{ + pjsua_vid_win_id wid = PJSUA_INVALID_ID; + unsigned i; + + PJSUA_LOCK(); + + /* Get real capture ID, if set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV */ + if (id == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(id, &info); + id = info.id; + } + + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + pjsua_vid_win *w = &pjsua_var.win[i]; + if (w->type == PJSUA_WND_TYPE_PREVIEW && w->preview_cap_id == id) { + wid = i; + break; + } + } + + if (wid != PJSUA_INVALID_ID && running_only) { + pjsua_vid_win *w = &pjsua_var.win[wid]; + wid = w->preview_running ? wid : PJSUA_INVALID_ID; + } + + PJSUA_UNLOCK(); + + return wid; +} + +/* + * NOTE: internal function don't use this!!! Use vid_preview_get_win() + * instead. This is because this function will only return window ID + * if preview is currently running. + */ +PJ_DEF(pjsua_vid_win_id) pjsua_vid_preview_get_win(pjmedia_vid_dev_index id) +{ + return vid_preview_get_win(id, PJ_TRUE); +} + +PJ_DEF(void) pjsua_vid_win_reset(pjsua_vid_win_id wid) +{ + pjsua_vid_win *w = &pjsua_var.win[wid]; + pj_pool_t *pool = w->pool; + + pj_bzero(w, sizeof(*w)); + if (pool) pj_pool_reset(pool); + w->ref_cnt = 0; + w->pool = pool; + w->preview_cap_id = PJMEDIA_VID_INVALID_DEV; +} + +/* Allocate and initialize pjsua video window: + * - If the type is preview, video capture, tee, and render + * will be instantiated. + * - If the type is stream, only renderer will be created. + */ +static pj_status_t create_vid_win(pjsua_vid_win_type type, + const pjmedia_format *fmt, + pjmedia_vid_dev_index rend_id, + pjmedia_vid_dev_index cap_id, + pj_bool_t show, + unsigned wnd_flags, + pjsua_vid_win_id *id) +{ + pj_bool_t enable_native_preview; + pjsua_vid_win_id wid = PJSUA_INVALID_ID; + pjsua_vid_win *w = NULL; + pjmedia_vid_port_param vp_param; + pjmedia_format fmt_; + pj_status_t status; + unsigned i; + + enable_native_preview = pjsua_var.media_cfg.vid_preview_enable_native; + + PJ_LOG(4,(THIS_FILE, + "Creating video window: type=%s, cap_id=%d, rend_id=%d", + pjsua_vid_win_type_name(type), cap_id, rend_id)); + pj_log_push_indent(); + + /* If type is preview, check if it exists already */ + if (type == PJSUA_WND_TYPE_PREVIEW) { + wid = vid_preview_get_win(cap_id, PJ_FALSE); + if (wid != PJSUA_INVALID_ID) { + /* Yes, it exists */ + /* Show/hide window */ + pjmedia_vid_dev_stream *strm; + pj_bool_t hide = !show; + + w = &pjsua_var.win[wid]; + + PJ_LOG(4,(THIS_FILE, + "Window already exists for cap_dev=%d, returning wid=%d", + cap_id, wid)); + + + if (w->is_native) { + strm = pjmedia_vid_port_get_stream(w->vp_cap); + } else { + strm = pjmedia_vid_port_get_stream(w->vp_rend); + } + + pj_assert(strm); + status = pjmedia_vid_dev_stream_set_cap( + strm, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, + &hide); + + pjmedia_vid_dev_stream_set_cap( + strm, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS, + &wnd_flags); + + /* Done */ + *id = wid; + pj_log_pop_indent(); + + return status; + } + } + + /* Allocate window */ + for (i=0; i<PJSUA_MAX_VID_WINS; ++i) { + w = &pjsua_var.win[i]; + if (w->type == PJSUA_WND_TYPE_NONE) { + wid = i; + w->type = type; + break; + } + } + if (i == PJSUA_MAX_VID_WINS) { + pj_log_pop_indent(); + return PJ_ETOOMANY; + } + + /* Initialize window */ + pjmedia_vid_port_param_default(&vp_param); + + if (w->type == PJSUA_WND_TYPE_PREVIEW) { + pjmedia_vid_dev_info vdi; + + /* + * Determine if the device supports native preview. + */ + status = pjmedia_vid_dev_get_info(cap_id, &vdi); + if (status != PJ_SUCCESS) + goto on_error; + + if (enable_native_preview && + (vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW)) + { + /* Device supports native preview! */ + w->is_native = PJ_TRUE; + } + + status = pjmedia_vid_dev_default_param(w->pool, cap_id, + &vp_param.vidparam); + if (status != PJ_SUCCESS) + goto on_error; + + if (w->is_native) { + vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE; + vp_param.vidparam.window_hide = !show; + vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS; + vp_param.vidparam.window_flags = wnd_flags; + } + + /* Normalize capture ID, in case it was set to + * PJMEDIA_VID_DEFAULT_CAPTURE_DEV + */ + cap_id = vp_param.vidparam.cap_id; + + /* Assign preview capture device ID */ + w->preview_cap_id = cap_id; + + /* Create capture video port */ + vp_param.active = PJ_TRUE; + vp_param.vidparam.dir = PJMEDIA_DIR_CAPTURE; + if (fmt) + vp_param.vidparam.fmt = *fmt; + + status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_cap); + if (status != PJ_SUCCESS) + goto on_error; + + /* Update format info */ + fmt_ = vp_param.vidparam.fmt; + fmt = &fmt_; + + /* Create video tee */ + status = pjmedia_vid_tee_create(w->pool, fmt, VID_TEE_MAX_PORT, + &w->tee); + if (status != PJ_SUCCESS) + goto on_error; + + /* Connect capturer to the video tee */ + status = pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE); + if (status != PJ_SUCCESS) + goto on_error; + + /* If device supports native preview, enable it */ + if (w->is_native) { + pjmedia_vid_dev_stream *cap_dev; + pj_bool_t enabled = PJ_TRUE; + + cap_dev = pjmedia_vid_port_get_stream(w->vp_cap); + status = pjmedia_vid_dev_stream_set_cap( + cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW, + &enabled); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error activating native preview, falling back " + "to software preview..")); + w->is_native = PJ_FALSE; + } + } + } + + /* Create renderer video port, only if it's not a native preview */ + if (!w->is_native) { + status = pjmedia_vid_dev_default_param(w->pool, rend_id, + &vp_param.vidparam); + if (status != PJ_SUCCESS) + goto on_error; + + vp_param.active = (w->type == PJSUA_WND_TYPE_STREAM); + vp_param.vidparam.dir = PJMEDIA_DIR_RENDER; + vp_param.vidparam.fmt = *fmt; + vp_param.vidparam.disp_size = fmt->det.vid.size; + vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE; + vp_param.vidparam.window_hide = !show; + vp_param.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS; + vp_param.vidparam.window_flags = wnd_flags; + + status = pjmedia_vid_port_create(w->pool, &vp_param, &w->vp_rend); + if (status != PJ_SUCCESS) + goto on_error; + + /* For preview window, connect capturer & renderer (via tee) */ + if (w->type == PJSUA_WND_TYPE_PREVIEW) { + pjmedia_port *rend_port; + + rend_port = pjmedia_vid_port_get_passive_port(w->vp_rend); + status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, rend_port); + if (status != PJ_SUCCESS) + goto on_error; + } + + PJ_LOG(4,(THIS_FILE, + "%s window id %d created for cap_dev=%d rend_dev=%d", + pjsua_vid_win_type_name(type), wid, cap_id, rend_id)); + } else { + PJ_LOG(4,(THIS_FILE, + "Preview window id %d created for cap_dev %d, " + "using built-in preview!", + wid, cap_id)); + } + + + /* Done */ + *id = wid; + + PJ_LOG(4,(THIS_FILE, "Window %d created", wid)); + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + free_vid_win(wid); + pj_log_pop_indent(); + return status; +} + + +static void free_vid_win(pjsua_vid_win_id wid) +{ + pjsua_vid_win *w = &pjsua_var.win[wid]; + + PJ_LOG(4,(THIS_FILE, "Window %d: destroying..", wid)); + pj_log_push_indent(); + + if (w->vp_cap) { + pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL, + w->vp_cap); + pjmedia_vid_port_stop(w->vp_cap); + pjmedia_vid_port_disconnect(w->vp_cap); + pjmedia_vid_port_destroy(w->vp_cap); + } + if (w->vp_rend) { + pjmedia_event_unsubscribe(NULL, &call_media_on_event, NULL, + w->vp_rend); + pjmedia_vid_port_stop(w->vp_rend); + pjmedia_vid_port_destroy(w->vp_rend); + } + if (w->tee) { + pjmedia_port_destroy(w->tee); + } + pjsua_vid_win_reset(wid); + + pj_log_pop_indent(); +} + + +static void inc_vid_win(pjsua_vid_win_id wid) +{ + pjsua_vid_win *w; + + pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS); + + w = &pjsua_var.win[wid]; + pj_assert(w->type != PJSUA_WND_TYPE_NONE); + ++w->ref_cnt; +} + +static void dec_vid_win(pjsua_vid_win_id wid) +{ + pjsua_vid_win *w; + + pj_assert(wid >= 0 && wid < PJSUA_MAX_VID_WINS); + + w = &pjsua_var.win[wid]; + pj_assert(w->type != PJSUA_WND_TYPE_NONE); + if (--w->ref_cnt == 0) + free_vid_win(wid); +} + +/* Initialize video call media */ +pj_status_t pjsua_vid_channel_init(pjsua_call_media *call_med) +{ + pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; + + call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev; + call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev; + if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info); + call_med->strm.v.rdr_dev = info.id; + } + if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info); + call_med->strm.v.cap_dev = info.id; + } + + return PJ_SUCCESS; +} + +/* Internal function: update video channel after SDP negotiation */ +pj_status_t pjsua_vid_channel_update(pjsua_call_media *call_med, + pj_pool_t *tmp_pool, + pjmedia_vid_stream_info *si, + const pjmedia_sdp_session *local_sdp, + const pjmedia_sdp_session *remote_sdp) +{ + pjsua_call *call = call_med->call; + pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; + pjmedia_port *media_port; + pj_status_t status; + + PJ_UNUSED_ARG(tmp_pool); + PJ_UNUSED_ARG(local_sdp); + PJ_UNUSED_ARG(remote_sdp); + + PJ_LOG(4,(THIS_FILE, "Video channel update..")); + pj_log_push_indent(); + + si->rtcp_sdes_bye_disabled = PJ_TRUE; + + /* Check if no media is active */ + if (si->dir != PJMEDIA_DIR_NONE) { + /* Optionally, application may modify other stream settings here + * (such as jitter buffer parameters, codec ptime, etc.) + */ + si->jb_init = pjsua_var.media_cfg.jb_init; + si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre; + si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre; + si->jb_max = pjsua_var.media_cfg.jb_max; + + /* Set SSRC */ + si->ssrc = call_med->ssrc; + + /* Set RTP timestamp & sequence, normally these value are intialized + * automatically when stream session created, but for some cases (e.g: + * call reinvite, call update) timestamp and sequence need to be kept + * contigue. + */ + si->rtp_ts = call_med->rtp_tx_ts; + si->rtp_seq = call_med->rtp_tx_seq; + si->rtp_seq_ts_set = call_med->rtp_tx_seq_ts_set; + + /* Set rate control config from account setting */ + si->rc_cfg = acc->cfg.vid_stream_rc_cfg; + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 + /* Enable/disable stream keep-alive and NAT hole punch. */ + si->use_ka = pjsua_var.acc[call->acc_id].cfg.use_stream_ka; +#endif + + /* Try to get shared format ID between the capture device and + * the encoder to avoid format conversion in the capture device. + */ + if (si->dir & PJMEDIA_DIR_ENCODING) { + pjmedia_vid_dev_info dev_info; + pjmedia_vid_codec_info *codec_info = &si->codec_info; + unsigned i, j; + + status = pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, + &dev_info); + if (status != PJ_SUCCESS) + goto on_error; + + /* Find matched format ID */ + for (i = 0; i < codec_info->dec_fmt_id_cnt; ++i) { + for (j = 0; j < dev_info.fmt_cnt; ++j) { + if (codec_info->dec_fmt_id[i] == + (pjmedia_format_id)dev_info.fmt[j].id) + { + /* Apply the matched format ID to the codec */ + si->codec_param->dec_fmt.id = + codec_info->dec_fmt_id[i]; + + /* Force outer loop to break */ + i = codec_info->dec_fmt_id_cnt; + break; + } + } + } + } + + /* Create session based on session info. */ + status = pjmedia_vid_stream_create(pjsua_var.med_endpt, NULL, si, + call_med->tp, NULL, + &call_med->strm.v.stream); + if (status != PJ_SUCCESS) + goto on_error; + + /* Start stream */ + status = pjmedia_vid_stream_start(call_med->strm.v.stream); + if (status != PJ_SUCCESS) + goto on_error; + + /* Setup decoding direction */ + if (si->dir & PJMEDIA_DIR_DECODING) + { + pjsua_vid_win_id wid; + pjsua_vid_win *w; + + PJ_LOG(4,(THIS_FILE, "Setting up RX..")); + pj_log_push_indent(); + + status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_DECODING, + &media_port); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + goto on_error; + } + + /* Create stream video window */ + status = create_vid_win(PJSUA_WND_TYPE_STREAM, + &media_port->info.fmt, + call_med->strm.v.rdr_dev, + //acc->cfg.vid_rend_dev, + PJSUA_INVALID_ID, + acc->cfg.vid_in_auto_show, + acc->cfg.vid_wnd_flags, + &wid); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + goto on_error; + } + + w = &pjsua_var.win[wid]; + +#if ENABLE_EVENT + /* Register to video events */ + pjmedia_event_subscribe(NULL, &call_media_on_event, + call_med, w->vp_rend); +#endif + + /* Connect renderer to stream */ + status = pjmedia_vid_port_connect(w->vp_rend, media_port, + PJ_FALSE); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + goto on_error; + } + + /* Start renderer */ + status = pjmedia_vid_port_start(w->vp_rend); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + goto on_error; + } + + /* Done */ + inc_vid_win(wid); + call_med->strm.v.rdr_win_id = wid; + pj_log_pop_indent(); + } + + /* Setup encoding direction */ + if (si->dir & PJMEDIA_DIR_ENCODING && !call->local_hold) + { + pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; + pjsua_vid_win *w; + pjsua_vid_win_id wid; + pj_bool_t just_created = PJ_FALSE; + + PJ_LOG(4,(THIS_FILE, "Setting up TX..")); + pj_log_push_indent(); + + status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING, + &media_port); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + goto on_error; + } + + /* Note: calling pjsua_vid_preview_get_win() even though + * create_vid_win() will automatically create the window + * if it doesn't exist, because create_vid_win() will modify + * existing window SHOW/HIDE value. + */ + wid = vid_preview_get_win(call_med->strm.v.cap_dev, PJ_FALSE); + if (wid == PJSUA_INVALID_ID) { + /* Create preview video window */ + status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, + &media_port->info.fmt, + call_med->strm.v.rdr_dev, + call_med->strm.v.cap_dev, + //acc->cfg.vid_rend_dev, + //acc->cfg.vid_cap_dev, + PJSUA_HIDE_WINDOW, + acc->cfg.vid_wnd_flags, + &wid); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + return status; + } + just_created = PJ_TRUE; + } + + w = &pjsua_var.win[wid]; +#if ENABLE_EVENT + pjmedia_event_subscribe(NULL, &call_media_on_event, + call_med, w->vp_cap); +#endif + + /* Connect stream to capturer (via video window tee) */ + status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + goto on_error; + } + + /* Start capturer */ + if (just_created) { + status = pjmedia_vid_port_start(w->vp_cap); + if (status != PJ_SUCCESS) { + pj_log_pop_indent(); + goto on_error; + } + } + + /* Done */ + inc_vid_win(wid); + call_med->strm.v.cap_win_id = wid; + pj_log_pop_indent(); + } + + } + + if (!acc->cfg.vid_out_auto_transmit && call_med->strm.v.stream) { + status = pjmedia_vid_stream_pause(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING); + if (status != PJ_SUCCESS) + goto on_error; + } + + pj_log_pop_indent(); + return PJ_SUCCESS; + +on_error: + pj_log_pop_indent(); + return status; +} + + +/* Internal function to stop video stream */ +void pjsua_vid_stop_stream(pjsua_call_media *call_med) +{ + pjmedia_vid_stream *strm = call_med->strm.v.stream; + pjmedia_rtcp_stat stat; + + pj_assert(call_med->type == PJMEDIA_TYPE_VIDEO); + + if (!strm) + return; + + PJ_LOG(4,(THIS_FILE, "Stopping video stream..")); + pj_log_push_indent(); + + if (call_med->strm.v.cap_win_id != PJSUA_INVALID_ID) { + pjmedia_port *media_port; + pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.cap_win_id]; + pj_status_t status; + + /* Stop the capture before detaching stream and unsubscribing event */ + pjmedia_vid_port_stop(w->vp_cap); + + /* Disconnect video stream from capture device */ + status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING, + &media_port); + if (status == PJ_SUCCESS) { + pjmedia_vid_tee_remove_dst_port(w->tee, media_port); + } + + /* Unsubscribe event */ + pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, + w->vp_cap); + + /* Re-start capture again, if it is used by other stream */ + if (w->ref_cnt > 1) + pjmedia_vid_port_start(w->vp_cap); + + dec_vid_win(call_med->strm.v.cap_win_id); + call_med->strm.v.cap_win_id = PJSUA_INVALID_ID; + } + + if (call_med->strm.v.rdr_win_id != PJSUA_INVALID_ID) { + pjsua_vid_win *w = &pjsua_var.win[call_med->strm.v.rdr_win_id]; + + /* Stop the render before unsubscribing event */ + pjmedia_vid_port_stop(w->vp_rend); + pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, + w->vp_rend); + + dec_vid_win(call_med->strm.v.rdr_win_id); + call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID; + } + + if ((call_med->dir & PJMEDIA_DIR_ENCODING) && + (pjmedia_vid_stream_get_stat(strm, &stat) == PJ_SUCCESS)) + { + /* Save RTP timestamp & sequence, so when media session is + * restarted, those values will be restored as the initial + * RTP timestamp & sequence of the new media session. So in + * the same call session, RTP timestamp and sequence are + * guaranteed to be contigue. + */ + call_med->rtp_tx_seq_ts_set = 1 | (1 << 1); + call_med->rtp_tx_seq = stat.rtp_tx_last_seq; + call_med->rtp_tx_ts = stat.rtp_tx_last_ts; + } + + pjmedia_vid_stream_destroy(strm); + call_med->strm.v.stream = NULL; + + pj_log_pop_indent(); +} + +/* + * Does it have built-in preview support. + */ +PJ_DEF(pj_bool_t) pjsua_vid_preview_has_native(pjmedia_vid_dev_index id) +{ + pjmedia_vid_dev_info vdi; + + return (pjmedia_vid_dev_get_info(id, &vdi)==PJ_SUCCESS) ? + ((vdi.caps & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW)!=0) : PJ_FALSE; +} + +/* + * Start video preview window for the specified capture device. + */ +PJ_DEF(pj_status_t) pjsua_vid_preview_start(pjmedia_vid_dev_index id, + const pjsua_vid_preview_param *prm) +{ + pjsua_vid_win_id wid; + pjsua_vid_win *w; + pjmedia_vid_dev_index rend_id; + pjsua_vid_preview_param default_param; + pj_status_t status; + + if (!prm) { + pjsua_vid_preview_param_default(&default_param); + prm = &default_param; + } + + PJ_LOG(4,(THIS_FILE, "Starting preview for cap_dev=%d, show=%d", + id, prm->show)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + rend_id = prm->rend_id; + + status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, NULL, rend_id, id, + prm->show, prm->wnd_flags, &wid); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; + } + + w = &pjsua_var.win[wid]; + if (w->preview_running) { + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_SUCCESS; + } + + /* Start renderer, unless it's native preview */ + if (w->is_native && !pjmedia_vid_port_is_running(w->vp_cap)) { + pjmedia_vid_dev_stream *cap_dev; + pj_bool_t enabled = PJ_TRUE; + + cap_dev = pjmedia_vid_port_get_stream(w->vp_cap); + status = pjmedia_vid_dev_stream_set_cap( + cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW, + &enabled); + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, + "Error activating native preview, falling back " + "to software preview..")); + w->is_native = PJ_FALSE; + } + } + + if (!w->is_native && !pjmedia_vid_port_is_running(w->vp_rend)) { + status = pjmedia_vid_port_start(w->vp_rend); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; + } + } + + /* Start capturer */ + if (!pjmedia_vid_port_is_running(w->vp_cap)) { + status = pjmedia_vid_port_start(w->vp_cap); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; + } + } + + inc_vid_win(wid); + w->preview_running = PJ_TRUE; + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_SUCCESS; +} + +/* + * Stop video preview. + */ +PJ_DEF(pj_status_t) pjsua_vid_preview_stop(pjmedia_vid_dev_index id) +{ + pjsua_vid_win_id wid = PJSUA_INVALID_ID; + pjsua_vid_win *w; + pj_status_t status; + + PJSUA_LOCK(); + wid = pjsua_vid_preview_get_win(id); + if (wid == PJSUA_INVALID_ID) { + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return PJ_ENOTFOUND; + } + + PJ_LOG(4,(THIS_FILE, "Stopping preview for cap_dev=%d", id)); + pj_log_push_indent(); + + w = &pjsua_var.win[wid]; + if (w->preview_running) { + if (w->is_native) { + pjmedia_vid_dev_stream *cap_dev; + pj_bool_t enabled = PJ_FALSE; + + cap_dev = pjmedia_vid_port_get_stream(w->vp_cap); + status = pjmedia_vid_dev_stream_set_cap( + cap_dev, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW, + &enabled); + } else { + status = pjmedia_vid_port_stop(w->vp_rend); + } + + if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, "Error stopping %spreview", + (w->is_native ? "native " : ""))); + PJSUA_UNLOCK(); + pj_log_pop_indent(); + return status; + } + + dec_vid_win(wid); + w->preview_running = PJ_FALSE; + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * Window + */ + + +/* + * Enumerates all video windows. + */ +PJ_DEF(pj_status_t) pjsua_vid_enum_wins( pjsua_vid_win_id wids[], + unsigned *count) +{ + unsigned i, cnt; + + cnt = 0; + + for (i=0; i<PJSUA_MAX_VID_WINS && cnt <*count; ++i) { + pjsua_vid_win *w = &pjsua_var.win[i]; + if (w->type != PJSUA_WND_TYPE_NONE) + wids[cnt++] = i; + } + + *count = cnt; + + return PJ_SUCCESS; +} + + +/* + * Get window info. + */ +PJ_DEF(pj_status_t) pjsua_vid_win_get_info( pjsua_vid_win_id wid, + pjsua_vid_win_info *wi) +{ + pjsua_vid_win *w; + pjmedia_vid_dev_stream *s; + pjmedia_vid_dev_param vparam; + pj_status_t status; + + PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && wi, PJ_EINVAL); + + pj_bzero(wi, sizeof(*wi)); + + PJSUA_LOCK(); + w = &pjsua_var.win[wid]; + + wi->is_native = w->is_native; + + if (w->is_native) { + pjmedia_vid_dev_stream *cap_strm; + pjmedia_vid_dev_cap cap = PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; + + cap_strm = pjmedia_vid_port_get_stream(w->vp_cap); + if (!cap_strm) { + status = PJ_EINVAL; + } else { + status = pjmedia_vid_dev_stream_get_cap(cap_strm, cap, &wi->hwnd); + } + + PJSUA_UNLOCK(); + return status; + } + + if (w->vp_rend == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + status = pjmedia_vid_dev_stream_get_param(s, &vparam); + if (status != PJ_SUCCESS) { + PJSUA_UNLOCK(); + return status; + } + + wi->rdr_dev = vparam.rend_id; + wi->hwnd = vparam.window; + wi->show = !vparam.window_hide; + wi->pos = vparam.window_pos; + wi->size = vparam.disp_size; + + PJSUA_UNLOCK(); + + return PJ_SUCCESS; +} + +/* + * Show or hide window. + */ +PJ_DEF(pj_status_t) pjsua_vid_win_set_show( pjsua_vid_win_id wid, + pj_bool_t show) +{ + pjsua_vid_win *w; + pjmedia_vid_dev_stream *s; + pj_bool_t hide; + pj_status_t status; + + PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL); + + PJSUA_LOCK(); + w = &pjsua_var.win[wid]; + if (w->vp_rend == NULL) { + /* Native window */ + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + /* Make sure that renderer gets started before shown up */ + if (show && !pjmedia_vid_port_is_running(w->vp_rend)) + status = pjmedia_vid_port_start(w->vp_rend); + + hide = !show; + status = pjmedia_vid_dev_stream_set_cap(s, + PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, &hide); + + PJSUA_UNLOCK(); + + return status; +} + +/* + * Set video window position. + */ +PJ_DEF(pj_status_t) pjsua_vid_win_set_pos( pjsua_vid_win_id wid, + const pjmedia_coord *pos) +{ + pjsua_vid_win *w; + pjmedia_vid_dev_stream *s; + pj_status_t status; + + PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && pos, PJ_EINVAL); + + PJSUA_LOCK(); + w = &pjsua_var.win[wid]; + if (w->vp_rend == NULL) { + /* Native window */ + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + status = pjmedia_vid_dev_stream_set_cap(s, + PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION, pos); + + PJSUA_UNLOCK(); + + return status; +} + +/* + * Resize window. + */ +PJ_DEF(pj_status_t) pjsua_vid_win_set_size( pjsua_vid_win_id wid, + const pjmedia_rect_size *size) +{ + pjsua_vid_win *w; + pjmedia_vid_dev_stream *s; + pj_status_t status; + + PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS && size, PJ_EINVAL); + + PJSUA_LOCK(); + w = &pjsua_var.win[wid]; + if (w->vp_rend == NULL) { + /* Native window */ + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + status = pjmedia_vid_dev_stream_set_cap(s, + PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE, size); + + PJSUA_UNLOCK(); + + return status; +} + +/* + * Set video orientation. + */ +PJ_DEF(pj_status_t) pjsua_vid_win_rotate( pjsua_vid_win_id wid, + int angle) +{ + pjsua_vid_win *w; + pjmedia_vid_dev_stream *s; + pjmedia_orient orient; + pj_status_t status; + + PJ_ASSERT_RETURN(wid >= 0 && wid < PJSUA_MAX_VID_WINS, PJ_EINVAL); + PJ_ASSERT_RETURN((angle % 90) == 0, PJ_EINVAL); + + /* Normalize angle, so it must be 0, 90, 180, or 270. */ + angle %= 360; + if (angle < 0) + angle += 360; + + /* Convert angle to pjmedia_orient */ + switch(angle) { + case 0: + /* No rotation */ + return PJ_SUCCESS; + case 90: + orient = PJMEDIA_ORIENT_ROTATE_90DEG; + break; + case 180: + orient = PJMEDIA_ORIENT_ROTATE_180DEG; + break; + case 270: + orient = PJMEDIA_ORIENT_ROTATE_270DEG; + break; + default: + pj_assert(!"Angle must have been validated"); + return PJ_EBUG; + } + + PJSUA_LOCK(); + w = &pjsua_var.win[wid]; + if (w->vp_rend == NULL) { + /* Native window */ + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + s = pjmedia_vid_port_get_stream(w->vp_rend); + if (s == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVAL; + } + + status = pjmedia_vid_dev_stream_set_cap(s, + PJMEDIA_VID_DEV_CAP_ORIENTATION, &orient); + + PJSUA_UNLOCK(); + + return status; +} + + +static void call_get_vid_strm_info(pjsua_call *call, + int *first_active, + int *first_inactive, + unsigned *active_cnt, + unsigned *cnt) +{ + unsigned i, var_cnt = 0; + + if (first_active && ++var_cnt) + *first_active = -1; + if (first_inactive && ++var_cnt) + *first_inactive = -1; + if (active_cnt && ++var_cnt) + *active_cnt = 0; + if (cnt && ++var_cnt) + *cnt = 0; + + for (i = 0; i < call->med_cnt && var_cnt; ++i) { + if (call->media[i].type == PJMEDIA_TYPE_VIDEO) { + if (call->media[i].dir != PJMEDIA_DIR_NONE) + { + if (first_active && *first_active == -1) { + *first_active = i; + --var_cnt; + } + if (active_cnt) + ++(*active_cnt); + } else if (first_inactive && *first_inactive == -1) { + *first_inactive = i; + --var_cnt; + } + if (cnt) + ++(*cnt); + } + } +} + + +/* Send SDP reoffer. */ +static pj_status_t call_reoffer_sdp(pjsua_call_id call_id, + const pjmedia_sdp_session *sdp) +{ + pjsua_call *call; + pjsip_tx_data *tdata; + pjsip_dialog *dlg; + pj_status_t status; + + status = acquire_call("call_reoffer_sdp()", call_id, &call, &dlg); + if (status != PJ_SUCCESS) + return status; + + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed")); + pjsip_dlg_dec_lock(dlg); + return PJSIP_ESESSIONSTATE; + } + + /* Create re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + pjsip_dlg_dec_lock(dlg); + return status; + } + + /* Send the request */ + status = pjsip_inv_send_msg( call->inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + pjsip_dlg_dec_lock(dlg); + return status; + } + + pjsip_dlg_dec_lock(dlg); + + return PJ_SUCCESS; +} + +/* Add a new video stream into a call */ +static pj_status_t call_add_video(pjsua_call *call, + pjmedia_vid_dev_index cap_dev, + pjmedia_dir dir) +{ + pj_pool_t *pool = call->inv->pool_prov; + pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg; + pjsua_call_media *call_med; + const pjmedia_sdp_session *current_sdp; + pjmedia_sdp_session *sdp; + pjmedia_sdp_media *sdp_m; + pjmedia_transport_info tpinfo; + pj_status_t status; + + /* Verify media slot availability */ + if (call->med_cnt == PJSUA_MAX_CALL_MEDIA) + return PJ_ETOOMANY; + + /* Get active local SDP and clone it */ + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, ¤t_sdp); + if (status != PJ_SUCCESS) + return status; + + sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, current_sdp); + + /* Clean up provisional media before using it */ + pjsua_media_prov_clean_up(call->index); + + /* Update provisional media from call media */ + call->med_prov_cnt = call->med_cnt; + pj_memcpy(call->media_prov, call->media, + sizeof(call->media[0]) * call->med_cnt); + + /* Initialize call media */ + call_med = &call->media_prov[call->med_prov_cnt++]; + status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO, + &acc_cfg->rtp_cfg, call->secure_level, + NULL, PJ_FALSE, NULL); + if (status != PJ_SUCCESS) + goto on_error; + + /* Override default capture device setting */ + call_med->strm.v.cap_dev = cap_dev; + + /* Init transport media */ + status = pjmedia_transport_media_create(call_med->tp, pool, 0, + NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_INIT); + + /* Get transport address info */ + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); + + /* Create SDP media line */ + status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool, + &tpinfo.sock_info, 0, &sdp_m); + if (status != PJ_SUCCESS) + goto on_error; + + sdp->media[sdp->media_count++] = sdp_m; + + /* Update media direction, if it is not 'sendrecv' */ + if (dir != PJMEDIA_DIR_ENCODING_DECODING) { + pjmedia_sdp_attr *a; + + /* Remove sendrecv direction attribute, if any */ + pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv"); + + if (dir == PJMEDIA_DIR_ENCODING) + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + else if (dir == PJMEDIA_DIR_DECODING) + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + else + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + + pjmedia_sdp_media_add_attr(sdp_m, a); + } + + /* Update SDP media line by media transport */ + status = pjmedia_transport_encode_sdp(call_med->tp, pool, + sdp, NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + + status = call_reoffer_sdp(call->index, sdp); + if (status != PJ_SUCCESS) + goto on_error; + + call->opt.vid_cnt++; + + return PJ_SUCCESS; + +on_error: + if (call_med->tp) { + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_NULL); + pjmedia_transport_close(call_med->tp); + call_med->tp = call_med->tp_orig = NULL; + } + + return status; +} + + +/* Modify a video stream from a call, i.e: update direction, + * remove/disable. + */ +static pj_status_t call_modify_video(pjsua_call *call, + int med_idx, + pjmedia_dir dir, + pj_bool_t remove) +{ + pjsua_call_media *call_med; + const pjmedia_sdp_session *current_sdp; + pjmedia_sdp_session *sdp; + pj_status_t status; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + /* Clean up provisional media before using it */ + pjsua_media_prov_clean_up(call->index); + + /* Update provisional media from call media */ + call->med_prov_cnt = call->med_cnt; + pj_memcpy(call->media_prov, call->media, + sizeof(call->media[0]) * call->med_cnt); + + call_med = &call->media_prov[med_idx]; + + /* Verify if the stream media type is video */ + if (call_med->type != PJMEDIA_TYPE_VIDEO) + return PJ_EINVAL; + + /* Verify if the stream dir is not changed */ + if ((!remove && call_med->dir == dir) || + ( remove && (call_med->tp_st == PJSUA_MED_TP_DISABLED || + call_med->tp == NULL))) + { + return PJ_SUCCESS; + } + + /* Get active local SDP and clone it */ + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, ¤t_sdp); + if (status != PJ_SUCCESS) + return status; + + sdp = pjmedia_sdp_session_clone(call->inv->pool_prov, current_sdp); + + pj_assert(med_idx < (int)sdp->media_count); + + if (!remove) { + pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg; + pj_pool_t *pool = call->inv->pool_prov; + pjmedia_sdp_media *sdp_m; + + /* Enabling video */ + if (call_med->dir == PJMEDIA_DIR_NONE) { + unsigned i, vid_cnt = 0; + + /* Check if vid_cnt in call option needs to be increased */ + for (i = 0; i < call->med_cnt; ++i) { + if (call->media[i].type == PJMEDIA_TYPE_VIDEO && + call->media[i].dir != PJMEDIA_DIR_NONE) + { + ++vid_cnt; + } + } + if (call->opt.vid_cnt <= vid_cnt) + call->opt.vid_cnt++; + } + + status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO, + &acc_cfg->rtp_cfg, call->secure_level, + NULL, PJ_FALSE, NULL); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init transport media */ + if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) { + status = pjmedia_transport_media_create(call_med->tp, pool, 0, + NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + } + + sdp_m = sdp->media[med_idx]; + + /* Create new SDP media line if the stream is disabled */ + if (sdp->media[med_idx]->desc.port == 0) { + pjmedia_transport_info tpinfo; + + /* Get transport address info */ + pjmedia_transport_info_init(&tpinfo); + pjmedia_transport_get_info(call_med->tp, &tpinfo); + + status = pjmedia_endpt_create_video_sdp(pjsua_var.med_endpt, pool, + &tpinfo.sock_info, 0, &sdp_m); + if (status != PJ_SUCCESS) + goto on_error; + } + + { + pjmedia_sdp_attr *a; + + /* Remove any direction attributes */ + pjmedia_sdp_media_remove_all_attr(sdp_m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(sdp_m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(sdp_m, "recvonly"); + pjmedia_sdp_media_remove_all_attr(sdp_m, "inactive"); + + /* Update media direction */ + if (dir == PJMEDIA_DIR_ENCODING_DECODING) + a = pjmedia_sdp_attr_create(pool, "sendrecv", NULL); + else if (dir == PJMEDIA_DIR_ENCODING) + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + else if (dir == PJMEDIA_DIR_DECODING) + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + else + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + + pjmedia_sdp_media_add_attr(sdp_m, a); + } + + sdp->media[med_idx] = sdp_m; + + /* Update SDP media line by media transport */ + status = pjmedia_transport_encode_sdp(call_med->tp, pool, + sdp, NULL, call_med->idx); + if (status != PJ_SUCCESS) + goto on_error; + +on_error: + if (status != PJ_SUCCESS) { + pjsua_media_prov_clean_up(call->index); + return status; + } + + } else { + + pj_pool_t *pool = call->inv->pool_prov; + + /* Mark media transport to disabled */ + // Don't close this here, as SDP negotiation has not been + // done and stream may be still active. + pjsua_set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED); + + /* Deactivate the stream */ + pjmedia_sdp_media_deactivate(pool, sdp->media[med_idx]); + + call->opt.vid_cnt--; + } + + status = call_reoffer_sdp(call->index, sdp); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + + +/* Change capture device of a video stream in a call */ +static pj_status_t call_change_cap_dev(pjsua_call *call, + int med_idx, + pjmedia_vid_dev_index cap_dev) +{ + pjsua_call_media *call_med; + pjmedia_vid_dev_stream *old_dev; + pjmedia_vid_dev_switch_param switch_prm; + pjmedia_vid_dev_info info; + pjsua_vid_win *w, *new_w = NULL; + pjsua_vid_win_id wid, new_wid; + pjmedia_port *media_port; + pj_status_t status; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + call_med = &call->media[med_idx]; + + /* Verify if the stream media type is video */ + if (call_med->type != PJMEDIA_TYPE_VIDEO) + return PJ_EINVAL; + + /* Verify the capture device */ + status = pjmedia_vid_dev_get_info(cap_dev, &info); + if (status != PJ_SUCCESS || info.dir != PJMEDIA_DIR_CAPTURE) + return PJ_EINVAL; + + /* The specified capture device is being used already */ + if (call_med->strm.v.cap_dev == cap_dev) + return PJ_SUCCESS; + + /* == Apply the new capture device == */ + + wid = call_med->strm.v.cap_win_id; + w = &pjsua_var.win[wid]; + pj_assert(w->type == PJSUA_WND_TYPE_PREVIEW && w->vp_cap); + + /* If the old device supports fast switching, then that's excellent! */ + old_dev = pjmedia_vid_port_get_stream(w->vp_cap); + pjmedia_vid_dev_switch_param_default(&switch_prm); + switch_prm.target_id = cap_dev; + status = pjmedia_vid_dev_stream_set_cap(old_dev, + PJMEDIA_VID_DEV_CAP_SWITCH, + &switch_prm); + if (status == PJ_SUCCESS) { + w->preview_cap_id = cap_dev; + call_med->strm.v.cap_dev = cap_dev; + return PJ_SUCCESS; + } + + /* No it doesn't support fast switching. Do slow switching then.. */ + status = pjmedia_vid_stream_get_port(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING, &media_port); + if (status != PJ_SUCCESS) + return status; + + pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, + w->vp_cap); + + /* temporarily disconnect while we operate on the tee. */ + pjmedia_vid_port_disconnect(w->vp_cap); + + /* = Detach stream port from the old capture device's tee = */ + status = pjmedia_vid_tee_remove_dst_port(w->tee, media_port); + if (status != PJ_SUCCESS) { + /* Something wrong, assume that media_port has been removed + * and continue. + */ + PJ_PERROR(4,(THIS_FILE, status, + "Warning: call %d: unable to remove video from tee", + call->index)); + } + + /* Reconnect again immediately. We're done with w->tee */ + pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE); + + /* = Attach stream port to the new capture device = */ + + /* Note: calling pjsua_vid_preview_get_win() even though + * create_vid_win() will automatically create the window + * if it doesn't exist, because create_vid_win() will modify + * existing window SHOW/HIDE value. + */ + new_wid = vid_preview_get_win(cap_dev, PJ_FALSE); + if (new_wid == PJSUA_INVALID_ID) { + pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; + + /* Create preview video window */ + status = create_vid_win(PJSUA_WND_TYPE_PREVIEW, + &media_port->info.fmt, + call_med->strm.v.rdr_dev, + cap_dev, + PJSUA_HIDE_WINDOW, + acc->cfg.vid_wnd_flags, + &new_wid); + if (status != PJ_SUCCESS) + goto on_error; + } + + inc_vid_win(new_wid); + new_w = &pjsua_var.win[new_wid]; + + /* Connect stream to capturer (via video window tee) */ + status = pjmedia_vid_tee_add_dst_port2(new_w->tee, 0, media_port); + if (status != PJ_SUCCESS) + goto on_error; + + if (w->vp_rend) { + /* Start renderer */ + status = pjmedia_vid_port_start(new_w->vp_rend); + if (status != PJ_SUCCESS) + goto on_error; + } + +#if ENABLE_EVENT + pjmedia_event_subscribe(NULL, &call_media_on_event, + call_med, new_w->vp_cap); +#endif + + /* Start capturer */ + if (!pjmedia_vid_port_is_running(new_w->vp_cap)) { + status = pjmedia_vid_port_start(new_w->vp_cap); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Finally */ + call_med->strm.v.cap_dev = cap_dev; + call_med->strm.v.cap_win_id = new_wid; + dec_vid_win(wid); + + return PJ_SUCCESS; + +on_error: + PJ_PERROR(4,(THIS_FILE, status, + "Call %d: error changing capture device to %d", + call->index, cap_dev)); + + if (new_w) { + /* Unsubscribe, just in case */ + pjmedia_event_unsubscribe(NULL, &call_media_on_event, call_med, + new_w->vp_cap); + /* Disconnect media port from the new capturer */ + pjmedia_vid_tee_remove_dst_port(new_w->tee, media_port); + /* Release the new capturer */ + dec_vid_win(new_wid); + } + + /* Revert back to the old capturer */ + pjmedia_vid_port_disconnect(w->vp_cap); + status = pjmedia_vid_tee_add_dst_port2(w->tee, 0, media_port); + pjmedia_vid_port_connect(w->vp_cap, w->tee, PJ_FALSE); + if (status != PJ_SUCCESS) + return status; + +#if ENABLE_EVENT + /* Resubscribe */ + pjmedia_event_subscribe(NULL, &call_media_on_event, + call_med, w->vp_cap); +#endif + + return status; +} + + +/* Start/stop transmitting video stream in a call */ +static pj_status_t call_set_tx_video(pjsua_call *call, + int med_idx, + pj_bool_t enable) +{ + pjsua_call_media *call_med; + pj_status_t status; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + call_med = &call->media[med_idx]; + + /* Verify if the stream is transmitting video */ + if (call_med->type != PJMEDIA_TYPE_VIDEO || + (enable && (call_med->dir & PJMEDIA_DIR_ENCODING) == 0)) + { + return PJ_EINVAL; + } + + if (enable) { + /* Start stream in encoding direction */ + status = pjmedia_vid_stream_resume(call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING); + } else { + /* Pause stream in encoding direction */ + status = pjmedia_vid_stream_pause( call_med->strm.v.stream, + PJMEDIA_DIR_ENCODING); + } + + return status; +} + + +static pj_status_t call_send_vid_keyframe(pjsua_call *call, + int med_idx) +{ + pjsua_call_media *call_med; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + call_med = &call->media[med_idx]; + + /* Verify media type and stream instance. */ + if (call_med->type != PJMEDIA_TYPE_VIDEO || !call_med->strm.v.stream) + return PJ_EINVAL; + + return pjmedia_vid_stream_send_keyframe(call_med->strm.v.stream); +} + + +/* + * Start, stop, and/or manipulate video transmission for the specified call. + */ +PJ_DEF(pj_status_t) pjsua_call_set_vid_strm ( + pjsua_call_id call_id, + pjsua_call_vid_strm_op op, + const pjsua_call_vid_strm_op_param *param) +{ + pjsua_call *call; + pjsua_call_vid_strm_op_param param_; + pj_status_t status; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + PJ_ASSERT_RETURN(op != PJSUA_CALL_VID_STRM_NO_OP, PJ_EINVAL); + + PJ_LOG(4,(THIS_FILE, "Call %d: set video stream, op=%d", + call_id, op)); + pj_log_push_indent(); + + PJSUA_LOCK(); + + call = &pjsua_var.calls[call_id]; + + if (param) { + param_ = *param; + } else { + pjsua_call_vid_strm_op_param_default(¶m_); + } + + /* If set to PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with + * account default video capture device. + */ + if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjsua_acc_config *acc_cfg = &pjsua_var.acc[call->acc_id].cfg; + param_.cap_dev = acc_cfg->vid_cap_dev; + + /* If the account default video capture device is + * PJMEDIA_VID_DEFAULT_CAPTURE_DEV, replace it with + * global default video capture device. + */ + if (param_.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(param_.cap_dev, &info); + pj_assert(info.dir == PJMEDIA_DIR_CAPTURE); + param_.cap_dev = info.id; + } + } + + switch (op) { + case PJSUA_CALL_VID_STRM_ADD: + status = call_add_video(call, param_.cap_dev, param_.dir); + break; + case PJSUA_CALL_VID_STRM_REMOVE: + status = call_modify_video(call, param_.med_idx, PJMEDIA_DIR_NONE, + PJ_TRUE); + break; + case PJSUA_CALL_VID_STRM_CHANGE_DIR: + status = call_modify_video(call, param_.med_idx, param_.dir, PJ_FALSE); + break; + case PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV: + status = call_change_cap_dev(call, param_.med_idx, param_.cap_dev); + break; + case PJSUA_CALL_VID_STRM_START_TRANSMIT: + status = call_set_tx_video(call, param_.med_idx, PJ_TRUE); + break; + case PJSUA_CALL_VID_STRM_STOP_TRANSMIT: + status = call_set_tx_video(call, param_.med_idx, PJ_FALSE); + break; + case PJSUA_CALL_VID_STRM_SEND_KEYFRAME: + status = call_send_vid_keyframe(call, param_.med_idx); + break; + default: + status = PJ_EINVALIDOP; + break; + } + + PJSUA_UNLOCK(); + pj_log_pop_indent(); + + return status; +} + + +/* + * Get the media stream index of the default video stream in the call. + */ +PJ_DEF(int) pjsua_call_get_vid_stream_idx(pjsua_call_id call_id) +{ + pjsua_call *call; + int first_active, first_inactive; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + PJSUA_LOCK(); + call = &pjsua_var.calls[call_id]; + call_get_vid_strm_info(call, &first_active, &first_inactive, NULL, NULL); + PJSUA_UNLOCK(); + + if (first_active == -1) + return first_inactive; + + return first_active; +} + + +/* + * Determine if video stream for the specified call is currently running + * for the specified direction. + */ +PJ_DEF(pj_bool_t) pjsua_call_vid_stream_is_running( pjsua_call_id call_id, + int med_idx, + pjmedia_dir dir) +{ + pjsua_call *call; + pjsua_call_media *call_med; + + PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, + PJ_EINVAL); + + /* Verify and normalize media index */ + if (med_idx == -1) { + med_idx = pjsua_call_get_vid_stream_idx(call_id); + } + + call = &pjsua_var.calls[call_id]; + PJ_ASSERT_RETURN(med_idx >= 0 && med_idx < (int)call->med_cnt, PJ_EINVAL); + + call_med = &call->media[med_idx]; + + /* Verify if the stream is transmitting video */ + if (call_med->type != PJMEDIA_TYPE_VIDEO || (call_med->dir & dir) == 0 || + !call_med->strm.v.stream) + { + return PJ_FALSE; + } + + return pjmedia_vid_stream_is_running(call_med->strm.v.stream, dir); +} + +#endif /* PJSUA_HAS_VIDEO */ + +#endif /* PJSUA_MEDIA_HAS_PJMEDIA */ diff --git a/pjsip/src/test/dlg_core_test.c b/pjsip/src/test/dlg_core_test.c new file mode 100644 index 0000000..ef2f35b --- /dev/null +++ b/pjsip/src/test/dlg_core_test.c @@ -0,0 +1,23 @@ +/* $Id: dlg_core_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> + diff --git a/pjsip/src/test/dns_test.c b/pjsip/src/test/dns_test.c new file mode 100644 index 0000000..7f6f3b0 --- /dev/null +++ b/pjsip/src/test/dns_test.c @@ -0,0 +1,618 @@ +/* $Id: dns_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> +#include <pjlib-util.h> + +/* For logging purpose. */ +#define THIS_FILE "dns_test.c" + +struct result +{ + pj_status_t status; + pjsip_server_addresses servers; +}; + + +static void cb(pj_status_t status, + void *token, + const struct pjsip_server_addresses *addr) +{ + struct result *result = (struct result*) token; + + result->status = status; + if (status == PJ_SUCCESS) + pj_memcpy(&result->servers, addr, sizeof(*addr)); +} + + +static void add_dns_entries(pj_dns_resolver *resv) +{ + /* Inject DNS SRV entry */ + pj_dns_parsed_packet pkt; + pj_dns_parsed_query q; + pj_dns_parsed_rr ans[4]; + pj_dns_parsed_rr ar[5]; + pj_str_t tmp; + unsigned i; + + /* + * This is answer to SRV query to "example.com" domain, and + * the answer contains full reference to the A records of + * the server. The full DNS records is : + + _sip._udp.example.com 3600 IN SRV 0 0 5060 sip01.example.com. + _sip._udp.example.com 3600 IN SRV 0 20 5060 sip02.example.com. + _sip._udp.example.com 3600 IN SRV 0 10 5060 sip03.example.com. + _sip._udp.example.com 3600 IN SRV 1 0 5060 sip04.example.com. + + sip01.example.com. 3600 IN A 1.1.1.1 + sip02.example.com. 3600 IN A 2.2.2.2 + sip03.example.com. 3600 IN A 3.3.3.3 + sip04.example.com. 3600 IN A 4.4.4.4 + + ; Additionally, add A record for "example.com" + example.com. 3600 IN A 5.5.5.5 + + */ + pj_bzero(&pkt, sizeof(pkt)); + pj_bzero(ans, sizeof(ans)); + pj_bzero(ar, sizeof(ar)); + + pkt.hdr.flags = PJ_DNS_SET_QR(1); + pkt.hdr.anscount = PJ_ARRAY_SIZE(ans); + pkt.hdr.arcount = 0; + pkt.ans = ans; + pkt.arr = ar; + + ans[0].name = pj_str("_sip._udp.example.com"); + ans[0].type = PJ_DNS_TYPE_SRV; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.srv.prio = 0; + ans[0].rdata.srv.weight = 0; + ans[0].rdata.srv.port = 5060; + ans[0].rdata.srv.target = pj_str("sip01.example.com"); + + ans[1].name = pj_str("_sip._udp.example.com"); + ans[1].type = PJ_DNS_TYPE_SRV; + ans[1].dnsclass = PJ_DNS_CLASS_IN; + ans[1].ttl = 3600; + ans[1].rdata.srv.prio = 0; + ans[1].rdata.srv.weight = 20; + ans[1].rdata.srv.port = 5060; + ans[1].rdata.srv.target = pj_str("sip02.example.com"); + + ans[2].name = pj_str("_sip._udp.example.com"); + ans[2].type = PJ_DNS_TYPE_SRV; + ans[2].dnsclass = PJ_DNS_CLASS_IN; + ans[2].ttl = 3600; + ans[2].rdata.srv.prio = 0; + ans[2].rdata.srv.weight = 10; + ans[2].rdata.srv.port = 5060; + ans[2].rdata.srv.target = pj_str("sip03.example.com"); + + ans[3].name = pj_str("_sip._udp.example.com"); + ans[3].type = PJ_DNS_TYPE_SRV; + ans[3].dnsclass = PJ_DNS_CLASS_IN; + ans[3].ttl = 3600; + ans[3].rdata.srv.prio = 1; + ans[3].rdata.srv.weight = 0; + ans[3].rdata.srv.port = 5060; + ans[3].rdata.srv.target = pj_str("sip04.example.com"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + ar[0].name = pj_str("sip01.example.com"); + ar[0].type = PJ_DNS_TYPE_A; + ar[0].dnsclass = PJ_DNS_CLASS_IN; + ar[0].ttl = 3600; + ar[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "1.1.1.1")); + + ar[1].name = pj_str("sip02.example.com"); + ar[1].type = PJ_DNS_TYPE_A; + ar[1].dnsclass = PJ_DNS_CLASS_IN; + ar[1].ttl = 3600; + ar[1].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "2.2.2.2")); + + ar[2].name = pj_str("sip03.example.com"); + ar[2].type = PJ_DNS_TYPE_A; + ar[2].dnsclass = PJ_DNS_CLASS_IN; + ar[2].ttl = 3600; + ar[2].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "3.3.3.3")); + + ar[3].name = pj_str("sip04.example.com"); + ar[3].type = PJ_DNS_TYPE_A; + ar[3].dnsclass = PJ_DNS_CLASS_IN; + ar[3].ttl = 3600; + ar[3].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "4.4.4.4")); + + ar[4].name = pj_str("example.com"); + ar[4].type = PJ_DNS_TYPE_A; + ar[4].dnsclass = PJ_DNS_CLASS_IN; + ar[4].ttl = 3600; + ar[4].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "5.5.5.5")); + + /* + * Create individual A records for all hosts in "example.com" domain. + */ + for (i=0; i<PJ_ARRAY_SIZE(ar); ++i) { + pj_bzero(&pkt, sizeof(pkt)); + pkt.hdr.flags = PJ_DNS_SET_QR(1); + pkt.hdr.qdcount = 1; + pkt.q = &q; + q.name = ar[i].name; + q.type = ar[i].type; + q.dnsclass = PJ_DNS_CLASS_IN; + pkt.hdr.anscount = 1; + pkt.ans = &ar[i]; + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + } + + /* + * Simulate DNS error response by creating these answers. + * Sample of invalid SRV records: _sip._udp.sip01.example.com. + */ + for (i=0; i<PJ_ARRAY_SIZE(ans); ++i) { + pj_dns_parsed_query q; + char buf[128]; + char *services[] = { "_sip._udp.", "_sip._tcp.", "_sips._tcp."}; + unsigned j; + + for (j=0; j<PJ_ARRAY_SIZE(services); ++j) { + q.dnsclass = PJ_DNS_CLASS_IN; + q.type = PJ_DNS_TYPE_SRV; + + q.name.ptr = buf; + pj_bzero(buf, sizeof(buf)); + pj_strcpy2(&q.name, services[j]); + pj_strcat(&q.name, &ans[i].rdata.srv.target); + + pj_bzero(&pkt, sizeof(pkt)); + pkt.hdr.qdcount = 1; + pkt.hdr.flags = PJ_DNS_SET_QR(1) | + PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN); + pkt.q = &q; + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + } + } + + + /* + * ANOTHER DOMAIN. + * + * This time we let SRV and A get answered in different DNS + * query. + */ + + /* The "domain.com" DNS records (note the different the port): + + _sip._tcp.domain.com 3600 IN SRV 1 0 50060 sip06.domain.com. + _sip._tcp.domain.com 3600 IN SRV 2 0 50060 sip07.domain.com. + + sip06.domain.com. 3600 IN A 6.6.6.6 + sip07.domain.com. 3600 IN A 7.7.7.7 + */ + + pj_bzero(&pkt, sizeof(pkt)); + pj_bzero(&ans, sizeof(ans)); + pkt.hdr.flags = PJ_DNS_SET_QR(1); + pkt.hdr.anscount = 2; + pkt.ans = ans; + + /* Add the SRV records, with reverse priority (to test that sorting + * works. + */ + ans[0].name = pj_str("_sip._tcp.domain.com"); + ans[0].type = PJ_DNS_TYPE_SRV; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.srv.prio = 2; + ans[0].rdata.srv.weight = 0; + ans[0].rdata.srv.port = 50060; + ans[0].rdata.srv.target = pj_str("SIP07.DOMAIN.COM"); + + ans[1].name = pj_str("_sip._tcp.domain.com"); + ans[1].type = PJ_DNS_TYPE_SRV; + ans[1].dnsclass = PJ_DNS_CLASS_IN; + ans[1].ttl = 3600; + ans[1].rdata.srv.prio = 1; + ans[1].rdata.srv.weight = 0; + ans[1].rdata.srv.port = 50060; + ans[1].rdata.srv.target = pj_str("SIP06.DOMAIN.COM"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + /* From herein there is only one answer */ + pkt.hdr.anscount = 1; + + /* Add a single SRV for UDP */ + ans[0].name = pj_str("_sip._udp.domain.com"); + ans[0].type = PJ_DNS_TYPE_SRV; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.srv.prio = 0; + ans[0].rdata.srv.weight = 0; + ans[0].rdata.srv.port = 50060; + ans[0].rdata.srv.target = pj_str("SIP06.DOMAIN.COM"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + + /* Add the A record for sip06.domain.com */ + ans[0].name = pj_str("sip06.domain.com"); + ans[0].type = PJ_DNS_TYPE_A; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "6.6.6.6")); + + pkt.hdr.qdcount = 1; + pkt.q = &q; + q.name = ans[0].name; + q.type = ans[0].type; + q.dnsclass = ans[0].dnsclass; + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + /* Add the A record for sip07.domain.com */ + ans[0].name = pj_str("sip07.domain.com"); + ans[0].type = PJ_DNS_TYPE_A; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "7.7.7.7")); + + pkt.hdr.qdcount = 1; + pkt.q = &q; + q.name = ans[0].name; + q.type = ans[0].type; + q.dnsclass = ans[0].dnsclass; + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + pkt.hdr.qdcount = 0; +} + + +/* + * Perform server resolution where the results are expected to + * come in strict order. + */ +static int test_resolve(const char *title, + pj_pool_t *pool, + pjsip_transport_type_e type, + char *name, + int port, + pjsip_server_addresses *ref) +{ + pjsip_host_info dest; + struct result result; + + PJ_LOG(3,(THIS_FILE, " test_resolve(): %s", title)); + + dest.type = type; + dest.flag = pjsip_transport_get_flag_from_type(type); + dest.addr.host = pj_str(name); + dest.addr.port = port; + + result.status = 0x12345678; + + pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb); + + while (result.status == 0x12345678) { + int i = 0; + pj_time_val timeout = { 1, 0 }; + pjsip_endpt_handle_events(endpt, &timeout); + if (i == 1) + pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE); + } + + if (result.status != PJ_SUCCESS) { + app_perror(" pjsip_endpt_resolve() error", result.status); + return result.status; + } + + if (ref) { + unsigned i; + + if (ref->count != result.servers.count) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 10: result count mismatch")); + return 10; + } + + for (i=0; i<ref->count; ++i) { + pj_sockaddr_in *ra = (pj_sockaddr_in *)&ref->entry[i].addr; + pj_sockaddr_in *rb = (pj_sockaddr_in *)&result.servers.entry[i].addr; + + if (ra->sin_addr.s_addr != rb->sin_addr.s_addr) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 20: IP address mismatch")); + return 20; + } + if (ra->sin_port != rb->sin_port) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 30: port mismatch")); + return 30; + } + if (ref->entry[i].addr_len != result.servers.entry[i].addr_len) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 40: addr_len mismatch")); + return 40; + } + if (ref->entry[i].type != result.servers.entry[i].type) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 50: transport type mismatch")); + return 50; + } + } + } + + return PJ_SUCCESS; +} + +/* + * Perform round-robin/load balance test. + */ +static int round_robin_test(pj_pool_t *pool) +{ + enum { COUNT = 400, PCT_ALLOWANCE = 5 }; + unsigned i; + struct server_hit + { + char *ip_addr; + unsigned percent; + unsigned hits; + } server_hit[] = + { + { "1.1.1.1", 3, 0 }, + { "2.2.2.2", 65, 0 }, + { "3.3.3.3", 32, 0 }, + { "4.4.4.4", 0, 0 } + }; + + PJ_LOG(3,(THIS_FILE, " Performing round-robin/load-balance test..")); + + /* Do multiple resolve request to "example.com". + * The resolver should select the server based on the weight proportion + * the the servers in the SRV entry. + */ + for (i=0; i<COUNT; ++i) { + pjsip_host_info dest; + struct result result; + unsigned j; + + dest.type = PJSIP_TRANSPORT_UDP; + dest.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP); + dest.addr.host = pj_str("example.com"); + dest.addr.port = 0; + + result.status = 0x12345678; + + pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb); + + while (result.status == 0x12345678) { + int i = 0; + pj_time_val timeout = { 1, 0 }; + pjsip_endpt_handle_events(endpt, &timeout); + if (i == 1) + pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE); + } + + /* Find which server was "hit" */ + for (j=0; j<PJ_ARRAY_SIZE(server_hit); ++j) { + pj_str_t tmp; + pj_in_addr a1; + pj_sockaddr_in *a2; + + tmp = pj_str(server_hit[j].ip_addr); + a1 = pj_inet_addr(&tmp); + a2 = (pj_sockaddr_in*) &result.servers.entry[0].addr; + + if (a1.s_addr == a2->sin_addr.s_addr) { + server_hit[j].hits++; + break; + } + } + + if (j == PJ_ARRAY_SIZE(server_hit)) { + PJ_LOG(1,(THIS_FILE, "..round_robin_test() error 10: returned address mismatch")); + return 10; + } + } + + /* Print the actual hit rate */ + for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) { + PJ_LOG(3,(THIS_FILE, " ..Server %s: weight=%d%%, hit %d%% times", + server_hit[i].ip_addr, server_hit[i].percent, + (server_hit[i].hits * 100) / COUNT)); + } + + /* Compare the actual hit with the weight proportion */ + for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) { + int actual_pct = (server_hit[i].hits * 100) / COUNT; + + if (actual_pct + PCT_ALLOWANCE < (int)server_hit[i].percent || + actual_pct - PCT_ALLOWANCE > (int)server_hit[i].percent) + { + PJ_LOG(1,(THIS_FILE, + "..round_robin_test() error 20: " + "hit rate difference for server %s (%d%%) is more than " + "tolerable allowance (%d%%)", + server_hit[i].ip_addr, + actual_pct - server_hit[i].percent, + PCT_ALLOWANCE)); + return 20; + } + } + + PJ_LOG(3,(THIS_FILE, + " Load balance test success, hit-rate is " + "within %d%% allowance", PCT_ALLOWANCE)); + return PJ_SUCCESS; +} + + +#define C(expr) status = expr; \ + if (status != PJ_SUCCESS) app_perror(THIS_FILE, "Error", status); + +static void add_ref(pjsip_server_addresses *r, + pjsip_transport_type_e type, + char *addr, + int port) +{ + pj_sockaddr_in *a; + pj_str_t tmp; + + r->entry[r->count].type = type; + r->entry[r->count].priority = 0; + r->entry[r->count].weight = 0; + r->entry[r->count].addr_len = sizeof(pj_sockaddr_in); + + a = (pj_sockaddr_in *)&r->entry[r->count].addr; + a->sin_family = pj_AF_INET(); + tmp = pj_str(addr); + a->sin_addr = pj_inet_addr(&tmp); + a->sin_port = pj_htons((pj_uint16_t)port); + + r->count++; +} + +static void create_ref(pjsip_server_addresses *r, + pjsip_transport_type_e type, + char *addr, + int port) +{ + r->count = 0; + add_ref(r, type, addr, port); +} + + +/* + * Main test entry. + */ +int resolve_test(void) +{ + pj_pool_t *pool; + pj_dns_resolver *resv; + pj_str_t nameserver; + pj_uint16_t port = 5353; + pj_status_t status; + + pool = pjsip_endpt_create_pool(endpt, NULL, 4000, 4000); + + status = pjsip_endpt_create_resolver(endpt, &resv); + + nameserver = pj_str("192.168.0.106"); + pj_dns_resolver_set_ns(resv, 1, &nameserver, &port); + pjsip_endpt_set_resolver(endpt, resv); + + add_dns_entries(resv); + + /* These all should be resolved as IP addresses (DNS A query) */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "1.1.1.1", 5060); + status = test_resolve("IP address without transport and port", pool, PJSIP_TRANSPORT_UNSPECIFIED, "1.1.1.1", 0, &ref); + if (status != PJ_SUCCESS) + return -100; + } + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "1.1.1.1", 5060); + status = test_resolve("IP address with explicit port", pool, PJSIP_TRANSPORT_UNSPECIFIED, "1.1.1.1", 5060, &ref); + if (status != PJ_SUCCESS) + return -110; + } + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TCP, "1.1.1.1", 5060); + status = test_resolve("IP address without port (TCP)", pool, PJSIP_TRANSPORT_TCP,"1.1.1.1", 0, &ref); + if (status != PJ_SUCCESS) + return -120; + } + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TLS, "1.1.1.1", 5061); + status = test_resolve("IP address without port (TLS)", pool, PJSIP_TRANSPORT_TLS, "1.1.1.1", 0, &ref); + if (status != PJ_SUCCESS) + return -130; + } + + /* This should be resolved as DNS A record (because port is present) */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "5.5.5.5", 5060); + status = test_resolve("domain name with port should resolve to A record", pool, PJSIP_TRANSPORT_UNSPECIFIED, "example.com", 5060, &ref); + if (status != PJ_SUCCESS) + return -140; + } + + /* This will fail to be resolved as SRV, resolver should fallback to + * resolving to A record. + */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "2.2.2.2", 5060); + status = test_resolve("failure with SRV fallback to A record", pool, PJSIP_TRANSPORT_UNSPECIFIED, "sip02.example.com", 0, &ref); + if (status != PJ_SUCCESS) + return -150; + } + + /* Same as above, but explicitly for TLS. */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TLS, "2.2.2.2", 5061); + status = test_resolve("failure with SRV fallback to A record (for TLS)", pool, PJSIP_TRANSPORT_TLS, "sip02.example.com", 0, &ref); + if (status != PJ_SUCCESS) + return -150; + } + + /* Standard DNS SRV followed by A recolution */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "6.6.6.6", 50060); + status = test_resolve("standard SRV resolution", pool, PJSIP_TRANSPORT_UNSPECIFIED, "domain.com", 0, &ref); + if (status != PJ_SUCCESS) + return -155; + } + + /* Standard DNS SRV followed by A recolution (explicit transport) */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TCP, "6.6.6.6", 50060); + add_ref(&ref, PJSIP_TRANSPORT_TCP, "7.7.7.7", 50060); + status = test_resolve("standard SRV resolution with explicit transport (TCP)", pool, PJSIP_TRANSPORT_TCP, "domain.com", 0, &ref); + if (status != PJ_SUCCESS) + return -160; + } + + + /* Round robin/load balance test */ + if (round_robin_test(pool) != 0) + return -170; + + /* Timeout test */ + { + status = test_resolve("timeout test", pool, PJSIP_TRANSPORT_UNSPECIFIED, "an.invalid.address", 0, NULL); + if (status == PJ_SUCCESS) + return -150; + } + + return 0; +} + diff --git a/pjsip/src/test/inv_offer_answer_test.c b/pjsip/src/test/inv_offer_answer_test.c new file mode 100644 index 0000000..7fdb11e --- /dev/null +++ b/pjsip/src/test/inv_offer_answer_test.c @@ -0,0 +1,691 @@ +/* $Id: inv_offer_answer_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip_ua.h> +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "inv_offer_answer_test.c" +#define PORT 5068 +#define CONTACT "sip:127.0.0.1:5068" +#define TRACE_(x) PJ_LOG(3,x) + +static struct oa_sdp_t +{ + const char *offer; + const char *answer; + unsigned pt_result; +} oa_sdp[] = +{ + { + /* Offer: */ + "v=0\r\n" + "o=alice 1 1 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 1 1 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n", + + 0 + }, + + { + /* Offer: */ + "v=0\r\n" + "o=alice 2 2 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 8\r\n" + "a=rtpmap:0 PCMA/8000\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 2 2 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 8\r\n" + "a=rtpmap:0 PCMA/8000\r\n", + + 8 + }, + + { + /* Offer: */ + "v=0\r\n" + "o=alice 3 3 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 3\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 3 3 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 3\r\n", + + 3 + }, + + { + /* Offer: */ + "v=0\r\n" + "o=alice 4 4 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 4\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 4 4 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 4\r\n", + + 4 + } +}; + + + +typedef enum oa_t +{ + OFFERER_NONE, + OFFERER_UAC, + OFFERER_UAS +} oa_t; + +typedef struct inv_test_param_t +{ + char *title; + unsigned inv_option; + pj_bool_t need_established; + unsigned count; + oa_t oa[4]; +} inv_test_param_t; + +typedef struct inv_test_t +{ + inv_test_param_t param; + pjsip_inv_session *uac; + pjsip_inv_session *uas; + + pj_bool_t complete; + pj_bool_t uas_complete, + uac_complete; + + unsigned oa_index; + unsigned uac_update_cnt, + uas_update_cnt; +} inv_test_t; + + +/**************** GLOBALS ******************/ +static inv_test_t inv_test; +static unsigned job_cnt; + +typedef enum job_type +{ + SEND_OFFER, + ESTABLISH_CALL +} job_type; + +typedef struct job_t +{ + job_type type; + pjsip_role_e who; +} job_t; + +static job_t jobs[128]; + + +/**************** UTILS ******************/ +static pjmedia_sdp_session *create_sdp(pj_pool_t *pool, const char *body) +{ + pjmedia_sdp_session *sdp; + pj_str_t dup; + pj_status_t status; + + pj_strdup2_with_null(pool, &dup, body); + status = pjmedia_sdp_parse(pool, dup.ptr, dup.slen, &sdp); + pj_assert(status == PJ_SUCCESS); + + return sdp; +} + +/**************** INVITE SESSION CALLBACKS ******************/ +static void on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer) +{ + pjmedia_sdp_session *sdp; + + PJ_UNUSED_ARG(offer); + + sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].answer); + pjsip_inv_set_sdp_answer(inv, sdp); + + if (inv_test.oa_index == inv_test.param.count-1 && + inv_test.param.need_established) + { + jobs[job_cnt].type = ESTABLISH_CALL; + jobs[job_cnt].who = PJSIP_ROLE_UAS; + job_cnt++; + } +} + + +static void on_create_offer(pjsip_inv_session *inv, + pjmedia_sdp_session **p_offer) +{ + PJ_UNUSED_ARG(inv); + PJ_UNUSED_ARG(p_offer); + + pj_assert(!"Should not happen"); +} + +static void on_media_update(pjsip_inv_session *inv_ses, + pj_status_t status) +{ + PJ_UNUSED_ARG(status); + + if (inv_ses == inv_test.uas) { + inv_test.uas_update_cnt++; + pj_assert(inv_test.uas_update_cnt - inv_test.uac_update_cnt <= 1); + TRACE_((THIS_FILE, " Callee media is established")); + } else if (inv_ses == inv_test.uac) { + inv_test.uac_update_cnt++; + pj_assert(inv_test.uac_update_cnt - inv_test.uas_update_cnt <= 1); + TRACE_((THIS_FILE, " Caller media is established")); + + } else { + pj_assert(!"Unknown session!"); + } + + if (inv_test.uac_update_cnt == inv_test.uas_update_cnt) { + inv_test.oa_index++; + + if (inv_test.oa_index < inv_test.param.count) { + switch (inv_test.param.oa[inv_test.oa_index]) { + case OFFERER_UAC: + jobs[job_cnt].type = SEND_OFFER; + jobs[job_cnt].who = PJSIP_ROLE_UAC; + job_cnt++; + break; + case OFFERER_UAS: + jobs[job_cnt].type = SEND_OFFER; + jobs[job_cnt].who = PJSIP_ROLE_UAS; + job_cnt++; + break; + default: + pj_assert(!"Invalid oa"); + } + } + + pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs)); + } +} + +static void on_state_changed(pjsip_inv_session *inv, pjsip_event *e) +{ + const char *who = NULL; + + PJ_UNUSED_ARG(e); + + if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { + TRACE_((THIS_FILE, " %s call disconnected", + (inv==inv_test.uas ? "Callee" : "Caller"))); + return; + } + + if (inv->state != PJSIP_INV_STATE_CONFIRMED) + return; + + if (inv == inv_test.uas) { + inv_test.uas_complete = PJ_TRUE; + who = "Callee"; + } else if (inv == inv_test.uac) { + inv_test.uac_complete = PJ_TRUE; + who = "Caller"; + } else + pj_assert(!"No session"); + + TRACE_((THIS_FILE, " %s call is confirmed", who)); + + if (inv_test.uac_complete && inv_test.uas_complete) + inv_test.complete = PJ_TRUE; +} + + +/**************** MODULE TO RECEIVE INITIAL INVITE ******************/ + +static pj_bool_t on_rx_request(pjsip_rx_data *rdata) +{ + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG && + rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) + { + pjsip_dialog *dlg; + pjmedia_sdp_session *sdp = NULL; + pj_str_t uri; + pjsip_tx_data *tdata; + pj_status_t status; + + /* + * Create UAS + */ + uri = pj_str(CONTACT); + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, + &uri, &dlg); + pj_assert(status == PJ_SUCCESS); + + if (inv_test.param.oa[0] == OFFERER_UAC) + sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].answer); + else if (inv_test.param.oa[0] == OFFERER_UAS) + sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].offer); + else + pj_assert(!"Invalid offerer type"); + + status = pjsip_inv_create_uas(dlg, rdata, sdp, inv_test.param.inv_option, &inv_test.uas); + pj_assert(status == PJ_SUCCESS); + + TRACE_((THIS_FILE, " Sending 183 with SDP")); + + /* + * Answer with 183 + */ + status = pjsip_inv_initial_answer(inv_test.uas, rdata, 183, NULL, + NULL, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv_test.uas, tdata); + pj_assert(status == PJ_SUCCESS); + + return PJ_TRUE; + } + + return PJ_FALSE; +} + +static pjsip_module mod_inv_oa_test = +{ + NULL, NULL, /* prev, next. */ + { "mod-inv-oa-test", 15 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + + +/**************** THE TEST ******************/ +static void run_job(job_t *j) +{ + pjsip_inv_session *inv; + pjsip_tx_data *tdata; + pjmedia_sdp_session *sdp; + pj_status_t status; + + if (j->who == PJSIP_ROLE_UAC) + inv = inv_test.uac; + else + inv = inv_test.uas; + + switch (j->type) { + case SEND_OFFER: + sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].offer); + + TRACE_((THIS_FILE, " Sending UPDATE with offer")); + status = pjsip_inv_update(inv, NULL, sdp, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv, tdata); + pj_assert(status == PJ_SUCCESS); + break; + case ESTABLISH_CALL: + TRACE_((THIS_FILE, " Sending 200/OK")); + status = pjsip_inv_answer(inv, 200, NULL, NULL, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv, tdata); + pj_assert(status == PJ_SUCCESS); + break; + } +} + + +static int perform_test(inv_test_param_t *param) +{ + pj_str_t uri; + pjsip_dialog *dlg; + pjmedia_sdp_session *sdp; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " %s", param->title)); + + pj_bzero(&inv_test, sizeof(inv_test)); + pj_memcpy(&inv_test.param, param, sizeof(*param)); + job_cnt = 0; + + uri = pj_str(CONTACT); + + /* + * Create UAC + */ + status = pjsip_dlg_create_uac(pjsip_ua_instance(), + &uri, &uri, &uri, &uri, &dlg); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -10); + + if (inv_test.param.oa[0] == OFFERER_UAC) + sdp = create_sdp(dlg->pool, oa_sdp[0].offer); + else + sdp = NULL; + + status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20); + + TRACE_((THIS_FILE, " Sending INVITE %s offer", (sdp ? "with" : "without"))); + + /* + * Make call! + */ + status = pjsip_inv_invite(inv_test.uac, &tdata); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30); + + status = pjsip_inv_send_msg(inv_test.uac, tdata); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30); + + /* + * Wait until test completes + */ + while (!inv_test.complete) { + pj_time_val delay = {0, 20}; + + pjsip_endpt_handle_events(endpt, &delay); + + while (job_cnt) { + job_t j; + + j = jobs[0]; + pj_array_erase(jobs, sizeof(jobs[0]), job_cnt, 0); + --job_cnt; + + run_job(&j); + } + } + + flush_events(100); + + /* + * Hangup + */ + TRACE_((THIS_FILE, " Disconnecting call")); + status = pjsip_inv_end_session(inv_test.uas, PJSIP_SC_DECLINE, 0, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv_test.uas, tdata); + pj_assert(status == PJ_SUCCESS); + + flush_events(500); + + return 0; +} + + +static pj_bool_t log_on_rx_msg(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + char info[80]; + + if (msg->type == PJSIP_REQUEST_MSG) + pj_ansi_snprintf(info, sizeof(info), "%.*s", + (int)msg->line.req.method.name.slen, + msg->line.req.method.name.ptr); + else + pj_ansi_snprintf(info, sizeof(info), "%d/%.*s", + msg->line.status.code, + (int)rdata->msg_info.cseq->method.name.slen, + rdata->msg_info.cseq->method.name.ptr); + + TRACE_((THIS_FILE, " Received %s %s sdp", info, + (msg->body ? "with" : "without"))); + + return PJ_FALSE; +} + + +/* Message logger module. */ +static pjsip_module mod_msg_logger = +{ + NULL, NULL, /* prev and next */ + { "mod-msg-loggee", 14}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &log_on_rx_msg, /* on_rx_request() */ + &log_on_rx_msg, /* on_rx_response() */ + NULL, /* on_tx_request() */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +static inv_test_param_t test_params[] = +{ +/* Normal scenario: + + UAC UAS + INVITE (offer) --> + 200/INVITE (answer) <-- + ACK --> + */ +#if 0 + { + "Standard INVITE with offer", + 0, + PJ_TRUE, + 1, + { OFFERER_UAC } + }, + + { + "Standard INVITE with offer, with 100rel", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 1, + { OFFERER_UAC } + }, +#endif + +/* Delayed offer: + UAC UAS + INVITE (no SDP) --> + 200/INVITE (offer) <-- + ACK (answer) --> + */ +#if 1 + { + "INVITE with no offer", + 0, + PJ_TRUE, + 1, + { OFFERER_UAS } + }, + + { + "INVITE with no offer, with 100rel", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 1, + { OFFERER_UAS } + }, +#endif + +/* Subsequent UAC offer with UPDATE: + + UAC UAS + INVITE (offer) --> + 180/rel (answer) <-- + UPDATE (offer) --> inv_update() on_rx_offer() + set_sdp_answer() + 200/UPDATE (answer) <-- + 200/INVITE <-- + ACK --> +*/ +#if 1 + { + "INVITE and UPDATE by UAC", + 0, + PJ_TRUE, + 2, + { OFFERER_UAC, OFFERER_UAC } + }, + { + "INVITE and UPDATE by UAC, with 100rel", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 2, + { OFFERER_UAC, OFFERER_UAC } + }, +#endif + +/* Subsequent UAS offer with UPDATE: + + INVITE (offer --> + 180/rel (answer) <-- + UPDATE (offer) <-- inv_update() + on_rx_offer() + set_sdp_answer() + 200/UPDATE (answer) --> + UPDATE (offer) --> on_rx_offer() + set_sdp_answer() + 200/UPDATE (answer) <-- + 200/INVITE <-- + ACK --> + + */ + { + "INVITE and many UPDATE by UAC and UAS", + 0, + PJ_TRUE, + 4, + { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS } + }, + +}; + + +static pjsip_dialog* on_dlg_forked(pjsip_dialog *first_set, pjsip_rx_data *res) +{ + PJ_UNUSED_ARG(first_set); + PJ_UNUSED_ARG(res); + + return NULL; +} + + +static void on_new_session(pjsip_inv_session *inv, pjsip_event *e) +{ + PJ_UNUSED_ARG(inv); + PJ_UNUSED_ARG(e); +} + + +int inv_offer_answer_test(void) +{ + unsigned i; + int rc = 0; + + /* Init UA layer */ + if (pjsip_ua_instance()->id == -1) { + pjsip_ua_init_param ua_param; + pj_bzero(&ua_param, sizeof(ua_param)); + ua_param.on_dlg_forked = &on_dlg_forked; + pjsip_ua_init_module(endpt, &ua_param); + } + + /* Init inv-usage */ + if (pjsip_inv_usage_instance()->id == -1) { + pjsip_inv_callback inv_cb; + pj_bzero(&inv_cb, sizeof(inv_cb)); + inv_cb.on_media_update = &on_media_update; + inv_cb.on_rx_offer = &on_rx_offer; + inv_cb.on_create_offer = &on_create_offer; + inv_cb.on_state_changed = &on_state_changed; + inv_cb.on_new_session = &on_new_session; + pjsip_inv_usage_init(endpt, &inv_cb); + } + + /* 100rel module */ + pjsip_100rel_init_module(endpt); + + /* Our module */ + pjsip_endpt_register_module(endpt, &mod_inv_oa_test); + pjsip_endpt_register_module(endpt, &mod_msg_logger); + + /* Create SIP UDP transport */ + { + pj_sockaddr_in addr; + pjsip_transport *tp; + pj_status_t status; + + pj_sockaddr_in_init(&addr, NULL, PORT); + status = pjsip_udp_transport_start(endpt, &addr, NULL, 1, &tp); + pj_assert(status == PJ_SUCCESS); + } + + /* Do tests */ + for (i=0; i<PJ_ARRAY_SIZE(test_params); ++i) { + rc = perform_test(&test_params[i]); + if (rc != 0) + goto on_return; + } + + +on_return: + return rc; +} + diff --git a/pjsip/src/test/main.c b/pjsip/src/test/main.c new file mode 100644 index 0000000..423e8cb --- /dev/null +++ b/pjsip/src/test/main.c @@ -0,0 +1,92 @@ +/* $Id: main.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +extern const char *system_name; + +static void usage() +{ + puts("Usage: test-pjsip"); + puts("Options:"); + puts(" -i,--interractive Key input at the end."); + puts(" -h,--help Show this screen"); + puts(" -l,--log-level N Set log level (0-6)"); +} + +int main(int argc, char *argv[]) +{ + int interractive = 0; + int retval; + char **opt_arg; + + PJ_UNUSED_ARG(argc); + + /* Parse arguments. */ + opt_arg = argv+1; + while (*opt_arg) { + if (strcmp(*opt_arg, "-i") == 0 || + strcmp(*opt_arg, "--interractive") == 0) + { + interractive = 1; + } else if (strcmp(*opt_arg, "-h") == 0 || + strcmp(*opt_arg, "--help") == 0) + { + usage(); + return 1; + } else if (strcmp(*opt_arg, "-l") == 0 || + strcmp(*opt_arg, "--log-level") == 0) + { + ++opt_arg; + if (!opt_arg) { + usage(); + return 1; + } + log_level = atoi(*opt_arg); + } else if (strcmp(*opt_arg, "-s") == 0 || + strcmp(*opt_arg, "--system") == 0) + { + ++opt_arg; + if (!opt_arg) { + usage(); + return 1; + } + system_name = *opt_arg; + } else { + usage(); + return 1; + } + + ++opt_arg; + } + + retval = test_main(); + + if (interractive) { + char s[10]; + printf("<Press ENTER to quit>\n"); fflush(stdout); + if (fgets(s, sizeof(s), stdin) == NULL) + return retval; + } + + return retval; +} diff --git a/pjsip/src/test/main_rtems.c b/pjsip/src/test/main_rtems.c new file mode 100644 index 0000000..a4d14e5 --- /dev/null +++ b/pjsip/src/test/main_rtems.c @@ -0,0 +1,12 @@ + +/* + * !! OIY OIY !! + * + * The purpose of this file is only to get pjsip-test linked. I haven't + * actually tried to run pjsip-test on RTEMS!! + * + */ + +#include "../../pjlib/src/pjlib-test/main_rtems.c" + + diff --git a/pjsip/src/test/main_win32.c b/pjsip/src/test/main_win32.c new file mode 100644 index 0000000..3043a39 --- /dev/null +++ b/pjsip/src/test/main_win32.c @@ -0,0 +1 @@ +#include "../../pjlib/src/pjlib-test/main_win32.c" diff --git a/pjsip/src/test/msg_err_test.c b/pjsip/src/test/msg_err_test.c new file mode 100644 index 0000000..d3c5636 --- /dev/null +++ b/pjsip/src/test/msg_err_test.c @@ -0,0 +1,104 @@ +/* $Id: msg_err_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "msg_err_test.c" + + +static pj_bool_t verify_success(pjsip_msg *msg, + pjsip_parser_err_report *err_list) +{ + PJ_UNUSED_ARG(msg); + PJ_UNUSED_ARG(err_list); + + return PJ_TRUE; +} + +static struct test_entry +{ + char msg[1024]; + pj_bool_t (*verify)(pjsip_msg *msg, + pjsip_parser_err_report *err_list); + +} test_entries[] = +{ + /* Syntax error in status line */ + { + "SIP/2.0 200\r\n" + "H-Name: H-Value\r\n" + "\r\n", + &verify_success + }, + + /* Syntax error in header */ + { + "SIP/2.0 200 OK\r\n" + "Via: SIP/2.0\r\n" + "H-Name: H-Value\r\n" + "\r\n", + &verify_success + }, + + /* Multiple syntax errors in headers */ + { + "SIP/2.0 200 OK\r\n" + "Via: SIP/2.0\r\n" + "H-Name: H-Value\r\n" + "Via: SIP/2.0\r\n" + "\r\n", + &verify_success + } +}; + + +int msg_err_test(void) +{ + pj_pool_t *pool; + unsigned i; + + PJ_LOG(3,(THIS_FILE, "Testing parsing error")); + + pool = pjsip_endpt_create_pool(endpt, "msgerrtest", 4000, 4000); + + for (i=0; i<PJ_ARRAY_SIZE(test_entries); ++i) { + pjsip_parser_err_report err_list, *e; + pjsip_msg *msg; + + PJ_LOG(3,(THIS_FILE, " Parsing msg %d", i)); + pj_list_init(&err_list); + msg = pjsip_parse_msg(pool, test_entries[i].msg, + strlen(test_entries[i].msg), &err_list); + + e = err_list.next; + while (e != &err_list) { + PJ_LOG(3,(THIS_FILE, + " reported syntax error at line %d col %d for %.*s", + e->line, e->col, + (int)e->hname.slen, + e->hname.ptr)); + e = e->next; + } + } + + pj_pool_release(pool); + return 0; +} diff --git a/pjsip/src/test/msg_logger.c b/pjsip/src/test/msg_logger.c new file mode 100644 index 0000000..aaeda7d --- /dev/null +++ b/pjsip/src/test/msg_logger.c @@ -0,0 +1,104 @@ +/* $Id: msg_logger.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "msg_logger.c" + +static pj_bool_t msg_log_enabled; + +static pj_bool_t on_rx_msg(pjsip_rx_data *rdata) +{ + if (msg_log_enabled) { + PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%s:%d:\n" + "%.*s\n" + "--end msg--", + rdata->msg_info.len, + pjsip_rx_data_get_info(rdata), + rdata->tp_info.transport->type_name, + rdata->pkt_info.src_name, + rdata->pkt_info.src_port, + rdata->msg_info.len, + rdata->msg_info.msg_buf)); + } + + return PJ_FALSE; +} + +static pj_status_t on_tx_msg(pjsip_tx_data *tdata) +{ + if (msg_log_enabled) { + PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%s:%d:\n" + "%.*s\n" + "--end msg--", + (tdata->buf.cur - tdata->buf.start), + pjsip_tx_data_get_info(tdata), + tdata->tp_info.transport->type_name, + tdata->tp_info.dst_name, + tdata->tp_info.dst_port, + (tdata->buf.cur - tdata->buf.start), + tdata->buf.start)); + } + return PJ_SUCCESS; +} + + +/* Message logger module. */ +static pjsip_module mod_msg_logger = +{ + NULL, NULL, /* prev and next */ + { "mod-msg-logger", 14}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &on_rx_msg, /* on_rx_request() */ + &on_rx_msg, /* on_rx_response() */ + &on_tx_msg, /* on_tx_request() */ + &on_tx_msg, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +int init_msg_logger(void) +{ + pj_status_t status; + + if (mod_msg_logger.id != -1) + return 0; + + status = pjsip_endpt_register_module(endpt, &mod_msg_logger); + if (status != PJ_SUCCESS) { + app_perror(" error registering module", status); + return -10; + } + + return 0; +} + +int msg_logger_set_enabled(pj_bool_t enabled) +{ + int val = msg_log_enabled; + msg_log_enabled = enabled; + return val; +} + diff --git a/pjsip/src/test/msg_test.c b/pjsip/src/test/msg_test.c new file mode 100644 index 0000000..f64906c --- /dev/null +++ b/pjsip/src/test/msg_test.c @@ -0,0 +1,2084 @@ +/* $Id: msg_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define POOL_SIZE 8000 +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 +# define LOOP 10000 +#else +# define LOOP 100000 +#endif +#define AVERAGE_MSG_LEN 800 +#define THIS_FILE "msg_test.c" + +static pjsip_msg *create_msg0(pj_pool_t *pool); +static pjsip_msg *create_msg1(pj_pool_t *pool); + +#define STATUS_PARTIAL 1 +#define STATUS_SYNTAX_ERROR 2 + +#define FLAG_DETECT_ONLY 1 +#define FLAG_PARSE_ONLY 4 +#define FLAG_PRINT_ONLY 8 + +struct test_msg +{ + char msg[1024]; + pjsip_msg *(*creator)(pj_pool_t *pool); + pj_size_t len; + int expected_status; +} test_array[] = +{ +{ + /* 'Normal' message with all headers. */ + "INVITE sip:user@foo SIP/2.0\n" + "from: Hi I'm Joe <sip:joe.user@bar.otherdomain.com>;tag=123457890123456\r" + "To: Fellow User <sip:user@foo.bar.domain.com>\r\n" + "Call-ID: 12345678901234567890@bar\r\n" + "Content-Length: 0\r\n" + "CSeq: 123456 INVITE\n" + "Contact: <sip:joe@bar> ; q=0.5;expires=3600,sip:user@host;q=0.500\r" + " ,sip:user2@host2\n" + "Content-Type: text/html ; charset=ISO-8859-4\r" + "Route: <sip:bigbox3.site3.atlanta.com;lr>,\r\n" + " <sip:server10.biloxi.com;lr>\r" + "Record-Route: <sip:server10.biloxi.com>,\r\n" /* multiple routes+folding*/ + " <sip:bigbox3.site3.atlanta.com;lr>\n" + "v: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c230\n" + "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n" /* folding. */ + " ;received=192.0.2.1\r\n" + "Via: SIP/2.0/UDP 10.2.1.1, SIP/2.0/TCP 192.168.1.1\n" + "Organization: \r" + "Max-Forwards: 70\n" + "X-Header: \r\n" /* empty header */ + "P-Associated-URI:\r\n" /* empty header without space */ + "\r\n", + &create_msg0, + 0, + PJ_SUCCESS +}, +{ + /* Typical response message. */ + "SIP/2.0 200 OK\r\n" + "Via: SIP/2.0/SCTP server10.biloxi.com;branch=z9hG4bKnashds8;rport;received=192.0.2.1\r\n" + "Via: SIP/2.0/UDP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2\r\n" + "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds ;received=192.0.2.3\r\n" + "Route: <sip:proxy.sipprovider.com>\r\n" + "Route: <sip:proxy.supersip.com:5060>\r\n" + "Max-Forwards: 70\r\n" + "To: Bob <sip:bob@biloxi.com>;tag=a6c85cf\r\n" + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n" + "Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n" + "CSeq: 314159 INVITE\r\n" + "Contact: <sips:bob@192.0.2.4>\r\n" + "Content-Type: application/sdp\r\n" + "Content-Length: 150\r\n" + "\r\n" + "v=0\r\n" + "o=alice 53655765 2353687637 IN IP4 pc33.atlanta.com\r\n" + "s=-\r\n" + "t=0 0\r\n" + "c=IN IP4 pc33.atlanta.com\r\n" + "m=audio 3456 RTP/AVP 0 1 3 99\r\n" + "a=rtpmap:0 PCMU/8000\r\n", + &create_msg1, + 0, + PJ_SUCCESS +}, +{ + /* Torture message from RFC 4475 + * 3.1.1.1 A short tortuous INVITE + */ + "INVITE sip:vivekg@chair-dnrc.example.com;unknownparam SIP/2.0\n" + "TO :\n" + " sip:vivekg@chair-dnrc.example.com ; tag = 1918181833n\n" + "from : \"J Rosenberg \\\\\\\"\" <sip:jdrosen@example.com>\n" + " ;\n" + " tag = 98asjd8\n" + "MaX-fOrWaRdS: 0068\n" + "Call-ID: wsinv.ndaksdj@192.0.2.1\n" + "Content-Length : 150\n" + "cseq: 0009\n" + " INVITE\n" + "Via : SIP / 2.0\n" + " /UDP\n" + " 192.0.2.2;rport;branch=390skdjuw\n" + "s :\n" + "NewFangledHeader: newfangled value\n" + " continued newfangled value\n" + "UnknownHeaderWithUnusualValue: ;;,,;;,;\n" + "Content-Type: application/sdp\n" + "Route:\n" + " <sip:services.example.com;lr;unknownwith=value;unknown-no-value>\n" + "v: SIP / 2.0 / TCP spindle.example.com ;\n" + " branch = z9hG4bK9ikj8 ,\n" + " SIP / 2.0 / UDP 192.168.255.111 ; branch=\n" + " z9hG4bK30239\n" + "m:\"Quoted string \\\"\\\"\" <sip:jdrosen@example.com> ; newparam =\n" + " newvalue ;\n" + " secondparam ; q = 0.33\r\n" + "\r\n" + "v=0\r\n" + "o=mhandley 29739 7272939 IN IP4 192.0.2.3\r\n" + "s=-\r\n" + "c=IN IP4 192.0.2.4\r\n" + "t=0 0\r\n" + "m=audio 49217 RTP/AVP 0 12\r\n" + "m=video 3227 RTP/AVP 31\r\n" + "a=rtpmap:31 LPC\r\n", + NULL, + 0, + PJ_SUCCESS +}, +{ + /* Torture message from RFC 4475 + * 3.1.1.2 Wide Range of Valid Characters + */ + "!interesting-Method0123456789_*+`.%indeed'~ sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*:&it+has=1,weird!*pas$wo~d_too.(doesn't-it)@example.com SIP/2.0\n" + "Via: SIP/2.0/UDP host1.example.com;rport;branch=z9hG4bK-.!%66*_+`'~\n" + "To: \"BEL:\\\x07 NUL:\\\x00 DEL:\\\x7F\" <sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*@example.com>\n" + "From: token1~` token2'+_ token3*%!.- <sip:mundane@example.com> ;fromParam''~+*_!.-%=\"\xD1\x80\xD0\xB0\xD0\xB1\xD0\xBE\xD1\x82\xD0\xB0\xD1\x8E\xD1\x89\xD0\xB8\xD0\xB9\";tag=_token~1'+`*%!-.\n" + "Call-ID: intmeth.word%ZK-!.*_+'@word`~)(><:\\/\"][?}{\n" + "CSeq: 139122385 !interesting-Method0123456789_*+`.%indeed'~\n" + "Max-Forwards: 255\n" + "extensionHeader-!.%*+_`'~: \xEF\xBB\xBF\xE5\xA4\xA7\xE5\x81\x9C\xE9\x9B\xBB\n" + "Content-Length: 0\r\n\r\n", + NULL, + 641, + PJ_SUCCESS +}, +{ + /* Torture message from RFC 4475 + * 3.1.1.3 Valid Use of the % Escaping Mechanism + */ + "INVITE sip:sips%3Auser%40example.com@example.net SIP/2.0\n" + "To: sip:%75se%72@example.com\n" + "From: <sip:I%20have%20spaces@example.net>;tag=1234\n" + "Max-Forwards: 87\n" + "i: esc01.239409asdfakjkn23onasd0-3234\n" + "CSeq: 234234 INVITE\n" + "Via: SIP/2.0/UDP host5.example.net;rport;branch=z9hG4bKkdjuw\n" + "C: application/sdp\n" + "Contact:\n" + " <sip:cal%6Cer@192.168.0.2:5060;%6C%72;n%61me=v%61lue%25%34%31>\n" + "Content-Length: 150\r\n" + "\r\n" + "v=0\r\n" + "o=mhandley 29739 7272939 IN IP4 192.0.2.1\r\n" + "s=-\r\n" + "c=IN IP4 192.0.2.1\r\n" + "t=0 0\r\n" + "m=audio 49217 RTP/AVP 0 12\r\n" + "m=video 3227 RTP/AVP 31\r\n" + "a=rtpmap:31 LPC\r\n", + NULL, + 0, + PJ_SUCCESS +}, +{ + /* Torture message from RFC 4475 + * 3.1.1.4 Escaped Nulls in URIs + */ + "REGISTER sip:example.com SIP/2.0\r\n" + "To: sip:null-%00-null@example.com\r\n" + "From: sip:null-%00-null@example.com;tag=839923423\r\n" + "Max-Forwards: 70\r\n" + "Call-ID: escnull.39203ndfvkjdasfkq3w4otrq0adsfdfnavd\r\n" + "CSeq: 14398234 REGISTER\r\n" + "Via: SIP/2.0/UDP host5.example.com;rport;branch=z9hG4bKkdjuw\r\n" + "Contact: <sip:%00@host5.example.com>\r\n" + "Contact: <sip:%00%00@host5.example.com>\r\n" + "L:0\r\n" + "\r\n", + NULL, + 0, + PJ_SUCCESS +}, +{ + /* Torture message from RFC 4475 + * 3.1.1.5 Use of % When It Is Not an Escape + */ + "RE%47IST%45R sip:registrar.example.com SIP/2.0\r\n" + "To: \"%Z%45\" <sip:resource@example.com>\r\n" + "From: \"%Z%45\" <sip:resource@example.com>;tag=f232jadfj23\r\n" + "Call-ID: esc02.asdfnqwo34rq23i34jrjasdcnl23nrlknsdf\r\n" + "Via: SIP/2.0/TCP host.example.com;rport;branch=z9hG4bK209%fzsnel234\r\n" + "CSeq: 29344 RE%47IST%45R\r\n" + "Max-Forwards: 70\r\n" + "Contact: <sip:alias1@host1.example.com>\r\n" + "C%6Fntact: <sip:alias2@host2.example.com>\r\n" + "Contact: <sip:alias3@host3.example.com>\r\n" + "l: 0\r\n" + "\r\n", + NULL, + 0, + PJ_SUCCESS +} +}; + +static struct +{ + int flag; + pj_highprec_t detect_len, parse_len, print_len; + pj_timestamp detect_time, parse_time, print_time; +} var; + +static pj_status_t test_entry( pj_pool_t *pool, struct test_msg *entry ) +{ + pjsip_msg *parsed_msg, *ref_msg = NULL; + static pjsip_msg *print_msg; + pj_status_t status = PJ_SUCCESS; + int len; + pj_str_t str1, str2; + pjsip_hdr *hdr1, *hdr2; + pj_timestamp t1, t2; + pjsip_parser_err_report err_list; + pj_size_t msg_size; + char msgbuf1[PJSIP_MAX_PKT_LEN]; + char msgbuf2[PJSIP_MAX_PKT_LEN]; + enum { BUFLEN = 512 }; + + if (entry->len==0) + entry->len = pj_ansi_strlen(entry->msg); + + if (var.flag & FLAG_PARSE_ONLY) + goto parse_msg; + + if (var.flag & FLAG_PRINT_ONLY) { + if (print_msg == NULL) + print_msg = entry->creator(pool); + goto print_msg; + } + + /* Detect message. */ + var.detect_len = var.detect_len + entry->len; + pj_get_timestamp(&t1); + status = pjsip_find_msg(entry->msg, entry->len, PJ_FALSE, &msg_size); + if (status != PJ_SUCCESS) { + if (status!=PJSIP_EPARTIALMSG || + entry->expected_status!=STATUS_PARTIAL) + { + app_perror(" error: unable to detect message", status); + return -5; + } + } + if (msg_size != entry->len) { + PJ_LOG(3,(THIS_FILE, " error: size mismatch")); + return -6; + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.detect_time, &t2); + + if (var.flag & FLAG_DETECT_ONLY) + return PJ_SUCCESS; + + /* Parse message. */ +parse_msg: + var.parse_len = var.parse_len + entry->len; + pj_get_timestamp(&t1); + pj_list_init(&err_list); + parsed_msg = pjsip_parse_msg(pool, entry->msg, entry->len, &err_list); + if (parsed_msg == NULL) { + if (entry->expected_status != STATUS_SYNTAX_ERROR) { + status = -10; + if (err_list.next != &err_list) { + PJ_LOG(3,(THIS_FILE, " Syntax error in line %d col %d", + err_list.next->line, err_list.next->col)); + } + goto on_return; + } + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.parse_time, &t2); + + if ((var.flag & FLAG_PARSE_ONLY) || entry->creator==NULL) + return PJ_SUCCESS; + + /* Create reference message. */ + ref_msg = entry->creator(pool); + + /* Create buffer for comparison. */ + str1.ptr = (char*)pj_pool_alloc(pool, BUFLEN); + str2.ptr = (char*)pj_pool_alloc(pool, BUFLEN); + + /* Compare message type. */ + if (parsed_msg->type != ref_msg->type) { + status = -20; + goto on_return; + } + + /* Compare request or status line. */ + if (parsed_msg->type == PJSIP_REQUEST_MSG) { + pjsip_method *m1 = &parsed_msg->line.req.method; + pjsip_method *m2 = &ref_msg->line.req.method; + + if (pjsip_method_cmp(m1, m2) != 0) { + status = -30; + goto on_return; + } + status = pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI, + parsed_msg->line.req.uri, + ref_msg->line.req.uri); + if (status != PJ_SUCCESS) { + app_perror(" error: request URI mismatch", status); + status = -31; + goto on_return; + } + } else { + if (parsed_msg->line.status.code != ref_msg->line.status.code) { + PJ_LOG(3,(THIS_FILE, " error: status code mismatch")); + status = -32; + goto on_return; + } + if (pj_strcmp(&parsed_msg->line.status.reason, + &ref_msg->line.status.reason) != 0) + { + PJ_LOG(3,(THIS_FILE, " error: status text mismatch")); + status = -33; + goto on_return; + } + } + + /* Compare headers. */ + hdr1 = parsed_msg->hdr.next; + hdr2 = ref_msg->hdr.next; + + while (hdr1 != &parsed_msg->hdr && hdr2 != &ref_msg->hdr) { + len = pjsip_hdr_print_on(hdr1, str1.ptr, BUFLEN); + if (len < 0) { + status = -40; + goto on_return; + } + str1.ptr[len] = '\0'; + str1.slen = len; + + len = pjsip_hdr_print_on(hdr2, str2.ptr, BUFLEN); + if (len < 0) { + status = -50; + goto on_return; + } + str2.ptr[len] = '\0'; + str2.slen = len; + + if (pj_strcmp(&str1, &str2) != 0) { + status = -60; + PJ_LOG(3,(THIS_FILE, " error: header string mismatch:\n" + " h1='%s'\n" + " h2='%s'\n", + str1.ptr, str2.ptr)); + goto on_return; + } + + hdr1 = hdr1->next; + hdr2 = hdr2->next; + } + + if (hdr1 != &parsed_msg->hdr || hdr2 != &ref_msg->hdr) { + status = -70; + goto on_return; + } + + /* Compare body? */ + if (parsed_msg->body==NULL && ref_msg->body==NULL) + goto print_msg; + + /* Compare msg body length. */ + if (parsed_msg->body->len != ref_msg->body->len) { + status = -80; + goto on_return; + } + + /* Compare msg body content type. */ + if (pj_strcmp(&parsed_msg->body->content_type.type, + &ref_msg->body->content_type.type) != 0) { + status = -90; + goto on_return; + } + if (pj_strcmp(&parsed_msg->body->content_type.subtype, + &ref_msg->body->content_type.subtype) != 0) { + status = -100; + goto on_return; + } + + /* Compare body content. */ + str1.slen = parsed_msg->body->print_body(parsed_msg->body, + msgbuf1, sizeof(msgbuf1)); + if (str1.slen < 1) { + status = -110; + goto on_return; + } + str1.ptr = msgbuf1; + + str2.slen = ref_msg->body->print_body(ref_msg->body, + msgbuf2, sizeof(msgbuf2)); + if (str2.slen < 1) { + status = -120; + goto on_return; + } + str2.ptr = msgbuf2; + + if (pj_strcmp(&str1, &str2) != 0) { + status = -140; + goto on_return; + } + + /* Print message. */ +print_msg: + var.print_len = var.print_len + entry->len; + pj_get_timestamp(&t1); + if (var.flag && FLAG_PRINT_ONLY) + ref_msg = print_msg; + len = pjsip_msg_print(ref_msg, msgbuf1, PJSIP_MAX_PKT_LEN); + if (len < 1) { + status = -150; + goto on_return; + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.print_time, &t2); + + + status = PJ_SUCCESS; + +on_return: + return status; +} + + +static pjsip_msg *create_msg0(pj_pool_t *pool) +{ + + pjsip_msg *msg; + pjsip_name_addr *name_addr; + pjsip_sip_uri *url; + pjsip_fromto_hdr *fromto; + pjsip_cid_hdr *cid; + pjsip_clen_hdr *clen; + pjsip_cseq_hdr *cseq; + pjsip_contact_hdr *contact; + pjsip_ctype_hdr *ctype; + pjsip_routing_hdr *routing; + pjsip_via_hdr *via; + pjsip_generic_string_hdr *generic; + pjsip_param *prm; + pj_str_t str; + + msg = pjsip_msg_create(pool, PJSIP_REQUEST_MSG); + + /* "INVITE sip:user@foo SIP/2.0\n" */ + pjsip_method_set(&msg->line.req.method, PJSIP_INVITE_METHOD); + url = pjsip_sip_uri_create(pool, 0); + msg->line.req.uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "user"); + pj_strdup2(pool, &url->host, "foo"); + + /* "From: Hi I'm Joe <sip:joe.user@bar.otherdomain.com>;tag=123457890123456\r" */ + fromto = pjsip_from_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto); + pj_strdup2(pool, &fromto->tag, "123457890123456"); + name_addr = pjsip_name_addr_create(pool); + fromto->uri = (pjsip_uri*)name_addr; + pj_strdup2(pool, &name_addr->display, "Hi I'm Joe"); + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "joe.user"); + pj_strdup2(pool, &url->host, "bar.otherdomain.com"); + + /* "To: Fellow User <sip:user@foo.bar.domain.com>\r\n" */ + fromto = pjsip_to_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto); + name_addr = pjsip_name_addr_create(pool); + fromto->uri = (pjsip_uri*)name_addr; + pj_strdup2(pool, &name_addr->display, "Fellow User"); + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "user"); + pj_strdup2(pool, &url->host, "foo.bar.domain.com"); + + /* "Call-ID: 12345678901234567890@bar\r\n" */ + cid = pjsip_cid_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)cid); + pj_strdup2(pool, &cid->id, "12345678901234567890@bar"); + + /* "Content-Length: 0\r\n" */ + clen = pjsip_clen_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)clen); + clen->len = 0; + + /* "CSeq: 123456 INVITE\n" */ + cseq = pjsip_cseq_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)cseq); + cseq->cseq = 123456; + pjsip_method_set(&cseq->method, PJSIP_INVITE_METHOD); + + /* "Contact: <sip:joe@bar>;q=0.5;expires=3600*/ + contact = pjsip_contact_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact); + contact->q1000 = 500; + contact->expires = 3600; + name_addr = pjsip_name_addr_create(pool); + contact->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "joe"); + pj_strdup2(pool, &url->host, "bar"); + + /*, sip:user@host;q=0.500\r" */ + contact = pjsip_contact_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact); + contact->q1000 = 500; + name_addr = pjsip_name_addr_create(pool); + contact->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "user"); + pj_strdup2(pool, &url->host, "host"); + + /* " ,sip:user2@host2\n" */ + contact = pjsip_contact_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact); + name_addr = pjsip_name_addr_create(pool); + contact->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "user2"); + pj_strdup2(pool, &url->host, "host2"); + + /* "Content-Type: text/html; charset=ISO-8859-4\r" */ + ctype = pjsip_ctype_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)ctype); + pj_strdup2(pool, &ctype->media.type, "text"); + pj_strdup2(pool, &ctype->media.subtype, "html"); + prm = PJ_POOL_ALLOC_T(pool, pjsip_param); + prm->name = pj_str("charset"); + prm->value = pj_str("ISO-8859-4"); + pj_list_push_back(&ctype->media.param, prm); + + /* "Route: <sip:bigbox3.site3.atlanta.com;lr>,\r\n" */ + routing = pjsip_route_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing); + url = pjsip_sip_uri_create(pool, 0); + routing->name_addr.uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com"); + url->lr_param = 1; + + /* " <sip:server10.biloxi.com;lr>\r" */ + routing = pjsip_route_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing); + url = pjsip_sip_uri_create(pool, 0); + routing->name_addr.uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->host, "server10.biloxi.com"); + url->lr_param = 1; + + /* "Record-Route: <sip:server10.biloxi.com>,\r\n" */ + routing = pjsip_rr_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing); + url = pjsip_sip_uri_create(pool, 0); + routing->name_addr.uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->host, "server10.biloxi.com"); + url->lr_param = 0; + + /* " <sip:bigbox3.site3.atlanta.com;lr>\n" */ + routing = pjsip_rr_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing); + url = pjsip_sip_uri_create(pool, 0); + routing->name_addr.uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com"); + url->lr_param = 1; + + /* "Via: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c230\n" */ + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + pj_strdup2(pool, &via->transport, "SCTP"); + pj_strdup2(pool, &via->sent_by.host, "bigbox3.site3.atlanta.com"); + pj_strdup2(pool, &via->branch_param, "z9hG4bK77ef4c230"); + + /* "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n" + " ;received=192.0.2.1\r\n" */ + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + pj_strdup2(pool, &via->transport, "UDP"); + pj_strdup2(pool, &via->sent_by.host, "pc33.atlanta.com"); + pj_strdup2(pool, &via->branch_param, "z9hG4bKnashds8"); + pj_strdup2(pool, &via->recvd_param, "192.0.2.1"); + + + /* "Via: SIP/2.0/UDP 10.2.1.1, */ + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + pj_strdup2(pool, &via->transport, "UDP"); + pj_strdup2(pool, &via->sent_by.host, "10.2.1.1"); + + + /*SIP/2.0/TCP 192.168.1.1\n" */ + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + pj_strdup2(pool, &via->transport, "TCP"); + pj_strdup2(pool, &via->sent_by.host, "192.168.1.1"); + + /* "Organization: \r" */ + str.ptr = "Organization"; + str.slen = 12; + generic = pjsip_generic_string_hdr_create(pool, &str, NULL); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic); + generic->hvalue.ptr = NULL; + generic->hvalue.slen = 0; + + /* "Max-Forwards: 70\n" */ + str.ptr = "Max-Forwards"; + str.slen = 12; + generic = pjsip_generic_string_hdr_create(pool, &str, NULL); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic); + str.ptr = "70"; + str.slen = 2; + generic->hvalue = str; + + /* "X-Header: \r\n" */ + str.ptr = "X-Header"; + str.slen = 8; + generic = pjsip_generic_string_hdr_create(pool, &str, NULL); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic); + str.ptr = NULL; + str.slen = 0; + generic->hvalue = str; + + /* P-Associated-URI:\r\n */ + str.ptr = "P-Associated-URI"; + str.slen = 16; + generic = pjsip_generic_string_hdr_create(pool, &str, NULL); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic); + str.ptr = NULL; + str.slen = 0; + generic->hvalue = str; + + return msg; +} + +static pjsip_msg *create_msg1(pj_pool_t *pool) +{ + pjsip_via_hdr *via; + pjsip_route_hdr *route; + pjsip_name_addr *name_addr; + pjsip_sip_uri *url; + pjsip_max_fwd_hdr *max_fwd; + pjsip_to_hdr *to; + pjsip_from_hdr *from; + pjsip_contact_hdr *contact; + pjsip_ctype_hdr *ctype; + pjsip_cid_hdr *cid; + pjsip_clen_hdr *clen; + pjsip_cseq_hdr *cseq; + pjsip_msg *msg = pjsip_msg_create(pool, PJSIP_RESPONSE_MSG); + pjsip_msg_body *body; + + //"SIP/2.0 200 OK\r\n" + msg->line.status.code = 200; + msg->line.status.reason = pj_str("OK"); + + //"Via: SIP/2.0/SCTP server10.biloxi.com;branch=z9hG4bKnashds8;rport;received=192.0.2.1\r\n" + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + via->transport = pj_str("SCTP"); + via->sent_by.host = pj_str("server10.biloxi.com"); + via->branch_param = pj_str("z9hG4bKnashds8"); + via->rport_param = 0; + via->recvd_param = pj_str("192.0.2.1"); + + //"Via: SIP/2.0/UDP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2\r\n" + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + via->transport = pj_str("UDP"); + via->sent_by.host = pj_str("bigbox3.site3.atlanta.com"); + via->branch_param = pj_str("z9hG4bK77ef4c2312983.1"); + via->recvd_param = pj_str("192.0.2.2"); + + //"Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds ;received=192.0.2.3\r\n" + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + via->transport = pj_str("UDP"); + via->sent_by.host = pj_str("pc33.atlanta.com"); + via->branch_param = pj_str("z9hG4bK776asdhds"); + via->recvd_param = pj_str("192.0.2.3"); + + //"Route: <sip:proxy.sipprovider.com>\r\n" + route = pjsip_route_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)route); + url = pjsip_sip_uri_create(pool, PJ_FALSE); + route->name_addr.uri = (pjsip_uri*)url; + url->host = pj_str("proxy.sipprovider.com"); + + //"Route: <sip:proxy.supersip.com:5060>\r\n" + route = pjsip_route_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)route); + url = pjsip_sip_uri_create(pool, PJ_FALSE); + route->name_addr.uri = (pjsip_uri*)url; + url->host = pj_str("proxy.supersip.com"); + url->port = 5060; + + //"Max-Forwards: 70\r\n" + max_fwd = pjsip_max_fwd_hdr_create(pool, 70); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)max_fwd); + + //"To: Bob <sip:bob@biloxi.com>;tag=a6c85cf\r\n" + to = pjsip_to_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)to); + name_addr = pjsip_name_addr_create(pool); + name_addr->display = pj_str("Bob"); + to->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, PJ_FALSE); + name_addr->uri = (pjsip_uri*)url; + url->user = pj_str("bob"); + url->host = pj_str("biloxi.com"); + to->tag = pj_str("a6c85cf"); + + //"From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n" + from = pjsip_from_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)from); + name_addr = pjsip_name_addr_create(pool); + name_addr->display = pj_str("Alice"); + from->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, PJ_FALSE); + name_addr->uri = (pjsip_uri*)url; + url->user = pj_str("alice"); + url->host = pj_str("atlanta.com"); + from->tag = pj_str("1928301774"); + + //"Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n" + cid = pjsip_cid_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)cid); + cid->id = pj_str("a84b4c76e66710@pc33.atlanta.com"); + + //"CSeq: 314159 INVITE\r\n" + cseq = pjsip_cseq_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)cseq); + cseq->cseq = 314159; + pjsip_method_set(&cseq->method, PJSIP_INVITE_METHOD); + + //"Contact: <sips:bob@192.0.2.4>\r\n" + contact = pjsip_contact_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact); + name_addr = pjsip_name_addr_create(pool); + contact->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, PJ_TRUE); + name_addr->uri = (pjsip_uri*)url; + url->user = pj_str("bob"); + url->host = pj_str("192.0.2.4"); + + //"Content-Type: application/sdp\r\n" + ctype = pjsip_ctype_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)ctype); + ctype->media.type = pj_str("application"); + ctype->media.subtype = pj_str("sdp"); + + //"Content-Length: 150\r\n" + clen = pjsip_clen_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)clen); + clen->len = 150; + + // Body + body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + msg->body = body; + body->content_type.type = pj_str("application"); + body->content_type.subtype = pj_str("sdp"); + body->data = (void*) + "v=0\r\n" + "o=alice 53655765 2353687637 IN IP4 pc33.atlanta.com\r\n" + "s=-\r\n" + "t=0 0\r\n" + "c=IN IP4 pc33.atlanta.com\r\n" + "m=audio 3456 RTP/AVP 0 1 3 99\r\n" + "a=rtpmap:0 PCMU/8000\r\n"; + body->len = pj_ansi_strlen((const char*) body->data); + body->print_body = &pjsip_print_text_body; + + return msg; +} + +/*****************************************************************************/ + +static pj_status_t simple_test(void) +{ + char stbuf[] = "SIP/2.0 180 Ringing like it never rings before"; + unsigned i; + pjsip_status_line st_line; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " simple test..")); + + status = pjsip_parse_status_line(stbuf, pj_ansi_strlen(stbuf), &st_line); + if (status != PJ_SUCCESS) + return status; + + for (i=0; i<PJ_ARRAY_SIZE(test_array); ++i) { + pj_pool_t *pool; + pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE); + status = test_entry( pool, &test_array[i] ); + pjsip_endpt_release_pool(endpt, pool); + + if (status != PJ_SUCCESS) + return status; + } + + return PJ_SUCCESS; +} + + +#if INCLUDE_BENCHMARKS +static int msg_benchmark(unsigned *p_detect, unsigned *p_parse, + unsigned *p_print) +{ + pj_pool_t *pool; + int i, loop; + pj_timestamp zero; + pj_time_val elapsed; + pj_highprec_t avg_detect, avg_parse, avg_print, kbytes; + pj_status_t status = PJ_SUCCESS; + + pj_bzero(&var, sizeof(var)); + zero.u64 = 0; + + for (loop=0; loop<LOOP; ++loop) { + for (i=0; i<(int)PJ_ARRAY_SIZE(test_array); ++i) { + pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE); + status = test_entry( pool, &test_array[i] ); + pjsip_endpt_release_pool(endpt, pool); + + if (status != PJ_SUCCESS) + return status; + } + } + + kbytes = var.detect_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.detect_time); + avg_detect = pj_elapsed_usec(&zero, &var.detect_time); + pj_highprec_mul(avg_detect, AVERAGE_MSG_LEN); + pj_highprec_div(avg_detect, var.detect_len); + avg_detect = 1000000 / avg_detect; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB detected in %d.%03ds (avg=%d msg detection/sec)", + (unsigned)(var.detect_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_detect)); + *p_detect = (unsigned)avg_detect; + + kbytes = var.parse_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.parse_time); + avg_parse = pj_elapsed_usec(&zero, &var.parse_time); + pj_highprec_mul(avg_parse, AVERAGE_MSG_LEN); + pj_highprec_div(avg_parse, var.parse_len); + avg_parse = 1000000 / avg_parse; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB parsed in %d.%03ds (avg=%d msg parsing/sec)", + (unsigned)(var.parse_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_parse)); + *p_parse = (unsigned)avg_parse; + + kbytes = var.print_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.print_time); + avg_print = pj_elapsed_usec(&zero, &var.print_time); + pj_highprec_mul(avg_print, AVERAGE_MSG_LEN); + pj_highprec_div(avg_print, var.print_len); + avg_print = 1000000 / avg_print; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB printed in %d.%03ds (avg=%d msg print/sec)", + (unsigned)(var.print_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_print)); + + *p_print = (unsigned)avg_print; + return status; +} +#endif /* INCLUDE_BENCHMARKS */ + +/*****************************************************************************/ +/* Test various header parsing and production */ +static int hdr_test_success(pjsip_hdr *h); +static int hdr_test_accept0(pjsip_hdr *h); +static int hdr_test_accept1(pjsip_hdr *h); +static int hdr_test_accept2(pjsip_hdr *h); +static int hdr_test_allow0(pjsip_hdr *h); +static int hdr_test_authorization(pjsip_hdr *h); +static int hdr_test_cid(pjsip_hdr *h); +static int hdr_test_contact0(pjsip_hdr *h); +static int hdr_test_contact1(pjsip_hdr *h); +static int hdr_test_contact_q0(pjsip_hdr *h); +static int hdr_test_contact_q1(pjsip_hdr *h); +static int hdr_test_contact_q2(pjsip_hdr *h); +static int hdr_test_contact_q3(pjsip_hdr *h); +static int hdr_test_contact_q4(pjsip_hdr *h); +static int hdr_test_content_length(pjsip_hdr *h); +static int hdr_test_content_type(pjsip_hdr *h); +static int hdr_test_from(pjsip_hdr *h); +static int hdr_test_proxy_authenticate(pjsip_hdr *h); +static int hdr_test_record_route(pjsip_hdr *h); +static int hdr_test_supported(pjsip_hdr *h); +static int hdr_test_to(pjsip_hdr *h); +static int hdr_test_via(pjsip_hdr *h); +static int hdr_test_via_ipv6_1(pjsip_hdr *h); +static int hdr_test_via_ipv6_2(pjsip_hdr *h); +static int hdr_test_via_ipv6_3(pjsip_hdr *h); +static int hdr_test_retry_after1(pjsip_hdr *h); +static int hdr_test_subject_utf(pjsip_hdr *h); + + +#define GENERIC_PARAM "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3" +#define GENERIC_PARAM_PARSED "p0=a;p1=\"ab:;cd\";p2=ab:cd;p3" +#define PARAM_CHAR "][/:&+$" +#define SIMPLE_ADDR_SPEC "sip:host" +#define ADDR_SPEC SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\"" +#define NAME_ADDR "<" ADDR_SPEC ">" + +#define HDR_FLAG_PARSE_FAIL 1 +#define HDR_FLAG_DONT_PRINT 2 + +struct hdr_test_t +{ + char *hname; + char *hshort_name; + char *hcontent; + int (*test)(pjsip_hdr*); + unsigned flags; +} hdr_test_data[] = +{ + { + /* Empty Accept */ + "Accept", NULL, + "", + &hdr_test_accept0 + }, + + { + /* Overflowing generic string header */ + "Accept", NULL, + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a", + &hdr_test_success, + HDR_FLAG_PARSE_FAIL + }, + + { + /* Normal Accept */ + "Accept", NULL, + "application/*, text/plain", + &hdr_test_accept1 + }, + + { + /* Accept with params */ + "Accept", NULL, + "application/*;p1=v1, text/plain", + &hdr_test_accept2 + }, + + { + /* Empty Allow */ + "Allow", NULL, + "", + &hdr_test_allow0, + }, + + { + /* Authorization, testing which params should be quoted */ + "Authorization", NULL, + "Digest username=\"username\", realm=\"realm\", nonce=\"nonce\", " \ + "uri=\"sip:domain\", response=\"RESPONSE\", algorithm=MD5, " \ + "cnonce=\"CNONCE\", opaque=\"OPAQUE\", qop=auth, nc=00000001", + &hdr_test_authorization + }, + + { + /* Call ID */ + "Call-ID", "i", + "-.!%*_+`'~()<>:\\\"/[]?{}", + &hdr_test_cid, + }, + + { + /* Parameter belong to hparam */ + "Contact", "m", + SIMPLE_ADDR_SPEC ";p1=v1", + &hdr_test_contact0, + HDR_FLAG_DONT_PRINT + }, + + { + /* generic-param in Contact header */ + "Contact", "m", + NAME_ADDR ";" GENERIC_PARAM, + &hdr_test_contact1 + }, + + { + /* q=0 parameter in Contact header */ + "Contact", "m", + NAME_ADDR ";q=0", + &hdr_test_contact_q0, + HDR_FLAG_DONT_PRINT + }, + + { + /* q=0.5 parameter in Contact header */ + "Contact", "m", + NAME_ADDR ";q=0.5", + &hdr_test_contact_q1 + }, + + { + /* q=1 parameter in Contact header */ + "Contact", "m", + NAME_ADDR ";q=1", + &hdr_test_contact_q2 + }, + + { + /* q=1.0 parameter in Contact header */ + "Contact", "m", + NAME_ADDR ";q=1.0", + &hdr_test_contact_q3, + HDR_FLAG_DONT_PRINT + }, + + { + /* q=1.1 parameter in Contact header */ + "Contact", "m", + NAME_ADDR ";q=1.15", + &hdr_test_contact_q4 + }, + + { + /* Content-Length */ + "Content-Length", "l", + "10", + &hdr_test_content_length + }, + + { + /* Content-Type, with generic-param */ + "Content-Type", "c", + "application/sdp" ";" GENERIC_PARAM, + &hdr_test_content_type, + HDR_FLAG_DONT_PRINT + }, + + { + /* From, testing parameters and generic-param */ + "From", "f", + NAME_ADDR ";" GENERIC_PARAM, + &hdr_test_from + }, + + { + /* Proxy-Authenticate, testing which params should be quoted */ + "Proxy-Authenticate", NULL, + "Digest realm=\"realm\",domain=\"sip:domain\",nonce=\"nonce\"," \ + "opaque=\"opaque\",stale=true,algorithm=MD5,qop=\"auth\"", + &hdr_test_proxy_authenticate + }, + + { + /* Record-Route, param belong to header */ + "Record-Route", NULL, + NAME_ADDR ";" GENERIC_PARAM, + &hdr_test_record_route + }, + + { + /* Empty Supported */ + "Supported", "k", + "", + &hdr_test_supported, + }, + + { + /* To */ + "To", "t", + NAME_ADDR ";" GENERIC_PARAM, + &hdr_test_to + }, + + { + /* Via */ + "Via", "v", + "SIP/2.0/XYZ host" ";" GENERIC_PARAM, + &hdr_test_via + }, + + { + /* Via with IPv6 */ + "Via", "v", + "SIP/2.0/UDP [::1]", + &hdr_test_via_ipv6_1 + }, + + { + /* Via with IPv6 */ + "Via", "v", + "SIP/2.0/UDP [::1]:5061", + &hdr_test_via_ipv6_2 + }, + + { + /* Via with IPv6 */ + "Via", "v", + "SIP/2.0/UDP [::1];rport=5061;received=::2", + &hdr_test_via_ipv6_3 + }, + + { + /* Retry-After header with comment */ + "Retry-After", NULL, + "10(Already Pending Register)", + &hdr_test_retry_after1 + }, + + { + /* Non-ASCII UTF-8 characters in Subject */ + "Subject", NULL, + "\xC0\x81", + &hdr_test_subject_utf + } +}; + +static int hdr_test_success(pjsip_hdr *h) +{ + PJ_UNUSED_ARG(h); + return 0; +} + +/* "" */ +static int hdr_test_accept0(pjsip_hdr *h) +{ + pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h; + + if (h->type != PJSIP_H_ACCEPT) + return -1010; + + if (hdr->count != 0) + return -1020; + + return 0; +} + +/* "application/ *, text/plain\r\n" */ +static int hdr_test_accept1(pjsip_hdr *h) +{ + pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h; + + if (h->type != PJSIP_H_ACCEPT) + return -1110; + + if (hdr->count != 2) + return -1120; + + if (pj_strcmp2(&hdr->values[0], "application/*")) + return -1130; + + if (pj_strcmp2(&hdr->values[1], "text/plain")) + return -1140; + + return 0; +} + +/* "application/ *;p1=v1, text/plain\r\n" */ +static int hdr_test_accept2(pjsip_hdr *h) +{ + pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h; + + if (h->type != PJSIP_H_ACCEPT) + return -1210; + + if (hdr->count != 2) + return -1220; + + if (pj_strcmp2(&hdr->values[0], "application/*;p1=v1")) + return -1230; + + if (pj_strcmp2(&hdr->values[1], "text/plain")) + return -1240; + + return 0; +} + +/* "" */ +static int hdr_test_allow0(pjsip_hdr *h) +{ + pjsip_allow_hdr *hdr = (pjsip_allow_hdr*)h; + + if (h->type != PJSIP_H_ALLOW) + return -1310; + + if (hdr->count != 0) + return -1320; + + return 0; + +} + + +/* + "Digest username=\"username\", realm=\"realm\", nonce=\"nonce\", " \ + "uri=\"sip:domain\", response=\"RESPONSE\", algorithm=MD5, " \ + "cnonce=\"CNONCE\", opaque=\"OPAQUE\", qop=auth, nc=00000001", + */ +static int hdr_test_authorization(pjsip_hdr *h) +{ + pjsip_authorization_hdr *hdr = (pjsip_authorization_hdr*)h; + + if (h->type != PJSIP_H_AUTHORIZATION) + return -1410; + + if (pj_strcmp2(&hdr->scheme, "Digest")) + return -1420; + + if (pj_strcmp2(&hdr->credential.digest.username, "username")) + return -1421; + + if (pj_strcmp2(&hdr->credential.digest.realm, "realm")) + return -1422; + + if (pj_strcmp2(&hdr->credential.digest.nonce, "nonce")) + return -1423; + + if (pj_strcmp2(&hdr->credential.digest.uri, "sip:domain")) + return -1424; + + if (pj_strcmp2(&hdr->credential.digest.response, "RESPONSE")) + return -1425; + + if (pj_strcmp2(&hdr->credential.digest.algorithm, "MD5")) + return -1426; + + if (pj_strcmp2(&hdr->credential.digest.cnonce, "CNONCE")) + return -1427; + + if (pj_strcmp2(&hdr->credential.digest.opaque, "OPAQUE")) + return -1428; + + if (pj_strcmp2(&hdr->credential.digest.qop, "auth")) + return -1429; + + if (pj_strcmp2(&hdr->credential.digest.nc, "00000001")) + return -1430; + + return 0; +} + + +/* + "-.!%*_+`'~()<>:\\\"/[]?{}\r\n" + */ +static int hdr_test_cid(pjsip_hdr *h) +{ + pjsip_cid_hdr *hdr = (pjsip_cid_hdr*)h; + + if (h->type != PJSIP_H_CALL_ID) + return -1510; + + if (pj_strcmp2(&hdr->id, "-.!%*_+`'~()<>:\\\"/[]?{}")) + return -1520; + + return 0; +} + +/* + #define SIMPLE_ADDR_SPEC "sip:host" + */ +static int test_simple_addr_spec(pjsip_uri *uri) +{ + pjsip_sip_uri *sip_uri = (pjsip_sip_uri *)pjsip_uri_get_uri(uri); + + if (!PJSIP_URI_SCHEME_IS_SIP(uri)) + return -900; + + if (pj_strcmp2(&sip_uri->host, "host")) + return -910; + + if (sip_uri->port != 0) + return -920; + + return 0; +} + +/* +#define PARAM_CHAR "][/:&+$" +#define SIMPLE_ADDR_SPEC "sip:host" +#define ADDR_SPEC SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\"" +#define NAME_ADDR "<" ADDR_SPEC ">" + */ +static int nameaddr_test(void *uri) +{ + pjsip_sip_uri *sip_uri=(pjsip_sip_uri *)pjsip_uri_get_uri((pjsip_uri*)uri); + pjsip_param *param; + int rc; + + if (!PJSIP_URI_SCHEME_IS_SIP(uri)) + return -930; + + rc = test_simple_addr_spec((pjsip_uri*)sip_uri); + if (rc != 0) + return rc; + + if (pj_list_size(&sip_uri->other_param) != 2) + return -940; + + param = sip_uri->other_param.next; + + if (pj_strcmp2(¶m->name, PARAM_CHAR)) + return -942; + + if (pj_strcmp2(¶m->value, PARAM_CHAR)) + return -943; + + param = param->next; + if (pj_strcmp2(¶m->name, "p1")) + return -942; + if (pj_strcmp2(¶m->value, "\";\"")) + return -943; + + return 0; +} + +/* +#define GENERIC_PARAM "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3" + */ +static int generic_param_test(pjsip_param *param_head) +{ + pjsip_param *param; + + if (pj_list_size(param_head) != 4) + return -950; + + param = param_head->next; + + if (pj_strcmp2(¶m->name, "p0")) + return -952; + if (pj_strcmp2(¶m->value, "a")) + return -953; + + param = param->next; + if (pj_strcmp2(¶m->name, "p1")) + return -954; + if (pj_strcmp2(¶m->value, "\"ab:;cd\"")) + return -955; + + param = param->next; + if (pj_strcmp2(¶m->name, "p2")) + return -956; + if (pj_strcmp2(¶m->value, "ab:cd")) + return -957; + + param = param->next; + if (pj_strcmp2(¶m->name, "p3")) + return -958; + if (pj_strcmp2(¶m->value, "")) + return -959; + + return 0; +} + + + +/* + SIMPLE_ADDR_SPEC ";p1=v1\r\n" + */ +static int hdr_test_contact0(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + pjsip_param *param; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1610; + + rc = test_simple_addr_spec(hdr->uri); + if (rc != 0) + return rc; + + if (pj_list_size(&hdr->other_param) != 1) + return -1620; + + param = hdr->other_param.next; + + if (pj_strcmp2(¶m->name, "p1")) + return -1630; + + if (pj_strcmp2(¶m->value, "v1")) + return -1640; + + return 0; +} + +/* + NAME_ADDR GENERIC_PARAM "\r\n", + */ +static int hdr_test_contact1(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + rc = generic_param_test(&hdr->other_param); + if (rc != 0) + return rc; + + return 0; +} + +/* + NAME_ADDR ";q=0" + */ +static int hdr_test_contact_q0(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + if (hdr->q1000 != 0) + return -1711; + + return 0; +} + +/* + NAME_ADDR ";q=0.5" + */ +static int hdr_test_contact_q1(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + if (hdr->q1000 != 500) + return -1712; + + return 0; +} + +/* + NAME_ADDR ";q=1" + */ +static int hdr_test_contact_q2(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + if (hdr->q1000 != 1000) + return -1713; + + return 0; +} + +/* + NAME_ADDR ";q=1.0" + */ +static int hdr_test_contact_q3(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + if (hdr->q1000 != 1000) + return -1714; + + return 0; +} + +/* + NAME_ADDR ";q=1.15" + */ +static int hdr_test_contact_q4(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + if (hdr->q1000 != 1150) + return -1715; + + return 0; +} + +/* + "10" + */ +static int hdr_test_content_length(pjsip_hdr *h) +{ + pjsip_clen_hdr *hdr = (pjsip_clen_hdr*)h; + + if (h->type != PJSIP_H_CONTENT_LENGTH) + return -1810; + + if (hdr->len != 10) + return -1820; + + return 0; +} + +/* + "application/sdp" GENERIC_PARAM, + */ +static int hdr_test_content_type(pjsip_hdr *h) +{ + pjsip_ctype_hdr *hdr = (pjsip_ctype_hdr*)h; + const pjsip_param *prm; + + if (h->type != PJSIP_H_CONTENT_TYPE) + return -1910; + + if (pj_strcmp2(&hdr->media.type, "application")) + return -1920; + + if (pj_strcmp2(&hdr->media.subtype, "sdp")) + return -1930; + + /* Currently, if the media parameter contains escaped characters, + * pjsip will print the parameter unescaped. + */ + prm = hdr->media.param.next; + if (prm == &hdr->media.param) return -1940; + if (pj_strcmp2(&prm->name, "p0")) return -1941; + if (pj_strcmp2(&prm->value, "a")) return -1942; + + prm = prm->next; + if (prm == &hdr->media.param) return -1950; + if (pj_strcmp2(&prm->name, "p1")) { PJ_LOG(3,("", "%.*s", (int)prm->name.slen, prm->name.ptr)); return -1951; } + if (pj_strcmp2(&prm->value, "\"ab:;cd\"")) { PJ_LOG(3,("", "%.*s", (int)prm->value.slen, prm->value.ptr)); return -1952; } + + prm = prm->next; + if (prm == &hdr->media.param) return -1960; + if (pj_strcmp2(&prm->name, "p2")) return -1961; + if (pj_strcmp2(&prm->value, "ab:cd")) return -1962; + + prm = prm->next; + if (prm == &hdr->media.param) return -1970; + if (pj_strcmp2(&prm->name, "p3")) return -1971; + if (pj_strcmp2(&prm->value, "")) return -1972; + + return 0; +} + +/* + NAME_ADDR GENERIC_PARAM, + */ +static int hdr_test_from(pjsip_hdr *h) +{ + pjsip_from_hdr *hdr = (pjsip_from_hdr*)h; + int rc; + + if (h->type != PJSIP_H_FROM) + return -2010; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + rc = generic_param_test(&hdr->other_param); + if (rc != 0) + return rc; + + return 0; +} + +/* + "Digest realm=\"realm\", domain=\"sip:domain\", nonce=\"nonce\", " \ + "opaque=\"opaque\", stale=true, algorithm=MD5, qop=\"auth\"", + */ +static int hdr_test_proxy_authenticate(pjsip_hdr *h) +{ + pjsip_proxy_authenticate_hdr *hdr = (pjsip_proxy_authenticate_hdr*)h; + + if (h->type != PJSIP_H_PROXY_AUTHENTICATE) + return -2110; + + if (pj_strcmp2(&hdr->scheme, "Digest")) + return -2120; + + if (pj_strcmp2(&hdr->challenge.digest.realm, "realm")) + return -2130; + + if (pj_strcmp2(&hdr->challenge.digest.domain, "sip:domain")) + return -2140; + + if (pj_strcmp2(&hdr->challenge.digest.nonce, "nonce")) + return -2150; + + if (pj_strcmp2(&hdr->challenge.digest.opaque, "opaque")) + return -2160; + + if (hdr->challenge.digest.stale != 1) + return -2170; + + if (pj_strcmp2(&hdr->challenge.digest.algorithm, "MD5")) + return -2180; + + if (pj_strcmp2(&hdr->challenge.digest.qop, "auth")) + return -2190; + + return 0; +} + +/* + NAME_ADDR GENERIC_PARAM, + */ +static int hdr_test_record_route(pjsip_hdr *h) +{ + pjsip_rr_hdr *hdr = (pjsip_rr_hdr*)h; + int rc; + + if (h->type != PJSIP_H_RECORD_ROUTE) + return -2210; + + rc = nameaddr_test(&hdr->name_addr); + if (rc != 0) + return rc; + + rc = generic_param_test(&hdr->other_param); + if (rc != 0) + return rc; + + return 0; + +} + +/* + " \r\n" + */ +static int hdr_test_supported(pjsip_hdr *h) +{ + pjsip_supported_hdr *hdr = (pjsip_supported_hdr*)h; + + if (h->type != PJSIP_H_SUPPORTED) + return -2310; + + if (hdr->count != 0) + return -2320; + + return 0; +} + +/* + NAME_ADDR GENERIC_PARAM, + */ +static int hdr_test_to(pjsip_hdr *h) +{ + pjsip_to_hdr *hdr = (pjsip_to_hdr*)h; + int rc; + + if (h->type != PJSIP_H_TO) + return -2410; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + rc = generic_param_test(&hdr->other_param); + if (rc != 0) + return rc; + + return 0; +} + +/* + "SIP/2.0 host" GENERIC_PARAM + */ +static int hdr_test_via(pjsip_hdr *h) +{ + pjsip_via_hdr *hdr = (pjsip_via_hdr*)h; + int rc; + + if (h->type != PJSIP_H_VIA) + return -2510; + + if (pj_strcmp2(&hdr->transport, "XYZ")) + return -2515; + + if (pj_strcmp2(&hdr->sent_by.host, "host")) + return -2520; + + if (hdr->sent_by.port != 0) + return -2530; + + rc = generic_param_test(&hdr->other_param); + if (rc != 0) + return rc; + + return 0; +} + + +/* + "SIP/2.0/UDP [::1]" + */ +static int hdr_test_via_ipv6_1(pjsip_hdr *h) +{ + pjsip_via_hdr *hdr = (pjsip_via_hdr*)h; + + if (h->type != PJSIP_H_VIA) + return -2610; + + if (pj_strcmp2(&hdr->transport, "UDP")) + return -2615; + + if (pj_strcmp2(&hdr->sent_by.host, "::1")) + return -2620; + + if (hdr->sent_by.port != 0) + return -2630; + + return 0; +} + +/* "SIP/2.0/UDP [::1]:5061" */ +static int hdr_test_via_ipv6_2(pjsip_hdr *h) +{ + pjsip_via_hdr *hdr = (pjsip_via_hdr*)h; + + if (h->type != PJSIP_H_VIA) + return -2710; + + if (pj_strcmp2(&hdr->transport, "UDP")) + return -2715; + + if (pj_strcmp2(&hdr->sent_by.host, "::1")) + return -2720; + + if (hdr->sent_by.port != 5061) + return -2730; + + return 0; +} + +/* "SIP/2.0/UDP [::1];rport=5061;received=::2" */ +static int hdr_test_via_ipv6_3(pjsip_hdr *h) +{ + pjsip_via_hdr *hdr = (pjsip_via_hdr*)h; + + if (h->type != PJSIP_H_VIA) + return -2810; + + if (pj_strcmp2(&hdr->transport, "UDP")) + return -2815; + + if (pj_strcmp2(&hdr->sent_by.host, "::1")) + return -2820; + + if (hdr->sent_by.port != 0) + return -2830; + + if (pj_strcmp2(&hdr->recvd_param, "::2")) + return -2840; + + if (hdr->rport_param != 5061) + return -2850; + + return 0; +} + +/* "10(Already Pending Register)" */ +static int hdr_test_retry_after1(pjsip_hdr *h) +{ + pjsip_retry_after_hdr *hdr = (pjsip_retry_after_hdr*)h; + + if (h->type != PJSIP_H_RETRY_AFTER) + return -2910; + + if (hdr->ivalue != 10) + return -2920; + + if (pj_strcmp2(&hdr->comment, "Already Pending Register")) + return -2930; + + return 0; +} + +/* Subject: \xC0\x81 */ +static int hdr_test_subject_utf(pjsip_hdr *h) +{ + pjsip_subject_hdr *hdr = (pjsip_subject_hdr*)h; + + if (pj_strcmp2(&h->name, "Subject")) + return -2950; + + if (pj_strcmp2(&hdr->hvalue, "\xC0\x81")) + return -2960; + + return 0; +} + +static int hdr_test(void) +{ + unsigned i; + + PJ_LOG(3,(THIS_FILE, " testing header parsing..")); + + for (i=0; i<PJ_ARRAY_SIZE(hdr_test_data); ++i) { + struct hdr_test_t *test = &hdr_test_data[i]; + pj_str_t hname; + int len, parsed_len; + pj_pool_t *pool; + pjsip_hdr *parsed_hdr1=NULL, *parsed_hdr2=NULL; + char *input, *output; +#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + static char hcontent[1024]; +#else + char *hcontent; +#endif + int rc; + + pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE); + + /* Parse the header */ + hname = pj_str(test->hname); + len = strlen(test->hcontent); +#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + PJ_ASSERT_RETURN(len < sizeof(hcontent), PJSIP_EMSGTOOLONG); + strcpy(hcontent, test->hcontent); +#else + hcontent = test->hcontent; +#endif + + parsed_hdr1 = (pjsip_hdr*) pjsip_parse_hdr(pool, &hname, + hcontent, len, + &parsed_len); + if (parsed_hdr1 == NULL) { + if (test->flags & HDR_FLAG_PARSE_FAIL) { + pj_pool_release(pool); + continue; + } + PJ_LOG(3,(THIS_FILE, " error parsing header %s: %s", test->hname, test->hcontent)); + return -500; + } + + /* Test the parsing result */ + if (test->test && (rc=test->test(parsed_hdr1)) != 0) { + PJ_LOG(3,(THIS_FILE, " validation failed for header %s: %s", test->hname, test->hcontent)); + PJ_LOG(3,(THIS_FILE, " error code is %d", rc)); + return -502; + } + +#if 1 + /* Parse with hshortname, if present */ + if (test->hshort_name) { + hname = pj_str(test->hshort_name); + len = strlen(test->hcontent); +#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + PJ_ASSERT_RETURN(len < sizeof(hcontent), PJSIP_EMSGTOOLONG); + strcpy(hcontent, test->hcontent); +#else + hcontent = test->hcontent; +#endif + + parsed_hdr2 = (pjsip_hdr*) pjsip_parse_hdr(pool, &hname, hcontent, len, &parsed_len); + if (parsed_hdr2 == NULL) { + PJ_LOG(3,(THIS_FILE, " error parsing header %s: %s", test->hshort_name, test->hcontent)); + return -510; + } + } +#endif + + if (test->flags & HDR_FLAG_DONT_PRINT) { + pj_pool_release(pool); + continue; + } + + /* Print the original header */ + input = (char*) pj_pool_alloc(pool, 1024); + len = pj_ansi_snprintf(input, 1024, "%s: %s", test->hname, test->hcontent); + if (len < 1 || len >= 1024) + return -520; + + /* Print the parsed header*/ + output = (char*) pj_pool_alloc(pool, 1024); + len = pjsip_hdr_print_on(parsed_hdr1, output, 1024); + if (len < 0 || len >= 1024) { + PJ_LOG(3,(THIS_FILE, " header too long: %s: %s", test->hname, test->hcontent)); + return -530; + } + output[len] = 0; + + if (strcmp(input, output) != 0) { + PJ_LOG(3,(THIS_FILE, " header character by character comparison failed.")); + PJ_LOG(3,(THIS_FILE, " original header=|%s|", input)); + PJ_LOG(3,(THIS_FILE, " parsed header =|%s|", output)); + return -540; + } + + pj_pool_release(pool); + } + + return 0; +} + + +/*****************************************************************************/ + +int msg_test(void) +{ + enum { COUNT = 1, DETECT=0, PARSE=1, PRINT=2 }; + struct { + unsigned detect; + unsigned parse; + unsigned print; + } run[COUNT]; + unsigned i, max, avg_len; + char desc[250]; + pj_status_t status; + + status = hdr_test(); + if (status != 0) + return status; + + status = simple_test(); + if (status != PJ_SUCCESS) + return status; + +#if INCLUDE_BENCHMARKS + for (i=0; i<COUNT; ++i) { + PJ_LOG(3,(THIS_FILE, " benchmarking (%d of %d)..", i+1, COUNT)); + status = msg_benchmark(&run[i].detect, &run[i].parse, &run[i].print); + if (status != PJ_SUCCESS) + return status; + } + + /* Calculate average message length */ + for (i=0, avg_len=0; i<PJ_ARRAY_SIZE(test_array); ++i) { + avg_len += test_array[i].len; + } + avg_len /= PJ_ARRAY_SIZE(test_array); + + + /* Print maximum detect/sec */ + for (i=0, max=0; i<COUNT; ++i) + if (run[i].detect > max) max = run[i].detect; + + PJ_LOG(3,("", " Maximum message detection/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP messages " + "can be pre-parse by <tt>pjsip_find_msg()</tt> " + "per second (tested with %d message sets with " + "average message length of " + "%d bytes)", (int)PJ_ARRAY_SIZE(test_array), avg_len); + report_ival("msg-detect-per-sec", max, "msg/sec", desc); + + /* Print maximum parse/sec */ + for (i=0, max=0; i<COUNT; ++i) + if (run[i].parse > max) max = run[i].parse; + + PJ_LOG(3,("", " Maximum message parsing/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP messages " + "can be <b>parsed</b> by <tt>pjsip_parse_msg()</tt> " + "per second (tested with %d message sets with " + "average message length of " + "%d bytes)", (int)PJ_ARRAY_SIZE(test_array), avg_len); + report_ival("msg-parse-per-sec", max, "msg/sec", desc); + + /* Msg parsing bandwidth */ + report_ival("msg-parse-bandwidth-mb", avg_len*max/1000000, "MB/sec", + "Message parsing bandwidth in megabytes (number of megabytes" + " worth of SIP messages that can be parsed per second). " + "The value is derived from msg-parse-per-sec above."); + + + /* Print maximum print/sec */ + for (i=0, max=0; i<COUNT; ++i) + if (run[i].print > max) max = run[i].print; + + PJ_LOG(3,("", " Maximum message print/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP messages " + "can be <b>printed</b> by <tt>pjsip_msg_print()</tt>" + " per second (tested with %d message sets with " + "average message length of " + "%d bytes)", (int)PJ_ARRAY_SIZE(test_array), avg_len); + + report_ival("msg-print-per-sec", max, "msg/sec", desc); + + /* Msg print bandwidth */ + report_ival("msg-printed-bandwidth-mb", avg_len*max/1000000, "MB/sec", + "Message print bandwidth in megabytes (total size of " + "SIP messages printed per second). " + "The value is derived from msg-print-per-sec above."); + +#endif /* INCLUDE_BENCHMARKS */ + + return PJ_SUCCESS; +} + + + + diff --git a/pjsip/src/test/multipart_test.c b/pjsip/src/test/multipart_test.c new file mode 100644 index 0000000..30d6126 --- /dev/null +++ b/pjsip/src/test/multipart_test.c @@ -0,0 +1,266 @@ +/* $Id: multipart_test.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * 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 "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "" + +/* + * multipart tests + */ +typedef pj_status_t (*verify_ptr)(pj_pool_t*,pjsip_msg_body*); + +static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body); + +static struct test_t +{ + char *ctype; + char *csubtype; + char *boundary; + const char *msg; + verify_ptr verify; +} p_tests[] = +{ + { + /* Content-type */ + "multipart", "mixed", "12345", + + /* Body: */ + "This is the prolog, which should be ignored.\r\n" + "--12345\r\n" + "Content-Type: my/text\r\n" + "\r\n" + "Header and body\r\n" + "--12345 \t\r\n" + "Content-Type: hello/world\r\n" + "Content-Length: 0\r\n" + "\r\n" + "--12345\r\n" + "\r\n" + "Body only\r\n" + "--12345\r\n" + "Content-Type: multipart/mixed;boundary=6789\r\n" + "\r\n" + "Prolog of the subbody, should be ignored\r\n" + "--6789\r\n" + "\r\n" + "Subbody\r\n" + "--6789--\r\n" + "Epilogue of the subbody, should be ignored\r\n" + "--12345--\r\n" + "This is epilogue, which should be ignored too", + + &verify1 + } +}; + +static void init_media_type(pjsip_media_type *mt, + char *type, char *subtype, char *boundary) +{ + static pjsip_param prm; + + pjsip_media_type_init(mt, NULL, NULL); + if (type) mt->type = pj_str(type); + if (subtype) mt->subtype = pj_str(subtype); + if (boundary) { + pj_list_init(&prm); + prm.name = pj_str("boundary"); + prm.value = pj_str(boundary); + pj_list_push_back(&mt->param, &prm); + } +} + +static int verify_part(pjsip_multipart_part *part, + char *h_content_type, + char *h_content_subtype, + char *boundary, + int h_content_length, + const char *body) +{ + pjsip_ctype_hdr *ctype_hdr = NULL; + pjsip_clen_hdr *clen_hdr = NULL; + pjsip_hdr *hdr; + pj_str_t the_body; + + hdr = part->hdr.next; + while (hdr != &part->hdr) { + if (hdr->type == PJSIP_H_CONTENT_TYPE) + ctype_hdr = (pjsip_ctype_hdr*)hdr; + else if (hdr->type == PJSIP_H_CONTENT_LENGTH) + clen_hdr = (pjsip_clen_hdr*)hdr; + hdr = hdr->next; + } + + if (h_content_type) { + pjsip_media_type mt; + + if (ctype_hdr == NULL) + return -10; + + init_media_type(&mt, h_content_type, h_content_subtype, boundary); + + if (pjsip_media_type_cmp(&ctype_hdr->media, &mt, 2) != 0) + return -20; + + } else { + if (ctype_hdr) + return -30; + } + + if (h_content_length >= 0) { + if (clen_hdr == NULL) + return -50; + if (clen_hdr->len != h_content_length) + return -60; + } else { + if (clen_hdr) + return -70; + } + + the_body.ptr = (char*)part->body->data; + the_body.slen = part->body->len; + + if (pj_strcmp2(&the_body, body) != 0) + return -90; + + return 0; +} + +static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body) +{ + pjsip_media_type mt; + pjsip_multipart_part *part; + int rc; + + PJ_UNUSED_ARG(pool); + + /* Check content-type: "multipart/mixed;boundary=12345" */ + init_media_type(&mt, "multipart", "mixed", "12345"); + if (pjsip_media_type_cmp(&body->content_type, &mt, 2) != 0) + return -200; + + /* First part: + "Content-Type: my/text\r\n" + "\r\n" + "Header and body\r\n" + */ + part = pjsip_multipart_get_first_part(body); + if (!part) + return -210; + if (verify_part(part, "my", "text", NULL, -1, "Header and body")) + return -220; + + /* Next part: + "Content-Type: hello/world\r\n" + "Content-Length: 0\r\n" + "\r\n" + */ + part = pjsip_multipart_get_next_part(body, part); + if (!part) + return -230; + if ((rc=verify_part(part, "hello", "world", NULL, 0, ""))!=0) { + PJ_LOG(3,(THIS_FILE, " err: verify_part rc=%d", rc)); + return -240; + } + + /* Next part: + "\r\n" + "Body only\r\n" + */ + part = pjsip_multipart_get_next_part(body, part); + if (!part) + return -260; + if (verify_part(part, NULL, NULL, NULL, -1, "Body only")) + return -270; + + /* Next part: + "Content-Type: multipart/mixed;boundary=6789\r\n" + "\r\n" + "Prolog of the subbody, should be ignored\r\n" + "--6789\r\n" + "\r\n" + "Subbody\r\n" + "--6789--\r\n" + "Epilogue of the subbody, should be ignored\r\n" + + */ + part = pjsip_multipart_get_next_part(body, part); + if (!part) + return -280; + if ((rc=verify_part(part, "multipart", "mixed", "6789", -1, + "Prolog of the subbody, should be ignored\r\n" + "--6789\r\n" + "\r\n" + "Subbody\r\n" + "--6789--\r\n" + "Epilogue of the subbody, should be ignored"))!=0) { + PJ_LOG(3,(THIS_FILE, " err: verify_part rc=%d", rc)); + return -290; + } + + return 0; +} + +static int parse_test(void) +{ + unsigned i; + + for (i=0; i<PJ_ARRAY_SIZE(p_tests); ++i) { + pj_pool_t *pool; + pjsip_media_type ctype; + pjsip_msg_body *body; + pj_str_t str; + int rc; + + pool = pjsip_endpt_create_pool(endpt, NULL, 512, 512); + + init_media_type(&ctype, p_tests[i].ctype, p_tests[i].csubtype, + p_tests[i].boundary); + + pj_strdup2_with_null(pool, &str, p_tests[i].msg); + body = pjsip_multipart_parse(pool, str.ptr, str.slen, &ctype, 0); + if (!body) + return -100; + + if (p_tests[i].verify) { + rc = p_tests[i].verify(pool, body); + } else { + rc = 0; + } + + pj_pool_release(pool); + if (rc) + return rc; + } + + return 0; +} + +int multipart_test(void) +{ + int rc; + + rc = parse_test(); + if (rc) + return rc; + + return rc; +} + diff --git a/pjsip/src/test/regc_test.c b/pjsip/src/test/regc_test.c new file mode 100644 index 0000000..45912a3 --- /dev/null +++ b/pjsip/src/test/regc_test.c @@ -0,0 +1,1162 @@ +/* $Id: regc_test.c 4094 2012-04-26 09:31:00Z bennylp $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip_ua.h> +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "regc_test.c" + + +/************************************************************************/ +/* A module to inject error into outgoing sending operation */ +static pj_status_t mod_send_on_tx_request(pjsip_tx_data *tdata); + +static struct +{ + pjsip_module mod; + unsigned count; + unsigned count_before_reject; +} send_mod = +{ + { + NULL, NULL, /* prev, next. */ + { "mod-send", 8 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + &mod_send_on_tx_request, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + }, + 0, + 0xFFFF +}; + + +static pj_status_t mod_send_on_tx_request(pjsip_tx_data *tdata) +{ + PJ_UNUSED_ARG(tdata); + + if (++send_mod.count > send_mod.count_before_reject) + return PJ_ECANCELLED; + else + return PJ_SUCCESS; +}; + + +/************************************************************************/ +/* Registrar for testing */ +static pj_bool_t regs_rx_request(pjsip_rx_data *rdata); + +enum contact_op +{ + NONE, /* don't put Contact header */ + EXACT, /* return exact contact */ + MODIFIED, /* return modified Contact header */ +}; + +struct registrar_cfg +{ + pj_bool_t respond; /* should it respond at all */ + unsigned status_code; /* final response status code */ + pj_bool_t authenticate; /* should we authenticate? */ + enum contact_op contact_op; /* What should we do with Contact */ + unsigned expires_param; /* non-zero to put in expires param */ + unsigned expires; /* non-zero to put in Expires header*/ + + pj_str_t more_contacts; /* Additional Contact headers to put*/ +}; + +static struct registrar +{ + pjsip_module mod; + struct registrar_cfg cfg; + unsigned response_cnt; +} registrar = +{ + { + NULL, NULL, /* prev, next. */ + { "registrar", 9 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + ®s_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + } +}; + +static pj_bool_t regs_rx_request(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + pjsip_hdr hdr_list; + int code; + pj_status_t status; + + if (msg->line.req.method.id != PJSIP_REGISTER_METHOD) + return PJ_FALSE; + + if (!registrar.cfg.respond) + return PJ_TRUE; + + pj_list_init(&hdr_list); + + if (registrar.cfg.authenticate && + pjsip_msg_find_hdr(msg, PJSIP_H_AUTHORIZATION, NULL)==NULL) + { + pjsip_generic_string_hdr *hwww; + const pj_str_t hname = pj_str("WWW-Authenticate"); + const pj_str_t hvalue = pj_str("Digest realm=\"test\""); + + hwww = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &hname, + &hvalue); + pj_list_push_back(&hdr_list, hwww); + + code = 401; + + } else { + if (registrar.cfg.contact_op == EXACT || + registrar.cfg.contact_op == MODIFIED) + { + pjsip_hdr *hsrc; + + for (hsrc=msg->hdr.next; hsrc!=&msg->hdr; hsrc=hsrc->next) { + pjsip_contact_hdr *hdst; + + if (hsrc->type != PJSIP_H_CONTACT) + continue; + + hdst = (pjsip_contact_hdr*) + pjsip_hdr_clone(rdata->tp_info.pool, hsrc); + + if (hdst->expires==0) + continue; + + if (registrar.cfg.contact_op == MODIFIED) { + if (PJSIP_URI_SCHEME_IS_SIP(hdst->uri) || + PJSIP_URI_SCHEME_IS_SIPS(hdst->uri)) + { + pjsip_sip_uri *sip_uri = (pjsip_sip_uri*) + pjsip_uri_get_uri(hdst->uri); + sip_uri->host = pj_str("x-modified-host"); + sip_uri->port = 1; + } + } + + if (registrar.cfg.expires_param) + hdst->expires = registrar.cfg.expires_param; + + pj_list_push_back(&hdr_list, hdst); + } + } + + if (registrar.cfg.more_contacts.slen) { + pjsip_generic_string_hdr *hcontact; + const pj_str_t hname = pj_str("Contact"); + + hcontact = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &hname, + ®istrar.cfg.more_contacts); + pj_list_push_back(&hdr_list, hcontact); + } + + if (registrar.cfg.expires) { + pjsip_expires_hdr *hexp; + + hexp = pjsip_expires_hdr_create(rdata->tp_info.pool, + registrar.cfg.expires); + pj_list_push_back(&hdr_list, hexp); + } + + registrar.response_cnt++; + + code = registrar.cfg.status_code; + } + + status = pjsip_endpt_respond(endpt, NULL, rdata, code, NULL, + &hdr_list, NULL, NULL); + pj_assert(status == PJ_SUCCESS); + + return PJ_TRUE; +} + + +/************************************************************************/ +/* Client registration test session */ +struct client +{ + /* Result/expected result */ + int error; + int code; + pj_bool_t have_reg; + int expiration; + unsigned contact_cnt; + pj_bool_t auth; + + /* Commands */ + pj_bool_t destroy_on_cb; + + /* Status */ + pj_bool_t done; + + /* Additional results */ + int interval; + int next_reg; +}; + +/* regc callback */ +static void client_cb(struct pjsip_regc_cbparam *param) +{ + struct client *client = (struct client*) param->token; + pjsip_regc_info info; + pj_status_t status; + + client->done = PJ_TRUE; + + status = pjsip_regc_get_info(param->regc, &info); + pj_assert(status == PJ_SUCCESS); + + client->error = (param->status != PJ_SUCCESS); + client->code = param->code; + + if (client->error) + return; + + client->have_reg = info.auto_reg && info.interval>0 && + param->expiration>0; + client->expiration = param->expiration; + client->contact_cnt = param->contact_cnt; + client->interval = info.interval; + client->next_reg = info.next_reg; + + if (client->destroy_on_cb) + pjsip_regc_destroy(param->regc); +} + + +/* Generic client test session */ +static struct client client_result; +static int do_test(const char *title, + const struct registrar_cfg *srv_cfg, + const struct client *client_cfg, + const pj_str_t *registrar_uri, + unsigned contact_cnt, + const pj_str_t contacts[], + unsigned expires, + pj_bool_t leave_session, + pjsip_regc **p_regc) +{ + pjsip_regc *regc; + unsigned i; + const pj_str_t aor = pj_str("<sip:regc-test@pjsip.org>"); + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " %s", title)); + + /* Modify registrar settings */ + pj_memcpy(®istrar.cfg, srv_cfg, sizeof(*srv_cfg)); + + pj_bzero(&client_result, sizeof(client_result)); + client_result.destroy_on_cb = client_cfg->destroy_on_cb; + + status = pjsip_regc_create(endpt, &client_result, &client_cb, ®c); + if (status != PJ_SUCCESS) + return -100; + + status = pjsip_regc_init(regc, registrar_uri, &aor, &aor, contact_cnt, + contacts, expires ? expires : 60); + if (status != PJ_SUCCESS) { + pjsip_regc_destroy(regc); + return -110; + } + + if (client_cfg->auth) { + pjsip_cred_info cred; + + pj_bzero(&cred, sizeof(cred)); + cred.realm = pj_str("*"); + cred.scheme = pj_str("digest"); + cred.username = pj_str("user"); + cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; + cred.data = pj_str("password"); + + status = pjsip_regc_set_credentials(regc, 1, &cred); + if (status != PJ_SUCCESS) { + pjsip_regc_destroy(regc); + return -115; + } + } + + /* Register */ + status = pjsip_regc_register(regc, PJ_TRUE, &tdata); + if (status != PJ_SUCCESS) { + pjsip_regc_destroy(regc); + return -120; + } + status = pjsip_regc_send(regc, tdata); + + /* That's it, wait until the callback is sent */ + for (i=0; i<600 && !client_result.done; ++i) { + flush_events(100); + } + + if (!client_result.done) { + PJ_LOG(3,(THIS_FILE, " error: test has timed out")); + pjsip_regc_destroy(regc); + return -200; + } + + /* Destroy the regc, we're done with the test, unless we're + * instructed to leave the session open. + */ + if (!leave_session && !client_cfg->destroy_on_cb) + pjsip_regc_destroy(regc); + + /* Compare results with expected results */ + + if (client_result.error != client_cfg->error) { + PJ_LOG(3,(THIS_FILE, " error: expecting err=%d, got err=%d", + client_cfg->error, client_result.error)); + return -210; + } + if (client_result.code != client_cfg->code) { + PJ_LOG(3,(THIS_FILE, " error: expecting code=%d, got code=%d", + client_cfg->code, client_result.code)); + return -220; + } + if (client_result.expiration != client_cfg->expiration) { + PJ_LOG(3,(THIS_FILE, " error: expecting expiration=%d, got expiration=%d", + client_cfg->expiration, client_result.expiration)); + return -240; + } + if (client_result.contact_cnt != client_cfg->contact_cnt) { + PJ_LOG(3,(THIS_FILE, " error: expecting contact_cnt=%d, got contact_cnt=%d", + client_cfg->contact_cnt, client_result.contact_cnt)); + return -250; + } + if (client_result.have_reg != client_cfg->have_reg) { + PJ_LOG(3,(THIS_FILE, " error: expecting have_reg=%d, got have_reg=%d", + client_cfg->have_reg, client_result.have_reg)); + return -260; + } + if (client_result.interval != client_result.expiration) { + PJ_LOG(3,(THIS_FILE, " error: interval (%d) is different than expiration (%d)", + client_result.interval, client_result.expiration)); + return -270; + } + if (client_result.expiration > 0 && client_result.next_reg < 1) { + PJ_LOG(3,(THIS_FILE, " error: next_reg=%d, expecting positive number because expiration is %d", + client_result.next_reg, client_result.expiration)); + return -280; + } + + /* Looks like everything is okay. */ + if (leave_session) { + *p_regc = regc; + } + + return 0; +} + + +/************************************************************************/ +/* Customized tests */ + +/* Check that client is sending register refresh */ +static int keep_alive_test(const pj_str_t *registrar_uri) +{ + enum { TIMEOUT = 40 }; + struct registrar_cfg server_cfg = + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, EXACT, TIMEOUT, 0, {NULL, 0}}; + struct client client_cfg = + /* error code have_reg expiration contact_cnt auth? destroy*/ + { PJ_FALSE, 200, PJ_TRUE, TIMEOUT, 1, PJ_FALSE,PJ_FALSE}; + pj_str_t contact = pj_str("<sip:c@C>"); + + + pjsip_regc *regc; + unsigned i; + int ret; + + ret = do_test("register refresh (takes ~40 secs)", &server_cfg, &client_cfg, registrar_uri, + 1, &contact, TIMEOUT, PJ_TRUE, ®c); + if (ret != 0) + return ret; + + /* Reset server response_cnt */ + registrar.response_cnt = 0; + + /* Wait until keep-alive/refresh is done */ + for (i=0; i<(TIMEOUT-1)*10 && registrar.response_cnt==0; ++i) { + flush_events(100); + } + + if (registrar.response_cnt==0) { + PJ_LOG(3,(THIS_FILE, " error: no refresh is received")); + return -400; + } + + if (client_result.error) { + PJ_LOG(3,(THIS_FILE, " error: got error")); + return -410; + } + if (client_result.code != 200) { + PJ_LOG(3,(THIS_FILE, " error: expecting code=%d, got code=%d", + 200, client_result.code)); + return -420; + } + if (client_result.expiration != TIMEOUT) { + PJ_LOG(3,(THIS_FILE, " error: expecting expiration=%d, got expiration=%d", + TIMEOUT, client_result.expiration)); + return -440; + } + if (client_result.contact_cnt != 1) { + PJ_LOG(3,(THIS_FILE, " error: expecting contact_cnt=%d, got contact_cnt=%d", + TIMEOUT, client_result.contact_cnt)); + return -450; + } + if (client_result.have_reg == 0) { + PJ_LOG(3,(THIS_FILE, " error: expecting have_reg=%d, got have_reg=%d", + 1, client_result.have_reg)); + return -460; + } + if (client_result.interval != TIMEOUT) { + PJ_LOG(3,(THIS_FILE, " error: interval (%d) is different than expiration (%d)", + client_result.interval, TIMEOUT)); + return -470; + } + if (client_result.expiration > 0 && client_result.next_reg < 1) { + PJ_LOG(3,(THIS_FILE, " error: next_reg=%d, expecting positive number because expiration is %d", + client_result.next_reg, client_result.expiration)); + return -480; + } + + /* Success */ + pjsip_regc_destroy(regc); + return 0; +} + + +/* Send error on refresh */ +static int refresh_error(const pj_str_t *registrar_uri, + pj_bool_t destroy_on_cb) +{ + enum { TIMEOUT = 40 }; + struct registrar_cfg server_cfg = + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, EXACT, TIMEOUT, 0, {NULL, 0}}; + struct client client_cfg = + /* error code have_reg expiration contact_cnt auth? destroy*/ + { PJ_FALSE, 200, PJ_TRUE, TIMEOUT, 1, PJ_FALSE,PJ_FALSE}; + pj_str_t contact = pj_str("<sip:c@C>"); + + pjsip_regc *regc; + unsigned i; + int ret; + + ret = do_test("refresh error (takes ~40 secs)", &server_cfg, &client_cfg, registrar_uri, + 1, &contact, TIMEOUT, PJ_TRUE, ®c); + if (ret != 0) + return ret; + + /* Reset server response_cnt */ + registrar.response_cnt = 0; + + /* inject error for transmission */ + send_mod.count = 0; + send_mod.count_before_reject = 0; + + /* reconfigure client */ + client_result.done = PJ_FALSE; + client_result.destroy_on_cb = destroy_on_cb; + + /* Wait until keep-alive/refresh is done */ + for (i=0; i<TIMEOUT*10 && !client_result.done; ++i) { + flush_events(100); + } + + send_mod.count_before_reject = 0xFFFF; + + if (!destroy_on_cb) + pjsip_regc_destroy(regc); + + if (!client_result.done) { + PJ_LOG(3,(THIS_FILE, " error: test has timed out")); + return -500; + } + + /* Expecting error */ + if (client_result.error==PJ_FALSE && client_result.code/100==2) { + PJ_LOG(3,(THIS_FILE, " error: expecting error got successfull result")); + return -510; + } + + return PJ_SUCCESS; +}; + + +/* Send error on refresh */ +static int update_test(const pj_str_t *registrar_uri) +{ + enum { TIMEOUT = 40 }; + struct registrar_cfg server_cfg = + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, EXACT, TIMEOUT, 0, {NULL, 0}}; + struct client client_cfg = + /* error code have_reg expiration contact_cnt auth? destroy*/ + { PJ_FALSE, 200, PJ_TRUE, TIMEOUT, 1, PJ_FALSE,PJ_FALSE}; + pj_str_t contacts[] = { + { "<sip:a>", 7 }, + { "<sip:b>", 7 }, + { "<sip:c>", 7 } + }; + + pjsip_regc *regc; + pjsip_contact_hdr *h1, *h2; + pjsip_sip_uri *u1, *u2; + unsigned i; + pj_status_t status; + pjsip_tx_data *tdata = NULL; + int ret = 0; + + /* initially only has 1 contact */ + ret = do_test("update test", &server_cfg, &client_cfg, registrar_uri, + 1, &contacts[0], TIMEOUT, PJ_TRUE, ®c); + if (ret != 0) { + return -600; + } + + /***** + * replace the contact with new one + */ + PJ_LOG(3,(THIS_FILE, " replacing contact")); + status = pjsip_regc_update_contact(regc, 1, &contacts[1]); + if (status != PJ_SUCCESS) { + ret = -610; + goto on_return; + } + + status = pjsip_regc_register(regc, PJ_TRUE, &tdata); + if (status != PJ_SUCCESS) { + ret = -620; + goto on_return; + } + + /* Check that the REGISTER contains two Contacts: + * - <sip:a>;expires=0, + * - <sip:b> + */ + h1 = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL); + if (!h1) { + ret = -630; + goto on_return; + } + if ((void*)h1->next == (void*)&tdata->msg->hdr) + h2 = NULL; + else + h2 = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h1->next); + if (!h2) { + ret = -640; + goto on_return; + } + /* must not have other Contact header */ + if ((void*)h2->next != (void*)&tdata->msg->hdr && + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h2->next) != NULL) + { + ret = -645; + goto on_return; + } + + u1 = (pjsip_sip_uri*) pjsip_uri_get_uri(h1->uri); + u2 = (pjsip_sip_uri*) pjsip_uri_get_uri(h2->uri); + + if (*u1->host.ptr == 'a') { + if (h1->expires != 0) { + ret = -650; + goto on_return; + } + if (h2->expires == 0) { + ret = -660; + goto on_return; + } + + } else { + pj_assert(*u1->host.ptr == 'b'); + if (h1->expires == 0) { + ret = -670; + goto on_return; + } + if (h2->expires != 0) { + ret = -680; + goto on_return; + } + } + + /* Destroy tdata */ + pjsip_tx_data_dec_ref(tdata); + tdata = NULL; + + + + /** + * First loop, it will update with more contacts. Second loop + * should do nothing. + */ + for (i=0; i<2; ++i) { + if (i==0) + PJ_LOG(3,(THIS_FILE, " replacing with more contacts")); + else + PJ_LOG(3,(THIS_FILE, " updating contacts with same contacts")); + + status = pjsip_regc_update_contact(regc, 2, &contacts[1]); + if (status != PJ_SUCCESS) { + ret = -710; + goto on_return; + } + + status = pjsip_regc_register(regc, PJ_TRUE, &tdata); + if (status != PJ_SUCCESS) { + ret = -720; + goto on_return; + } + + /* Check that the REGISTER contains two Contacts: + * - <sip:b> + * - <sip:c> + */ + h1 = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL); + if (!h1) { + ret = -730; + goto on_return; + } + if ((void*)h1->next == (void*)&tdata->msg->hdr) + h2 = NULL; + else + h2 = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h1->next); + if (!h2) { + ret = -740; + goto on_return; + } + /* must not have other Contact header */ + if ((void*)h2->next != (void*)&tdata->msg->hdr && + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h2->next) != NULL) + { + ret = -745; + goto on_return; + } + + /* both contacts must not have expires=0 parameter */ + if (h1->expires == 0) { + ret = -750; + goto on_return; + } + if (h2->expires == 0) { + ret = -760; + goto on_return; + } + + /* Destroy tdata */ + pjsip_tx_data_dec_ref(tdata); + tdata = NULL; + } + +on_return: + if (tdata) pjsip_tx_data_dec_ref(tdata); + pjsip_regc_destroy(regc); + return ret; +}; + + +/* send error on authentication */ +static int auth_send_error(const pj_str_t *registrar_uri, + pj_bool_t destroy_on_cb) +{ + enum { TIMEOUT = 40 }; + struct registrar_cfg server_cfg = + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_TRUE, EXACT, 75, 0, {NULL, 0}}; + struct client client_cfg = + /* error code have_reg expiration contact_cnt auth? destroy*/ + { PJ_TRUE, 401, PJ_FALSE, -1, 0, PJ_TRUE, PJ_TRUE}; + pj_str_t contact = pj_str("<sip:c@C>"); + + pjsip_regc *regc; + int ret; + + client_cfg.destroy_on_cb = destroy_on_cb; + + /* inject error for second request retry */ + send_mod.count = 0; + send_mod.count_before_reject = 1; + + ret = do_test("auth send error", &server_cfg, &client_cfg, registrar_uri, + 1, &contact, TIMEOUT, PJ_TRUE, ®c); + + send_mod.count_before_reject = 0xFFFF; + + return ret; +}; + + + + +/************************************************************************/ +enum +{ + OFF = 1, + ON = 2, + ON_OFF = 3, +}; + +int regc_test(void) +{ + struct test_rec { + unsigned check_contact; + unsigned add_xuid_param; + + const char *title; + char *alt_registrar; + unsigned contact_cnt; + char *contacts[4]; + unsigned expires; + struct registrar_cfg server_cfg; + struct client client_cfg; + } test_rec[] = + { + /* immediate error */ + { + OFF, /* check_contact */ + OFF, /* add_xuid_param */ + "immediate error", /* title */ + "sip:unresolved-host-xyy", /* alt_registrar */ + 1, /* contact cnt */ + { "sip:user@127.0.0.1:5060" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_FALSE, 200, PJ_FALSE, NONE, 0, 0, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 502, PJ_FALSE, -1, 0, PJ_FALSE} + }, + + /* timeout test */ + { + OFF, /* check_contact */ + OFF, /* add_xuid_param */ + "timeout test (takes ~32 secs)",/* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "sip:user@127.0.0.1:5060" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_FALSE, 200, PJ_FALSE, NONE, 0, 0, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth? */ + { PJ_FALSE, 408, PJ_FALSE, -1, 0, PJ_FALSE} + }, + + /* Basic successful registration scenario: + * a good registrar returns the Contact header as is and + * add expires parameter. In this test no additional bindings + * are returned. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "basic", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060;transport=udp;x-param=1234>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, EXACT, 75, 65, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 75, 1, PJ_FALSE} + }, + + /* Basic successful registration scenario with authentication + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "authentication", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060;transport=udp;x-param=1234>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_TRUE, EXACT, 75, 65, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 75, 1, PJ_TRUE} + }, + + /* a good registrar returns the Contact header as is and + * add expires parameter. Also it adds bindings from other + * clients in this test. + */ + { + ON_OFF, /* check_contact */ + ON, /* add_xuid_param */ + "more bindings in response", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060;transport=udp>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, EXACT, 75, 65, {"<sip:a@a>;expires=70", 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 75, 2, PJ_FALSE} + }, + + + /* a bad registrar returns modified Contact header, but it + * still returns all parameters intact. In this case + * the expiration is taken from the expires param because + * of matching xuid param or because the number of + * Contact header matches. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "registrar modifies Contact header", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, MODIFIED, 75, 65, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 75, 1, PJ_FALSE} + }, + + + /* a bad registrar returns modified Contact header, but it + * still returns all parameters intact. In addition it returns + * bindings from other clients. + * + * In this case the expiration is taken from the expires param + * because add_xuid_param is enabled. + */ + { + ON_OFF, /* check_contact */ + ON, /* add_xuid_param */ + "registrar modifies Contact header and add bindings", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, MODIFIED, 75, 65, {"<sip:a@a>;expires=70", 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 75, 2, PJ_FALSE} + }, + + + /* a bad registrar returns completely different Contact and + * all parameters are gone. In this case the expiration is + * also taken from the expires param since the number of + * header matches. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "registrar replaces Contact header", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 202, PJ_FALSE, NONE, 0, 65, {"<sip:a@A>;expires=75", 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 202, PJ_TRUE, 75, 1, PJ_FALSE} + }, + + + /* a bad registrar returns completely different Contact (and + * all parameters are gone) and it also includes bindings from + * other clients. + * In this case the expiration is taken from the Expires header. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + " as above with additional bindings", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, NONE, 0, 65, {"<sip:a@A>;expires=75, <sip:b@B;expires=70>", 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 65, 2, PJ_FALSE} + }, + + /* the registrar doesn't return any bindings, but for some + * reason it includes an Expires header. + * In this case the expiration is taken from the Expires header. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "no Contact but with Expires", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, NONE, 0, 65, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 65, 0, PJ_FALSE} + }, + + /* Neither Contact header nor Expires header are present. + * In this case the expiration is taken from the request. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "no Contact and no Expires", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" },/* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, NONE, 0, 0, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 600, 0, PJ_FALSE} + }, + }; + + unsigned i; + pj_sockaddr_in addr; + pjsip_transport *udp = NULL; + pj_uint16_t port; + char registrar_uri_buf[80]; + pj_str_t registrar_uri; + int rc = 0; + + pj_sockaddr_in_init(&addr, 0, 0); + + /* Acquire existing transport, if any */ + rc = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP, &addr, sizeof(addr), NULL, &udp); + if (rc == PJ_SUCCESS) { + port = pj_sockaddr_get_port(&udp->local_addr); + pjsip_transport_dec_ref(udp); + udp = NULL; + } else { + rc = pjsip_udp_transport_start(endpt, NULL, NULL, 1, &udp); + if (rc != PJ_SUCCESS) { + app_perror(" error creating UDP transport", rc); + rc = -2; + goto on_return; + } + + port = pj_sockaddr_get_port(&udp->local_addr); + } + + /* Register registrar module */ + rc = pjsip_endpt_register_module(endpt, ®istrar.mod); + if (rc != PJ_SUCCESS) { + app_perror(" error registering module", rc); + rc = -3; + goto on_return; + } + + /* Register send module */ + rc = pjsip_endpt_register_module(endpt, &send_mod.mod); + if (rc != PJ_SUCCESS) { + app_perror(" error registering module", rc); + rc = -3; + goto on_return; + } + + pj_ansi_snprintf(registrar_uri_buf, sizeof(registrar_uri_buf), + "sip:127.0.0.1:%d", (int)port); + registrar_uri = pj_str(registrar_uri_buf); + + for (i=0; i<PJ_ARRAY_SIZE(test_rec); ++i) { + struct test_rec *t = &test_rec[i]; + unsigned j, x; + pj_str_t reg_uri; + pj_str_t contacts[8]; + + /* Fill in the registrar address if it's not specified */ + if (t->alt_registrar == NULL) { + reg_uri = registrar_uri; + } else { + reg_uri = pj_str(t->alt_registrar); + } + + /* Build contact pj_str_t's */ + for (j=0; j<t->contact_cnt; ++j) { + contacts[j] = pj_str(t->contacts[j]); + } + + /* Normalize more_contacts field */ + if (t->server_cfg.more_contacts.ptr) + t->server_cfg.more_contacts.slen = strlen(t->server_cfg.more_contacts.ptr); + + /* Do tests with three combinations: + * - check_contact on/off + * - add_xuid_param on/off + * - destroy_on_callback on/off + */ + for (x=1; x<=2; ++x) { + unsigned y; + + if ((t->check_contact & x) == 0) + continue; + + pjsip_cfg()->regc.check_contact = (x-1); + + for (y=1; y<=2; ++y) { + unsigned z; + + if ((t->add_xuid_param & y) == 0) + continue; + + pjsip_cfg()->regc.add_xuid_param = (y-1); + + for (z=0; z<=1; ++z) { + char new_title[200]; + + t->client_cfg.destroy_on_cb = z; + + sprintf(new_title, "%s [check=%d, xuid=%d, destroy=%d]", + t->title, pjsip_cfg()->regc.check_contact, + pjsip_cfg()->regc.add_xuid_param, z); + rc = do_test(new_title, &t->server_cfg, &t->client_cfg, + ®_uri, t->contact_cnt, contacts, + t->expires, PJ_FALSE, NULL); + if (rc != 0) + goto on_return; + } + + } + } + + /* Sleep between test groups to avoid using up too many + * active transactions. + */ + pj_thread_sleep(1000); + } + + /* keep-alive test */ + rc = keep_alive_test(®istrar_uri); + if (rc != 0) + goto on_return; + + /* Send error on refresh without destroy on callback */ + rc = refresh_error(®istrar_uri, PJ_FALSE); + if (rc != 0) + goto on_return; + + /* Send error on refresh, destroy on callback */ + rc = refresh_error(®istrar_uri, PJ_TRUE); + if (rc != 0) + goto on_return; + + /* Updating contact */ + rc = update_test(®istrar_uri); + if (rc != 0) + goto on_return; + + /* Send error during auth, don't destroy on callback */ + rc = auth_send_error(®istrar_uri, PJ_FALSE); + if (rc != 0) + goto on_return; + + /* Send error during auth, destroy on callback */ + rc = auth_send_error(®istrar_uri, PJ_FALSE); + if (rc != 0) + goto on_return; + +on_return: + if (registrar.mod.id != -1) { + pjsip_endpt_unregister_module(endpt, ®istrar.mod); + } + if (send_mod.mod.id != -1) { + pjsip_endpt_unregister_module(endpt, &send_mod.mod); + } + if (udp) { + pjsip_transport_dec_ref(udp); + } + return rc; +} + + diff --git a/pjsip/src/test/test.c b/pjsip/src/test/test.c new file mode 100644 index 0000000..4d17939 --- /dev/null +++ b/pjsip/src/test/test.c @@ -0,0 +1,398 @@ +/* $Id: test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "test.h" +#include <pjlib.h> +#include <pjlib-util.h> +#include <pjsip.h> + +#define THIS_FILE "test.c" + +#define DO_TEST(test) do { \ + PJ_LOG(3, (THIS_FILE, "Running %s...", #test)); \ + rc = test; \ + PJ_LOG(3, (THIS_FILE, \ + "%s(%d)", \ + (rc ? "..ERROR" : "..success"), rc)); \ + if (rc!=0) goto on_return; \ + } while (0) + +#define DO_TSX_TEST(test, param) \ + do { \ + PJ_LOG(3, (THIS_FILE, "Running %s(%s)...", #test, (param)->tp_type)); \ + rc = test(param); \ + PJ_LOG(3, (THIS_FILE, \ + "%s(%d)", \ + (rc ? "..ERROR" : "..success"), rc)); \ + if (rc!=0) goto on_return; \ + } while (0) + + +pjsip_endpoint *endpt; +int log_level = 3; +int param_log_decor = PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_TIME | + PJ_LOG_HAS_MICRO_SEC; + +static pj_oshandle_t fd_report; +const char *system_name = "Unknown"; +static char buf[1024]; + +void app_perror(const char *msg, pj_status_t rc) +{ + char errbuf[256]; + + PJ_CHECK_STACK(); + + pj_strerror(rc, errbuf, sizeof(errbuf)); + PJ_LOG(3,(THIS_FILE, "%s: [pj_status_t=%d] %s", msg, rc, errbuf)); + +} + +void flush_events(unsigned duration) +{ + pj_time_val stop_time; + + pj_gettimeofday(&stop_time); + stop_time.msec += duration; + pj_time_val_normalize(&stop_time); + + /* Process all events for the specified duration. */ + for (;;) { + pj_time_val timeout = {0, 1}, now; + + pjsip_endpt_handle_events(endpt, &timeout); + + pj_gettimeofday(&now); + if (PJ_TIME_VAL_GTE(now, stop_time)) + break; + } +} + +pj_status_t register_static_modules(pj_size_t *count, pjsip_module **modules) +{ + PJ_UNUSED_ARG(modules); + + *count = 0; + return PJ_SUCCESS; +} + +static pj_status_t init_report(void) +{ + char tmp[80]; + pj_time_val timestamp; + pj_parsed_time date_time; + pj_ssize_t len; + pj_status_t status; + + pj_ansi_sprintf(tmp, "pjsip-static-bench-%s-%s.htm", PJ_OS_NAME, PJ_CC_NAME); + + status = pj_file_open(NULL, tmp, PJ_O_WRONLY, &fd_report); + if (status != PJ_SUCCESS) + return status; + + /* Title */ + len = pj_ansi_sprintf(buf, "<HTML>\n" + " <HEAD>\n" + " <TITLE>PJSIP %s (%s) - Static Benchmark</TITLE>\n" + " </HEAD>\n" + "<BODY>\n" + "\n", + PJ_VERSION, + (PJ_DEBUG ? "Debug" : "Release")); + pj_file_write(fd_report, buf, &len); + + + /* Title */ + len = pj_ansi_sprintf(buf, "<H1>PJSIP %s (%s) - Static Benchmark</H1>\n", + PJ_VERSION, + (PJ_DEBUG ? "Debug" : "Release")); + pj_file_write(fd_report, buf, &len); + + len = pj_ansi_sprintf(buf, "<P>Below is the benchmark result generated " + "by <b>test-pjsip</b> program. The program " + "is single-threaded only.</P>\n"); + pj_file_write(fd_report, buf, &len); + + + /* Write table heading */ + len = pj_ansi_sprintf(buf, "<TABLE border=\"1\" cellpadding=\"4\">\n" + " <TR><TD bgColor=\"aqua\" align=\"center\">Variable</TD>\n" + " <TD bgColor=\"aqua\" align=\"center\">Value</TD>\n" + " <TD bgColor=\"aqua\" align=\"center\">Description</TD>\n" + " </TR>\n"); + pj_file_write(fd_report, buf, &len); + + + /* Write version */ + report_sval("version", PJ_VERSION, "", "PJLIB/PJSIP version"); + + + /* Debug or release */ + report_sval("build-type", (PJ_DEBUG ? "Debug" : "Release"), "", "Build type"); + + + /* Write timestamp */ + pj_gettimeofday(×tamp); + report_ival("timestamp", timestamp.sec, "", "System timestamp of the test"); + + + /* Write time of day */ + pj_time_decode(×tamp, &date_time); + len = pj_ansi_sprintf(tmp, "%04d-%02d-%02d %02d:%02d:%02d", + date_time.year, date_time.mon+1, date_time.day, + date_time.hour, date_time.min, date_time.sec); + report_sval("date-time", tmp, "", "Date/time of the test"); + + + /* Write System */ + report_sval("system", system_name, "", "System description"); + + + /* Write OS type */ + report_sval("os-family", PJ_OS_NAME, "", "Operating system family"); + + + /* Write CC name */ + len = pj_ansi_sprintf(tmp, "%s-%d.%d.%d", PJ_CC_NAME, + PJ_CC_VER_1, PJ_CC_VER_2, PJ_CC_VER_2); + report_sval("cc-name", tmp, "", "Compiler name and version"); + + + return PJ_SUCCESS; +} + +void report_sval(const char *name, const char* value, const char *valname, + const char *desc) +{ + pj_ssize_t len; + + len = pj_ansi_sprintf(buf, " <TR><TD><TT>%s</TT></TD>\n" + " <TD align=\"right\"><B>%s %s</B></TD>\n" + " <TD>%s</TD>\n" + " </TR>\n", + name, value, valname, desc); + pj_file_write(fd_report, buf, &len); +} + + +void report_ival(const char *name, int value, const char *valname, + const char *desc) +{ + pj_ssize_t len; + + len = pj_ansi_sprintf(buf, " <TR><TD><TT>%s</TT></TD>\n" + " <TD align=\"right\"><B>%d %s</B></TD>\n" + " <TD>%s</TD>\n" + " </TR>\n", + name, value, valname, desc); + pj_file_write(fd_report, buf, &len); + +} + +static void close_report(void) +{ + pj_ssize_t len; + + if (fd_report) { + len = pj_ansi_sprintf(buf, "</TABLE>\n</BODY>\n</HTML>\n"); + pj_file_write(fd_report, buf, &len); + + pj_file_close(fd_report); + } +} + + +int test_main(void) +{ + pj_status_t rc; + pj_caching_pool caching_pool; + const char *filename; + unsigned tsx_test_cnt=0; + struct tsx_test_param tsx_test[10]; + pj_status_t status; +#if INCLUDE_TSX_TEST + unsigned i; + pjsip_transport *tp; +#if PJ_HAS_TCP + pjsip_tpfactory *tpfactory; +#endif /* PJ_HAS_TCP */ +#endif /* INCLUDE_TSX_TEST */ + int line; + + pj_log_set_level(log_level); + pj_log_set_decor(param_log_decor); + + if ((rc=pj_init()) != PJ_SUCCESS) { + app_perror("pj_init", rc); + return rc; + } + + if ((rc=pjlib_util_init()) != PJ_SUCCESS) { + app_perror("pj_init", rc); + return rc; + } + + status = init_report(); + if (status != PJ_SUCCESS) + return status; + + pj_dump_config(); + + pj_caching_pool_init( &caching_pool, &pj_pool_factory_default_policy, + PJSIP_TEST_MEM_SIZE ); + + rc = pjsip_endpt_create(&caching_pool.factory, "endpt", &endpt); + if (rc != PJ_SUCCESS) { + app_perror("pjsip_endpt_create", rc); + pj_caching_pool_destroy(&caching_pool); + return rc; + } + + PJ_LOG(3,(THIS_FILE,"")); + + /* Init logger module. */ + init_msg_logger(); + msg_logger_set_enabled(1); + + /* Start transaction layer module. */ + rc = pjsip_tsx_layer_init_module(endpt); + if (rc != PJ_SUCCESS) { + app_perror(" Error initializing transaction module", rc); + goto on_return; + } + + /* Create loop transport. */ + rc = pjsip_loop_start(endpt, NULL); + if (rc != PJ_SUCCESS) { + app_perror(" error: unable to create datagram loop transport", + rc); + goto on_return; + } + tsx_test[tsx_test_cnt].port = 5060; + tsx_test[tsx_test_cnt].tp_type = "loop-dgram"; + tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_LOOP_DGRAM; + ++tsx_test_cnt; + + +#if INCLUDE_URI_TEST + DO_TEST(uri_test()); +#endif + +#if INCLUDE_MSG_TEST + DO_TEST(msg_test()); + DO_TEST(msg_err_test()); +#endif + +#if INCLUDE_MULTIPART_TEST + DO_TEST(multipart_test()); +#endif + +#if INCLUDE_TXDATA_TEST + DO_TEST(txdata_test()); +#endif + +#if INCLUDE_TSX_BENCH + DO_TEST(tsx_bench()); +#endif + +#if INCLUDE_UDP_TEST + DO_TEST(transport_udp_test()); +#endif + +#if INCLUDE_LOOP_TEST + DO_TEST(transport_loop_test()); +#endif + +#if INCLUDE_TCP_TEST + DO_TEST(transport_tcp_test()); +#endif + +#if INCLUDE_RESOLVE_TEST + DO_TEST(resolve_test()); +#endif + + +#if INCLUDE_TSX_TEST + status = pjsip_udp_transport_start(endpt, NULL, NULL, 1, &tp); + if (status == PJ_SUCCESS) { + tsx_test[tsx_test_cnt].port = tp->local_name.port; + tsx_test[tsx_test_cnt].tp_type = "udp"; + tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_UDP; + ++tsx_test_cnt; + } + +#if PJ_HAS_TCP + status = pjsip_tcp_transport_start(endpt, NULL, 1, &tpfactory); + if (status == PJ_SUCCESS) { + tsx_test[tsx_test_cnt].port = tpfactory->addr_name.port; + tsx_test[tsx_test_cnt].tp_type = "tcp"; + tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_TCP; + ++tsx_test_cnt; + } else { + app_perror("Unable to create TCP", status); + rc = -4; + goto on_return; + } +#endif + + + for (i=0; i<tsx_test_cnt; ++i) { + DO_TSX_TEST(tsx_basic_test, &tsx_test[i]); + DO_TSX_TEST(tsx_uac_test, &tsx_test[i]); + DO_TSX_TEST(tsx_uas_test, &tsx_test[i]); + } +#endif + +#if INCLUDE_INV_OA_TEST + DO_TEST(inv_offer_answer_test()); +#endif + +#if INCLUDE_REGC_TEST + DO_TEST(regc_test()); +#endif + + +on_return: + flush_events(500); + + /* Dumping memory pool usage */ + PJ_LOG(3,(THIS_FILE, "Peak memory size=%u MB", + caching_pool.peak_used_size / 1000000)); + + pjsip_endpt_destroy(endpt); + pj_caching_pool_destroy(&caching_pool); + + PJ_LOG(3,(THIS_FILE, "")); + + pj_thread_get_stack_info(pj_thread_this(), &filename, &line); + PJ_LOG(3,(THIS_FILE, "Stack max usage: %u, deepest: %s:%u", + pj_thread_get_stack_max_usage(pj_thread_this()), + filename, line)); + if (rc == 0) + PJ_LOG(3,(THIS_FILE, "Looks like everything is okay!..")); + else + PJ_LOG(3,(THIS_FILE, "Test completed with error(s)")); + + report_ival("test-status", rc, "", "Overall test status/result (0==success)"); + close_report(); + return rc; +} + diff --git a/pjsip/src/test/test.h b/pjsip/src/test/test.h new file mode 100644 index 0000000..d046682 --- /dev/null +++ b/pjsip/src/test/test.h @@ -0,0 +1,127 @@ +/* $Id: test.h 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __TEST_H__ +#define __TEST_H__ + +#include <pjsip/sip_types.h> + +extern pjsip_endpoint *endpt; + +#define TEST_UDP_PORT 15060 +#define TEST_UDP_PORT_STR "15060" + +/** + * Memory size to use in caching pool. + * Default: 2MB + */ +#ifndef PJSIP_TEST_MEM_SIZE +# define PJSIP_TEST_MEM_SIZE (2*1024*1024) +#endif + + + +#define INCLUDE_MESSAGING_GROUP 1 +#define INCLUDE_TRANSPORT_GROUP 1 +#define INCLUDE_TSX_GROUP 1 +#define INCLUDE_INV_GROUP 1 +#define INCLUDE_REGC_GROUP 1 + +#define INCLUDE_BENCHMARKS 1 + +/* + * Include tests that normally would fail under certain gcc + * optimization levels. + */ +#ifndef INCLUDE_GCC_TEST +# define INCLUDE_GCC_TEST 0 +#endif + + +#define INCLUDE_URI_TEST INCLUDE_MESSAGING_GROUP +#define INCLUDE_MSG_TEST INCLUDE_MESSAGING_GROUP +#define INCLUDE_MULTIPART_TEST INCLUDE_MESSAGING_GROUP +#define INCLUDE_TXDATA_TEST INCLUDE_MESSAGING_GROUP +#define INCLUDE_TSX_BENCH INCLUDE_MESSAGING_GROUP +#define INCLUDE_UDP_TEST INCLUDE_TRANSPORT_GROUP +#define INCLUDE_LOOP_TEST INCLUDE_TRANSPORT_GROUP +#define INCLUDE_TCP_TEST INCLUDE_TRANSPORT_GROUP +#define INCLUDE_RESOLVE_TEST INCLUDE_TRANSPORT_GROUP +#define INCLUDE_TSX_TEST INCLUDE_TSX_GROUP +#define INCLUDE_INV_OA_TEST INCLUDE_INV_GROUP +#define INCLUDE_REGC_TEST INCLUDE_REGC_GROUP + + +/* The tests */ +int uri_test(void); +int msg_test(void); +int msg_err_test(void); +int multipart_test(void); +int txdata_test(void); +int tsx_bench(void); +int transport_udp_test(void); +int transport_loop_test(void); +int transport_tcp_test(void); +int resolve_test(void); +int regc_test(void); + +struct tsx_test_param +{ + int type; + int port; + char *tp_type; +}; + +int tsx_basic_test(struct tsx_test_param *param); +int tsx_uac_test(struct tsx_test_param *param); +int tsx_uas_test(struct tsx_test_param *param); + +/* Transport test helpers (transport_test.c). */ +int generic_transport_test(pjsip_transport *tp); +int transport_send_recv_test( pjsip_transport_type_e tp_type, + pjsip_transport *ref_tp, + char *target_url, + int *p_usec_rtt); +int transport_rt_test( pjsip_transport_type_e tp_type, + pjsip_transport *ref_tp, + char *target_url, + int *pkt_lost); +int transport_load_test(char *target_url); + +/* Invite session */ +int inv_offer_answer_test(void); + +/* Test main entry */ +int test_main(void); + +/* Test utilities. */ +void app_perror(const char *msg, pj_status_t status); +int init_msg_logger(void); +int msg_logger_set_enabled(pj_bool_t enabled); +void flush_events(unsigned duration); + + +void report_ival(const char *name, int value, const char *valname, const char *desc); +void report_sval(const char *name, const char* value, const char *valname, const char *desc); + + +/* Settings. */ +extern int log_level; + +#endif /* __TEST_H__ */ diff --git a/pjsip/src/test/transport_loop_test.c b/pjsip/src/test/transport_loop_test.c new file mode 100644 index 0000000..6c60036 --- /dev/null +++ b/pjsip/src/test/transport_loop_test.c @@ -0,0 +1,127 @@ +/* $Id: transport_loop_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "transport_loop_test.c" + +static int datagram_loop_test() +{ + enum { LOOP = 8 }; + pjsip_transport *loop; + int i, pkt_lost; + pj_sockaddr_in addr; + pj_status_t status; + long ref_cnt; + int rtt[LOOP], min_rtt; + + PJ_LOG(3,(THIS_FILE, "testing datagram loop transport")); + + /* Test acquire transport. */ + status = pjsip_endpt_acquire_transport( endpt, PJSIP_TRANSPORT_LOOP_DGRAM, + &addr, sizeof(addr), NULL, &loop); + if (status != PJ_SUCCESS) { + app_perror(" error: loop transport is not configured", status); + return -20; + } + + /* Get initial reference counter */ + ref_cnt = pj_atomic_get(loop->ref_cnt); + + /* Test basic transport attributes */ + status = generic_transport_test(loop); + if (status != PJ_SUCCESS) + return status; + + /* Basic transport's send/receive loopback test. */ + for (i=0; i<LOOP; ++i) { + status = transport_send_recv_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop, + "sip:bob@130.0.0.1;transport=loop-dgram", + &rtt[i]); + if (status != 0) + return status; + } + + min_rtt = 0xFFFFFFF; + for (i=0; i<LOOP; ++i) + if (rtt[i] < min_rtt) min_rtt = rtt[i]; + + report_ival("loop-rtt-usec", min_rtt, "usec", + "Best Loopback transport round trip time, in microseconds " + "(time from sending request until response is received. " + "Tests were performed on local machine only)"); + + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop, + "sip:bob@130.0.0.1;transport=loop-dgram", + &pkt_lost); + if (status != 0) + return status; + + if (pkt_lost != 0) { + PJ_LOG(3,(THIS_FILE, " error: %d packet(s) was lost", pkt_lost)); + return -40; + } + + /* Put delay. */ + PJ_LOG(3,(THIS_FILE," setting network delay to 10 ms")); + pjsip_loop_set_delay(loop, 10); + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop, + "sip:bob@130.0.0.1;transport=loop-dgram", + &pkt_lost); + if (status != 0) + return status; + + if (pkt_lost != 0) { + PJ_LOG(3,(THIS_FILE, " error: %d packet(s) was lost", pkt_lost)); + return -50; + } + + /* Restore delay. */ + pjsip_loop_set_delay(loop, 0); + + /* Check reference counter. */ + if (pj_atomic_get(loop->ref_cnt) != ref_cnt) { + PJ_LOG(3,(THIS_FILE, " error: ref counter is not %d (%d)", + ref_cnt, pj_atomic_get(loop->ref_cnt))); + return -51; + } + + /* Decrement reference. */ + pjsip_transport_dec_ref(loop); + + return 0; +} + +int transport_loop_test(void) +{ + int status; + + status = datagram_loop_test(); + if (status != 0) + return status; + + return 0; +} diff --git a/pjsip/src/test/transport_tcp_test.c b/pjsip/src/test/transport_tcp_test.c new file mode 100644 index 0000000..4bc8c1c --- /dev/null +++ b/pjsip/src/test/transport_tcp_test.c @@ -0,0 +1,155 @@ +/* $Id: transport_tcp_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "transport_tcp_test.c" + + +/* + * TCP transport test. + */ +#if PJ_HAS_TCP +int transport_tcp_test(void) +{ + enum { SEND_RECV_LOOP = 8 }; + pjsip_tpfactory *tpfactory; + pjsip_transport *tcp; + pj_sockaddr_in rem_addr; + pj_status_t status; + char url[PJSIP_MAX_URL_SIZE]; + int rtt[SEND_RECV_LOOP], min_rtt; + int i, pkt_lost; + + /* Start TCP listener on arbitrary port. */ + status = pjsip_tcp_transport_start(endpt, NULL, 1, &tpfactory); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to start TCP transport", status); + return -10; + } + + + /* Get the listener address */ + status = pj_sockaddr_in_init(&rem_addr, &tpfactory->addr_name.host, + (pj_uint16_t)tpfactory->addr_name.port); + if (status != PJ_SUCCESS) { + app_perror(" Error: possibly invalid TCP address name", status); + return -14; + } + + pj_ansi_sprintf(url, "sip:alice@%s:%d;transport=tcp", + pj_inet_ntoa(rem_addr.sin_addr), + pj_ntohs(rem_addr.sin_port)); + + + /* Acquire one TCP transport. */ + status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_TCP, + &rem_addr, sizeof(rem_addr), + NULL, &tcp); + if (status != PJ_SUCCESS || tcp == NULL) { + app_perror(" Error: unable to acquire TCP transport", status); + return -17; + } + + /* After pjsip_endpt_acquire_transport, TCP transport must have + * reference counter 1. + */ + if (pj_atomic_get(tcp->ref_cnt) != 1) + return -20; + + /* Test basic transport attributes */ + status = generic_transport_test(tcp); + if (status != PJ_SUCCESS) + return status; + + + /* Check again that reference counter is 1. */ + if (pj_atomic_get(tcp->ref_cnt) != 1) + return -40; + + /* Load test */ + if (transport_load_test(url) != 0) + return -60; + + /* Basic transport's send/receive loopback test. */ + for (i=0; i<SEND_RECV_LOOP; ++i) { + status = transport_send_recv_test(PJSIP_TRANSPORT_TCP, tcp, url, &rtt[i]); + + if (status != 0) { + pjsip_transport_dec_ref(tcp); + flush_events(500); + return -72; + } + } + + min_rtt = 0xFFFFFFF; + for (i=0; i<SEND_RECV_LOOP; ++i) + if (rtt[i] < min_rtt) min_rtt = rtt[i]; + + report_ival("tcp-rtt-usec", min_rtt, "usec", + "Best TCP transport round trip time, in microseconds " + "(time from sending request until response is received. " + "Tests were performed on local machine only, and after " + "TCP socket has been established by previous test)"); + + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_TCP, tcp, url, &pkt_lost); + if (status != 0) { + pjsip_transport_dec_ref(tcp); + return status; + } + + if (pkt_lost != 0) + PJ_LOG(3,(THIS_FILE, " note: %d packet(s) was lost", pkt_lost)); + + /* Check again that reference counter is still 1. */ + if (pj_atomic_get(tcp->ref_cnt) != 1) + return -80; + + /* Destroy this transport. */ + pjsip_transport_dec_ref(tcp); + + /* Force destroy this transport. */ + status = pjsip_transport_destroy(tcp); + if (status != PJ_SUCCESS) + return -90; + + /* Unregister factory */ + status = pjsip_tpmgr_unregister_tpfactory(pjsip_endpt_get_tpmgr(endpt), + tpfactory); + if (status != PJ_SUCCESS) + return -95; + + /* Flush events. */ + PJ_LOG(3,(THIS_FILE, " Flushing events, 1 second...")); + flush_events(1000); + + /* Done */ + return 0; +} +#else /* PJ_HAS_TCP */ +int transport_tcp_test(void) +{ + return 0; +} +#endif /* PJ_HAS_TCP */ diff --git a/pjsip/src/test/transport_test.c b/pjsip/src/test/transport_test.c new file mode 100644 index 0000000..409ec5a --- /dev/null +++ b/pjsip/src/test/transport_test.c @@ -0,0 +1,771 @@ +/* $Id: transport_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "transport_test.c" + +/////////////////////////////////////////////////////////////////////////////// +/* + * Generic testing for transport, to make sure that basic + * attributes have been initialized properly. + */ +int generic_transport_test(pjsip_transport *tp) +{ + PJ_LOG(3,(THIS_FILE, " structure test...")); + + /* Check that local address name is valid. */ + { + struct pj_in_addr addr; + + /* Note: inet_aton() returns non-zero if addr is valid! */ + if (pj_inet_aton(&tp->local_name.host, &addr) != 0) { + if (addr.s_addr==PJ_INADDR_ANY || addr.s_addr==PJ_INADDR_NONE) { + PJ_LOG(3,(THIS_FILE, " Error: invalid address name")); + return -420; + } + } else { + /* It's okay. local_name.host may be a hostname instead of + * IP address. + */ + } + } + + /* Check that port is valid. */ + if (tp->local_name.port <= 0) { + return -430; + } + + /* Check length of address (for now we only check against sockaddr_in). */ + if (tp->addr_len != sizeof(pj_sockaddr_in)) + return -440; + + /* Check type. */ + if (tp->key.type == PJSIP_TRANSPORT_UNSPECIFIED) + return -450; + + /* That's it. */ + return PJ_SUCCESS; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Send/receive test. + * + * This test sends a request to loopback address; as soon as request is + * received, response will be sent, and time is recorded. + * + * The main purpose is to test that the basic transport functionalities works, + * before we continue with more complicated tests. + */ +#define FROM_HDR "Bob <sip:bob@example.com>" +#define CONTACT_HDR "Bob <sip:bob@127.0.0.1>" +#define CALL_ID_HDR "SendRecv-Test" +#define CSEQ_VALUE 100 +#define BODY "Hello World!" + +static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata); + +/* Flag to indicate message has been received + * (or failed to send) + */ +#define NO_STATUS -2 +static int send_status = NO_STATUS; +static int recv_status = NO_STATUS; +static pj_timestamp my_send_time, my_recv_time; + +/* Module to receive messages for this test. */ +static pjsip_module my_module = +{ + NULL, NULL, /* prev and next */ + { "Transport-Test", 14}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &my_on_rx_request, /* on_rx_request() */ + &my_on_rx_response, /* on_rx_response() */ + NULL, /* on_tsx_state() */ +}; + + +static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata) +{ + /* Check that this is our request. */ + if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) { + /* It is! */ + /* Send response. */ + pjsip_tx_data *tdata; + pjsip_response_addr res_addr; + pj_status_t status; + + status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) { + recv_status = status; + return PJ_TRUE; + } + status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr); + if (status != PJ_SUCCESS) { + recv_status = status; + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL); + if (status != PJ_SUCCESS) { + recv_status = status; + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + return PJ_TRUE; + } + + /* Not ours. */ + return PJ_FALSE; +} + +static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata) +{ + if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) { + pj_get_timestamp(&my_recv_time); + recv_status = PJ_SUCCESS; + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* Transport callback. */ +static void send_msg_callback(pjsip_send_state *stateless_data, + pj_ssize_t sent, pj_bool_t *cont) +{ + PJ_UNUSED_ARG(stateless_data); + + if (sent < 1) { + /* Obtain the error code. */ + send_status = -sent; + } else { + send_status = PJ_SUCCESS; + } + + /* Don't want to continue. */ + *cont = PJ_FALSE; +} + + +/* Test that we receive loopback message. */ +int transport_send_recv_test( pjsip_transport_type_e tp_type, + pjsip_transport *ref_tp, + char *target_url, + int *p_usec_rtt) +{ + pj_bool_t msg_log_enabled; + pj_status_t status; + pj_str_t target, from, to, contact, call_id, body; + pjsip_method method; + pjsip_tx_data *tdata; + pj_time_val timeout; + + PJ_UNUSED_ARG(tp_type); + PJ_UNUSED_ARG(ref_tp); + + PJ_LOG(3,(THIS_FILE, " single message round-trip test...")); + + /* Register out test module to receive the message (if necessary). */ + if (my_module.id == -1) { + status = pjsip_endpt_register_module( endpt, &my_module ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to register module", status); + return -500; + } + } + + /* Disable message logging. */ + msg_log_enabled = msg_logger_set_enabled(0); + + /* Create a request message. */ + target = pj_str(target_url); + from = pj_str(FROM_HDR); + to = pj_str(target_url); + contact = pj_str(CONTACT_HDR); + call_id = pj_str(CALL_ID_HDR); + body = pj_str(BODY); + + pjsip_method_set(&method, PJSIP_OPTIONS_METHOD); + status = pjsip_endpt_create_request( endpt, &method, &target, &from, &to, + &contact, &call_id, CSEQ_VALUE, + &body, &tdata ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -510; + } + + /* Reset statuses */ + send_status = recv_status = NO_STATUS; + + /* Start time. */ + pj_get_timestamp(&my_send_time); + + /* Send the message (statelessly). */ + PJ_LOG(5,(THIS_FILE, "Sending request to %.*s", + (int)target.slen, target.ptr)); + status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL, + &send_msg_callback); + if (status != PJ_SUCCESS) { + /* Immediate error! */ + pjsip_tx_data_dec_ref(tdata); + send_status = status; + } + + /* Set the timeout (2 seconds from now) */ + pj_gettimeofday(&timeout); + timeout.sec += 2; + + /* Loop handling events until we get status */ + do { + pj_time_val now; + pj_time_val poll_interval = { 0, 10 }; + + pj_gettimeofday(&now); + if (PJ_TIME_VAL_GTE(now, timeout)) { + PJ_LOG(3,(THIS_FILE, " error: timeout in send/recv test")); + status = -540; + goto on_return; + } + + if (send_status!=NO_STATUS && send_status!=PJ_SUCCESS) { + app_perror(" error sending message", send_status); + status = -550; + goto on_return; + } + + if (recv_status!=NO_STATUS && recv_status!=PJ_SUCCESS) { + app_perror(" error receiving message", recv_status); + status = -560; + goto on_return; + } + + if (send_status!=NO_STATUS && recv_status!=NO_STATUS) { + /* Success! */ + break; + } + + pjsip_endpt_handle_events(endpt, &poll_interval); + + } while (1); + + if (status == PJ_SUCCESS) { + unsigned usec_rt; + usec_rt = pj_elapsed_usec(&my_send_time, &my_recv_time); + + PJ_LOG(3,(THIS_FILE, " round-trip = %d usec", usec_rt)); + + *p_usec_rtt = usec_rt; + } + + /* Restore message logging. */ + msg_logger_set_enabled(msg_log_enabled); + + status = PJ_SUCCESS; + +on_return: + return status; +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Multithreaded round-trip test + * + * This test will spawn multiple threads, each of them send a request. As soon + * as request is received, response will be sent, and time is recorded. + * + * The main purpose of this test is to ensure there's no crash when multiple + * threads are sending/receiving messages. + * + */ +static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata); + +static pjsip_module rt_module = +{ + NULL, NULL, /* prev and next */ + { "Transport-RT-Test", 17}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &rt_on_rx_request, /* on_rx_request() */ + &rt_on_rx_response, /* on_rx_response() */ + NULL, /* tsx_handler() */ +}; + +static struct +{ + pj_thread_t *thread; + pj_timestamp send_time; + pj_timestamp total_rt_time; + int sent_request_count, recv_response_count; + pj_str_t call_id; + pj_timer_entry timeout_timer; + pj_timer_entry tx_timer; + pj_mutex_t *mutex; +} rt_test_data[16]; + +static char rt_target_uri[64]; +static pj_bool_t rt_stop; +static pj_str_t rt_call_id; + +static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata) +{ + if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) { + pjsip_tx_data *tdata; + pjsip_response_addr res_addr; + pj_status_t status; + + status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error creating response", status); + return PJ_TRUE; + } + status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr); + if (status != PJ_SUCCESS) { + app_perror(" error in get response address", status); + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror(" error sending response", status); + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + return PJ_TRUE; + + } + return PJ_FALSE; +} + +static pj_status_t rt_send_request(int thread_id) +{ + pj_status_t status; + pj_str_t target, from, to, contact, call_id; + pjsip_tx_data *tdata; + pj_time_val timeout_delay; + + pj_mutex_lock(rt_test_data[thread_id].mutex); + + /* Create a request message. */ + target = pj_str(rt_target_uri); + from = pj_str(FROM_HDR); + to = pj_str(rt_target_uri); + contact = pj_str(CONTACT_HDR); + call_id = rt_test_data[thread_id].call_id; + + status = pjsip_endpt_create_request( endpt, &pjsip_options_method, + &target, &from, &to, + &contact, &call_id, -1, + NULL, &tdata ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + pj_mutex_unlock(rt_test_data[thread_id].mutex); + return -610; + } + + /* Start time. */ + pj_get_timestamp(&rt_test_data[thread_id].send_time); + + /* Send the message (statelessly). */ + status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL, NULL); + if (status != PJ_SUCCESS) { + /* Immediate error! */ + app_perror(" error: send request", status); + pjsip_tx_data_dec_ref(tdata); + pj_mutex_unlock(rt_test_data[thread_id].mutex); + return -620; + } + + /* Update counter. */ + rt_test_data[thread_id].sent_request_count++; + + /* Set timeout timer. */ + if (rt_test_data[thread_id].timeout_timer.user_data != NULL) { + pjsip_endpt_cancel_timer(endpt, &rt_test_data[thread_id].timeout_timer); + } + timeout_delay.sec = 100; timeout_delay.msec = 0; + rt_test_data[thread_id].timeout_timer.user_data = (void*)1; + pjsip_endpt_schedule_timer(endpt, &rt_test_data[thread_id].timeout_timer, + &timeout_delay); + + pj_mutex_unlock(rt_test_data[thread_id].mutex); + return PJ_SUCCESS; +} + +static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata) +{ + if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) { + char *pos = pj_strchr(&rdata->msg_info.cid->id, '/')+1; + int thread_id = (*pos - '0'); + pj_timestamp recv_time; + + pj_mutex_lock(rt_test_data[thread_id].mutex); + + /* Stop timer. */ + pjsip_endpt_cancel_timer(endpt, &rt_test_data[thread_id].timeout_timer); + + /* Update counter and end-time. */ + rt_test_data[thread_id].recv_response_count++; + pj_get_timestamp(&recv_time); + + pj_sub_timestamp(&recv_time, &rt_test_data[thread_id].send_time); + pj_add_timestamp(&rt_test_data[thread_id].total_rt_time, &recv_time); + + if (!rt_stop) { + pj_time_val tx_delay = { 0, 0 }; + pj_assert(rt_test_data[thread_id].tx_timer.user_data == NULL); + rt_test_data[thread_id].tx_timer.user_data = (void*)1; + pjsip_endpt_schedule_timer(endpt, &rt_test_data[thread_id].tx_timer, + &tx_delay); + } + + pj_mutex_unlock(rt_test_data[thread_id].mutex); + + return PJ_TRUE; + } + return PJ_FALSE; +} + +static void rt_timeout_timer( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry ) +{ + pj_mutex_lock(rt_test_data[entry->id].mutex); + + PJ_UNUSED_ARG(timer_heap); + PJ_LOG(3,(THIS_FILE, " timeout waiting for response")); + rt_test_data[entry->id].timeout_timer.user_data = NULL; + + if (rt_test_data[entry->id].tx_timer.user_data == NULL) { + pj_time_val delay = { 0, 0 }; + rt_test_data[entry->id].tx_timer.user_data = (void*)1; + pjsip_endpt_schedule_timer(endpt, &rt_test_data[entry->id].tx_timer, + &delay); + } + + pj_mutex_unlock(rt_test_data[entry->id].mutex); +} + +static void rt_tx_timer( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry ) +{ + pj_mutex_lock(rt_test_data[entry->id].mutex); + + PJ_UNUSED_ARG(timer_heap); + pj_assert(rt_test_data[entry->id].tx_timer.user_data != NULL); + rt_test_data[entry->id].tx_timer.user_data = NULL; + rt_send_request(entry->id); + + pj_mutex_unlock(rt_test_data[entry->id].mutex); +} + + +static int rt_worker_thread(void *arg) +{ + int i; + pj_time_val poll_delay = { 0, 10 }; + + PJ_UNUSED_ARG(arg); + + /* Sleep to allow main threads to run. */ + pj_thread_sleep(10); + + while (!rt_stop) { + pjsip_endpt_handle_events(endpt, &poll_delay); + } + + /* Exhaust responses. */ + for (i=0; i<100; ++i) + pjsip_endpt_handle_events(endpt, &poll_delay); + + return 0; +} + +int transport_rt_test( pjsip_transport_type_e tp_type, + pjsip_transport *ref_tp, + char *target_url, + int *lost) +{ + enum { THREADS = 4, INTERVAL = 10 }; + int i; + pj_status_t status; + pj_pool_t *pool; + pj_bool_t logger_enabled; + + pj_timestamp zero_time, total_time; + unsigned usec_rt; + unsigned total_sent; + unsigned total_recv; + + PJ_UNUSED_ARG(tp_type); + PJ_UNUSED_ARG(ref_tp); + + PJ_LOG(3,(THIS_FILE, " multithreaded round-trip test (%d threads)...", + THREADS)); + PJ_LOG(3,(THIS_FILE, " this will take approx %d seconds, please wait..", + INTERVAL)); + + /* Make sure msg logger is disabled. */ + logger_enabled = msg_logger_set_enabled(0); + + /* Register module (if not yet registered) */ + if (rt_module.id == -1) { + status = pjsip_endpt_register_module( endpt, &rt_module ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to register module", status); + return -600; + } + } + + /* Create pool for this test. */ + pool = pjsip_endpt_create_pool(endpt, NULL, 4000, 4000); + if (!pool) + return -610; + + /* Initialize static test data. */ + pj_ansi_strcpy(rt_target_uri, target_url); + rt_call_id = pj_str("RT-Call-Id/"); + rt_stop = PJ_FALSE; + + /* Initialize thread data. */ + for (i=0; i<THREADS; ++i) { + char buf[1]; + pj_str_t str_id; + + pj_strset(&str_id, buf, 1); + pj_bzero(&rt_test_data[i], sizeof(rt_test_data[i])); + + /* Init timer entry */ + rt_test_data[i].tx_timer.id = i; + rt_test_data[i].tx_timer.cb = &rt_tx_timer; + rt_test_data[i].timeout_timer.id = i; + rt_test_data[i].timeout_timer.cb = &rt_timeout_timer; + + /* Generate Call-ID for each thread. */ + rt_test_data[i].call_id.ptr = (char*) pj_pool_alloc(pool, rt_call_id.slen+1); + pj_strcpy(&rt_test_data[i].call_id, &rt_call_id); + buf[0] = '0' + (char)i; + pj_strcat(&rt_test_data[i].call_id, &str_id); + + /* Init mutex. */ + status = pj_mutex_create_recursive(pool, "rt", &rt_test_data[i].mutex); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create mutex", status); + return -615; + } + + /* Create thread, suspended. */ + status = pj_thread_create(pool, "rttest%p", &rt_worker_thread, (void*)(long)i, 0, + PJ_THREAD_SUSPENDED, &rt_test_data[i].thread); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create thread", status); + return -620; + } + } + + /* Start threads! */ + for (i=0; i<THREADS; ++i) { + pj_time_val delay = {0,0}; + pj_thread_resume(rt_test_data[i].thread); + + /* Schedule first message transmissions. */ + rt_test_data[i].tx_timer.user_data = (void*)1; + pjsip_endpt_schedule_timer(endpt, &rt_test_data[i].tx_timer, &delay); + } + + /* Sleep for some time. */ + pj_thread_sleep(INTERVAL * 1000); + + /* Signal thread to stop. */ + rt_stop = PJ_TRUE; + + /* Wait threads to complete. */ + for (i=0; i<THREADS; ++i) { + pj_thread_join(rt_test_data[i].thread); + pj_thread_destroy(rt_test_data[i].thread); + } + + /* Destroy rt_test_data */ + for (i=0; i<THREADS; ++i) { + pj_mutex_destroy(rt_test_data[i].mutex); + pjsip_endpt_cancel_timer(endpt, &rt_test_data[i].timeout_timer); + } + + /* Gather statistics. */ + pj_bzero(&total_time, sizeof(total_time)); + pj_bzero(&zero_time, sizeof(zero_time)); + usec_rt = total_sent = total_recv = 0; + for (i=0; i<THREADS; ++i) { + total_sent += rt_test_data[i].sent_request_count; + total_recv += rt_test_data[i].recv_response_count; + pj_add_timestamp(&total_time, &rt_test_data[i].total_rt_time); + } + + /* Display statistics. */ + if (total_recv) + total_time.u64 = total_time.u64/total_recv; + else + total_time.u64 = 0; + usec_rt = pj_elapsed_usec(&zero_time, &total_time); + PJ_LOG(3,(THIS_FILE, " done.")); + PJ_LOG(3,(THIS_FILE, " total %d messages sent", total_sent)); + PJ_LOG(3,(THIS_FILE, " average round-trip=%d usec", usec_rt)); + + pjsip_endpt_release_pool(endpt, pool); + + *lost = total_sent-total_recv; + + /* Flush events. */ + flush_events(500); + + /* Restore msg logger. */ + msg_logger_set_enabled(logger_enabled); + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Transport load testing + */ +static pj_bool_t load_on_rx_request(pjsip_rx_data *rdata); + +static struct mod_load_test +{ + pjsip_module mod; + pj_int32_t next_seq; + pj_bool_t err; +} mod_load = +{ + { + NULL, NULL, /* prev and next */ + { "mod-load-test", 13}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &load_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* tsx_handler() */ + } +}; + + +static pj_bool_t load_on_rx_request(pjsip_rx_data *rdata) +{ + if (rdata->msg_info.cseq->cseq != mod_load.next_seq) { + PJ_LOG(1,("THIS_FILE", " err: expecting cseq %u, got %u", + mod_load.next_seq, rdata->msg_info.cseq->cseq)); + mod_load.err = PJ_TRUE; + mod_load.next_seq = rdata->msg_info.cseq->cseq + 1; + } else + mod_load.next_seq++; + return PJ_TRUE; +} + +int transport_load_test(char *target_url) +{ + enum { COUNT = 2000 }; + unsigned i; + pj_status_t status = PJ_SUCCESS; + + /* exhaust packets */ + do { + pj_time_val delay = {1, 0}; + i = 0; + pjsip_endpt_handle_events2(endpt, &delay, &i); + } while (i != 0); + + PJ_LOG(3,(THIS_FILE, " transport load test...")); + + if (mod_load.mod.id == -1) { + status = pjsip_endpt_register_module( endpt, &mod_load.mod); + if (status != PJ_SUCCESS) { + app_perror("error registering module", status); + return -1; + } + } + mod_load.err = PJ_FALSE; + mod_load.next_seq = 0; + + for (i=0; i<COUNT && !mod_load.err; ++i) { + pj_str_t target, from, call_id; + pjsip_tx_data *tdata; + + target = pj_str(target_url); + from = pj_str("<sip:user@host>"); + call_id = pj_str("thecallid"); + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, + &target, &from, + &target, &from, &call_id, + i, NULL, &tdata ); + if (status != PJ_SUCCESS) { + app_perror("error creating request", status); + goto on_return; + } + + status = pjsip_endpt_send_request_stateless(endpt, tdata, NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror("error sending request", status); + goto on_return; + } + } + + do { + pj_time_val delay = {1, 0}; + i = 0; + pjsip_endpt_handle_events2(endpt, &delay, &i); + } while (i != 0); + + if (mod_load.next_seq != COUNT) { + PJ_LOG(1,("THIS_FILE", " err: expecting %u msg, got only %u", + COUNT, mod_load.next_seq)); + status = -2; + goto on_return; + } + +on_return: + if (mod_load.mod.id != -1) { + pjsip_endpt_unregister_module( endpt, &mod_load.mod); + mod_load.mod.id = -1; + } + if (status != PJ_SUCCESS || mod_load.err) { + return -2; + } + PJ_LOG(3,(THIS_FILE, " success")); + return 0; +} + + diff --git a/pjsip/src/test/transport_udp_test.c b/pjsip/src/test/transport_udp_test.c new file mode 100644 index 0000000..3c92632 --- /dev/null +++ b/pjsip/src/test/transport_udp_test.c @@ -0,0 +1,128 @@ +/* $Id: transport_udp_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "transport_udp_test.c" + + +/* + * UDP transport test. + */ +int transport_udp_test(void) +{ + enum { SEND_RECV_LOOP = 8 }; + pjsip_transport *udp_tp, *tp; + pj_sockaddr_in addr, rem_addr; + pj_str_t s; + pj_status_t status; + int rtt[SEND_RECV_LOOP], min_rtt; + int i, pkt_lost; + + pj_sockaddr_in_init(&addr, NULL, TEST_UDP_PORT); + + /* Start UDP transport. */ + status = pjsip_udp_transport_start( endpt, &addr, NULL, 1, &udp_tp); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to start UDP transport", status); + return -10; + } + + /* UDP transport must have initial reference counter set to 1. */ + if (pj_atomic_get(udp_tp->ref_cnt) != 1) + return -20; + + /* Test basic transport attributes */ + status = generic_transport_test(udp_tp); + if (status != PJ_SUCCESS) + return status; + + /* Test that transport manager is returning the correct + * transport. + */ + pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "1.1.1.1"), 80); + status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP, + &rem_addr, sizeof(rem_addr), + NULL, &tp); + if (status != PJ_SUCCESS) + return -50; + if (tp != udp_tp) + return -60; + + /* pjsip_endpt_acquire_transport() adds reference, so we need + * to decrement it. + */ + pjsip_transport_dec_ref(tp); + + /* Check again that reference counter is 1. */ + if (pj_atomic_get(udp_tp->ref_cnt) != 1) + return -70; + + /* Basic transport's send/receive loopback test. */ + pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "127.0.0.1"), TEST_UDP_PORT); + for (i=0; i<SEND_RECV_LOOP; ++i) { + status = transport_send_recv_test(PJSIP_TRANSPORT_UDP, tp, + "sip:alice@127.0.0.1:"TEST_UDP_PORT_STR, + &rtt[i]); + if (status != 0) + return status; + } + + min_rtt = 0xFFFFFFF; + for (i=0; i<SEND_RECV_LOOP; ++i) + if (rtt[i] < min_rtt) min_rtt = rtt[i]; + + report_ival("udp-rtt-usec", min_rtt, "usec", + "Best UDP transport round trip time, in microseconds " + "(time from sending request until response is received. " + "Tests were performed on local machine only)"); + + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_UDP, tp, + "sip:alice@127.0.0.1:"TEST_UDP_PORT_STR, + &pkt_lost); + if (status != 0) + return status; + + if (pkt_lost != 0) + PJ_LOG(3,(THIS_FILE, " note: %d packet(s) was lost", pkt_lost)); + + /* Check again that reference counter is 1. */ + if (pj_atomic_get(udp_tp->ref_cnt) != 1) + return -80; + + /* Destroy this transport. */ + pjsip_transport_dec_ref(udp_tp); + + /* Force destroy this transport. */ + status = pjsip_transport_destroy(udp_tp); + if (status != PJ_SUCCESS) + return -90; + + /* Flush events. */ + PJ_LOG(3,(THIS_FILE, " Flushing events, 1 second...")); + flush_events(1000); + + /* Done */ + return 0; +} diff --git a/pjsip/src/test/tsx_basic_test.c b/pjsip/src/test/tsx_basic_test.c new file mode 100644 index 0000000..20b93a0 --- /dev/null +++ b/pjsip/src/test/tsx_basic_test.c @@ -0,0 +1,157 @@ +/* $Id: tsx_basic_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "tsx_basic_test.c" + +static char TARGET_URI[PJSIP_MAX_URL_SIZE]; +static char FROM_URI[PJSIP_MAX_URL_SIZE]; + + +/* Test transaction layer. */ +static int tsx_layer_test(void) +{ + pj_str_t target, from, tsx_key; + pjsip_tx_data *tdata; + pjsip_transaction *tsx, *found; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " transaction layer test")); + + target = pj_str(TARGET_URI); + from = pj_str(FROM_URI); + + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target, + &from, &target, NULL, NULL, -1, NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -110; + } + + status = pjsip_tsx_create_uac(NULL, tdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + return -120; + } + + pj_strdup(tdata->pool, &tsx_key, &tsx->transaction_key); + + found = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE); + if (found != tsx) { + return -130; + } + + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + flush_events(500); + + if (pjsip_tx_data_dec_ref(tdata) != PJSIP_EBUFDESTROYED) { + return -140; + } + + return 0; +} + +/* Double terminate test. */ +static int double_terminate(void) +{ + pj_str_t target, from, tsx_key; + pjsip_tx_data *tdata; + pjsip_transaction *tsx; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " double terminate test")); + + target = pj_str(TARGET_URI); + from = pj_str(FROM_URI); + + /* Create request. */ + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target, + &from, &target, NULL, NULL, -1, NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -10; + } + + /* Create transaction. */ + status = pjsip_tsx_create_uac(NULL, tdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + return -20; + } + + /* Save transaction key for later. */ + pj_strdup_with_null(tdata->pool, &tsx_key, &tsx->transaction_key); + + /* Add reference to transmit buffer (tsx_send_msg() will dec txdata). */ + pjsip_tx_data_add_ref(tdata); + + /* Send message to start timeout timer. */ + status = pjsip_tsx_send_msg(tsx, NULL); + + /* Terminate transaction. */ + status = pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to terminate transaction", status); + return -30; + } + + tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); + if (tsx) { + /* Terminate transaction again. */ + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to terminate transaction", status); + return -40; + } + pj_mutex_unlock(tsx->mutex); + } + + flush_events(500); + if (pjsip_tx_data_dec_ref(tdata) != PJSIP_EBUFDESTROYED) { + return -50; + } + + return PJ_SUCCESS; +} + +int tsx_basic_test(struct tsx_test_param *param) +{ + int status; + + pj_ansi_sprintf(TARGET_URI, "sip:bob@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + + status = tsx_layer_test(); + if (status != 0) + return status; + + status = double_terminate(); + if (status != 0) + return status; + + return 0; +} diff --git a/pjsip/src/test/tsx_bench.c b/pjsip/src/test/tsx_bench.c new file mode 100644 index 0000000..834f444 --- /dev/null +++ b/pjsip/src/test/tsx_bench.c @@ -0,0 +1,280 @@ +/* $Id: tsx_bench.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "tsx_uas_test.c" + + +static pjsip_module mod_tsx_user; + +static int uac_tsx_bench(unsigned working_set, pj_timestamp *p_elapsed) +{ + unsigned i; + pjsip_tx_data *request; + pjsip_transaction **tsx; + pj_timestamp t1, t2, elapsed; + pjsip_via_hdr *via; + pj_status_t status; + + /* Create the request first. */ + pj_str_t str_target = pj_str("sip:someuser@someprovider.com"); + pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>"); + pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>"); + pj_str_t str_contact = str_from; + + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, + &str_target, &str_from, &str_to, + &str_contact, NULL, -1, NULL, + &request); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return status; + } + + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_VIA, + NULL); + + /* Create transaction array */ + tsx = (pjsip_transaction**) pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*)); + + pj_bzero(&mod_tsx_user, sizeof(mod_tsx_user)); + mod_tsx_user.id = -1; + + /* Benchmark */ + elapsed.u64 = 0; + pj_get_timestamp(&t1); + for (i=0; i<working_set; ++i) { + status = pjsip_tsx_create_uac(&mod_tsx_user, request, &tsx[i]); + if (status != PJ_SUCCESS) + goto on_error; + /* Reset branch param */ + via->branch_param.slen = 0; + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&elapsed, &t2); + + p_elapsed->u64 = elapsed.u64; + status = PJ_SUCCESS; + +on_error: + for (i=0; i<working_set; ++i) { + if (tsx[i]) { + pjsip_tsx_terminate(tsx[i], 601); + tsx[i] = NULL; + } + } + pjsip_tx_data_dec_ref(request); + flush_events(2000); + return status; +} + + + +static int uas_tsx_bench(unsigned working_set, pj_timestamp *p_elapsed) +{ + unsigned i; + pjsip_tx_data *request; + pjsip_via_hdr *via; + pjsip_rx_data rdata; + pj_sockaddr_in remote; + pjsip_transaction **tsx; + pj_timestamp t1, t2, elapsed; + char branch_buf[80] = PJSIP_RFC3261_BRANCH_ID "0000000000"; + pj_status_t status; + + /* Create the request first. */ + pj_str_t str_target = pj_str("sip:someuser@someprovider.com"); + pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>"); + pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>"); + pj_str_t str_contact = str_from; + + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, + &str_target, &str_from, &str_to, + &str_contact, NULL, -1, NULL, + &request); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return status; + } + + /* Create Via */ + via = pjsip_via_hdr_create(request->pool); + via->sent_by.host = pj_str("192.168.0.7"); + via->sent_by.port = 5061; + via->transport = pj_str("udp"); + via->rport_param = 1; + via->recvd_param = pj_str("192.168.0.7"); + pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*)via); + + + /* Create "dummy" rdata from the tdata */ + pj_bzero(&rdata, sizeof(pjsip_rx_data)); + rdata.tp_info.pool = request->pool; + rdata.msg_info.msg = request->msg; + rdata.msg_info.from = (pjsip_from_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL); + rdata.msg_info.to = (pjsip_to_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL); + rdata.msg_info.cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL); + rdata.msg_info.cid = (pjsip_cid_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL); + rdata.msg_info.via = via; + + pj_sockaddr_in_init(&remote, 0, 0); + status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM, + &remote, sizeof(pj_sockaddr_in), + NULL, &rdata.tp_info.transport); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to get loop transport", status); + return status; + } + + + /* Create transaction array */ + tsx = (pjsip_transaction**) pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*)); + + pj_bzero(&mod_tsx_user, sizeof(mod_tsx_user)); + mod_tsx_user.id = -1; + + + /* Benchmark */ + elapsed.u64 = 0; + pj_get_timestamp(&t1); + for (i=0; i<working_set; ++i) { + via->branch_param.ptr = branch_buf; + via->branch_param.slen = PJSIP_RFC3261_BRANCH_LEN + + pj_ansi_sprintf(branch_buf+PJSIP_RFC3261_BRANCH_LEN, + "-%d", i); + status = pjsip_tsx_create_uas(&mod_tsx_user, &rdata, &tsx[i]); + if (status != PJ_SUCCESS) + goto on_error; + + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&elapsed, &t2); + + p_elapsed->u64 = elapsed.u64; + status = PJ_SUCCESS; + +on_error: + for (i=0; i<working_set; ++i) { + if (tsx[i]) { + pjsip_tsx_terminate(tsx[i], 601); + tsx[i] = NULL; + } + } + pjsip_tx_data_dec_ref(request); + flush_events(2000); + return status; +} + + + +int tsx_bench(void) +{ + enum { WORKING_SET=10000, REPEAT = 4 }; + unsigned i, speed; + pj_timestamp usec[REPEAT], min, freq; + char desc[250]; + int status; + + status = pj_get_timestamp_freq(&freq); + if (status != PJ_SUCCESS) + return status; + + + /* + * Benchmark UAC + */ + PJ_LOG(3,(THIS_FILE, " benchmarking UAC transaction creation:")); + for (i=0; i<REPEAT; ++i) { + PJ_LOG(3,(THIS_FILE, " test %d of %d..", + i+1, REPEAT)); + status = uac_tsx_bench(WORKING_SET, &usec[i]); + if (status != PJ_SUCCESS) + return status; + } + + min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF); + for (i=0; i<REPEAT; ++i) { + if (usec[i].u64 < min.u64) min.u64 = usec[i].u64; + } + + + /* Report time */ + pj_ansi_sprintf(desc, "Time to create %d UAC transactions, in miliseconds", + WORKING_SET); + report_ival("create-uac-time", (unsigned)(min.u64 * 1000 / freq.u64), "msec", desc); + + + /* Write speed */ + speed = (unsigned)(freq.u64 * WORKING_SET / min.u64); + PJ_LOG(3,(THIS_FILE, " UAC created at %d tsx/sec", speed)); + + pj_ansi_sprintf(desc, "Number of UAC transactions that potentially can be created per second " + "with <tt>pjsip_tsx_create_uac()</tt>, based on the time " + "to create %d simultaneous transactions above.", + WORKING_SET); + + report_ival("create-uac-tsx-per-sec", + speed, "tsx/sec", desc); + + + + /* + * Benchmark UAS + */ + PJ_LOG(3,(THIS_FILE, " benchmarking UAS transaction creation:")); + for (i=0; i<REPEAT; ++i) { + PJ_LOG(3,(THIS_FILE, " test %d of %d..", + i+1, REPEAT)); + status = uas_tsx_bench(WORKING_SET, &usec[i]); + if (status != PJ_SUCCESS) + return status; + } + + min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF); + for (i=0; i<REPEAT; ++i) { + if (usec[i].u64 < min.u64) min.u64 = usec[i].u64; + } + + + /* Report time */ + pj_ansi_sprintf(desc, "Time to create %d UAS transactions, in miliseconds", + WORKING_SET); + report_ival("create-uas-time", (unsigned)(min.u64 * 1000 / freq.u64), "msec", desc); + + + /* Write speed */ + speed = (unsigned)(freq.u64 * WORKING_SET / min.u64); + PJ_LOG(3,(THIS_FILE, " UAS created at %d tsx/sec", speed)); + + pj_ansi_sprintf(desc, "Number of UAS transactions that potentially can be created per second " + "with <tt>pjsip_tsx_create_uas()</tt>, based on the time " + "to create %d simultaneous transactions above.", + WORKING_SET); + + report_ival("create-uas-tsx-per-sec", + speed, "tsx/sec", desc); + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/test/tsx_uac_test.c b/pjsip/src/test/tsx_uac_test.c new file mode 100644 index 0000000..c8f3074 --- /dev/null +++ b/pjsip/src/test/tsx_uac_test.c @@ -0,0 +1,1456 @@ +/* $Id: tsx_uac_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "tsx_uac_test.c" + + +/***************************************************************************** + ** + ** UAC tests. + ** + ** This file performs various tests for UAC transactions. Each test will have + ** a different Via branch param so that message receiver module and + ** transaction user module can identify which test is being carried out. + ** + ** TEST1_BRANCH_ID + ** Perform basic retransmission and timeout test. Message receiver will + ** verify that retransmission is received at correct time. + ** This test verifies the following requirements: + ** - retransmit timer doubles for INVITE + ** - retransmit timer doubles and caps off for non-INVITE + ** - retransmit timer timer is precise + ** - correct timeout and retransmission count + ** Requirements not tested: + ** - retransmit timer only starts after resolving has completed. + ** + ** TEST2_BRANCH_ID + ** Test scenario where resolver is unable to resolve destination host. + ** + ** TEST3_BRANCH_ID + ** Test scenario where transaction is terminated while resolver is still + ** running. + ** + ** TEST4_BRANCH_ID + ** Test scenario where transport failed after several retransmissions. + ** + ** TEST5_BRANCH_ID + ** Test scenario where transaction is terminated by user after several + ** retransmissions. + ** + ** TEST6_BRANCH_ID + ** Test successfull non-INVITE transaction. + ** It tests the following requirements: + ** - transaction correctly moves to COMPLETED state. + ** - retransmission must cease. + ** - tx_data must be maintained until state is terminated. + ** + ** TEST7_BRANCH_ID + ** Test successfull non-INVITE transaction, with provisional response. + ** + ** TEST8_BRANCH_ID + ** Test failed INVITE transaction (e.g. ACK must be received) + ** + ** TEST9_BRANCH_ID + ** Test failed INVITE transaction with provisional response. + ** + ** + ***************************************************************************** + */ + +static char *TEST1_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test1"; +static char *TEST2_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test2"; +static char *TEST3_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test3"; +static char *TEST4_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test4"; +static char *TEST5_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test5"; +static char *TEST6_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test6"; +static char *TEST7_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test7"; +static char *TEST8_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test8"; +static char *TEST9_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test9"; + +#define TEST1_ALLOWED_DIFF (150) +#define TEST4_RETRANSMIT_CNT 3 +#define TEST5_RETRANSMIT_CNT 3 + +static char TARGET_URI[128]; +static char FROM_URI[128]; +static unsigned tp_flag; +static struct tsx_test_param *test_param; + +static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e); +static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata); + +/* UAC transaction user module. */ +static pjsip_module tsx_user = +{ + NULL, NULL, /* prev and next */ + { "Tsx-UAC-User", 12}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* 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() */ + &tsx_user_on_tsx_state, /* on_tsx_state() */ +}; + +/* Module to receive the loop-backed request. */ +static pjsip_module msg_receiver = +{ + NULL, NULL, /* prev and next */ + { "Msg-Receiver", 12}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &msg_receiver_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request() */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +/* Static vars, which will be reset on each test. */ +static int recv_count; +static pj_time_val recv_last; +static pj_bool_t test_complete; + +/* Loop transport instance. */ +static pjsip_transport *loop; + +/* General timer entry to be used by tests. */ +static struct my_timer +{ + pj_timer_entry entry; + char key_buf[1024]; + pj_str_t tsx_key; +} timer; + +/* + * This is the handler to receive state changed notification from the + * transaction. It is used to verify that the transaction behaves according + * to the test scenario. + */ +static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) +{ + if (pj_strcmp2(&tsx->branch, TEST1_BRANCH_ID)==0) { + /* + * Transaction with TEST1_BRANCH_ID should terminate with transaction + * timeout status. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + if (test_complete == 0) + test_complete = 1; + + /* Test the status code. */ + if (tsx->status_code != PJSIP_SC_TSX_TIMEOUT) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, PJSIP_SC_TSX_TIMEOUT)); + test_complete = -710; + } + + + /* If transport is reliable, then there must not be any + * retransmissions. + */ + if (tp_flag & PJSIP_TRANSPORT_RELIABLE) { + if (recv_count != 1) { + PJ_LOG(3,(THIS_FILE, + " error: there were %d (re)transmissions", + recv_count)); + test_complete = -715; + } + } else { + /* Check the number of transmissions, which must be + * 6 for INVITE and 10 for non-INVITE + */ + if (tsx->method.id==PJSIP_INVITE_METHOD && recv_count != 7) { + PJ_LOG(3,(THIS_FILE, + " error: there were %d (re)transmissions", + recv_count)); + test_complete = -716; + } else + if (tsx->method.id==PJSIP_OPTIONS_METHOD && recv_count != 11) { + PJ_LOG(3,(THIS_FILE, + " error: there were %d (re)transmissions", + recv_count)); + test_complete = -717; + } else + if (tsx->method.id!=PJSIP_INVITE_METHOD && + tsx->method.id!=PJSIP_OPTIONS_METHOD) + { + PJ_LOG(3,(THIS_FILE, " error: unexpected method")); + test_complete = -718; + } + } + } + + } else if (pj_strcmp2(&tsx->branch, TEST2_BRANCH_ID)==0) { + /* + * Transaction with TEST2_BRANCH_ID should terminate with transport error. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Test the status code. */ + if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, PJSIP_SC_TSX_TRANSPORT_ERROR)); + test_complete = -720; + } + + if (test_complete == 0) + test_complete = 1; + } + + } else if (pj_strcmp2(&tsx->branch, TEST3_BRANCH_ID)==0) { + /* + * This test terminates the transaction while resolver is still + * running. + */ + if (tsx->state == PJSIP_TSX_STATE_CALLING) { + + /* Terminate the transaction. */ + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Check if status code is correct. */ + if (tsx->status_code != PJSIP_SC_REQUEST_TERMINATED) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, PJSIP_SC_REQUEST_TERMINATED)); + test_complete = -730; + } + + if (test_complete == 0) + test_complete = 1; + + } + + } else if (pj_strcmp2(&tsx->branch, TEST4_BRANCH_ID)==0) { + /* + * This test simulates transport failure after several + * retransmissions. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Status code must be transport error. */ + if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, PJSIP_SC_TSX_TRANSPORT_ERROR)); + test_complete = -730; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != TEST4_RETRANSMIT_CNT) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, TEST4_RETRANSMIT_CNT)); + test_complete = -731; + } + + if (test_complete == 0) + test_complete = 1; + } + + + } else if (pj_strcmp2(&tsx->branch, TEST5_BRANCH_ID)==0) { + /* + * This test simulates transport failure after several + * retransmissions. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Status code must be PJSIP_SC_REQUEST_TERMINATED. */ + if (tsx->status_code != PJSIP_SC_REQUEST_TERMINATED) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, PJSIP_SC_REQUEST_TERMINATED)); + test_complete = -733; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != TEST5_RETRANSMIT_CNT) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, TEST5_RETRANSMIT_CNT)); + test_complete = -734; + } + + if (test_complete == 0) + test_complete = 1; + } + + + } else if (pj_strcmp2(&tsx->branch, TEST6_BRANCH_ID)==0) { + /* + * Successfull non-INVITE transaction. + */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Status code must be 202. */ + if (tsx->status_code != 202) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 202)); + test_complete = -736; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != 0) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, 0)); + test_complete = -737; + } + + /* Must still keep last_tx */ + if (tsx->last_tx == NULL) { + PJ_LOG(3,(THIS_FILE, + " error: transaction lost last_tx")); + test_complete = -738; + } + + if (test_complete == 0) { + test_complete = 1; + pjsip_tsx_terminate(tsx, 202); + } + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Previous state must be COMPLETED. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + test_complete = -7381; + } + + } + + } else if (pj_strcmp2(&tsx->branch, TEST7_BRANCH_ID)==0) { + /* + * Successfull non-INVITE transaction. + */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Check prev state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) { + PJ_LOG(3,(THIS_FILE, + " error: prev state is %s instead of %s", + pjsip_tsx_state_str((pjsip_tsx_state_e)e->body.tsx_state.prev_state), + pjsip_tsx_state_str(PJSIP_TSX_STATE_PROCEEDING))); + test_complete = -739; + } + + /* Status code must be 202. */ + if (tsx->status_code != 202) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 202)); + test_complete = -740; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != 0) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, 0)); + test_complete = -741; + } + + /* Must still keep last_tx */ + if (tsx->last_tx == NULL) { + PJ_LOG(3,(THIS_FILE, + " error: transaction lost last_tx")); + test_complete = -741; + } + + if (test_complete == 0) { + test_complete = 1; + pjsip_tsx_terminate(tsx, 202); + } + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Previous state must be COMPLETED. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + test_complete = -742; + } + + } + + + } else if (pj_strcmp2(&tsx->branch, TEST8_BRANCH_ID)==0) { + /* + * Failed INVITE transaction. + */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Status code must be 301. */ + if (tsx->status_code != 301) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 301)); + test_complete = -745; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != 0) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, 0)); + test_complete = -746; + } + + /* Must still keep last_tx */ + if (tsx->last_tx == NULL) { + PJ_LOG(3,(THIS_FILE, + " error: transaction lost last_tx")); + test_complete = -747; + } + + /* last_tx MUST be the INVITE request + * (authorization depends on this behavior) + */ + if (tsx->last_tx && tsx->last_tx->msg->line.req.method.id != + PJSIP_INVITE_METHOD) + { + PJ_LOG(3,(THIS_FILE, + " error: last_tx is not INVITE")); + test_complete = -748; + } + } + else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + test_complete = 1; + + /* Previous state must be COMPLETED. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + test_complete = -750; + } + + /* Status code must be 301. */ + if (tsx->status_code != 301) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 301)); + test_complete = -751; + } + + } + + + } else if (pj_strcmp2(&tsx->branch, TEST9_BRANCH_ID)==0) { + /* + * Failed INVITE transaction with provisional response. + */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Previous state must be PJSIP_TSX_STATE_PROCEEDING. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) { + test_complete = -760; + } + + /* Status code must be 302. */ + if (tsx->status_code != 302) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 302)); + test_complete = -761; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != 0) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, 0)); + test_complete = -762; + } + + /* Must still keep last_tx */ + if (tsx->last_tx == NULL) { + PJ_LOG(3,(THIS_FILE, + " error: transaction lost last_tx")); + test_complete = -763; + } + + /* last_tx MUST be INVITE. + * (authorization depends on this behavior) + */ + if (tsx->last_tx && tsx->last_tx->msg->line.req.method.id != + PJSIP_INVITE_METHOD) + { + PJ_LOG(3,(THIS_FILE, + " error: last_tx is not INVITE")); + test_complete = -764; + } + + } + else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + test_complete = 1; + + /* Previous state must be COMPLETED. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + test_complete = -767; + } + + /* Status code must be 302. */ + if (tsx->status_code != 302) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 302)); + test_complete = -768; + } + + } + + } +} + +/* + * This timer callback is called to send delayed response. + */ +struct response +{ + pjsip_response_addr res_addr; + pjsip_tx_data *tdata; +}; + +static void send_response_callback( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + struct response *r = (struct response*) entry->user_data; + pjsip_transport *tp = r->res_addr.transport; + + PJ_UNUSED_ARG(timer_heap); + + pjsip_endpt_send_response(endpt, &r->res_addr, r->tdata, NULL, NULL); + if (tp) + pjsip_transport_dec_ref(tp); +} + +/* Timer callback to terminate a transaction. */ +static void terminate_tsx_callback( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + struct my_timer *m = (struct my_timer *)entry; + pjsip_transaction *tsx = pjsip_tsx_layer_find_tsx(&m->tsx_key, PJ_FALSE); + int status_code = entry->id; + + PJ_UNUSED_ARG(timer_heap); + + if (tsx) { + pjsip_tsx_terminate(tsx, status_code); + } +} + + +#define DIFF(a,b) ((a<b) ? (b-a) : (a-b)) + +/* + * This is the handler to receive message for this test. It is used to + * control and verify the behavior of the message transmitted by the + * transaction. + */ +static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata) +{ + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST1_BRANCH_ID) == 0) { + /* + * The TEST1_BRANCH_ID test performs the verifications for transaction + * retransmission mechanism. It will not answer the incoming request + * with any response. + */ + pjsip_msg *msg = rdata->msg_info.msg; + + PJ_LOG(4,(THIS_FILE, " received request")); + + /* Only wants to take INVITE or OPTIONS method. */ + if (msg->line.req.method.id != PJSIP_INVITE_METHOD && + msg->line.req.method.id != PJSIP_OPTIONS_METHOD) + { + PJ_LOG(3,(THIS_FILE, " error: received unexpected method %.*s", + msg->line.req.method.name.slen, + msg->line.req.method.name.ptr)); + test_complete = -600; + return PJ_TRUE; + } + + if (recv_count == 0) { + recv_count++; + //pj_gettimeofday(&recv_last); + recv_last = rdata->pkt_info.timestamp; + } else { + pj_time_val now; + unsigned msec_expected, msec_elapsed; + int max_received; + + //pj_gettimeofday(&now); + now = rdata->pkt_info.timestamp; + PJ_TIME_VAL_SUB(now, recv_last); + msec_elapsed = now.sec*1000 + now.msec; + + ++recv_count; + msec_expected = (1<<(recv_count-2))*pjsip_cfg()->tsx.t1; + + if (msg->line.req.method.id != PJSIP_INVITE_METHOD) { + if (msec_expected > pjsip_cfg()->tsx.t2) + msec_expected = pjsip_cfg()->tsx.t2; + max_received = 11; + } else { + max_received = 7; + } + + if (DIFF(msec_expected, msec_elapsed) > TEST1_ALLOWED_DIFF) { + PJ_LOG(3,(THIS_FILE, + " error: expecting retransmission no. %d in %d " + "ms, received in %d ms", + recv_count-1, msec_expected, msec_elapsed)); + test_complete = -610; + } + + + if (recv_count > max_received) { + PJ_LOG(3,(THIS_FILE, + " error: too many messages (%d) received", + recv_count)); + test_complete = -620; + } + + //pj_gettimeofday(&recv_last); + recv_last = rdata->pkt_info.timestamp; + } + return PJ_TRUE; + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST4_BRANCH_ID) == 0) { + /* + * The TEST4_BRANCH_ID test simulates transport failure after several + * retransmissions. + */ + recv_count++; + + if (recv_count == TEST4_RETRANSMIT_CNT) { + /* Simulate transport failure. */ + pjsip_loop_set_failure(loop, 2, NULL); + + } else if (recv_count > TEST4_RETRANSMIT_CNT) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -631; + } + + return PJ_TRUE; + + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST5_BRANCH_ID) == 0) { + /* + * The TEST5_BRANCH_ID test simulates user terminating the transaction + * after several retransmissions. + */ + recv_count++; + + if (recv_count == TEST5_RETRANSMIT_CNT+1) { + pj_str_t key; + pjsip_transaction *tsx; + + pjsip_tsx_create_key( rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, + &rdata->msg_info.msg->line.req.method, rdata); + tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); + if (tsx) { + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + pj_mutex_unlock(tsx->mutex); + } else { + PJ_LOG(3,(THIS_FILE, " error: uac transaction not found!")); + test_complete = -633; + } + + } else if (recv_count > TEST5_RETRANSMIT_CNT+1) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -634; + } + + return PJ_TRUE; + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST6_BRANCH_ID) == 0) { + /* + * The TEST6_BRANCH_ID test successfull non-INVITE transaction. + */ + pj_status_t status; + + recv_count++; + + if (recv_count > 1) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -635; + } + + status = pjsip_endpt_respond_stateless(endpt, rdata, 202, NULL, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to send response", status); + test_complete = -636; + } + + return PJ_TRUE; + + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST7_BRANCH_ID) == 0) { + /* + * The TEST7_BRANCH_ID test successfull non-INVITE transaction + * with provisional response. + */ + pj_status_t status; + pjsip_response_addr res_addr; + struct response *r; + pjsip_tx_data *tdata; + pj_time_val delay = { 2, 0 }; + + recv_count++; + + if (recv_count > 1) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -640; + return PJ_TRUE; + } + + /* Respond with provisional response */ + status = pjsip_endpt_create_response(endpt, rdata, 100, NULL, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_endpt_send_response(endpt, &res_addr, tdata, + NULL, NULL); + pj_assert(status == PJ_SUCCESS); + + /* Create the final response. */ + status = pjsip_endpt_create_response(endpt, rdata, 202, NULL, &tdata); + pj_assert(status == PJ_SUCCESS); + + /* Schedule sending final response in couple of of secs. */ + r = PJ_POOL_ALLOC_T(tdata->pool, struct response); + r->res_addr = res_addr; + r->tdata = tdata; + if (r->res_addr.transport) + pjsip_transport_add_ref(r->res_addr.transport); + + timer.entry.cb = &send_response_callback; + timer.entry.user_data = r; + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); + + return PJ_TRUE; + + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST8_BRANCH_ID) == 0) { + /* + * The TEST8_BRANCH_ID test failed INVITE transaction. + */ + pjsip_method *method; + pj_status_t status; + + method = &rdata->msg_info.msg->line.req.method; + + recv_count++; + + if (method->id == PJSIP_INVITE_METHOD) { + + if (recv_count > 1) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -635; + } + + status = pjsip_endpt_respond_stateless(endpt, rdata, 301, NULL, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to send response", status); + test_complete = -636; + } + + } else if (method->id == PJSIP_ACK_METHOD) { + + if (recv_count == 2) { + pj_str_t key; + pj_time_val delay = { 5, 0 }; + + /* Schedule timer to destroy transaction after 5 seconds. + * This is to make sure that transaction does not + * retransmit ACK. + */ + pjsip_tsx_create_key(rdata->tp_info.pool, &key, + PJSIP_ROLE_UAC, &pjsip_invite_method, + rdata); + + pj_strcpy(&timer.tsx_key, &key); + timer.entry.id = 301; + timer.entry.cb = &terminate_tsx_callback; + + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); + } + + if (recv_count > 2) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -638; + } + + + } else { + PJ_LOG(3,(THIS_FILE," error: not expecting %s", + pjsip_rx_data_get_info(rdata))); + test_complete = -639; + + } + + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST9_BRANCH_ID) == 0) { + /* + * The TEST9_BRANCH_ID test failed INVITE transaction with + * provisional response. + */ + pjsip_method *method; + pj_status_t status; + + method = &rdata->msg_info.msg->line.req.method; + + recv_count++; + + if (method->id == PJSIP_INVITE_METHOD) { + + pjsip_response_addr res_addr; + struct response *r; + pjsip_tx_data *tdata; + pj_time_val delay = { 2, 0 }; + + if (recv_count > 1) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -650; + return PJ_TRUE; + } + + /* Respond with provisional response */ + status = pjsip_endpt_create_response(endpt, rdata, 100, NULL, + &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_endpt_send_response(endpt, &res_addr, tdata, + NULL, NULL); + pj_assert(status == PJ_SUCCESS); + + /* Create the final response. */ + status = pjsip_endpt_create_response(endpt, rdata, 302, NULL, + &tdata); + pj_assert(status == PJ_SUCCESS); + + /* Schedule sending final response in couple of of secs. */ + r = PJ_POOL_ALLOC_T(tdata->pool, struct response); + r->res_addr = res_addr; + r->tdata = tdata; + if (r->res_addr.transport) + pjsip_transport_add_ref(r->res_addr.transport); + + timer.entry.cb = &send_response_callback; + timer.entry.user_data = r; + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); + + } else if (method->id == PJSIP_ACK_METHOD) { + + if (recv_count == 2) { + pj_str_t key; + pj_time_val delay = { 5, 0 }; + + /* Schedule timer to destroy transaction after 5 seconds. + * This is to make sure that transaction does not + * retransmit ACK. + */ + pjsip_tsx_create_key(rdata->tp_info.pool, &key, + PJSIP_ROLE_UAC, &pjsip_invite_method, + rdata); + + pj_strcpy(&timer.tsx_key, &key); + timer.entry.id = 302; + timer.entry.cb = &terminate_tsx_callback; + + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); + } + + if (recv_count > 2) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -638; + } + + + } else { + PJ_LOG(3,(THIS_FILE," error: not expecting %s", + pjsip_rx_data_get_info(rdata))); + test_complete = -639; + + } + + return PJ_TRUE; + + } + + return PJ_FALSE; +} + +/* + * The generic test framework, used by most of the tests. + */ +static int perform_tsx_test(int dummy, char *target_uri, char *from_uri, + char *branch_param, int test_time, + const pjsip_method *method) +{ + pjsip_tx_data *tdata; + pjsip_transaction *tsx; + pj_str_t target, from, tsx_key; + pjsip_via_hdr *via; + pj_time_val timeout; + pj_status_t status; + + PJ_UNUSED_ARG(dummy); + + PJ_LOG(3,(THIS_FILE, + " please standby, this will take at most %d seconds..", + test_time)); + + /* Reset test. */ + recv_count = 0; + test_complete = 0; + + /* Init headers. */ + target = pj_str(target_uri); + from = pj_str(from_uri); + + /* Create request. */ + status = pjsip_endpt_create_request( endpt, method, &target, + &from, &target, NULL, NULL, -1, + NULL, &tdata); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to create request", status); + return -100; + } + + /* Set the branch param for test 1. */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->branch_param = pj_str(branch_param); + + /* Add additional reference to tdata to prevent transaction from + * deleting it. + */ + pjsip_tx_data_add_ref(tdata); + + /* Create transaction. */ + status = pjsip_tsx_create_uac( &tsx_user, tdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to create UAC transaction", status); + pjsip_tx_data_dec_ref(tdata); + return -110; + } + + /* Get transaction key. */ + pj_strdup(tdata->pool, &tsx_key, &tsx->transaction_key); + + /* Send the message. */ + status = pjsip_tsx_send_msg(tsx, NULL); + // Ignore send result. Some tests do deliberately triggers error + // when sending message. + if (status != PJ_SUCCESS) { + // app_perror(" Error: unable to send request", status); + pjsip_tx_data_dec_ref(tdata); + // return -120; + } + + + /* Set test completion time. */ + pj_gettimeofday(&timeout); + timeout.sec += test_time; + + /* Wait until test complete. */ + while (!test_complete) { + pj_time_val now, poll_delay = {0, 10}; + + pjsip_endpt_handle_events(endpt, &poll_delay); + + pj_gettimeofday(&now); + if (now.sec > timeout.sec) { + PJ_LOG(3,(THIS_FILE, " Error: test has timed out")); + pjsip_tx_data_dec_ref(tdata); + return -130; + } + } + + if (test_complete < 0) { + tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); + if (tsx) { + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + pj_mutex_unlock(tsx->mutex); + flush_events(1000); + } + pjsip_tx_data_dec_ref(tdata); + return test_complete; + + } else { + pj_time_val now; + + /* Allow transaction to destroy itself */ + flush_events(500); + + /* Wait until test completes */ + pj_gettimeofday(&now); + + if (PJ_TIME_VAL_LT(now, timeout)) { + pj_time_val interval; + interval = timeout; + PJ_TIME_VAL_SUB(interval, now); + flush_events(PJ_TIME_VAL_MSEC(interval)); + } + } + + /* Make sure transaction has been destroyed. */ + if (pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE) != NULL) { + PJ_LOG(3,(THIS_FILE, " Error: transaction has not been destroyed")); + pjsip_tx_data_dec_ref(tdata); + return -140; + } + + /* Check tdata reference counter. */ + if (pj_atomic_get(tdata->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " Error: tdata reference counter is %d", + pj_atomic_get(tdata->ref_cnt))); + pjsip_tx_data_dec_ref(tdata); + return -150; + } + + /* Destroy txdata */ + pjsip_tx_data_dec_ref(tdata); + + return PJ_SUCCESS; +} + +/***************************************************************************** + ** + ** TEST1_BRANCH_ID: UAC basic retransmission and timeout test. + ** + ** This will test the retransmission of the UAC transaction. Remote will not + ** answer the transaction, so the transaction should fail. The Via branch prm + ** TEST1_BRANCH_ID will be used for this test. + ** + ***************************************************************************** + */ +static int tsx_uac_retransmit_test(void) +{ + int status = 0, enabled; + int i; + struct { + const pjsip_method *method; + unsigned delay; + } sub_test[] = + { + { &pjsip_invite_method, 0}, + { &pjsip_invite_method, TEST1_ALLOWED_DIFF*2}, + { &pjsip_options_method, 0}, + { &pjsip_options_method, TEST1_ALLOWED_DIFF*2} + }; + + PJ_LOG(3,(THIS_FILE, " test1: basic uac retransmit and timeout test")); + + + /* For this test. message printing shound be disabled because it makes + * incorrect timing. + */ + enabled = msg_logger_set_enabled(0); + + for (i=0; i<(int)PJ_ARRAY_SIZE(sub_test); ++i) { + + PJ_LOG(3,(THIS_FILE, + " variant %c: %s with %d ms network delay", + ('a' + i), + sub_test[i].method->name.ptr, + sub_test[i].delay)); + + /* Configure transport */ + pjsip_loop_set_failure(loop, 0, NULL); + pjsip_loop_set_recv_delay(loop, sub_test[i].delay, NULL); + + /* Do the test. */ + status = perform_tsx_test(-500, TARGET_URI, FROM_URI, + TEST1_BRANCH_ID, + 35, sub_test[i].method); + if (status != 0) + break; + } + + /* Restore transport. */ + pjsip_loop_set_recv_delay(loop, 0, NULL); + + /* Restore msg logger. */ + msg_logger_set_enabled(enabled); + + /* Done. */ + return status; +} + +/***************************************************************************** + ** + ** TEST2_BRANCH_ID: UAC resolve error test. + ** + ** Test the scenario where destination host is unresolvable. There are + ** two variants: + ** (a) resolver returns immediate error + ** (b) resolver returns error via the callback. + ** + ***************************************************************************** + */ +static int tsx_resolve_error_test(void) +{ + int status = 0; + + PJ_LOG(3,(THIS_FILE, " test2: resolve error test")); + + /* + * Variant (a): immediate resolve error. + */ + PJ_LOG(3,(THIS_FILE, " variant a: immediate resolving error")); + + status = perform_tsx_test(-800, + "sip:bob@unresolved-host", + FROM_URI, TEST2_BRANCH_ID, 20, + &pjsip_options_method); + if (status != 0) + return status; + + /* + * Variant (b): error via callback. + */ + PJ_LOG(3,(THIS_FILE, " variant b: error via callback")); + + /* This only applies to "loop-dgram" transport */ + if (test_param->type == PJSIP_TRANSPORT_LOOP_DGRAM) { + /* Set loop transport to return delayed error. */ + pjsip_loop_set_failure(loop, 2, NULL); + pjsip_loop_set_send_callback_delay(loop, 10, NULL); + + status = perform_tsx_test(-800, TARGET_URI, FROM_URI, + TEST2_BRANCH_ID, 2, + &pjsip_options_method); + if (status != 0) + return status; + + /* Restore loop transport settings. */ + pjsip_loop_set_failure(loop, 0, NULL); + pjsip_loop_set_send_callback_delay(loop, 0, NULL); + } + + return status; +} + + +/***************************************************************************** + ** + ** TEST3_BRANCH_ID: UAC terminate while resolving test. + ** + ** Terminate the transaction while resolver is still running. + ** + ***************************************************************************** + */ +static int tsx_terminate_resolving_test(void) +{ + unsigned prev_delay; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " test3: terminate while resolving test")); + + /* Configure transport delay. */ + pjsip_loop_set_send_callback_delay(loop, 100, &prev_delay); + + /* Start the test. */ + status = perform_tsx_test(-900, TARGET_URI, FROM_URI, + TEST3_BRANCH_ID, 2, &pjsip_options_method); + + /* Restore delay. */ + pjsip_loop_set_send_callback_delay(loop, prev_delay, NULL); + + return status; +} + + +/***************************************************************************** + ** + ** TEST4_BRANCH_ID: Transport failed after several retransmissions + ** + ** There are two variants of this test: (a) failure occurs immediately when + ** transaction calls pjsip_transport_send() or (b) failure is reported via + ** transport callback. + ** + ***************************************************************************** + */ +static int tsx_retransmit_fail_test(void) +{ + int i; + unsigned delay[] = {0, 10}; + pj_status_t status = PJ_SUCCESS; + + PJ_LOG(3,(THIS_FILE, + " test4: transport fails after several retransmissions test")); + + + for (i=0; i<(int)PJ_ARRAY_SIZE(delay); ++i) { + + PJ_LOG(3,(THIS_FILE, + " variant %c: transport delay %d ms", ('a'+i), delay[i])); + + /* Configure transport delay. */ + pjsip_loop_set_send_callback_delay(loop, delay[i], NULL); + + /* Restore transport failure mode. */ + pjsip_loop_set_failure(loop, 0, 0); + + /* Start the test. */ + status = perform_tsx_test(-1000, TARGET_URI, FROM_URI, + TEST4_BRANCH_ID, 6, &pjsip_options_method); + + if (status != 0) + break; + + } + + /* Restore delay. */ + pjsip_loop_set_send_callback_delay(loop, 0, NULL); + + /* Restore transport failure mode. */ + pjsip_loop_set_failure(loop, 0, 0); + + return status; +} + + +/***************************************************************************** + ** + ** TEST5_BRANCH_ID: Terminate transaction after several retransmissions + ** + ***************************************************************************** + */ +static int tsx_terminate_after_retransmit_test(void) +{ + int status; + + PJ_LOG(3,(THIS_FILE, " test5: terminate after retransmissions")); + + /* Do the test. */ + status = perform_tsx_test(-1100, TARGET_URI, FROM_URI, + TEST5_BRANCH_ID, + 6, &pjsip_options_method); + + /* Done. */ + return status; +} + + +/***************************************************************************** + ** + ** TEST6_BRANCH_ID: Successfull non-invite transaction + ** TEST7_BRANCH_ID: Successfull non-invite transaction with provisional + ** TEST8_BRANCH_ID: Failed invite transaction + ** TEST9_BRANCH_ID: Failed invite transaction with provisional + ** + ***************************************************************************** + */ +static int perform_generic_test( const char *title, + char *branch_id, + const pjsip_method *method) +{ + int i, status = 0; + unsigned delay[] = { 1, 200 }; + + PJ_LOG(3,(THIS_FILE, " %s", title)); + + /* Do the test. */ + for (i=0; i<(int)PJ_ARRAY_SIZE(delay); ++i) { + + if (test_param->type == PJSIP_TRANSPORT_LOOP_DGRAM) { + PJ_LOG(3,(THIS_FILE, " variant %c: with %d ms transport delay", + ('a'+i), delay[i])); + + pjsip_loop_set_delay(loop, delay[i]); + } + + status = perform_tsx_test(-1200, TARGET_URI, FROM_URI, + branch_id, 10, method); + if (status != 0) + return status; + + if (test_param->type != PJSIP_TRANSPORT_LOOP_DGRAM) + break; + } + + pjsip_loop_set_delay(loop, 0); + + /* Done. */ + return status; +} + + +/***************************************************************************** + ** + ** UAC Transaction Test. + ** + ***************************************************************************** + */ +int tsx_uac_test(struct tsx_test_param *param) +{ + pj_sockaddr_in addr; + pj_status_t status; + + timer.tsx_key.ptr = timer.key_buf; + + test_param = param; + + /* Get transport flag */ + tp_flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)test_param->type); + + pj_ansi_sprintf(TARGET_URI, "sip:bob@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + + /* Check if loop transport is configured. */ + status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM, + &addr, sizeof(addr), NULL, &loop); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " Error: loop transport is not configured!")); + return -10; + } + + /* Register modules. */ + status = pjsip_endpt_register_module(endpt, &tsx_user); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to register module", status); + return -30; + } + status = pjsip_endpt_register_module(endpt, &msg_receiver); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to register module", status); + return -40; + } + + /* TEST1_BRANCH_ID: Basic retransmit and timeout test. */ + status = tsx_uac_retransmit_test(); + if (status != 0) + return status; + + /* TEST2_BRANCH_ID: Resolve error test. */ + status = tsx_resolve_error_test(); + if (status != 0) + return status; + + /* TEST3_BRANCH_ID: UAC terminate while resolving test. */ + status = tsx_terminate_resolving_test(); + if (status != 0) + return status; + + /* TEST4_BRANCH_ID: Transport failed after several retransmissions. + * Only applies to loop transport. + */ + if (test_param->type == PJSIP_TRANSPORT_LOOP_DGRAM) { + status = tsx_retransmit_fail_test(); + if (status != 0) + return status; + } + + /* TEST5_BRANCH_ID: Terminate transaction after several retransmissions + * Only applicable to non-reliable transports. + */ + if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) { + status = tsx_terminate_after_retransmit_test(); + if (status != 0) + return status; + } + + /* TEST6_BRANCH_ID: Successfull non-invite transaction */ + status = perform_generic_test("test6: successfull non-invite transaction", + TEST6_BRANCH_ID, &pjsip_options_method); + if (status != 0) + return status; + + /* TEST7_BRANCH_ID: Successfull non-invite transaction */ + status = perform_generic_test("test7: successfull non-invite transaction " + "with provisional response", + TEST7_BRANCH_ID, &pjsip_options_method); + if (status != 0) + return status; + + /* TEST8_BRANCH_ID: Failed invite transaction */ + status = perform_generic_test("test8: failed invite transaction", + TEST8_BRANCH_ID, &pjsip_invite_method); + if (status != 0) + return status; + + /* TEST9_BRANCH_ID: Failed invite transaction with provisional response */ + status = perform_generic_test("test9: failed invite transaction with " + "provisional response", + TEST9_BRANCH_ID, &pjsip_invite_method); + if (status != 0) + return status; + + pjsip_transport_dec_ref(loop); + flush_events(500); + + /* Unregister modules. */ + status = pjsip_endpt_unregister_module(endpt, &tsx_user); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to unregister module", status); + return -31; + } + status = pjsip_endpt_unregister_module(endpt, &msg_receiver); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to unregister module", status); + return -41; + } + + return 0; +} + diff --git a/pjsip/src/test/tsx_uas_test.c b/pjsip/src/test/tsx_uas_test.c new file mode 100644 index 0000000..045e958 --- /dev/null +++ b/pjsip/src/test/tsx_uas_test.c @@ -0,0 +1,1658 @@ +/* $Id: tsx_uas_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "tsx_uas_test.c" + + +/***************************************************************************** + ** + ** UAS tests. + ** + ** This file performs various tests for UAC transactions. Each test will have + ** a different Via branch param so that message receiver module and + ** transaction user module can identify which test is being carried out. + ** + ** TEST1_BRANCH_ID + ** Test that non-INVITE transaction returns 2xx response to the correct + ** transport and correctly terminates the transaction. + ** This also checks that transaction is destroyed immediately after + ** it sends final response when reliable transport is used. + ** + ** TEST2_BRANCH_ID + ** As above, for non-2xx final response. + ** + ** TEST3_BRANCH_ID + ** Transaction correctly progressing to PROCEEDING state when provisional + ** response is sent. + ** + ** TEST4_BRANCH_ID + ** Transaction retransmits last response (if any) without notifying + ** transaction user upon receiving request retransmissions on TRYING + ** state + ** + ** TEST5_BRANCH_ID + ** As above, in PROCEEDING state. + ** + ** TEST6_BRANCH_ID + ** As above, in COMPLETED state, with first sending provisional response. + ** (Only applicable for non-reliable transports). + ** + ** TEST7_BRANCH_ID + ** INVITE transaction MUST retransmit non-2xx final response. + ** + ** TEST8_BRANCH_ID + ** As above, for INVITE's 2xx final response (this is PJSIP specific). + ** + ** TEST9_BRANCH_ID + ** INVITE transaction MUST cease retransmission of final response when + ** ACK is received. (Note: PJSIP also retransmit 2xx final response + ** until it's terminated by user). + ** Transaction also MUST terminate in T4 seconds. + ** (Only applicable for non-reliable transports). + ** + ** TEST11_BRANCH_ID + ** Test scenario where transport fails before response is sent (i.e. + ** in TRYING state). + ** + ** TEST12_BRANCH_ID + ** As above, after provisional response is sent but before final + ** response is sent (i.e. in PROCEEDING state). + ** + ** TEST13_BRANCH_ID + ** As above, for INVITE, after final response has been sent but before + ** ACK is received (i.e. in CONNECTED state). + ** + ** TEST14_BRANCH_ID + ** When UAS failed to deliver the response with the selected transport, + ** it should try contacting the client with other transport or begin + ** RFC 3263 server resolution procedure. + ** This should be tested on: + ** a. TRYING state (when delivering first response). + ** b. PROCEEDING state (when failed to retransmit last response + ** upon receiving request retransmission). + ** c. COMPLETED state. + ** + **/ + +#define TEST1_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test1") +#define TEST2_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test2") +#define TEST3_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test3") +#define TEST4_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test4") +#define TEST5_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test5") +#define TEST6_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test6") +#define TEST7_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test7") +#define TEST8_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test8") +#define TEST9_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test9") +#define TEST10_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test10") +#define TEST11_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test11") +#define TEST12_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test12") +//#define TEST13_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test13") + +#define TEST1_STATUS_CODE 200 +#define TEST2_STATUS_CODE 301 +#define TEST3_PROVISIONAL_CODE PJSIP_SC_QUEUED +#define TEST3_STATUS_CODE 202 +#define TEST4_STATUS_CODE 200 +#define TEST4_REQUEST_COUNT 2 +#define TEST5_PROVISIONAL_CODE 100 +#define TEST5_STATUS_CODE 200 +#define TEST5_REQUEST_COUNT 2 +#define TEST5_RESPONSE_COUNT 2 +#define TEST6_PROVISIONAL_CODE 100 +#define TEST6_STATUS_CODE 200 /* Must be final */ +#define TEST6_REQUEST_COUNT 2 +#define TEST6_RESPONSE_COUNT 3 +#define TEST7_STATUS_CODE 301 +#define TEST8_STATUS_CODE 302 +#define TEST9_STATUS_CODE 301 + + +#define TEST4_TITLE "test4: absorbing request retransmission" +#define TEST5_TITLE "test5: retransmit last response in PROCEEDING state" +#define TEST6_TITLE "test6: retransmit last response in COMPLETED state" + + +static char TARGET_URI[128]; +static char FROM_URI[128]; +static struct tsx_test_param *test_param; +static unsigned tp_flag; + + +#define TEST_TIMEOUT_ERROR -30 +#define MAX_ALLOWED_DIFF 150 + +static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e); +static pj_bool_t on_rx_message(pjsip_rx_data *rdata); + +/* UAC transaction user module. */ +static pjsip_module tsx_user = +{ + NULL, NULL, /* prev and next */ + { "Tsx-UAS-User", 12}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* 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() */ + &tsx_user_on_tsx_state, /* on_tsx_state() */ +}; + +/* Module to send request. */ +static pjsip_module msg_sender = +{ + NULL, NULL, /* prev and next */ + { "Msg-Sender", 10}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &on_rx_message, /* on_rx_request() */ + &on_rx_message, /* on_rx_response() */ + NULL, /* on_tx_request() */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +/* Static vars, which will be reset on each test. */ +static int recv_count; +static pj_time_val recv_last; +static pj_bool_t test_complete; + +/* Loop transport instance. */ +static pjsip_transport *loop; + +/* UAS transaction key. */ +static char key_buf[64]; +static pj_str_t tsx_key = { key_buf, 0 }; + + +/* General timer entry to be used by tests. */ +//static pj_timer_entry timer; + +/* Timer to send response via transaction. */ +struct response +{ + pj_str_t tsx_key; + pjsip_tx_data *tdata; +}; + +/* Timer callback to send response. */ +static void send_response_timer( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pjsip_transaction *tsx; + struct response *r = (struct response*) entry->user_data; + pj_status_t status; + + PJ_UNUSED_ARG(timer_heap); + + tsx = pjsip_tsx_layer_find_tsx(&r->tsx_key, PJ_TRUE); + if (!tsx) { + PJ_LOG(3,(THIS_FILE," error: timer unable to find transaction")); + pjsip_tx_data_dec_ref(r->tdata); + return; + } + + status = pjsip_tsx_send_msg(tsx, r->tdata); + if (status != PJ_SUCCESS) { + // Some tests do expect failure! + //PJ_LOG(3,(THIS_FILE," error: timer unable to send response")); + pj_mutex_unlock(tsx->mutex); + pjsip_tx_data_dec_ref(r->tdata); + return; + } + + pj_mutex_unlock(tsx->mutex); +} + +/* Utility to send response. */ +static void send_response( pjsip_rx_data *rdata, + pjsip_transaction *tsx, + int status_code ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_endpt_create_response( endpt, rdata, status_code, NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create response", status); + test_complete = -196; + return; + } + + status = pjsip_tsx_send_msg(tsx, tdata); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + // Some tests do expect failure! + //app_perror(" error: unable to send response", status); + //test_complete = -197; + return; + } +} + +/* Schedule timer to send response for the specified UAS transaction */ +static void schedule_send_response( pjsip_rx_data *rdata, + const pj_str_t *tsx_key, + int status_code, + int msec_delay ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + pj_timer_entry *t; + struct response *r; + pj_time_val delay; + + status = pjsip_endpt_create_response( endpt, rdata, status_code, NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create response", status); + test_complete = -198; + return; + } + + r = PJ_POOL_ALLOC_T(tdata->pool, struct response); + pj_strdup(tdata->pool, &r->tsx_key, tsx_key); + r->tdata = tdata; + + delay.sec = 0; + delay.msec = msec_delay; + pj_time_val_normalize(&delay); + + t = PJ_POOL_ZALLOC_T(tdata->pool, pj_timer_entry); + t->user_data = r; + t->cb = &send_response_timer; + + status = pjsip_endpt_schedule_timer(endpt, t, &delay); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + app_perror(" error: unable to schedule timer", status); + test_complete = -199; + return; + } +} + + +/* Find and terminate tsx with the specified key. */ +static void terminate_our_tsx(int status_code) +{ + pjsip_transaction *tsx; + + tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); + if (!tsx) { + PJ_LOG(3,(THIS_FILE," error: timer unable to find transaction")); + return; + } + + pjsip_tsx_terminate(tsx, status_code); + pj_mutex_unlock(tsx->mutex); +} + +#if 0 /* Unused for now */ +/* Timer callback to terminate transaction. */ +static void terminate_tsx_timer( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + terminate_our_tsx(entry->id); +} + + +/* Schedule timer to terminate transaction. */ +static void schedule_terminate_tsx( pjsip_transaction *tsx, + int status_code, + int msec_delay ) +{ + pj_time_val delay; + + delay.sec = 0; + delay.msec = msec_delay; + pj_time_val_normalize(&delay); + + pj_assert(pj_strcmp(&tsx->transaction_key, &tsx_key)==0); + timer.user_data = NULL; + timer.id = status_code; + timer.cb = &terminate_tsx_timer; + pjsip_endpt_schedule_timer(endpt, &timer, &delay); +} +#endif + + +/* + * This is the handler to receive state changed notification from the + * transaction. It is used to verify that the transaction behaves according + * to the test scenario. + */ +static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) +{ + if (pj_strcmp2(&tsx->branch, TEST1_BRANCH_ID)==0 || + pj_strcmp2(&tsx->branch, TEST2_BRANCH_ID)==0) + { + /* + * TEST1_BRANCH_ID tests that non-INVITE transaction transmits final + * response using correct transport and terminates transaction after + * T4 (PJSIP_T4_TIMEOUT, 5 seconds). + * + * TEST2_BRANCH_ID does similar test for non-2xx final response. + */ + int status_code = (pj_strcmp2(&tsx->branch, TEST1_BRANCH_ID)==0) ? + TEST1_STATUS_CODE : TEST2_STATUS_CODE; + + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + test_complete = 1; + + /* Check that status code is status_code. */ + if (tsx->status_code != status_code) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -100; + } + + /* Previous state must be completed. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -101; + } + + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Previous state must be TRYING. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -102; + } + } + + } + else + if (pj_strcmp2(&tsx->branch, TEST3_BRANCH_ID)==0) { + /* + * TEST3_BRANCH_ID tests sending provisional response. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + test_complete = 1; + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST3_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -110; + } + + /* Previous state must be completed. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -111; + } + + } else if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) { + + /* Previous state must be TRYING. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -112; + } + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST3_PROVISIONAL_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -113; + } + + /* Check that event must be TX_MSG */ + if (e->body.tsx_state.type != PJSIP_EVENT_TX_MSG) { + PJ_LOG(3,(THIS_FILE, " error: incorrect event")); + test_complete = -114; + } + + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Previous state must be PROCEEDING. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -115; + } + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST3_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -116; + } + + /* Check that event must be TX_MSG */ + if (e->body.tsx_state.type != PJSIP_EVENT_TX_MSG) { + PJ_LOG(3,(THIS_FILE, " error: incorrect event")); + test_complete = -117; + } + + } + + } else + if (pj_strcmp2(&tsx->branch, TEST4_BRANCH_ID)==0) { + /* + * TEST4_BRANCH_ID tests receiving retransmissions in TRYING state. + */ + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + /* Request is received. */ + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST4_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, + " error: incorrect status code %d " + "(expecting %d)", tsx->status_code, + TEST4_STATUS_CODE)); + test_complete = -120; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -121; + } + + } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) + { + PJ_LOG(3,(THIS_FILE, " error: unexpected state %s (122)", + pjsip_tsx_state_str(tsx->state))); + test_complete = -122; + + } + + + } else + if (pj_strcmp2(&tsx->branch, TEST5_BRANCH_ID)==0) { + /* + * TEST5_BRANCH_ID tests receiving retransmissions in PROCEEDING state + */ + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + /* Request is received. */ + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST5_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -130; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -131; + } + + } else if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) { + + /* Check status code. */ + if (tsx->status_code != TEST5_PROVISIONAL_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -132; + } + + } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: unexpected state %s (133)", + pjsip_tsx_state_str(tsx->state))); + test_complete = -133; + + } + + } else + if (pj_strcmp2(&tsx->branch, TEST6_BRANCH_ID)==0) { + /* + * TEST6_BRANCH_ID tests receiving retransmissions in COMPLETED state + */ + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + /* Request is received. */ + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST6_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code %d " + "(expecting %d)", tsx->status_code, + TEST6_STATUS_CODE)); + test_complete = -140; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -141; + } + + } else if (tsx->state != PJSIP_TSX_STATE_PROCEEDING && + tsx->state != PJSIP_TSX_STATE_COMPLETED && + tsx->state != PJSIP_TSX_STATE_DESTROYED) + { + PJ_LOG(3,(THIS_FILE, " error: unexpected state %s (142)", + pjsip_tsx_state_str(tsx->state))); + test_complete = -142; + + } + + + } else + if (pj_strcmp2(&tsx->branch, TEST7_BRANCH_ID)==0 || + pj_strcmp2(&tsx->branch, TEST8_BRANCH_ID)==0) + { + /* + * TEST7_BRANCH_ID and TEST8_BRANCH_ID test retransmission of + * INVITE final response + */ + int code; + + if (pj_strcmp2(&tsx->branch, TEST7_BRANCH_ID) == 0) + code = TEST7_STATUS_CODE; + else + code = TEST8_STATUS_CODE; + + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + /* Request is received. */ + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + if (test_complete == 0) + test_complete = 1; + + /* Check status code. */ + if (tsx->status_code != PJSIP_SC_TSX_TIMEOUT) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -150; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -151; + } + + /* Check the number of retransmissions */ + if (tp_flag & PJSIP_TRANSPORT_RELIABLE) { + + if (tsx->retransmit_count != 0) { + PJ_LOG(3,(THIS_FILE, " error: should not retransmit")); + test_complete = -1510; + } + + } else { + + if (tsx->retransmit_count != 10) { + PJ_LOG(3,(THIS_FILE, + " error: incorrect retransmit count %d " + "(expecting 10)", + tsx->retransmit_count)); + test_complete = -1510; + } + + } + + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != code) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -152; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -153; + } + + } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) { + + PJ_LOG(3,(THIS_FILE, " error: unexpected state (154)")); + test_complete = -154; + + } + + + } else + if (pj_strcmp2(&tsx->branch, TEST9_BRANCH_ID)==0) { + /* + * TEST9_BRANCH_ID tests that retransmission of INVITE final response + * must cease when ACK is received. + */ + + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + /* Request is received. */ + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + if (test_complete == 0) + test_complete = 1; + + /* Check status code. */ + if (tsx->status_code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -160; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -161; + } + + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -162; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -163; + } + + + } else if (tsx->state == PJSIP_TSX_STATE_CONFIRMED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -164; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -165; + } + + } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) { + + PJ_LOG(3,(THIS_FILE, " error: unexpected state (166)")); + test_complete = -166; + + } + + + } else + if (pj_strcmp2(&tsx->branch, TEST10_BRANCH_ID)==0 || + pj_strcmp2(&tsx->branch, TEST11_BRANCH_ID)==0 || + pj_strcmp2(&tsx->branch, TEST12_BRANCH_ID)==0) + { + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + if (!test_complete) + test_complete = 1; + + if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) { + PJ_LOG(3,(THIS_FILE," error: incorrect status code")); + test_complete = -170; + } + } + } + +} + +/* Save transaction key to global variables. */ +static void save_key(pjsip_transaction *tsx) +{ + pj_str_t key; + + pj_strdup(tsx->pool, &key, &tsx->transaction_key); + pj_strcpy(&tsx_key, &key); +} + +#define DIFF(a,b) ((a<b) ? (b-a) : (a-b)) + +/* + * Message receiver handler. + */ +static pj_bool_t on_rx_message(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + pj_str_t branch_param = rdata->msg_info.via->branch_param; + pj_status_t status; + + if (pj_strcmp2(&branch_param, TEST1_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST2_BRANCH_ID) == 0) + { + /* + * TEST1_BRANCH_ID tests that non-INVITE transaction transmits 2xx + * final response using correct transport and terminates transaction + * after 32 seconds. + * + * TEST2_BRANCH_ID performs similar test for non-2xx final response. + */ + int status_code = (pj_strcmp2(&branch_param, TEST1_BRANCH_ID) == 0) ? + TEST1_STATUS_CODE : TEST2_STATUS_CODE; + + if (msg->type == PJSIP_REQUEST_MSG) { + /* On received request, create UAS and respond with final + * response. + */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -110; + return PJ_TRUE; + } + pjsip_tsx_recv_msg(tsx, rdata); + + save_key(tsx); + send_response(rdata, tsx, status_code); + + } else { + /* Verify the response received. */ + + ++recv_count; + + /* Verify status code. */ + if (msg->line.status.code != status_code) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -113; + } + + /* Verify that no retransmissions is received. */ + if (recv_count > 1) { + PJ_LOG(3,(THIS_FILE, " error: retransmission received")); + test_complete = -114; + } + + } + return PJ_TRUE; + + } else if (pj_strcmp2(&branch_param, TEST3_BRANCH_ID) == 0) { + + /* TEST3_BRANCH_ID tests provisional response. */ + + if (msg->type == PJSIP_REQUEST_MSG) { + /* On received request, create UAS and respond with provisional + * response, then schedule timer to send final response. + */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -116; + return PJ_TRUE; + } + pjsip_tsx_recv_msg(tsx, rdata); + + save_key(tsx); + + send_response(rdata, tsx, TEST3_PROVISIONAL_CODE); + schedule_send_response(rdata, &tsx->transaction_key, + TEST3_STATUS_CODE, 2000); + + } else { + /* Verify the response received. */ + + ++recv_count; + + if (recv_count == 1) { + /* Verify status code. */ + if (msg->line.status.code != TEST3_PROVISIONAL_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -123; + } + } else if (recv_count == 2) { + /* Verify status code. */ + if (msg->line.status.code != TEST3_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -124; + } + } else { + PJ_LOG(3,(THIS_FILE, " error: retransmission received")); + test_complete = -125; + } + + } + return PJ_TRUE; + + } else if (pj_strcmp2(&branch_param, TEST4_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST5_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST6_BRANCH_ID) == 0) + { + + /* TEST4_BRANCH_ID: absorbs retransmissions in TRYING state. */ + /* TEST5_BRANCH_ID: retransmit last response in PROCEEDING state. */ + /* TEST6_BRANCH_ID: retransmit last response in COMPLETED state. */ + + if (msg->type == PJSIP_REQUEST_MSG) { + /* On received request, create UAS. */ + pjsip_transaction *tsx; + + PJ_LOG(4,(THIS_FILE, " received request (probably retransmission)")); + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -130; + return PJ_TRUE; + } + + pjsip_tsx_recv_msg(tsx, rdata); + save_key(tsx); + + if (pj_strcmp2(&branch_param, TEST4_BRANCH_ID) == 0) { + + } else if (pj_strcmp2(&branch_param, TEST5_BRANCH_ID) == 0) { + send_response(rdata, tsx, TEST5_PROVISIONAL_CODE); + + } else if (pj_strcmp2(&branch_param, TEST6_BRANCH_ID) == 0) { + PJ_LOG(4,(THIS_FILE, " sending provisional response")); + send_response(rdata, tsx, TEST6_PROVISIONAL_CODE); + PJ_LOG(4,(THIS_FILE, " sending final response")); + send_response(rdata, tsx, TEST6_STATUS_CODE); + } + + } else { + /* Verify the response received. */ + + PJ_LOG(4,(THIS_FILE, " received response number %d", recv_count)); + + ++recv_count; + + if (pj_strcmp2(&branch_param, TEST4_BRANCH_ID) == 0) { + PJ_LOG(3,(THIS_FILE, " error: not expecting response!")); + test_complete = -132; + + } else if (pj_strcmp2(&branch_param, TEST5_BRANCH_ID) == 0) { + + if (rdata->msg_info.msg->line.status.code!=TEST5_PROVISIONAL_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code!")); + test_complete = -133; + + } + if (recv_count > TEST5_RESPONSE_COUNT) { + PJ_LOG(3,(THIS_FILE, " error: not expecting response!")); + test_complete = -134; + } + + } else if (pj_strcmp2(&branch_param, TEST6_BRANCH_ID) == 0) { + + int code = rdata->msg_info.msg->line.status.code; + + switch (recv_count) { + case 1: + if (code != TEST6_PROVISIONAL_CODE) { + PJ_LOG(3,(THIS_FILE, " error: invalid code!")); + test_complete = -135; + } + break; + case 2: + case 3: + if (code != TEST6_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: invalid code %d " + "(expecting %d)", code, TEST6_STATUS_CODE)); + test_complete = -136; + } + break; + default: + PJ_LOG(3,(THIS_FILE, " error: not expecting response")); + test_complete = -137; + break; + } + } + } + return PJ_TRUE; + + + } else if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST8_BRANCH_ID) == 0) + { + + /* + * TEST7_BRANCH_ID and TEST8_BRANCH_ID test the retransmission + * of INVITE final response + */ + if (msg->type == PJSIP_REQUEST_MSG) { + + /* On received request, create UAS. */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -140; + return PJ_TRUE; + } + + pjsip_tsx_recv_msg(tsx, rdata); + save_key(tsx); + + if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0) { + + send_response(rdata, tsx, TEST7_STATUS_CODE); + + } else { + + send_response(rdata, tsx, TEST8_STATUS_CODE); + + } + + } else { + int code; + + ++recv_count; + + if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0) + code = TEST7_STATUS_CODE; + else + code = TEST8_STATUS_CODE; + + if (recv_count==1) { + + if (rdata->msg_info.msg->line.status.code != code) { + PJ_LOG(3,(THIS_FILE," error: invalid status code")); + test_complete = -141; + } + + recv_last = rdata->pkt_info.timestamp; + + } else { + + pj_time_val now; + unsigned msec, msec_expected; + + now = rdata->pkt_info.timestamp; + + PJ_TIME_VAL_SUB(now, recv_last); + + msec = now.sec*1000 + now.msec; + msec_expected = (1 << (recv_count-2)) * pjsip_cfg()->tsx.t1; + if (msec_expected > pjsip_cfg()->tsx.t2) + msec_expected = pjsip_cfg()->tsx.t2; + + if (DIFF(msec, msec_expected) > MAX_ALLOWED_DIFF) { + PJ_LOG(3,(THIS_FILE, + " error: incorrect retransmission " + "time (%d ms expected, %d ms received", + msec_expected, msec)); + test_complete = -142; + } + + if (recv_count > 11) { + PJ_LOG(3,(THIS_FILE," error: too many responses (%d)", + recv_count)); + test_complete = -143; + } + + recv_last = rdata->pkt_info.timestamp; + } + + } + return PJ_TRUE; + + } else if (pj_strcmp2(&branch_param, TEST9_BRANCH_ID) == 0) { + + /* + * TEST9_BRANCH_ID tests that the retransmission of INVITE final + * response should cease when ACK is received. Transaction also MUST + * terminate in T4 seconds. + */ + if (msg->type == PJSIP_REQUEST_MSG) { + + /* On received request, create UAS. */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -150; + return PJ_TRUE; + } + + pjsip_tsx_recv_msg(tsx, rdata); + save_key(tsx); + send_response(rdata, tsx, TEST9_STATUS_CODE); + + + } else { + + ++recv_count; + + if (rdata->msg_info.msg->line.status.code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE," error: invalid status code")); + test_complete = -151; + } + + if (recv_count==1) { + + recv_last = rdata->pkt_info.timestamp; + + } else if (recv_count < 5) { + + /* Let UAS retransmit some messages before we send ACK. */ + pj_time_val now; + unsigned msec, msec_expected; + + now = rdata->pkt_info.timestamp; + + PJ_TIME_VAL_SUB(now, recv_last); + + msec = now.sec*1000 + now.msec; + msec_expected = (1 << (recv_count-2)) * pjsip_cfg()->tsx.t1; + if (msec_expected > pjsip_cfg()->tsx.t2) + msec_expected = pjsip_cfg()->tsx.t2; + + if (DIFF(msec, msec_expected) > MAX_ALLOWED_DIFF) { + PJ_LOG(3,(THIS_FILE, + " error: incorrect retransmission " + "time (%d ms expected, %d ms received", + msec_expected, msec)); + test_complete = -152; + } + + recv_last = rdata->pkt_info.timestamp; + + } else if (recv_count == 5) { + pjsip_tx_data *tdata; + pjsip_sip_uri *uri; + pjsip_via_hdr *via; + + status = pjsip_endpt_create_request_from_hdr( + endpt, &pjsip_ack_method, + rdata->msg_info.to->uri, + rdata->msg_info.from, + rdata->msg_info.to, + NULL, + rdata->msg_info.cid, + rdata->msg_info.cseq->cseq, + NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create ACK", status); + test_complete = -153; + return PJ_TRUE; + } + + uri=(pjsip_sip_uri*)pjsip_uri_get_uri(tdata->msg->line.req.uri); + uri->transport_param = pj_str("loop-dgram"); + + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->branch_param = pj_str(TEST9_BRANCH_ID); + + status = pjsip_endpt_send_request_stateless(endpt, tdata, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to send ACK", status); + test_complete = -154; + } + + } else { + PJ_LOG(3,(THIS_FILE," error: too many responses (%d)", + recv_count)); + test_complete = -155; + } + + } + return PJ_TRUE; + + } else if (pj_strcmp2(&branch_param, TEST10_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST11_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST12_BRANCH_ID) == 0) + { + int test_num, code1, code2; + + if (pj_strcmp2(&branch_param, TEST10_BRANCH_ID) == 0) + test_num=10, code1 = 100, code2 = 0; + else if (pj_strcmp2(&branch_param, TEST11_BRANCH_ID) == 0) + test_num=11, code1 = 100, code2 = 200; + else + test_num=12, code1 = 200, code2 = 0; + + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { + + /* On received response, create UAS. */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -150; + return PJ_TRUE; + } + + pjsip_tsx_recv_msg(tsx, rdata); + save_key(tsx); + + schedule_send_response(rdata, &tsx_key, code1, 1000); + + if (code2) + schedule_send_response(rdata, &tsx_key, code2, 2000); + + } else { + + } + + return PJ_TRUE; + } + + return PJ_FALSE; +} + +/* + * The generic test framework, used by most of the tests. + */ +static int perform_test( char *target_uri, char *from_uri, + char *branch_param, int test_time, + const pjsip_method *method, + int request_cnt, int request_interval_msec, + int expecting_timeout) +{ + pjsip_tx_data *tdata; + pj_str_t target, from; + pjsip_via_hdr *via; + pj_time_val timeout, next_send; + int sent_cnt; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, + " please standby, this will take at most %d seconds..", + test_time)); + + /* Reset test. */ + recv_count = 0; + test_complete = 0; + tsx_key.slen = 0; + + /* Init headers. */ + target = pj_str(target_uri); + from = pj_str(from_uri); + + /* Create request. */ + status = pjsip_endpt_create_request( endpt, method, &target, + &from, &target, NULL, NULL, -1, + NULL, &tdata); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to create request", status); + return -10; + } + + /* Set the branch param for test 1. */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->branch_param = pj_str(branch_param); + + /* Schedule first send. */ + sent_cnt = 0; + pj_gettimeofday(&next_send); + pj_time_val_normalize(&next_send); + + /* Set test completion time. */ + pj_gettimeofday(&timeout); + timeout.sec += test_time; + + /* Wait until test complete. */ + while (!test_complete) { + pj_time_val now, poll_delay = {0, 10}; + + pjsip_endpt_handle_events(endpt, &poll_delay); + + pj_gettimeofday(&now); + + if (sent_cnt < request_cnt && PJ_TIME_VAL_GTE(now, next_send)) { + /* Add additional reference to tdata to prevent transaction from + * deleting it. + */ + pjsip_tx_data_add_ref(tdata); + + /* (Re)Send the request. */ + PJ_LOG(4,(THIS_FILE, " (re)sending request %d", sent_cnt)); + + status = pjsip_endpt_send_request_stateless(endpt, tdata, 0, 0); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to send request", status); + pjsip_tx_data_dec_ref(tdata); + return -20; + } + + /* Schedule next send, if any. */ + sent_cnt++; + if (sent_cnt < request_cnt) { + pj_gettimeofday(&next_send); + next_send.msec += request_interval_msec; + pj_time_val_normalize(&next_send); + } + } + + if (now.sec > timeout.sec) { + if (!expecting_timeout) + PJ_LOG(3,(THIS_FILE, " Error: test has timed out")); + pjsip_tx_data_dec_ref(tdata); + return TEST_TIMEOUT_ERROR; + } + } + + if (test_complete < 0) { + pjsip_transaction *tsx; + + tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); + if (tsx) { + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + pj_mutex_unlock(tsx->mutex); + flush_events(1000); + } + pjsip_tx_data_dec_ref(tdata); + return test_complete; + } + + /* Allow transaction to destroy itself */ + flush_events(500); + + /* Make sure transaction has been destroyed. */ + if (pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE) != NULL) { + PJ_LOG(3,(THIS_FILE, " Error: transaction has not been destroyed")); + pjsip_tx_data_dec_ref(tdata); + return -40; + } + + /* Check tdata reference counter. */ + if (pj_atomic_get(tdata->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " Error: tdata reference counter is %d", + pj_atomic_get(tdata->ref_cnt))); + pjsip_tx_data_dec_ref(tdata); + return -50; + } + + /* Destroy txdata */ + pjsip_tx_data_dec_ref(tdata); + + return PJ_SUCCESS; + +} + + +/***************************************************************************** + ** + ** TEST1_BRANCH_ID: Basic 2xx final response + ** TEST2_BRANCH_ID: Basic non-2xx final response + ** + ***************************************************************************** + */ +static int tsx_basic_final_response_test(void) +{ + unsigned duration; + int status; + + PJ_LOG(3,(THIS_FILE," test1: basic sending 2xx final response")); + + /* Test duration must be greater than 32 secs if unreliable transport + * is used. + */ + duration = (tp_flag & PJSIP_TRANSPORT_RELIABLE) ? 1 : 33; + + status = perform_test(TARGET_URI, FROM_URI, TEST1_BRANCH_ID, + duration, &pjsip_options_method, 1, 0, 0); + if (status != 0) + return status; + + PJ_LOG(3,(THIS_FILE," test2: basic sending non-2xx final response")); + + status = perform_test(TARGET_URI, FROM_URI, TEST2_BRANCH_ID, + duration, &pjsip_options_method, 1, 0, 0); + if (status != 0) + return status; + + return 0; +} + + +/***************************************************************************** + ** + ** TEST3_BRANCH_ID: Sending provisional response + ** + ***************************************************************************** + */ +static int tsx_basic_provisional_response_test(void) +{ + unsigned duration; + int status; + + PJ_LOG(3,(THIS_FILE," test3: basic sending 2xx final response")); + + duration = (tp_flag & PJSIP_TRANSPORT_RELIABLE) ? 1 : 33; + duration += 2; + + status = perform_test(TARGET_URI, FROM_URI, TEST3_BRANCH_ID, duration, + &pjsip_options_method, 1, 0, 0); + + return status; +} + + +/***************************************************************************** + ** + ** TEST4_BRANCH_ID: Absorbs retransmissions in TRYING state + ** TEST5_BRANCH_ID: Absorbs retransmissions in PROCEEDING state + ** TEST6_BRANCH_ID: Absorbs retransmissions in COMPLETED state + ** + ***************************************************************************** + */ +static int tsx_retransmit_last_response_test(const char *title, + char *branch_id, + int request_cnt, + int status_code) +{ + int status; + + PJ_LOG(3,(THIS_FILE," %s", title)); + + status = perform_test(TARGET_URI, FROM_URI, branch_id, 5, + &pjsip_options_method, + request_cnt, 1000, 1); + if (status && status != TEST_TIMEOUT_ERROR) + return status; + if (!status) { + PJ_LOG(3,(THIS_FILE, " error: expecting timeout")); + return -31; + } + + terminate_our_tsx(status_code); + flush_events(100); + + if (test_complete != 1) + return test_complete; + + flush_events(100); + return 0; +} + +/***************************************************************************** + ** + ** TEST7_BRANCH_ID: INVITE non-2xx final response retransmission test + ** TEST8_BRANCH_ID: INVITE 2xx final response retransmission test + ** + ***************************************************************************** + */ +static int tsx_final_response_retransmission_test(void) +{ + int status; + + PJ_LOG(3,(THIS_FILE, + " test7: INVITE non-2xx final response retransmission")); + + status = perform_test(TARGET_URI, FROM_URI, TEST7_BRANCH_ID, + 33, /* Test duration must be greater than 32 secs */ + &pjsip_invite_method, 1, 0, 0); + if (status != 0) + return status; + + PJ_LOG(3,(THIS_FILE, + " test8: INVITE 2xx final response retransmission")); + + status = perform_test(TARGET_URI, FROM_URI, TEST8_BRANCH_ID, + 33, /* Test duration must be greater than 32 secs */ + &pjsip_invite_method, 1, 0, 0); + if (status != 0) + return status; + + return 0; +} + + +/***************************************************************************** + ** + ** TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must + ** cease when ACK is received + ** + ***************************************************************************** + */ +static int tsx_ack_test(void) +{ + int status; + + PJ_LOG(3,(THIS_FILE, + " test9: receiving ACK for non-2xx final response")); + + status = perform_test(TARGET_URI, FROM_URI, TEST9_BRANCH_ID, + 20, /* allow 5 retransmissions */ + &pjsip_invite_method, 1, 0, 0); + if (status != 0) + return status; + + + return 0; +} + + + +/***************************************************************************** + ** + ** TEST10_BRANCH_ID: test transport failure in TRYING state. + ** TEST11_BRANCH_ID: test transport failure in PROCEEDING state. + ** TEST12_BRANCH_ID: test transport failure in CONNECTED state. + ** TEST13_BRANCH_ID: test transport failure in CONFIRMED state. + ** + ***************************************************************************** + */ +static int tsx_transport_failure_test(void) +{ + struct test_desc + { + int transport_delay; + int fail_delay; + char *branch_id; + char *title; + } tests[] = + { + { 0, 10, TEST10_BRANCH_ID, "test10: failed transport in TRYING state (no delay)" }, + { 50, 10, TEST10_BRANCH_ID, "test10: failed transport in TRYING state (50 ms delay)" }, + { 0, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (no delay)" }, + { 50, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (50 ms delay)" }, + { 0, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (no delay)" }, + { 50, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (50 ms delay)" }, + }; + int i, status; + + for (i=0; i<(int)PJ_ARRAY_SIZE(tests); ++i) { + pj_time_val fail_time, end_test, now; + + PJ_LOG(3,(THIS_FILE, " %s", tests[i].title)); + pjsip_loop_set_failure(loop, 0, NULL); + pjsip_loop_set_delay(loop, tests[i].transport_delay); + + status = perform_test(TARGET_URI, FROM_URI, tests[i].branch_id, + 0, &pjsip_invite_method, 1, 0, 1); + if (status && status != TEST_TIMEOUT_ERROR) + return status; + if (!status) { + PJ_LOG(3,(THIS_FILE, " error: expecting timeout")); + return -40; + } + + pj_gettimeofday(&fail_time); + fail_time.msec += tests[i].fail_delay; + pj_time_val_normalize(&fail_time); + + do { + pj_time_val interval = { 0, 1 }; + pj_gettimeofday(&now); + pjsip_endpt_handle_events(endpt, &interval); + } while (PJ_TIME_VAL_LT(now, fail_time)); + + pjsip_loop_set_failure(loop, 1, NULL); + + end_test = now; + end_test.sec += 5; + + do { + pj_time_val interval = { 0, 1 }; + pj_gettimeofday(&now); + pjsip_endpt_handle_events(endpt, &interval); + } while (!test_complete && PJ_TIME_VAL_LT(now, end_test)); + + if (test_complete == 0) { + PJ_LOG(3,(THIS_FILE, " error: test has timed out")); + return -41; + } + + if (test_complete != 1) + return test_complete; + } + + return 0; +} + +/***************************************************************************** + ** + ** UAS Transaction Test. + ** + ***************************************************************************** + */ +int tsx_uas_test(struct tsx_test_param *param) +{ + pj_sockaddr_in addr; + pj_status_t status; + + test_param = param; + tp_flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)param->type); + + pj_ansi_sprintf(TARGET_URI, "sip:bob@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + + /* Check if loop transport is configured. */ + status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM, + &addr, sizeof(addr), NULL, &loop); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " Error: loop transport is not configured!")); + return -10; + } + /* Register modules. */ + status = pjsip_endpt_register_module(endpt, &tsx_user); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to register module", status); + return -3; + } + status = pjsip_endpt_register_module(endpt, &msg_sender); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to register module", status); + return -4; + } + + /* TEST1_BRANCH_ID: Basic 2xx final response. + * TEST2_BRANCH_ID: Basic non-2xx final response. + */ + status = tsx_basic_final_response_test(); + if (status != 0) + return status; + + /* TEST3_BRANCH_ID: with provisional response + */ + status = tsx_basic_provisional_response_test(); + if (status != 0) + return status; + + /* TEST4_BRANCH_ID: absorbs retransmissions in TRYING state + */ + status = tsx_retransmit_last_response_test(TEST4_TITLE, + TEST4_BRANCH_ID, + TEST4_REQUEST_COUNT, + TEST4_STATUS_CODE); + if (status != 0) + return status; + + /* TEST5_BRANCH_ID: retransmit last response in PROCEEDING state + */ + status = tsx_retransmit_last_response_test(TEST5_TITLE, + TEST5_BRANCH_ID, + TEST5_REQUEST_COUNT, + TEST5_STATUS_CODE); + if (status != 0) + return status; + + /* TEST6_BRANCH_ID: retransmit last response in COMPLETED state + * This only applies to non-reliable transports, + * since UAS transaction is destroyed as soon + * as final response is sent for reliable transports. + */ + if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) { + status = tsx_retransmit_last_response_test(TEST6_TITLE, + TEST6_BRANCH_ID, + TEST6_REQUEST_COUNT, + TEST6_STATUS_CODE); + if (status != 0) + return status; + } + + /* TEST7_BRANCH_ID: INVITE non-2xx final response retransmission test + * TEST8_BRANCH_ID: INVITE 2xx final response retransmission test + */ + status = tsx_final_response_retransmission_test(); + if (status != 0) + return status; + + /* TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must + * cease when ACK is received + * Only applicable for non-reliable transports. + */ + if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) { + status = tsx_ack_test(); + if (status != 0) + return status; + } + + + /* TEST10_BRANCH_ID: test transport failure in TRYING state. + * TEST11_BRANCH_ID: test transport failure in PROCEEDING state. + * TEST12_BRANCH_ID: test transport failure in CONNECTED state. + * TEST13_BRANCH_ID: test transport failure in CONFIRMED state. + */ + /* Only valid for loop-dgram */ + if (param->type == PJSIP_TRANSPORT_LOOP_DGRAM) { + status = tsx_transport_failure_test(); + if (status != 0) + return status; + } + + + /* Register modules. */ + status = pjsip_endpt_unregister_module(endpt, &tsx_user); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to unregister module", status); + return -8; + } + status = pjsip_endpt_unregister_module(endpt, &msg_sender); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to unregister module", status); + return -9; + } + + + if (loop) + pjsip_transport_dec_ref(loop); + + return 0; +} + diff --git a/pjsip/src/test/txdata_test.c b/pjsip/src/test/txdata_test.c new file mode 100644 index 0000000..fd111e2 --- /dev/null +++ b/pjsip/src/test/txdata_test.c @@ -0,0 +1,856 @@ +/* $Id: txdata_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + + +#define THIS_FILE "txdata_test.c" + + +#define HFIND(msg,h,H) ((pjsip_##h##_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_##H, NULL)) + +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 +# define LOOP 10000 +#else +# define LOOP 100000 +#endif + + +/* + * This tests various core message creation functions. + */ +static int core_txdata_test(void) +{ + pj_status_t status; + pj_str_t target, from, to, contact, body; + pjsip_rx_data dummy_rdata; + pjsip_tx_data *invite, *invite2, *cancel, *response, *ack; + + PJ_LOG(3,(THIS_FILE, " core transmit data test")); + + /* Create INVITE request. */ + target = pj_str("tel:+1"); + from = pj_str("tel:+0"); + to = pj_str("tel:+1"); + contact = pj_str("Bob <sip:+0@example.com;user=phone>"); + body = pj_str("Hello world!"); + + status = pjsip_endpt_create_request( endpt, &pjsip_invite_method, &target, + &from, &to, &contact, NULL, 10, &body, + &invite); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -10; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(invite) != 0) { + PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid")); + return -14; + } + /* Reference counter must be set to 1. */ + if (pj_atomic_get(invite->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " error: invalid reference counter")); + return -15; + } + /* Check message type. */ + if (invite->msg->type != PJSIP_REQUEST_MSG) + return -16; + /* Check method. */ + if (invite->msg->line.req.method.id != PJSIP_INVITE_METHOD) + return -17; + + /* Check that mandatory headers are present. */ + if (HFIND(invite->msg, from, FROM) == 0) + return -20; + if (HFIND(invite->msg, to, TO) == 0) + return -21; + if (HFIND(invite->msg, contact, CONTACT) == 0) + return -22; + if (HFIND(invite->msg, cid, CALL_ID) == 0) + return -23; + if (HFIND(invite->msg, cseq, CSEQ) == 0) + return -24; + do { + pjsip_via_hdr *via = HFIND(invite->msg, via, VIA); + if (via == NULL) + return -25; + /* Branch param must be empty. */ + if (via->branch_param.slen != 0) + return -26; + } while (0); + if (invite->msg->body == NULL) + return -28; + + /* Create another INVITE request from first request. */ + status = pjsip_endpt_create_request_from_hdr( endpt, &pjsip_invite_method, + invite->msg->line.req.uri, + HFIND(invite->msg,from,FROM), + HFIND(invite->msg,to,TO), + HFIND(invite->msg,contact,CONTACT), + HFIND(invite->msg,cid,CALL_ID), + 10, &body, &invite2); + if (status != PJ_SUCCESS) { + app_perror(" error: create second request failed", status); + return -30; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(invite2) != 0) { + PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid")); + return -34; + } + /* Reference counter must be set to 1. */ + if (pj_atomic_get(invite2->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " error: invalid reference counter")); + return -35; + } + /* Check message type. */ + if (invite2->msg->type != PJSIP_REQUEST_MSG) + return -36; + /* Check method. */ + if (invite2->msg->line.req.method.id != PJSIP_INVITE_METHOD) + return -37; + + /* Check that mandatory headers are again present. */ + if (HFIND(invite2->msg, from, FROM) == 0) + return -40; + if (HFIND(invite2->msg, to, TO) == 0) + return -41; + if (HFIND(invite2->msg, contact, CONTACT) == 0) + return -42; + if (HFIND(invite2->msg, cid, CALL_ID) == 0) + return -43; + if (HFIND(invite2->msg, cseq, CSEQ) == 0) + return -44; + if (HFIND(invite2->msg, via, VIA) == 0) + return -45; + /* + if (HFIND(invite2->msg, ctype, CONTENT_TYPE) == 0) + return -46; + if (HFIND(invite2->msg, clen, CONTENT_LENGTH) == 0) + return -47; + */ + if (invite2->msg->body == NULL) + return -48; + + /* Done checking invite2. We can delete this. */ + if (pjsip_tx_data_dec_ref(invite2) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: request buffer not destroyed!")); + return -49; + } + + /* Initialize dummy rdata (to simulate receiving a request) + * We should never do this in real application, as there are many + * many more fields need to be initialized!! + */ + dummy_rdata.msg_info.cid = HFIND(invite->msg, cid, CALL_ID); + dummy_rdata.msg_info.clen = NULL; + dummy_rdata.msg_info.cseq = HFIND(invite->msg, cseq, CSEQ); + dummy_rdata.msg_info.ctype = NULL; + dummy_rdata.msg_info.from = HFIND(invite->msg, from, FROM); + dummy_rdata.msg_info.max_fwd = NULL; + dummy_rdata.msg_info.msg = invite->msg; + dummy_rdata.msg_info.record_route = NULL; + dummy_rdata.msg_info.require = NULL; + dummy_rdata.msg_info.route = NULL; + dummy_rdata.msg_info.to = HFIND(invite->msg, to, TO); + dummy_rdata.msg_info.via = HFIND(invite->msg, via, VIA); + + /* Create a response message for the request. */ + status = pjsip_endpt_create_response( endpt, &dummy_rdata, 301, NULL, + &response); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create response", status); + return -50; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(response) != 0) { + PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid")); + return -54; + } + /* Check reference counter. */ + if (pj_atomic_get(response->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " error: invalid ref count in response")); + return -55; + } + /* Check message type. */ + if (response->msg->type != PJSIP_RESPONSE_MSG) + return -56; + /* Check correct status is set. */ + if (response->msg->line.status.code != 301) + return -57; + + /* Check that mandatory headers are again present. */ + if (HFIND(response->msg, from, FROM) == 0) + return -60; + if (HFIND(response->msg, to, TO) == 0) + return -61; + /* + if (HFIND(response->msg, contact, CONTACT) == 0) + return -62; + */ + if (HFIND(response->msg, cid, CALL_ID) == 0) + return -63; + if (HFIND(response->msg, cseq, CSEQ) == 0) + return -64; + if (HFIND(response->msg, via, VIA) == 0) + return -65; + + /* This response message will be used later when creating ACK */ + + /* Create CANCEL request for the original request. */ + status = pjsip_endpt_create_cancel( endpt, invite, &cancel); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create CANCEL request", status); + return -80; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(cancel) != 0) { + PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid")); + return -84; + } + /* Check reference counter. */ + if (pj_atomic_get(cancel->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " error: invalid ref count in CANCEL request")); + return -85; + } + /* Check message type. */ + if (cancel->msg->type != PJSIP_REQUEST_MSG) + return -86; + /* Check method. */ + if (cancel->msg->line.req.method.id != PJSIP_CANCEL_METHOD) + return -87; + + /* Check that mandatory headers are again present. */ + if (HFIND(cancel->msg, from, FROM) == 0) + return -90; + if (HFIND(cancel->msg, to, TO) == 0) + return -91; + /* + if (HFIND(cancel->msg, contact, CONTACT) == 0) + return -92; + */ + if (HFIND(cancel->msg, cid, CALL_ID) == 0) + return -93; + if (HFIND(cancel->msg, cseq, CSEQ) == 0) + return -94; + if (HFIND(cancel->msg, via, VIA) == 0) + return -95; + + /* Done checking CANCEL request. */ + if (pjsip_tx_data_dec_ref(cancel) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!")); + return -99; + } + + /* Modify dummy_rdata to simulate receiving response. */ + pj_bzero(&dummy_rdata, sizeof(dummy_rdata)); + dummy_rdata.msg_info.msg = response->msg; + dummy_rdata.msg_info.to = HFIND(response->msg, to, TO); + + /* Create ACK request */ + status = pjsip_endpt_create_ack( endpt, invite, &dummy_rdata, &ack ); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " error: unable to create ACK")); + return -100; + } + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(ack) != 0) { + PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid")); + return -104; + } + /* Check reference counter. */ + if (pj_atomic_get(ack->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " error: invalid ref count in ACK request")); + return -105; + } + /* Check message type. */ + if (ack->msg->type != PJSIP_REQUEST_MSG) + return -106; + /* Check method. */ + if (ack->msg->line.req.method.id != PJSIP_ACK_METHOD) + return -107; + /* Check Request-URI is present. */ + if (ack->msg->line.req.uri == NULL) + return -108; + + /* Check that mandatory headers are again present. */ + if (HFIND(ack->msg, from, FROM) == 0) + return -110; + if (HFIND(ack->msg, to, TO) == 0) + return -111; + if (HFIND(ack->msg, cid, CALL_ID) == 0) + return -112; + if (HFIND(ack->msg, cseq, CSEQ) == 0) + return -113; + if (HFIND(ack->msg, via, VIA) == 0) + return -114; + if (ack->msg->body != NULL) + return -115; + + /* Done checking invite message. */ + if (pjsip_tx_data_dec_ref(invite) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!")); + return -120; + } + + /* Done checking response message. */ + if (pjsip_tx_data_dec_ref(response) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!")); + return -130; + } + + /* Done checking ack message. */ + if (pjsip_tx_data_dec_ref(ack) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!")); + return -140; + } + + /* Done. */ + return 0; +} + + + +/* + * This test demonstrate the bug as reported in: + * http://bugzilla.pjproject.net/show_bug.cgi?id=49 + */ +#if INCLUDE_GCC_TEST +static int gcc_test() +{ + char msgbuf[512]; + pj_str_t target = pj_str("sip:alice@wonderland:5061;x-param=param%201" + "?X-Hdr-1=Header%201" + "&X-Empty-Hdr="); + pjsip_tx_data *tdata; + pjsip_parser_err_report err_list; + pjsip_msg *msg; + int len; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " header param in URI to create request")); + + /* Create request with header param in target URI. */ + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target, + &target, &target, &target, NULL, -1, + NULL, &tdata); + if (status != 0) { + app_perror(" error: Unable to create request", status); + return -200; + } + + /* Print and parse the request. + * We'll check that header params are not present in + */ + len = pjsip_msg_print(tdata->msg, msgbuf, sizeof(msgbuf)); + if (len < 1) { + PJ_LOG(3,(THIS_FILE, " error: printing message")); + pjsip_tx_data_dec_ref(tdata); + return -250; + } + msgbuf[len] = '\0'; + + PJ_LOG(5,(THIS_FILE, "%d bytes request created:--begin-msg--\n" + "%s\n" + "--end-msg--", len, msgbuf)); + + /* Now parse the message. */ + pj_list_init(&err_list); + msg = pjsip_parse_msg( tdata->pool, msgbuf, len, &err_list); + if (msg == NULL) { + pjsip_parser_err_report *e; + + PJ_LOG(3,(THIS_FILE, " error: parsing message message")); + + e = err_list.next; + while (e != &err_list) { + PJ_LOG(3,(THIS_FILE, " %s in line %d col %d hname=%.*s", + pj_exception_id_name(e->except_code), + e->line, e->col+1, + (int)e->hname.slen, + e->hname.ptr)); + e = e->next; + } + + pjsip_tx_data_dec_ref(tdata); + return -255; + } + + pjsip_tx_data_dec_ref(tdata); + return 0; +} +#endif + + +/* This tests the request creating functions against the following + * requirements: + * - header params in URI creates header in the request. + * - method and headers params are correctly shown or hidden in + * request URI, From, To, and Contact header. + */ +static int txdata_test_uri_params(void) +{ + char msgbuf[512]; + pj_str_t target = pj_str("sip:alice@wonderland:5061;x-param=param%201" + "?X-Hdr-1=Header%201" + "&X-Empty-Hdr="); + pj_str_t contact; + pj_str_t pname = pj_str("x-param"); + pj_str_t hname = pj_str("X-Hdr-1"); + pj_str_t hemptyname = pj_str("X-Empty-Hdr"); + pjsip_from_hdr *from_hdr; + pjsip_to_hdr *to_hdr; + pjsip_contact_hdr *contact_hdr; + pjsip_generic_string_hdr *hdr; + pjsip_tx_data *tdata; + pjsip_sip_uri *uri; + pjsip_param *param; + pjsip_via_hdr *via; + pjsip_parser_err_report err_list; + pjsip_msg *msg; + int len; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " header param in URI to create request")); + + /* Due to #930, contact argument is now parsed as Contact header, so + * must enclose it with <> to make it be parsed as URI. + */ + pj_ansi_snprintf(msgbuf, sizeof(msgbuf), "<%.*s>", + (int)target.slen, target.ptr); + contact.ptr = msgbuf; + contact.slen = strlen(msgbuf); + + /* Create request with header param in target URI. */ + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target, + &target, &target, &contact, NULL, -1, + NULL, &tdata); + if (status != 0) { + app_perror(" error: Unable to create request", status); + return -200; + } + + /* Fill up the Via header to prevent syntax error on parsing */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->transport = pj_str("TCP"); + via->sent_by.host = pj_str("127.0.0.1"); + + /* Print and parse the request. + * We'll check that header params are not present in + */ + len = pjsip_msg_print(tdata->msg, msgbuf, sizeof(msgbuf)); + if (len < 1) { + PJ_LOG(3,(THIS_FILE, " error: printing message")); + pjsip_tx_data_dec_ref(tdata); + return -250; + } + msgbuf[len] = '\0'; + + PJ_LOG(5,(THIS_FILE, "%d bytes request created:--begin-msg--\n" + "%s\n" + "--end-msg--", len, msgbuf)); + + /* Now parse the message. */ + pj_list_init(&err_list); + msg = pjsip_parse_msg( tdata->pool, msgbuf, len, &err_list); + if (msg == NULL) { + pjsip_parser_err_report *e; + + PJ_LOG(3,(THIS_FILE, " error: parsing message message")); + + e = err_list.next; + while (e != &err_list) { + PJ_LOG(3,(THIS_FILE, " %s in line %d col %d hname=%.*s", + pj_exception_id_name(e->except_code), + e->line, e->col+1, + (int)e->hname.slen, + e->hname.ptr)); + e = e->next; + } + + pjsip_tx_data_dec_ref(tdata); + return -256; + } + + /* Check the existence of port, other_param, and header param. + * Port is now allowed in To and From header. + */ + /* Port in request URI. */ + uri = (pjsip_sip_uri*) pjsip_uri_get_uri(msg->line.req.uri); + if (uri->port != 5061) { + PJ_LOG(3,(THIS_FILE, " error: port not present in request URI")); + pjsip_tx_data_dec_ref(tdata); + return -260; + } + /* other_param in request_uri */ + param = pjsip_param_find(&uri->other_param, &pname); + if (param == NULL || pj_strcmp2(¶m->value, "param 1") != 0) { + PJ_LOG(3,(THIS_FILE, " error: x-param not present in request URI")); + pjsip_tx_data_dec_ref(tdata); + return -261; + } + /* header param in request uri. */ + if (!pj_list_empty(&uri->header_param)) { + PJ_LOG(3,(THIS_FILE, " error: hparam in request URI")); + pjsip_tx_data_dec_ref(tdata); + return -262; + } + + /* Port in From header. */ + from_hdr = (pjsip_from_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_FROM, NULL); + uri = (pjsip_sip_uri*) pjsip_uri_get_uri(from_hdr->uri); + if (uri->port != 0) { + PJ_LOG(3,(THIS_FILE, " error: port most not exist in From header")); + pjsip_tx_data_dec_ref(tdata); + return -270; + } + /* other_param in From header */ + param = pjsip_param_find(&uri->other_param, &pname); + if (param == NULL || pj_strcmp2(¶m->value, "param 1") != 0) { + PJ_LOG(3,(THIS_FILE, " error: x-param not present in From header")); + pjsip_tx_data_dec_ref(tdata); + return -271; + } + /* header param in From header. */ + if (!pj_list_empty(&uri->header_param)) { + PJ_LOG(3,(THIS_FILE, " error: hparam in From header")); + pjsip_tx_data_dec_ref(tdata); + return -272; + } + + + /* Port in To header. */ + to_hdr = (pjsip_to_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_TO, NULL); + uri = (pjsip_sip_uri*) pjsip_uri_get_uri(to_hdr->uri); + if (uri->port != 0) { + PJ_LOG(3,(THIS_FILE, " error: port most not exist in To header")); + pjsip_tx_data_dec_ref(tdata); + return -280; + } + /* other_param in To header */ + param = pjsip_param_find(&uri->other_param, &pname); + if (param == NULL || pj_strcmp2(¶m->value, "param 1") != 0) { + PJ_LOG(3,(THIS_FILE, " error: x-param not present in To header")); + pjsip_tx_data_dec_ref(tdata); + return -281; + } + /* header param in From header. */ + if (!pj_list_empty(&uri->header_param)) { + PJ_LOG(3,(THIS_FILE, " error: hparam in To header")); + pjsip_tx_data_dec_ref(tdata); + return -282; + } + + + + /* Port in Contact header. */ + contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL); + uri = (pjsip_sip_uri*) pjsip_uri_get_uri(contact_hdr->uri); + if (uri->port != 5061) { + PJ_LOG(3,(THIS_FILE, " error: port not present in Contact header")); + pjsip_tx_data_dec_ref(tdata); + return -290; + } + /* other_param in Contact header */ + param = pjsip_param_find(&uri->other_param, &pname); + if (param == NULL || pj_strcmp2(¶m->value, "param 1") != 0) { + PJ_LOG(3,(THIS_FILE, " error: x-param not present in Contact header")); + pjsip_tx_data_dec_ref(tdata); + return -291; + } + /* header param in Contact header. */ + if (pj_list_empty(&uri->header_param)) { + PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header")); + pjsip_tx_data_dec_ref(tdata); + return -292; + } + /* Check for X-Hdr-1 */ + param = pjsip_param_find(&uri->header_param, &hname); + if (param == NULL || pj_strcmp2(¶m->value, "Header 1")!=0) { + PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header")); + pjsip_tx_data_dec_ref(tdata); + return -293; + } + /* Check for X-Empty-Hdr */ + param = pjsip_param_find(&uri->header_param, &hemptyname); + if (param == NULL || pj_strcmp2(¶m->value, "")!=0) { + PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header")); + pjsip_tx_data_dec_ref(tdata); + return -294; + } + + + /* Check that headers are present in the request. */ + hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(msg, &hname, NULL); + if (hdr == NULL || pj_strcmp2(&hdr->hvalue, "Header 1")!=0) { + PJ_LOG(3,(THIS_FILE, " error: header X-Hdr-1 not created")); + pjsip_tx_data_dec_ref(tdata); + return -300; + } + + hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(msg, &hemptyname, NULL); + if (hdr == NULL || pj_strcmp2(¶m->value, "")!=0) { + PJ_LOG(3,(THIS_FILE, " error: header X-Empty-Hdr not created")); + pjsip_tx_data_dec_ref(tdata); + return -330; + } + + pjsip_tx_data_dec_ref(tdata); + return 0; +} + + +/* + * create request benchmark + */ +static int create_request_bench(pj_timestamp *p_elapsed) +{ + enum { COUNT = 100 }; + unsigned i, j; + pjsip_tx_data *tdata[COUNT]; + pj_timestamp t1, t2, elapsed; + pj_status_t status; + + pj_str_t str_target = pj_str("sip:someuser@someprovider.com"); + pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>"); + pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>"); + pj_str_t str_contact = str_from; + + elapsed.u64 = 0; + + for (i=0; i<LOOP; i+=COUNT) { + pj_bzero(tdata, sizeof(tdata)); + + pj_get_timestamp(&t1); + + for (j=0; j<COUNT; ++j) { + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, + &str_target, &str_from, &str_to, + &str_contact, NULL, -1, NULL, + &tdata[j]); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + goto on_error; + } + } + + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&elapsed, &t2); + + for (j=0; j<COUNT; ++j) + pjsip_tx_data_dec_ref(tdata[j]); + } + + p_elapsed->u64 = elapsed.u64; + return PJ_SUCCESS; + +on_error: + for (i=0; i<COUNT; ++i) { + if (tdata[i]) + pjsip_tx_data_dec_ref(tdata[i]); + } + return -400; +} + + + +/* + * create response benchmark + */ +static int create_response_bench(pj_timestamp *p_elapsed) +{ + enum { COUNT = 100 }; + unsigned i, j; + pjsip_via_hdr *via; + pjsip_rx_data rdata; + pjsip_tx_data *request; + pjsip_tx_data *tdata[COUNT]; + pj_timestamp t1, t2, elapsed; + pj_status_t status; + + /* Create the request first. */ + pj_str_t str_target = pj_str("sip:someuser@someprovider.com"); + pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>"); + pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>"); + pj_str_t str_contact = str_from; + + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, + &str_target, &str_from, &str_to, + &str_contact, NULL, -1, NULL, + &request); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return status; + } + + /* Create several Via headers */ + via = pjsip_via_hdr_create(request->pool); + via->sent_by.host = pj_str("192.168.0.7"); + via->sent_by.port = 5061; + via->transport = pj_str("udp"); + via->rport_param = 0; + via->branch_param = pj_str("012345678901234567890123456789"); + via->recvd_param = pj_str("192.168.0.7"); + pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*) pjsip_hdr_clone(request->pool, via)); + pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*) pjsip_hdr_clone(request->pool, via)); + pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*)via); + + + /* Create "dummy" rdata from the tdata */ + pj_bzero(&rdata, sizeof(pjsip_rx_data)); + rdata.tp_info.pool = request->pool; + rdata.msg_info.msg = request->msg; + rdata.msg_info.from = (pjsip_from_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL); + rdata.msg_info.to = (pjsip_to_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL); + rdata.msg_info.cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL); + rdata.msg_info.cid = (pjsip_cid_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL); + rdata.msg_info.via = via; + + /* + * Now benchmark create_response + */ + elapsed.u64 = 0; + + for (i=0; i<LOOP; i+=COUNT) { + pj_bzero(tdata, sizeof(tdata)); + + pj_get_timestamp(&t1); + + for (j=0; j<COUNT; ++j) { + status = pjsip_endpt_create_response(endpt, &rdata, 200, NULL, &tdata[j]); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + goto on_error; + } + } + + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&elapsed, &t2); + + for (j=0; j<COUNT; ++j) + pjsip_tx_data_dec_ref(tdata[j]); + } + + p_elapsed->u64 = elapsed.u64; + pjsip_tx_data_dec_ref(request); + return PJ_SUCCESS; + +on_error: + for (i=0; i<COUNT; ++i) { + if (tdata[i]) + pjsip_tx_data_dec_ref(tdata[i]); + } + return -400; +} + + +int txdata_test(void) +{ + enum { REPEAT = 4 }; + unsigned i, msgs; + pj_timestamp usec[REPEAT], min, freq; + int status; + + status = pj_get_timestamp_freq(&freq); + if (status != PJ_SUCCESS) + return status; + + status = core_txdata_test(); + if (status != 0) + return status; + +#if INCLUDE_GCC_TEST + status = gcc_test(); + if (status != 0) + return status; +#endif + + status = txdata_test_uri_params(); + if (status != 0) + return status; + + + /* + * Benchmark create_request() + */ + PJ_LOG(3,(THIS_FILE, " benchmarking request creation:")); + for (i=0; i<REPEAT; ++i) { + PJ_LOG(3,(THIS_FILE, " test %d of %d..", + i+1, REPEAT)); + status = create_request_bench(&usec[i]); + if (status != PJ_SUCCESS) + return status; + } + + min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF); + for (i=0; i<REPEAT; ++i) { + if (usec[i].u64 < min.u64) min.u64 = usec[i].u64; + } + + msgs = (unsigned)(freq.u64 * LOOP / min.u64); + + PJ_LOG(3,(THIS_FILE, " Requests created at %d requests/sec", msgs)); + + report_ival("create-request-per-sec", + msgs, "msg/sec", + "Number of typical request messages that can be created " + "per second with <tt>pjsip_endpt_create_request()</tt>"); + + + /* + * Benchmark create_response() + */ + PJ_LOG(3,(THIS_FILE, " benchmarking response creation:")); + for (i=0; i<REPEAT; ++i) { + PJ_LOG(3,(THIS_FILE, " test %d of %d..", + i+1, REPEAT)); + status = create_response_bench(&usec[i]); + if (status != PJ_SUCCESS) + return status; + } + + min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF); + for (i=0; i<REPEAT; ++i) { + if (usec[i].u64 < min.u64) min.u64 = usec[i].u64; + } + + msgs = (unsigned)(freq.u64 * LOOP / min.u64); + + PJ_LOG(3,(THIS_FILE, " Responses created at %d responses/sec", msgs)); + + report_ival("create-response-per-sec", + msgs, "msg/sec", + "Number of typical response messages that can be created " + "per second with <tt>pjsip_endpt_create_response()</tt>"); + + + return 0; +} + diff --git a/pjsip/src/test/uri_test.c b/pjsip/src/test/uri_test.c new file mode 100644 index 0000000..8527faa --- /dev/null +++ b/pjsip/src/test/uri_test.c @@ -0,0 +1,1097 @@ +/* $Id: uri_test.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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "uri_test.c" + + +#define ALPHANUM "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "0123456789" +#define MARK "-_.!~*'()" +#define USER_CHAR ALPHANUM MARK "&=+$,;?/" +#define PASS_CHAR ALPHANUM MARK "&=+$," +#define PARAM_CHAR ALPHANUM MARK "[]/:&+$" + +#define POOL_SIZE 8000 +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 +# define LOOP_COUNT 10000 +#else +# define LOOP_COUNT 40000 +#endif +#define AVERAGE_URL_LEN 80 +#define THREAD_COUNT 4 + +static struct +{ + pj_highprec_t parse_len, print_len, cmp_len; + pj_timestamp parse_time, print_time, cmp_time; +} var; + + +/* URI creator functions. */ +static pjsip_uri *create_uri0( pj_pool_t *pool ); +static pjsip_uri *create_uri1( pj_pool_t *pool ); +static pjsip_uri *create_uri2( pj_pool_t *pool ); +static pjsip_uri *create_uri3( pj_pool_t *pool ); +static pjsip_uri *create_uri4( pj_pool_t *pool ); +static pjsip_uri *create_uri5( pj_pool_t *pool ); +static pjsip_uri *create_uri6( pj_pool_t *pool ); +static pjsip_uri *create_uri7( pj_pool_t *pool ); +static pjsip_uri *create_uri8( pj_pool_t *pool ); +static pjsip_uri *create_uri9( pj_pool_t *pool ); +static pjsip_uri *create_uri10( pj_pool_t *pool ); +static pjsip_uri *create_uri11( pj_pool_t *pool ); +static pjsip_uri *create_uri12( pj_pool_t *pool ); +static pjsip_uri *create_uri13( pj_pool_t *pool ); +static pjsip_uri *create_uri14( pj_pool_t *pool ); +static pjsip_uri *create_uri15( pj_pool_t *pool ); +static pjsip_uri *create_uri16( pj_pool_t *pool ); +static pjsip_uri *create_uri17( pj_pool_t *pool ); +static pjsip_uri *create_uri25( pj_pool_t *pool ); +static pjsip_uri *create_uri26( pj_pool_t *pool ); +static pjsip_uri *create_uri27( pj_pool_t *pool ); +static pjsip_uri *create_uri28( pj_pool_t *pool ); +static pjsip_uri *create_uri29( pj_pool_t *pool ); +static pjsip_uri *create_uri30( pj_pool_t *pool ); +static pjsip_uri *create_uri31( pj_pool_t *pool ); +static pjsip_uri *create_uri32( pj_pool_t *pool ); +static pjsip_uri *create_uri33( pj_pool_t *pool ); +static pjsip_uri *create_uri34( pj_pool_t *pool ); +static pjsip_uri *create_uri35( pj_pool_t *pool ); +static pjsip_uri *create_uri36( pj_pool_t *pool ); +static pjsip_uri *create_uri37( pj_pool_t *pool ); +static pjsip_uri *create_uri38( pj_pool_t *pool ); +static pjsip_uri *create_dummy( pj_pool_t *pool ); + +#define ERR_NOT_EQUAL -1001 +#define ERR_SYNTAX_ERR -1002 + +struct uri_test +{ + pj_status_t status; + char str[PJSIP_MAX_URL_SIZE]; + pjsip_uri *(*creator)(pj_pool_t *pool); + const char *printed; + pj_size_t len; +} uri_test_array[] = +{ + { + PJ_SUCCESS, + "sip:localhost", + &create_uri0 + }, + { + PJ_SUCCESS, + "sip:user@localhost", + &create_uri1 + }, + { + PJ_SUCCESS, + "sip:user:password@localhost:5060", + &create_uri2, }, + { + /* Port is specified should not match unspecified port. */ + ERR_NOT_EQUAL, + "sip:localhost:5060", + &create_uri3 + }, + { + /* All recognized parameters. */ + PJ_SUCCESS, + "sip:localhost;transport=tcp;user=ip;ttl=255;lr;maddr=127.0.0.1;method=ACK", + &create_uri4 + }, + { + /* Params mixed with other params and header params. */ + PJ_SUCCESS, + "sip:localhost;pickup=hurry;user=phone;message=I%20am%20sorry" + "?Subject=Hello%20There&Server=SIP%20Server", + &create_uri5 + }, + { + /* SIPS. */ + PJ_SUCCESS, + "sips:localhost", + &create_uri6, + }, + { + /* Name address */ + PJ_SUCCESS, + "<sip:localhost>", + &create_uri7 + }, + { + /* Name address with display name and SIPS scheme with some redundant + * whitespaced. + */ + PJ_SUCCESS, + " Power Administrator <sips:localhost>", + &create_uri8 + }, + { + /* Name address. */ + PJ_SUCCESS, + " \"User\" <sip:user@localhost:5071>", + &create_uri9 + }, + { + /* Escaped sequence in display name (display=Strange User\"\\\"). */ + PJ_SUCCESS, + " \"Strange User\\\"\\\\\\\"\" <sip:localhost>", + &create_uri10, + }, + { + /* Errorneous escaping in display name. */ + ERR_SYNTAX_ERR, + " \"Rogue User\\\" <sip:localhost>", + &create_uri11, + }, + { + /* Dangling quote in display name, but that should be OK. */ + PJ_SUCCESS, + "Strange User\" <sip:localhost>", + &create_uri12, + }, + { + /* Special characters in parameter value must be quoted. */ + PJ_SUCCESS, + "sip:localhost;pvalue=\"hello world\"", + &create_uri13, + }, + { + /* Excercise strange character sets allowed in display, user, password, + * host, and port. + */ + PJ_SUCCESS, + "This is -. !% *_+`'~ me <sip:a19A&=+$,;?/%2c:%40a&Zz=+$,@" + "my_proxy09.MY-domain.com:9801>", + &create_uri14, + }, + { + /* Another excercise to the allowed character sets to the hostname. */ + PJ_SUCCESS, + "sip:" ALPHANUM "-_.com", + &create_uri15, + }, + { + /* Another excercise to the allowed character sets to the username + * and password. + */ + PJ_SUCCESS, + "sip:" USER_CHAR ":" PASS_CHAR "@host", + &create_uri16, + }, + { + /* Excercise to the pname and pvalue, and mixup of other-param + * between 'recognized' params. + */ + PJ_SUCCESS, + "sip:host;user=ip;" PARAM_CHAR "%21=" PARAM_CHAR "%21" + ";lr;other=1;transport=sctp;other2", + &create_uri17, + }, + { + /* 18: This should trigger syntax error. */ + ERR_SYNTAX_ERR, + "sip:", + &create_dummy, + }, + { + /* 19: Syntax error: whitespace after scheme. */ + ERR_SYNTAX_ERR, + "sip :host", + &create_dummy, + }, + { + /* 20: Syntax error: whitespace before hostname. */ + ERR_SYNTAX_ERR, + "sip: host", + &create_dummy, + }, + { + /* 21: Syntax error: invalid port. */ + ERR_SYNTAX_ERR, + "sip:user:password", + &create_dummy, + }, + { + /* 22: Syntax error: no host. */ + ERR_SYNTAX_ERR, + "sip:user@", + &create_dummy, + }, + { + /* 23: Syntax error: no user/host. */ + ERR_SYNTAX_ERR, + "sip:@", + &create_dummy, + }, + { + /* 24: Syntax error: empty string. */ + ERR_SYNTAX_ERR, + "", + &create_dummy, + }, + { + /* 25: Simple tel: URI with global context */ + PJ_SUCCESS, + "tel:+1-201-555-0123", + &create_uri25, + "tel:+1-201-555-0123" + }, + { + /* 26: Simple tel: URI with local context */ + PJ_SUCCESS, + "tel:7042;phone-context=example.com", + &create_uri26, + "tel:7042;phone-context=example.com" + }, + { + /* 27: Simple tel: URI with local context */ + PJ_SUCCESS, + "tel:863-1234;phone-context=+1-914-555", + &create_uri27, + "tel:863-1234;phone-context=+1-914-555" + }, + { + /* 28: Comparison between local and global number */ + ERR_NOT_EQUAL, + "tel:+1", + &create_uri28, + "tel:+1" + }, + { + /* 29: tel: with some visual chars and spaces */ + PJ_SUCCESS, + "tel:(44).1234-*#+Deaf", + &create_uri29, + "tel:(44).1234-*#+Deaf" + }, + { + /* 30: isub parameters */ + PJ_SUCCESS, + "tel:+1;isub=/:@&$,-_.!~*'()[]/:&$aA1%21+=", + &create_uri30, + "tel:+1;isub=/:@&$,-_.!~*'()[]/:&$aA1!+%3d" + }, + { + /* 31: extension number parsing and encoding */ + PJ_SUCCESS, + "tel:+1;ext=+123", + &create_uri31, + "tel:+1;ext=%2b123" + }, + { + /* 32: context parameter parsing and encoding */ + PJ_SUCCESS, + "tel:911;phone-context=+1-911", + &create_uri32, + "tel:911;phone-context=+1-911" + }, + { + /* 33: case-insensitive comparison */ + PJ_SUCCESS, + "tel:911;phone-context=emergency.example.com", + &create_uri33, + "tel:911;phone-context=emergency.example.com" + }, + { + /* 34: parameter only appears in one URL */ + ERR_NOT_EQUAL, + "tel:911;p1=p1;p2=p2", + &create_uri34, + "tel:911;p1=p1;p2=p2" + }, + { + /* 35: IPv6 in host and maddr parameter */ + PJ_SUCCESS, + "sip:user@[::1];maddr=[::01]", + &create_uri35, + "sip:user@[::1];maddr=[::01]" + }, + { + /* 36: IPv6 in host and maddr, without username */ + PJ_SUCCESS, + "sip:[::1];maddr=[::01]", + &create_uri36, + "sip:[::1];maddr=[::01]" + }, + { + /* 37: Non-ASCII UTF-8 in display name, with quote */ + PJ_SUCCESS, + "\"\xC0\x81\" <sip:localhost>", + &create_uri37, + "\"\xC0\x81\" <sip:localhost>" + }, + { + /* 38: Non-ASCII UTF-8 in display name, without quote */ + PJ_SUCCESS, + "\xC0\x81 <sip:localhost>", + &create_uri38, + "\"\xC0\x81\" <sip:localhost>" + } + +}; + +static pjsip_uri *create_uri0(pj_pool_t *pool) +{ + /* "sip:localhost" */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri1(pj_pool_t *pool) +{ + /* "sip:user@localhost" */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2( pool, &url->user, "user"); + pj_strdup2( pool, &url->host, "localhost"); + + return (pjsip_uri*) url; +} + +static pjsip_uri *create_uri2(pj_pool_t *pool) +{ + /* "sip:user:password@localhost:5060" */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2( pool, &url->user, "user"); + pj_strdup2( pool, &url->passwd, "password"); + pj_strdup2( pool, &url->host, "localhost"); + url->port = 5060; + + return (pjsip_uri*) url; +} + +static pjsip_uri *create_uri3(pj_pool_t *pool) +{ + /* Like: "sip:localhost:5060", but without the port. */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri4(pj_pool_t *pool) +{ + /* "sip:localhost;transport=tcp;user=ip;ttl=255;lr;maddr=127.0.0.1;method=ACK" */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2(pool, &url->host, "localhost"); + pj_strdup2(pool, &url->transport_param, "tcp"); + pj_strdup2(pool, &url->user_param, "ip"); + url->ttl_param = 255; + url->lr_param = 1; + pj_strdup2(pool, &url->maddr_param, "127.0.0.1"); + pj_strdup2(pool, &url->method_param, "ACK"); + + return (pjsip_uri*)url; +} + +#define param_add(list,pname,pvalue) \ + do { \ + pjsip_param *param; \ + param=PJ_POOL_ALLOC_T(pool, pjsip_param); \ + param->name = pj_str(pname); \ + param->value = pj_str(pvalue); \ + pj_list_insert_before(&list, param); \ + } while (0) + +static pjsip_uri *create_uri5(pj_pool_t *pool) +{ + /* "sip:localhost;pickup=hurry;user=phone;message=I%20am%20sorry" + "?Subject=Hello%20There&Server=SIP%20Server" + */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2(pool, &url->host, "localhost"); + pj_strdup2(pool, &url->user_param, "phone"); + + //pj_strdup2(pool, &url->other_param, ";pickup=hurry;message=I%20am%20sorry"); + param_add(url->other_param, "pickup", "hurry"); + param_add(url->other_param, "message", "I am sorry"); + + //pj_strdup2(pool, &url->header_param, "?Subject=Hello%20There&Server=SIP%20Server"); + param_add(url->header_param, "Subject", "Hello There"); + param_add(url->header_param, "Server", "SIP Server"); + return (pjsip_uri*)url; + +} + +static pjsip_uri *create_uri6(pj_pool_t *pool) +{ + /* "sips:localhost" */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 1); + + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri7(pj_pool_t *pool) +{ + /* "<sip:localhost>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri8(pj_pool_t *pool) +{ + /* " Power Administrator <sips:localhost>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 1); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "Power Administrator"); + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri9(pj_pool_t *pool) +{ + /* " \"User\" <sip:user@localhost:5071>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "User"); + pj_strdup2(pool, &url->user, "user"); + pj_strdup2(pool, &url->host, "localhost"); + url->port = 5071; + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri10(pj_pool_t *pool) +{ + /* " \"Strange User\\\"\\\\\\\"\" <sip:localhost>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "Strange User\\\"\\\\\\\""); + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri11(pj_pool_t *pool) +{ + /* " \"Rogue User\\\" <sip:localhost>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "Rogue User\\"); + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri12(pj_pool_t *pool) +{ + /* "Strange User\" <sip:localhost>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "Strange User\""); + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri13(pj_pool_t *pool) +{ + /* "sip:localhost;pvalue=\"hello world\"" */ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + pj_strdup2(pool, &url->host, "localhost"); + //pj_strdup2(pool, &url->other_param, ";pvalue=\"hello world\""); + param_add(url->other_param, "pvalue", "\"hello world\""); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri14(pj_pool_t *pool) +{ + /* "This is -. !% *_+`'~ me <sip:a19A&=+$,;?/%2c:%40a&Zz=+$,@my_proxy09.my-domain.com:9801>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "This is -. !% *_+`'~ me"); + pj_strdup2(pool, &url->user, "a19A&=+$,;?/,"); + pj_strdup2(pool, &url->passwd, "@a&Zz=+$,"); + pj_strdup2(pool, &url->host, "my_proxy09.MY-domain.com"); + url->port = 9801; + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri15(pj_pool_t *pool) +{ + /* "sip:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.com" */ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + pj_strdup2(pool, &url->host, ALPHANUM "-_.com"); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri16(pj_pool_t *pool) +{ + /* "sip:" USER_CHAR ":" PASS_CHAR "@host" */ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + pj_strdup2(pool, &url->user, USER_CHAR); + pj_strdup2(pool, &url->passwd, PASS_CHAR); + pj_strdup2(pool, &url->host, "host"); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri17(pj_pool_t *pool) +{ + /* "sip:host;user=ip;" PARAM_CHAR "%21=" PARAM_CHAR "%21;lr;other=1;transport=sctp;other2" */ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + pj_strdup2(pool, &url->host, "host"); + pj_strdup2(pool, &url->user_param, "ip"); + pj_strdup2(pool, &url->transport_param, "sctp"); + param_add(url->other_param, PARAM_CHAR "!", PARAM_CHAR "!"); + param_add(url->other_param, "other", "1"); + param_add(url->other_param, "other2", ""); + url->lr_param = 1; + return (pjsip_uri*)url; +} + + +static pjsip_uri *create_uri25(pj_pool_t *pool) +{ + /* "tel:+1-201-555-0123" */ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("+1-201-555-0123"); + return (pjsip_uri*)uri; +} + +static pjsip_uri *create_uri26(pj_pool_t *pool) +{ + /* tel:7042;phone-context=example.com */ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("7042"); + uri->context = pj_str("example.com"); + return (pjsip_uri*)uri; +} + +static pjsip_uri *create_uri27(pj_pool_t *pool) +{ + /* "tel:863-1234;phone-context=+1-914-555" */ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("863-1234"); + uri->context = pj_str("+1-914-555"); + return (pjsip_uri*)uri; +} + +/* "tel:1" */ +static pjsip_uri *create_uri28(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("1"); + return (pjsip_uri*)uri; +} + +/* "tel:(44).1234-*#+Deaf" */ +static pjsip_uri *create_uri29(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("(44).1234-*#+Deaf"); + return (pjsip_uri*)uri; +} + +/* "tel:+1;isub=/:@&$,-_.!~*'()[]/:&$aA1%21+=" */ +static pjsip_uri *create_uri30(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("+1"); + uri->isub_param = pj_str("/:@&$,-_.!~*'()[]/:&$aA1!+="); + return (pjsip_uri*)uri; +} + +/* "tel:+1;ext=+123" */ +static pjsip_uri *create_uri31(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("+1"); + uri->ext_param = pj_str("+123"); + return (pjsip_uri*)uri; +} + +/* "tel:911;phone-context=+1-911" */ +static pjsip_uri *create_uri32(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("911"); + uri->context = pj_str("+1-911"); + return (pjsip_uri*)uri; +} + +/* "tel:911;phone-context=emergency.example.com" */ +static pjsip_uri *create_uri33(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("911"); + uri->context = pj_str("EMERGENCY.EXAMPLE.COM"); + return (pjsip_uri*)uri; +} + +/* "tel:911;p1=p1;p2=p2" */ +static pjsip_uri *create_uri34(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + pjsip_param *p; + + uri->number = pj_str("911"); + + p = PJ_POOL_ALLOC_T(pool, pjsip_param); + p->name = p->value = pj_str("p1"); + pj_list_insert_before(&uri->other_param, p); + + return (pjsip_uri*)uri; +} + +/* "sip:user@[::1];maddr=[::01]" */ +static pjsip_uri *create_uri35( pj_pool_t *pool ) +{ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + url->user = pj_str("user"); + url->host = pj_str("::1"); + url->maddr_param = pj_str("::01"); + return (pjsip_uri*)url; +} + +/* "sip:[::1];maddr=[::01]" */ +static pjsip_uri *create_uri36( pj_pool_t *pool ) +{ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + url->host = pj_str("::1"); + url->maddr_param = pj_str("::01"); + return (pjsip_uri*)url; + +} + +/* "\"\xC0\x81\" <sip:localhost>" */ +static pjsip_uri *create_uri37( pj_pool_t *pool ) +{ + pjsip_name_addr *name; + pjsip_sip_uri *url; + + name = pjsip_name_addr_create(pool); + name->display = pj_str("\xC0\x81"); + + url = pjsip_sip_uri_create(pool, 0); + url->host = pj_str("localhost"); + + name->uri = (pjsip_uri*)url; + + return (pjsip_uri*)name; + +} + +/* "\xC0\x81 <sip:localhost>" */ +static pjsip_uri *create_uri38( pj_pool_t *pool ) +{ + pjsip_name_addr *name; + pjsip_sip_uri *url; + + name = pjsip_name_addr_create(pool); + name->display = pj_str("\xC0\x81"); + + url = pjsip_sip_uri_create(pool, 0); + url->host = pj_str("localhost"); + + name->uri = (pjsip_uri*)url; + + return (pjsip_uri*)name; + +} + +static pjsip_uri *create_dummy(pj_pool_t *pool) +{ + PJ_UNUSED_ARG(pool); + return NULL; +} + +/*****************************************************************************/ + +/* + * Test one test entry. + */ +static pj_status_t do_uri_test(pj_pool_t *pool, struct uri_test *entry) +{ + pj_status_t status; + int len; + char *input; + pjsip_uri *parsed_uri, *ref_uri; + pj_str_t s1 = {NULL, 0}, s2 = {NULL, 0}; + pj_timestamp t1, t2; + + if (entry->len == 0) + entry->len = pj_ansi_strlen(entry->str); + +#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + input = pj_pool_alloc(pool, entry->len + 1); + pj_memcpy(input, entry->str, entry->len); + input[entry->len] = '\0'; +#else + input = entry->str; +#endif + + /* Parse URI text. */ + pj_get_timestamp(&t1); + var.parse_len = var.parse_len + entry->len; + parsed_uri = pjsip_parse_uri(pool, input, entry->len, 0); + if (!parsed_uri) { + /* Parsing failed. If the entry says that this is expected, then + * return OK. + */ + status = entry->status==ERR_SYNTAX_ERR ? PJ_SUCCESS : -10; + if (status != 0) { + PJ_LOG(3,(THIS_FILE, " uri parse error!\n" + " uri='%s'\n", + input)); + } + goto on_return; + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.parse_time, &t2); + + /* Create the reference URI. */ + ref_uri = entry->creator(pool); + + /* Print both URI. */ + s1.ptr = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); + s2.ptr = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); + + pj_get_timestamp(&t1); + len = pjsip_uri_print( PJSIP_URI_IN_OTHER, parsed_uri, s1.ptr, PJSIP_MAX_URL_SIZE); + if (len < 1) { + status = -20; + goto on_return; + } + s1.ptr[len] = '\0'; + s1.slen = len; + + var.print_len = var.print_len + len; + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.print_time, &t2); + + len = pjsip_uri_print( PJSIP_URI_IN_OTHER, ref_uri, s2.ptr, PJSIP_MAX_URL_SIZE); + if (len < 1) { + status = -30; + goto on_return; + } + s2.ptr[len] = '\0'; + s2.slen = len; + + /* Full comparison of parsed URI with reference URI. */ + pj_get_timestamp(&t1); + status = pjsip_uri_cmp(PJSIP_URI_IN_OTHER, parsed_uri, ref_uri); + if (status != 0) { + /* Not equal. See if this is the expected status. */ + status = entry->status==ERR_NOT_EQUAL ? PJ_SUCCESS : -40; + if (status != 0) { + PJ_LOG(3,(THIS_FILE, " uri comparison mismatch, status=%d:\n" + " uri1='%s'\n" + " uri2='%s'", + status, s1.ptr, s2.ptr)); + } + goto on_return; + + } else { + /* Equal. See if this is the expected status. */ + status = entry->status==PJ_SUCCESS ? PJ_SUCCESS : -50; + if (status != PJ_SUCCESS) { + goto on_return; + } + } + + var.cmp_len = var.cmp_len + len; + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.cmp_time, &t2); + + /* Compare text. */ + if (entry->printed) { + if (pj_strcmp2(&s1, entry->printed) != 0) { + /* Not equal. */ + PJ_LOG(3,(THIS_FILE, " uri print mismatch:\n" + " printed='%s'\n" + " expectd='%s'", + s1.ptr, entry->printed)); + status = -60; + } + } else { + if (pj_strcmp(&s1, &s2) != 0) { + /* Not equal. */ + PJ_LOG(3,(THIS_FILE, " uri print mismatch:\n" + " uri1='%s'\n" + " uri2='%s'", + s1.ptr, s2.ptr)); + status = -70; + } + } + +on_return: + return status; +} + + +static int simple_uri_test(void) +{ + unsigned i; + pj_pool_t *pool; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " simple test")); + for (i=0; i<PJ_ARRAY_SIZE(uri_test_array); ++i) { + pool = pjsip_endpt_create_pool(endpt, "", POOL_SIZE, POOL_SIZE); + status = do_uri_test(pool, &uri_test_array[i]); + pjsip_endpt_release_pool(endpt, pool); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " error %d when testing entry %d", + status, i)); + return status; + } + } + + return 0; +} + +#if INCLUDE_BENCHMARKS +static int uri_benchmark(unsigned *p_parse, unsigned *p_print, unsigned *p_cmp) +{ + unsigned i, loop; + pj_status_t status = PJ_SUCCESS; + pj_timestamp zero; + pj_time_val elapsed; + pj_highprec_t avg_parse, avg_print, avg_cmp, kbytes; + + pj_bzero(&var, sizeof(var)); + + zero.u32.hi = zero.u32.lo = 0; + + var.parse_len = var.print_len = var.cmp_len = 0; + var.parse_time.u32.hi = var.parse_time.u32.lo = 0; + var.print_time.u32.hi = var.print_time.u32.lo = 0; + var.cmp_time.u32.hi = var.cmp_time.u32.lo = 0; + for (loop=0; loop<LOOP_COUNT; ++loop) { + for (i=0; i<PJ_ARRAY_SIZE(uri_test_array); ++i) { + pj_pool_t *pool; + pool = pjsip_endpt_create_pool(endpt, "", POOL_SIZE, POOL_SIZE); + status = do_uri_test(pool, &uri_test_array[i]); + pjsip_endpt_release_pool(endpt, pool); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " error %d when testing entry %d", + status, i)); + pjsip_endpt_release_pool(endpt, pool); + goto on_return; + } + } + } + + kbytes = var.parse_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.parse_time); + avg_parse = pj_elapsed_usec(&zero, &var.parse_time); + pj_highprec_mul(avg_parse, AVERAGE_URL_LEN); + pj_highprec_div(avg_parse, var.parse_len); + if (avg_parse == 0) + avg_parse = 1; + avg_parse = 1000000 / avg_parse; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB of urls parsed in %d.%03ds (avg=%d urls/sec)", + (unsigned)(var.parse_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_parse)); + + *p_parse = (unsigned)avg_parse; + + kbytes = var.print_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.print_time); + avg_print = pj_elapsed_usec(&zero, &var.print_time); + pj_highprec_mul(avg_print, AVERAGE_URL_LEN); + pj_highprec_div(avg_print, var.parse_len); + if (avg_print == 0) + avg_print = 1; + avg_print = 1000000 / avg_print; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB of urls printed in %d.%03ds (avg=%d urls/sec)", + (unsigned)(var.print_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_print)); + + *p_print = (unsigned)avg_print; + + kbytes = var.cmp_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.cmp_time); + avg_cmp = pj_elapsed_usec(&zero, &var.cmp_time); + pj_highprec_mul(avg_cmp, AVERAGE_URL_LEN); + pj_highprec_div(avg_cmp, var.cmp_len); + if (avg_cmp == 0) + avg_cmp = 1; + avg_cmp = 1000000 / avg_cmp; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB of urls compared in %d.%03ds (avg=%d urls/sec)", + (unsigned)(var.cmp_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_cmp)); + + *p_cmp = (unsigned)avg_cmp; + +on_return: + return status; +} +#endif /* INCLUDE_BENCHMARKS */ + +/*****************************************************************************/ + +int uri_test(void) +{ + enum { COUNT = 1, DETECT=0, PARSE=1, PRINT=2 }; + struct { + unsigned parse; + unsigned print; + unsigned cmp; + } run[COUNT]; + unsigned i, max, avg_len; + char desc[200]; + pj_status_t status; + + status = simple_uri_test(); + if (status != PJ_SUCCESS) + return status; + +#if INCLUDE_BENCHMARKS + for (i=0; i<COUNT; ++i) { + PJ_LOG(3,(THIS_FILE, " benchmarking (%d of %d)...", i+1, COUNT)); + status = uri_benchmark(&run[i].parse, &run[i].print, &run[i].cmp); + if (status != PJ_SUCCESS) + return status; + } + + /* Calculate average URI length */ + for (i=0, avg_len=0; i<PJ_ARRAY_SIZE(uri_test_array); ++i) { + avg_len += uri_test_array[i].len; + } + avg_len /= PJ_ARRAY_SIZE(uri_test_array); + + + /* + * Print maximum parse/sec + */ + for (i=0, max=0; i<COUNT; ++i) + if (run[i].parse > max) max = run[i].parse; + + PJ_LOG(3,("", " Maximum URI parse/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP/TEL URIs that can be <B>parsed</B> with " + "<tt>pjsip_parse_uri()</tt> per second " + "(tested with %d URI set, with average length of " + "%d chars)", + (int)PJ_ARRAY_SIZE(uri_test_array), avg_len); + + report_ival("uri-parse-per-sec", max, "URI/sec", desc); + + /* URI parsing bandwidth */ + report_ival("uri-parse-bandwidth-mb", avg_len*max/1000000, "MB/sec", + "URI parsing bandwidth in megabytes (number of megabytes " + "worth of URI that can be parsed per second)"); + + + /* Print maximum print/sec */ + for (i=0, max=0; i<COUNT; ++i) + if (run[i].print > max) max = run[i].print; + + PJ_LOG(3,("", " Maximum URI print/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP/TEL URIs that can be <B>printed</B> with " + "<tt>pjsip_uri_print()</tt> per second " + "(tested with %d URI set, with average length of " + "%d chars)", + (int)PJ_ARRAY_SIZE(uri_test_array), avg_len); + + report_ival("uri-print-per-sec", max, "URI/sec", desc); + + /* Print maximum detect/sec */ + for (i=0, max=0; i<COUNT; ++i) + if (run[i].cmp > max) max = run[i].cmp; + + PJ_LOG(3,("", " Maximum URI comparison/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP/TEL URIs that can be <B>compared</B> with " + "<tt>pjsip_uri_cmp()</tt> per second " + "(tested with %d URI set, with average length of " + "%d chars)", + (int)PJ_ARRAY_SIZE(uri_test_array), avg_len); + + report_ival("uri-cmp-per-sec", max, "URI/sec", desc); + +#endif /* INCLUDE_BENCHMARKS */ + + return PJ_SUCCESS; +} + |