diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-03-01 19:31:18 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-03-01 19:31:18 +0000 |
commit | 2c5a0a8b70a04c11b85498562d3e1361a4b7ad7d (patch) | |
tree | 210fe3b59eb026bebb8a735fad964eac7945ca6c /pjsip-apps/src | |
parent | 6b8c1eb422e7f06384c7bad0c91f7680c6b73a33 (diff) |
Added pjsip-apps top level projects
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@254 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip-apps/src')
-rw-r--r-- | pjsip-apps/src/pjsip-perf/handler_call.c | 412 | ||||
-rw-r--r-- | pjsip-apps/src/pjsip-perf/handler_options.c | 148 | ||||
-rw-r--r-- | pjsip-apps/src/pjsip-perf/main.c | 668 | ||||
-rw-r--r-- | pjsip-apps/src/pjsip-perf/pjsip_perf.h | 164 | ||||
-rw-r--r-- | pjsip-apps/src/pjsua/main.c | 944 |
5 files changed, 2336 insertions, 0 deletions
diff --git a/pjsip-apps/src/pjsip-perf/handler_call.c b/pjsip-apps/src/pjsip-perf/handler_call.c new file mode 100644 index 00000000..469d72c8 --- /dev/null +++ b/pjsip-apps/src/pjsip-perf/handler_call.c @@ -0,0 +1,412 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "pjsip_perf.h" + +/* + * This file handles call generation and incoming calls. + */ +#define THIS_FILE "handler_call.c" + +/* + * Dummy SDP. + */ +static pjmedia_sdp_session *local_sdp; + + +#define TIMER_ID 1234 + +/* Call data, to be attached to invite session. */ +struct call_data +{ + pjsip_inv_session *inv; + pj_bool_t confirmed; + pj_timer_entry bye_timer; + void *test_data; + void (*completion_cb)(void*,pj_bool_t); +}; + + +/**************************************************************************** + * + * INCOMING CALL HANDLER + * + **************************************************************************** + */ + + +static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata); + +/* The module instance. */ +static pjsip_module mod_call = +{ + NULL, NULL, /* prev, next. */ + { "mod-perf-call", 13 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &mod_call_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + + + +/* + * Handle incoming requests. + * Because this module is registered to the INVITE module too, this + * callback may be called for requests inside a dialog. + */ +static pj_bool_t mod_call_on_rx_request(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + pjsip_dialog *dlg; + pjsip_inv_session *inv; + pjsip_tx_data *response; + struct call_data *call_data; + unsigned options; + 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 request that's already associated with + * existing dialog or transaction. + */ + if (pjsip_rdata_get_dlg(rdata) || pjsip_rdata_get_tsx(rdata)) + return PJ_FALSE; + + + /* Verify that we can handle the request. */ + options = 0; + status = pjsip_inv_verify_request(rdata, &options, NULL, NULL, + settings.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(settings.endpt, &res_addr, response, + NULL, NULL); + + } else { + + /* Respond with 500 (Internal Server Error) */ + pjsip_endpt_respond_stateless(settings.endpt, rdata, 500, NULL, + NULL, NULL); + } + + return PJ_TRUE; + } + + /* + * Yes we can handle the incoming INVITE request. + */ + + /* Create dialog. */ + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, NULL, &dlg); + if (status != PJ_SUCCESS) { + pjsip_dlg_respond(dlg, rdata, 500, NULL); + return PJ_TRUE; + } + + /* Create invite session: */ + status = pjsip_inv_create_uas( dlg, rdata, local_sdp, 0, &inv); + if (status != PJ_SUCCESS) { + + pjsip_dlg_respond(dlg, rdata, 500, NULL); + + // TODO: Need to delete dialog + return PJ_TRUE; + } + + /* Create and associate call data. */ + call_data = pj_pool_zalloc(inv->pool, sizeof(struct call_data)); + call_data->inv = inv; + call_data->bye_timer.user_data = call_data; + inv->mod_data[mod_call.id] = call_data; + + /* Answer with 200 straight away. */ + status = pjsip_inv_initial_answer(inv, rdata, 200, + NULL, NULL, &response); + if (status != PJ_SUCCESS) { + + app_perror(THIS_FILE, "Unable to create 200 response", status); + + pjsip_dlg_respond(dlg, rdata, 500, NULL); + + // TODO: Need to delete dialog + + } else { + status = pjsip_inv_send_msg(inv, response, NULL); + if (status != PJ_SUCCESS) + app_perror(THIS_FILE, "Unable to send 100 response", status); + } + + + return PJ_TRUE; +} + + +/**************************************************************************** + * + * OUTGOING CALL GENERATOR + * + **************************************************************************** + */ + +/** + * Make outgoing call. + */ +pj_status_t call_spawn_test( const pj_str_t *target, + const pj_str_t *from, + const pj_str_t *to, + unsigned cred_cnt, + const pjsip_cred_info cred[], + const pjsip_route_hdr *route_set, + void *test_data, + void (*completion_cb)(void*,pj_bool_t)) +{ + pjsip_dialog *dlg; + pjsip_inv_session *inv; + pjsip_tx_data *tdata; + struct call_data *call_data; + pj_status_t status; + + /* Create outgoing dialog: */ + status = pjsip_dlg_create_uac( pjsip_ua_instance(), + from, NULL, + to, target, + &dlg); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Dialog creation failed", status); + return status; + } + + /* Create the INVITE session: */ + status = pjsip_inv_create_uac( dlg, local_sdp, 0, &inv); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Invite session creation failed", status); + goto on_error; + } + + + /* Set dialog Route-Set: */ + if (route_set) + pjsip_dlg_set_route_set(dlg, route_set); + + + /* Set credentials: */ + pjsip_auth_clt_set_credentials( &dlg->auth_sess, cred_cnt, cred); + + + /* Create initial INVITE: */ + status = pjsip_inv_invite(inv, &tdata); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to create initial INVITE request", + status); + goto on_error; + } + + + /* Create and associate our call data */ + call_data = pj_pool_zalloc(inv->pool, sizeof(struct call_data)); + call_data->inv = inv; + call_data->test_data = test_data; + call_data->bye_timer.user_data = call_data; + call_data->completion_cb = completion_cb; + + inv->mod_data[mod_call.id] = call_data; + + + /* Send initial INVITE: */ + status = pjsip_inv_send_msg(inv, tdata, NULL); + if (status != PJ_SUCCESS) { + app_perror( THIS_FILE, "Unable to send initial INVITE request", + status); + goto on_error; + } + + + return PJ_SUCCESS; + + +on_error: + PJ_TODO(DESTROY_DIALOG_ON_FAIL); + return status; +} + + +/* Timer callback to send BYE. */ +static void bye_callback( pj_timer_heap_t *ht, pj_timer_entry *e) +{ + struct call_data *call_data = e->user_data; + pjsip_tx_data *tdata; + pj_status_t status; + + e->id = 0; + + status = pjsip_inv_end_session(call_data->inv, PJSIP_SC_REQUEST_TIMEOUT, + NULL, &tdata); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to create BYE", status); + return; + } + + status = pjsip_inv_send_msg(call_data->inv, tdata, NULL); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to send BYE", status); + return; + } + +} + +/* + * This callback receives notification from invite session when the + * session state has changed. + */ +static void call_on_state_changed( pjsip_inv_session *inv, pjsip_event *e) +{ + struct call_data *call_data; + + call_data = inv->mod_data[mod_call.id]; + if (call_data == NULL) + return; + + /* Once call has been confirmed, schedule timer to terminate the call. */ + if (inv->state == PJSIP_INV_STATE_CONFIRMED) { + + pj_time_val interval; + + call_data->confirmed = PJ_TRUE; + + /* For UAC, schedule time to send BYE. + * For UAS, schedule time to disconnect INVITE, just in case BYE + * is not received. + */ + if (inv->role == PJSIP_ROLE_UAC) + interval.sec = settings.duration, interval.msec = 0; + else + interval.sec = settings.duration+5, interval.msec = 0; + + call_data->bye_timer.id = TIMER_ID; + call_data->bye_timer.cb = &bye_callback; + pjsip_endpt_schedule_timer(settings.endpt, &call_data->bye_timer, + &interval); + + } + /* If call has been terminated, cancel our timer, if any. + * And call tester's callback. + */ + else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { + + /* Cancel timer, if any. */ + if (call_data->bye_timer.id == TIMER_ID) { + call_data->bye_timer.id = 0; + pjsip_endpt_cancel_timer(settings.endpt, &call_data->bye_timer); + } + + /* Detach call data from the invite session. */ + inv->mod_data[mod_call.id] = NULL; + + /* Call tester callback. */ + if (call_data->completion_cb) { + (*call_data->completion_cb)(call_data->test_data, + call_data->confirmed); + } + } +} + + +/* + * This callback is called by invite session framework when UAC session + * has forked. + */ +static void call_on_forked( pjsip_inv_session *inv, pjsip_event *e) +{ + PJ_UNUSED_ARG(inv); + PJ_UNUSED_ARG(e); + + PJ_TODO(HANDLE_FORKED_DIALOG); +} + + + +/**************************************************************************** + * + * INITIALIZATION + * + **************************************************************************** + */ + +pj_status_t call_handler_init(void) +{ + pjsip_inv_callback inv_cb; + pjmedia_sock_info skinfo; + pj_status_t status; + + /* Register incoming call handler. */ + status = pjsip_endpt_register_module(settings.endpt, &mod_call); + if (status != PJ_SUCCESS) { + app_perror( THIS_FILE, "Unable to register call handler", + status); + return status; + } + + /* Invite session callback: */ + pj_memset(&inv_cb, 0, sizeof(inv_cb)); + inv_cb.on_state_changed = &call_on_state_changed; + inv_cb.on_new_session = &call_on_forked; + + /* Initialize invite session module: */ + status = pjsip_inv_usage_init(settings.endpt, &mod_call, &inv_cb); + if (status != PJ_SUCCESS) { + app_perror( THIS_FILE, "Unable to initialize INVITE session module", + status); + return status; + } + + /* Create dummy SDP. */ + pj_memset(&skinfo, 0, sizeof(skinfo)); + pj_sockaddr_in_init(&skinfo.rtp_addr_name, pj_gethostname(), 4000); + pj_sockaddr_in_init(&skinfo.rtcp_addr_name, pj_gethostname(), 4001); + + status = pjmedia_endpt_create_sdp( settings.med_endpt, settings.pool, + 1, &skinfo, &local_sdp); + if (status != PJ_SUCCESS) { + app_perror( THIS_FILE, "Unable to generate local SDP", + status); + return status; + } + + return PJ_SUCCESS; +} + + diff --git a/pjsip-apps/src/pjsip-perf/handler_options.c b/pjsip-apps/src/pjsip-perf/handler_options.c new file mode 100644 index 00000000..0f643d7b --- /dev/null +++ b/pjsip-apps/src/pjsip-perf/handler_options.c @@ -0,0 +1,148 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "pjsip_perf.h" + + +/* + * This file handles OPTIONS generator and incoming OPTIONS requests. + */ +#define THIS_FILE "handler_options.c" + + +/**************************************************************************** + * + * INCOMING OPTIONS HANDLER + * + **************************************************************************** + */ + + +static pj_bool_t mod_options_on_rx_request(pjsip_rx_data *rdata); + + +/* The module instance. */ +static pjsip_module mod_perf_options = +{ + NULL, NULL, /* prev, next. */ + { "mod-perf-options", 16 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &mod_options_on_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 mod_options_on_rx_request(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + + if (msg->line.req.method.id == PJSIP_OPTIONS_METHOD) { + + if (settings.stateless) { + pjsip_endpt_respond_stateless( settings.endpt, rdata, 200, NULL, + NULL, NULL); + } else { + + pjsip_endpt_respond( settings.endpt, NULL, rdata, 200, NULL, + NULL, NULL, NULL); + } + + return PJ_TRUE; + } + + return PJ_FALSE; +} + + +/**************************************************************************** + * + * OUTGOING OPTIONS GENERATOR. + * + **************************************************************************** + */ + +struct callback_data +{ + void *test_data; + void (*completion_cb)(void*,pj_bool_t); +}; + +static void options_callback(void *token, const pjsip_event *e) +{ + struct callback_data *cb_data = token; + + if (e->type == PJSIP_EVENT_TSX_STATE) { + (*cb_data->completion_cb)(cb_data->test_data, + e->body.tsx_state.tsx->status_code/100==2); + } +} + +pj_status_t options_spawn_test(const pj_str_t *target, + const pj_str_t *from, + const pj_str_t *to, + unsigned cred_cnt, + const pjsip_cred_info cred[], + const pjsip_route_hdr *route_set, + void *test_data, + void (*completion_cb)(void*,pj_bool_t)) +{ + pj_status_t status; + struct callback_data *cb_data; + pjsip_tx_data *tdata; + + status = pjsip_endpt_create_request( settings.endpt, + &pjsip_options_method, + target, + from, + to, + NULL, NULL, -1, NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to create request", status); + return status; + } + + cb_data = pj_pool_alloc(tdata->pool, sizeof(struct callback_data)); + cb_data->test_data = test_data; + cb_data->completion_cb = completion_cb; + + status = pjsip_endpt_send_request( settings.endpt, tdata, -1, + cb_data, &options_callback); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to send request", status); + return status; + } + + return PJ_SUCCESS; +} + + +pj_status_t options_handler_init(void) +{ + return pjsip_endpt_register_module(settings.endpt, &mod_perf_options); +} + + diff --git a/pjsip-apps/src/pjsip-perf/main.c b/pjsip-apps/src/pjsip-perf/main.c new file mode 100644 index 00000000..91817e2c --- /dev/null +++ b/pjsip-apps/src/pjsip-perf/main.c @@ -0,0 +1,668 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "pjsip_perf.h" +#include <pjsua-lib/getopt.h> +#include <stdlib.h> /* atoi */ + +#define THIS_FILE "main.c" + +pjsip_perf_settings settings; + +/* Show error message. */ +void app_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(3,(sender, "%s: %s [code=%d]", title, errmsg, status)); +} + + +/* Init default settings. */ +static void init_settings(void) +{ + pj_status_t status; + + settings.stateless = 1; + settings.start_rate = 10; + settings.max_capacity = 64; + settings.duration = 0; + settings.thread_cnt = 1; + settings.local_port = 5060; + + pjsip_method_set(&settings.method, PJSIP_OPTIONS_METHOD); + + pj_init(); + + /* Create caching pool. */ + pj_caching_pool_init(&settings.cp, &pj_pool_factory_default_policy, + 4 * 1024 * 1024); + + /* Create application pool. */ + settings.pool = pj_pool_create(&settings.cp.factory, "pjsip-perf", 1024, + 1024, NULL); + + /* Create endpoint. */ + status = pjsip_endpt_create(&settings.cp.factory, NULL, &settings.endpt); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to create endpoint", status); + return; + } + +} + +/* Poll function. */ +static int PJ_THREAD_FUNC poll_pjsip(void *arg) +{ + pj_status_t last_err = 0; + + PJ_UNUSED_ARG(arg); + + do { + pj_time_val timeout = { 0, 10 }; + pj_status_t status; + + status = pjsip_endpt_handle_events (settings.endpt, &timeout); + if (status != last_err) { + last_err = status; + app_perror(THIS_FILE, "handle_events() returned error", status); + } + } while (!settings.quit_flag); + + return 0; +} + +/* Initialize */ +static pj_status_t initialize(void) +{ + pj_sockaddr_in addr; + int i; + pj_status_t status; + + /* Create UDP transport. */ + pj_memset(&addr, 0, sizeof(addr)); + addr.sin_family = PJ_AF_INET; + addr.sin_port = pj_htons((pj_uint16_t)settings.local_port); + status = pjsip_udp_transport_start(settings.endpt, &addr, NULL, + settings.thread_cnt, NULL); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to start UDP transport", status); + return status; + } + + + /* Initialize transaction layer: */ + status = pjsip_tsx_layer_init_module(settings.endpt); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Transaction layer initialization error", + status); + return status; + } + + /* Initialize UA layer module: */ + pjsip_ua_init_module( settings.endpt, NULL ); + + /* Init core SIMPLE module : */ + pjsip_evsub_init_module(settings.endpt); + + /* Init presence module: */ + pjsip_pres_init_module( settings.endpt, pjsip_evsub_instance()); + + /* Init xfer/REFER module */ + pjsip_xfer_init_module( settings.endpt ); + + /* Init multimedia endpoint. */ + status = pjmedia_endpt_create(&settings.cp.factory, &settings.med_endpt); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to create media endpoint", + status); + return status; + } + + /* Init OPTIONS test handler */ + status = options_handler_init(); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to create OPTIONS handler", status); + return status; + } + + /* Init call test handler */ + status = call_handler_init(); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to initialize call handler", status); + return status; + } + + + /* Start worker thread. */ + for (i=0; i<settings.thread_cnt; ++i) { + status = pj_thread_create(settings.pool, "pjsip-perf", &poll_pjsip, + NULL, 0, 0, &settings.thread[i]); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to create thread", status); + return status; + } + } + + pj_log_set_level(3); + return PJ_SUCCESS; +} + + +/* Shutdown */ +static void shutdown(void) +{ + int i; + + /* Signal and wait worker thread to quit. */ + settings.quit_flag = 1; + + for (i=0; i<settings.thread_cnt; ++i) { + pj_thread_join(settings.thread[i]); + pj_thread_destroy(settings.thread[i]); + } + + pjsip_endpt_destroy(settings.endpt); + pj_caching_pool_destroy(&settings.cp); +} + + +/* Verify that valid SIP url is given. */ +pj_status_t 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 -1; + + pool = pj_pool_create(&settings.cp.factory, "check%p", 1024, 0, NULL); + if (!pool) return -1; + + url = 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) + p = NULL; + + pj_pool_release(pool); + return p ? 0 : -1; +} + + + +/* Usage */ +static void usage(void) +{ + puts("Usage:"); + puts(" pjsip-perf [options] [target]"); + puts("where"); + puts(" target Optional default target URL"); + puts(""); + puts("General options:"); + puts(" --help Display this help screen"); + puts(" --version Display version info"); + puts(""); + puts("SIP options:"); + puts(" --local-port=N SIP local port"); + puts(" --stateless Handle incoming request statelessly if possible"); + puts(" --thread-cnt=N Number of worker threads (default=1)"); + puts(""); + puts("Rate control:"); + puts(" --start-rate=N Start rate in tasks per seconds (default=1)"); + puts(""); + puts("Capacity control:"); + puts(" --max-capacity=N Maximum outstanding sessions (default=64)"); + puts(""); + puts("Duration control:"); + puts(" --duration=secs Sessions duration (default=0)"); + puts(""); +} + + +/* Read options. */ +static pj_status_t parse_options(int argc, char *argv[]) +{ + enum { + OPT_HELP, + OPT_VERSION, + OPT_LOCAL_PORT, + OPT_STATELESS, + OPT_THREAD_CNT, + OPT_START_RATE, + OPT_MAX_CAPACITY, + OPT_DURATION + }; + struct option long_opts[] = { + { "help", 0, 0, OPT_HELP}, + { "version", 0, 0, OPT_VERSION}, + { "local-port", 1, 0, OPT_LOCAL_PORT}, + { "stateless", 0, 0, OPT_STATELESS}, + { "thread-cnt", 1, 0, OPT_THREAD_CNT}, + { "start-rate", 1, 0, OPT_START_RATE}, + { "max-capacity", 1, 0, OPT_MAX_CAPACITY}, + { "duration", 1, 0, OPT_DURATION}, + { NULL, 0, 0, 0} + }; + int c, option_index; + + optind = 0; + while ((c=getopt_long(argc, argv, "", long_opts, &option_index)) != -1) { + switch (c) { + + case OPT_HELP: + usage(); + return PJ_EINVAL; + + case OPT_VERSION: + pj_dump_config(); + return PJ_EINVAL; + + case OPT_LOCAL_PORT: + settings.local_port = atoi(optarg); + if (settings.local_port < 1 || settings.local_port > 65535) { + PJ_LOG(1,(THIS_FILE,"Invalid --local-port %s", optarg)); + return PJ_EINVAL; + } + break; + + case OPT_STATELESS: + settings.stateless = 1; + break; + + case OPT_THREAD_CNT: + settings.thread_cnt = atoi(optarg); + if (settings.thread_cnt < 1 || + settings.thread_cnt > PJ_ARRAY_SIZE(settings.thread)) + { + PJ_LOG(1,(THIS_FILE,"Invalid --thread-cnt %s", optarg)); + return PJ_EINVAL; + } + break; + + case OPT_START_RATE: + settings.start_rate = atoi(optarg); + if (settings.start_rate < 1 || settings.start_rate > 1000000) { + PJ_LOG(1,(THIS_FILE,"Invalid --start-rate %s", optarg)); + return PJ_EINVAL; + } + break; + + case OPT_MAX_CAPACITY: + settings.max_capacity = atoi(optarg); + if (settings.max_capacity < 1 || settings.max_capacity > 65000) { + PJ_LOG(1,(THIS_FILE, + "Invalid --max-capacity %s (range=1-65000)", + optarg)); + return PJ_EINVAL; + } + break; + + case OPT_DURATION: + settings.duration = atoi(optarg); + if (settings.duration < 0 || settings.duration > 1000000) { + PJ_LOG(1,(THIS_FILE,"Invalid --duration %s", optarg)); + return PJ_EINVAL; + } + break; + + } + } + + if (optind != argc) { + if (verify_sip_url(argv[optind]) != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, "Invalid SIP URL %s", argv[optind])); + return PJ_EINVAL; + } + + settings.target = pj_str(argv[optind]); + ++optind; + } + + if (optind != argc) { + printf("Error: unknown options %s\n", argv[optind]); + return PJ_EINVAL; + } + + return PJ_SUCCESS; +} + + +static void spawn_batch( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry ); + +/* Completion callback. */ +static void completion_cb(void *token, pj_bool_t success) +{ + batch *batch = token; + + if (success) + batch->success++; + else + batch->failed++; + + if (batch->success+batch->failed == batch->rate) { + pj_time_val elapsed, sess_elapsed; + unsigned msec; + + pj_gettimeofday(&batch->end_time); + elapsed = sess_elapsed = batch->end_time; + + PJ_TIME_VAL_SUB(elapsed, batch->start_time); + PJ_TIME_VAL_SUB(sess_elapsed, settings.session->start_time); + msec = PJ_TIME_VAL_MSEC(elapsed); + if (msec == 0) msec = 1; + + PJ_LOG(3,(THIS_FILE, "%02d:%02d:%02d: %d tasks in %d.%ds (%d tasks/sec)", + (sess_elapsed.sec / 3600), + (sess_elapsed.sec % 3600) / 60, + (sess_elapsed.sec % 60), + batch->rate, + elapsed.sec, elapsed.msec, + batch->rate * 1000 / msec)); + + if (!settings.session->stopping) { + pj_time_val interval; + + if (msec >= 1000) + interval.sec = interval.msec = 0; + else + interval.sec = 0, interval.msec = 1000-msec; + + settings.timer.cb = &spawn_batch; + pjsip_endpt_schedule_timer( settings.endpt, &settings.timer, &interval); + } else { + PJ_LOG(3,(THIS_FILE, "%.*s test session completed", + (int)settings.session->method.name.slen, + settings.session->method.name.ptr)); + pj_pool_release(settings.session->pool); + settings.session = NULL; + } + } +} + +/* Spawn new batch. */ +static void spawn_batch( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry ) +{ + session *sess = settings.session; + batch *batch; + pj_status_t status = PJ_SUCCESS; + pjsip_cred_info cred_info[1]; + pj_time_val now, spawn_time, sess_time; + + unsigned i; + + if (!pj_list_empty(&sess->free_list)) { + batch = sess->free_list.next; + pj_list_erase(batch); + } else { + batch = pj_pool_alloc(sess->pool, sizeof(struct batch)); + } + + pj_gettimeofday(&batch->start_time); + batch->rate = settings.cur_rate; + batch->started = 0; + batch->success = 0; + batch->failed = 0; + + pj_list_push_back(&sess->active_list, batch); + + for (i=0; i<batch->rate; ++i) { + pj_str_t from = { "sip:user@127.0.0.1", 18}; + + if (sess->method.id == PJSIP_OPTIONS_METHOD) { + status = options_spawn_test(&settings.target, &from, + &settings.target, + 0, cred_info, NULL, batch, + &completion_cb); + } else if (sess->method.id == PJSIP_INVITE_METHOD) { + status = call_spawn_test( &settings.target, &from, + &settings.target, + 0, cred_info, NULL, batch, + &completion_cb); + } + if (status != PJ_SUCCESS) + break; + + batch->started++; + } + + pj_gettimeofday(&now); + spawn_time = sess_time = now; + PJ_TIME_VAL_SUB(spawn_time, batch->start_time); + PJ_TIME_VAL_SUB(sess_time, sess->start_time); + + sess->total_created += batch->started; + + batch = sess->active_list.next; + sess->outstanding = 0; + while (batch != &sess->active_list) { + sess->outstanding += (batch->started - batch->success - batch->failed); + + if (batch->started == batch->success + batch->failed) { + struct batch *next = batch->next; + pj_list_erase(batch); + pj_list_push_back(&sess->free_list, batch); + batch = next; + } else { + batch = batch->next; + } + } +} + + +/* Start new session */ +static void start_session(pj_bool_t auto_repeat) +{ + pj_time_val interval = { 1, 0 }; + pj_pool_t *pool; + session *sess; + + pool = pjsip_endpt_create_pool(settings.endpt, "session", 4000, 4000); + if (!pool) { + app_perror(THIS_FILE, "Unable to create pool", PJ_ENOMEM); + return; + } + + sess = pj_pool_zalloc(pool, sizeof(session)); + sess->pool = pool; + sess->stopping = auto_repeat ? 0 : 1; + sess->method = settings.method; + + pj_list_init(&sess->active_list); + pj_list_init(&sess->free_list); + pj_gettimeofday(&sess->start_time); + + settings.session = sess; + + spawn_batch(NULL, NULL); +} + + +/* Dump state */ +static void dump(pj_bool_t detail) +{ + pjsip_endpt_dump(settings.endpt, detail); + pjsip_tsx_layer_dump(detail); + pjsip_ua_dump(detail); +} + + +/* help screen */ +static void help_screen(void) +{ + puts ("+============================================================================+"); + printf("| Current mode: %-10s Current rate: %-5d Call Capacity: %-7d |\n", + settings.method.name.ptr, settings.cur_rate, settings.max_capacity); + printf("| Call Duration: %-7d |\n", + settings.duration); + + puts ("+--------------------------------------+-------------------------------------+"); + puts ("| Test Settings | Misc Commands: |"); + puts ("| | |"); + puts ("| m Change mode | |"); + puts ("| + - Increment/decrement rate by 10 | d Dump status |"); + puts ("| * / Increment/decrement rate by 100 | d1 Dump detailed (e.g. tables) |"); + puts ("+--------------------------------------+-------------------------------------+"); + puts ("| Test Commands |"); + puts ("| |"); + puts ("| s Start single test batch |"); + puts ("| sc Start continuous test x Stop continuous tests |"); + puts ("+----------------------------------------------------------------------------+"); + puts ("| q: Quit |"); + puts ("+============================================================================+"); + puts (""); + +} + +/* + * Input simple string + */ +static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len) +{ + char *p; + + printf("%s (empty to cancel): ", title); fflush(stdout); + fgets(buf, len, stdin); + + /* Remove trailing newlines. */ + for (p=buf; ; ++p) { + if (*p=='\r' || *p=='\n') *p='\0'; + else if (!*p) break; + } + + if (!*buf) + return PJ_FALSE; + + return PJ_TRUE; +} + +/* Main input loop */ +static void test_main(void) +{ + char menuin[10]; + char input[80]; + + settings.cur_rate = settings.start_rate; + + help_screen(); + + for (;;) { + printf(">>>> "); fflush(stdout); + + fgets(menuin, sizeof(menuin), stdin); + + switch (menuin[0]) { + case 's': + if (settings.session != NULL) { + PJ_LOG(3,(THIS_FILE,"Error: another session is in progress")); + } else if (settings.target.slen == 0) { + PJ_LOG(3,(THIS_FILE,"Error: target URL is not configured")); + } else { + start_session(menuin[1]=='c'); + } + break; + + case 'x': + if (settings.session) { + settings.session->stopping = 1; + } else { + PJ_LOG(3,(THIS_FILE,"Error: no sessions")); + } + break; + + case 'm': + if (!simple_input("Change method [OPTIONS,INVITE]", input, sizeof(input))) + continue; + + if (pj_ansi_stricmp(input, "OPTIONS")==0) + pjsip_method_set(&settings.method, PJSIP_OPTIONS_METHOD); + else if (pj_ansi_stricmp(input, "INVITE")==0) + pjsip_method_set(&settings.method, PJSIP_INVITE_METHOD); + else { + puts("Error: invalid method"); + } + break; + + case 'd': + dump(menuin[1]=='1'); + break; + + case '+': + settings.cur_rate += 10; + PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate)); + break; + + case '-': + if (settings.cur_rate > 10) { + settings.cur_rate -= 10; + PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate)); + } + break; + + case '*': + settings.cur_rate += 100; + PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate)); + break; + + case '/': + if (settings.cur_rate > 100) { + settings.cur_rate -= 100; + PJ_LOG(3,(THIS_FILE, "Rate is now %d", settings.cur_rate)); + } + break; + + case 'q': + return; + + default: + help_screen(); + break; + + } + } +} + + +/* main() */ +int main(int argc, char *argv[]) +{ + pj_status_t status; + + init_settings(); + + status = parse_options(argc, argv); + if (status != PJ_SUCCESS) + return 1; + + status = initialize(); + if (status != PJ_SUCCESS) + return 1; + + + test_main(); + + shutdown(); + + return 0; +} + diff --git a/pjsip-apps/src/pjsip-perf/pjsip_perf.h b/pjsip-apps/src/pjsip-perf/pjsip_perf.h new file mode 100644 index 00000000..103ea205 --- /dev/null +++ b/pjsip-apps/src/pjsip-perf/pjsip_perf.h @@ -0,0 +1,164 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJSIP_PERF_H__ +#define __PJSIP_PERF_H__ + +#include <pjsua-lib/pjsua.h> + + +PJ_BEGIN_DECL + + +typedef struct batch batch; +typedef struct session session; + +/** + * A test batch. + */ +struct batch +{ + PJ_DECL_LIST_MEMBER(struct batch); + + unsigned rate; + unsigned started; + unsigned success; + unsigned failed; + pj_time_val start_time; + pj_time_val end_time; +}; + +/** + * Test session. + */ +struct session +{ + pj_pool_t *pool; + pj_time_val start_time; + pj_bool_t stopping; + pjsip_method method; + struct batch active_list; + struct batch free_list; + + unsigned outstanding; + unsigned total_created; +}; + + +/** + * Request parameter. + */ +struct request_param +{ + pj_str_t dst; + pj_str_t src; + pjsip_cred_info cred; +}; + + +typedef struct request_param request_param; + + +void app_perror(const char *sender, const char *title, pj_status_t status); + +/* OPTIONS test */ +pj_status_t options_handler_init(void); +pj_status_t options_spawn_test(const pj_str_t *target, + const pj_str_t *from, + const pj_str_t *to, + unsigned cred_cnt, + const pjsip_cred_info cred[], + const pjsip_route_hdr *route_set, + void *test_data, + void (*completion_cb)(void*,pj_bool_t)); + +/* CALL test */ +pj_status_t call_handler_init(void); +pj_status_t call_spawn_test( const pj_str_t *target, + const pj_str_t *from, + const pj_str_t *to, + unsigned cred_cnt, + const pjsip_cred_info cred[], + const pjsip_route_hdr *route_set, + void *test_data, + void (*completion_cb)(void*,pj_bool_t)); + + + +/** + * Global settings + */ +struct pjsip_perf_settings +{ + /* Global */ + pj_caching_pool cp; + pj_pool_t *pool; + pjsip_endpoint *endpt; + pj_mutex_t *mutex; + + /* Network: */ + int local_port; + + /* Threads. */ + pj_bool_t quit_flag; + int thread_cnt; + pj_thread_t *thread[16]; + + /* Outgoing request method: */ + pjsip_method method; + + /* Default target: */ + pj_str_t target; + + /* Media: */ + pjmedia_endpt *med_endpt; + pjmedia_conf *mconf; + + /* Handling incoming requests: */ + pj_bool_t stateless; + + /* Rate control. */ + pj_uint32_t start_rate; + pj_uint32_t cur_rate; + + /* Capacity control. */ + pj_uint32_t max_capacity; + + /* Duration control: */ + pj_uint32_t duration; + + /* Test control: */ + session *session; + pj_timer_entry timer; +}; + + +typedef struct pjsip_perf_settings pjsip_perf_settings; + +extern pjsip_perf_settings settings; + + + +PJ_END_DECL + + +#endif /* __PJSIP_PERF_H__ */ + + + + diff --git a/pjsip-apps/src/pjsua/main.c b/pjsip-apps/src/pjsua/main.c new file mode 100644 index 00000000..e1c584ae --- /dev/null +++ b/pjsip-apps/src/pjsua/main.c @@ -0,0 +1,944 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <stdlib.h> /* atoi */ + + +#define THIS_FILE "main.c" + +/* Current dialog */ +static int current_acc; +static int current_call = -1; + + +/* + * Find next call. + */ +static pj_bool_t find_next_call(void) +{ + int i; + + for (i=current_call+1; i<(int)pjsua.max_calls; ++i) { + if (pjsua.calls[i].inv != NULL) { + current_call = i; + return PJ_TRUE; + } + } + + for (i=0; i<current_call; ++i) { + if (pjsua.calls[i].inv != NULL) { + current_call = i; + return PJ_TRUE; + } + } + + current_call = -1; + return PJ_FALSE; +} + + +/* + * Find previous call. + */ +static pj_bool_t find_prev_call(void) +{ + int i; + + for (i=current_call-1; i>=0; --i) { + if (pjsua.calls[i].inv != NULL) { + current_call = i; + return PJ_TRUE; + } + } + + for (i=pjsua.max_calls-1; i>current_call; --i) { + if (pjsua.calls[i].inv != NULL) { + current_call = i; + return PJ_TRUE; + } + } + + current_call = -1; + return PJ_FALSE; +} + + + +/* + * Notify UI when invite state has changed. + */ +void pjsua_ui_inv_on_state_changed(int call_index, pjsip_event *e) +{ + pjsua_call *call = &pjsua.calls[call_index]; + + PJ_UNUSED_ARG(e); + + PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", + call_index, + pjsua_inv_state_names[call->inv->state])); + + if (call->inv->state == PJSIP_INV_STATE_DISCONNECTED) { + call->inv = NULL; + if ((int)call->index == current_call) { + find_next_call(); + } + + } else { + + if (call && current_call==-1) + current_call = call->index; + + } +} + +/** + * Notify UI when registration status has changed. + */ +void pjsua_ui_regc_on_state_changed(int code) +{ + PJ_UNUSED_ARG(code); + + // Log already written. +} + + +/* + * Print buddy list. + */ +static void print_buddy_list(void) +{ + int i; + + puts("Buddy list:"); + + if (pjsua.buddy_cnt == 0) + puts(" -none-"); + else { + for (i=0; i<pjsua.buddy_cnt; ++i) { + const char *status; + + if (pjsua.buddies[i].sub == NULL || + pjsua.buddies[i].status.info_cnt==0) + { + status = " ? "; + } + else if (pjsua.buddies[i].status.info[0].basic_open) + status = " Online"; + else + status = "Offline"; + + printf(" [%2d] <%s> %s\n", + i+1, status, pjsua.buddies[i].uri.ptr); + } + } + puts(""); +} + + +/* + * Print account status. + */ +static void print_acc_status(int acc_index) +{ + char reg_status[128]; + + if (pjsua.acc[acc_index].regc == NULL) { + pj_ansi_strcpy(reg_status, " -not registered to server-"); + + } else if (pjsua.acc[acc_index].reg_last_err != PJ_SUCCESS) { + pj_strerror(pjsua.acc[acc_index].reg_last_err, reg_status, sizeof(reg_status)); + + } else if (pjsua.acc[acc_index].reg_last_code>=200 && + pjsua.acc[acc_index].reg_last_code<=699) { + + pjsip_regc_info info; + + pjsip_regc_get_info(pjsua.acc[acc_index].regc, &info); + + pj_snprintf(reg_status, sizeof(reg_status), + "%s (%.*s;expires=%d)", + pjsip_get_status_text(pjsua.acc[acc_index].reg_last_code)->ptr, + (int)info.client_uri.slen, + info.client_uri.ptr, + info.next_reg); + + } else { + pj_sprintf(reg_status, "in progress (%d)", + pjsua.acc[acc_index].reg_last_code); + } + + printf("[%2d] Registration status: %s\n", acc_index, reg_status); + printf(" Online status: %s\n", + (pjsua.acc[acc_index].online_status ? "Online" : "Invisible")); +} + +/* + * Show a bit of help. + */ +static void keystroke_help(void) +{ + int i; + + printf(">>>>\n"); + + for (i=0; i<pjsua.acc_cnt; ++i) + print_acc_status(i); + + print_buddy_list(); + + //puts("Commands:"); + puts("+=============================================================================+"); + puts("| Call Commands: | IM & Presence: | Misc: |"); + puts("| | | |"); + puts("| m Make new call | i Send IM | o Send OPTIONS |"); + puts("| M Make multiple calls | s Subscribe presence | rr (Re-)register |"); + puts("| a Answer call | u Unsubscribe presence | ru Unregister |"); + puts("| h Hangup call | t ToGgle Online status | d Dump status |"); + puts("| H Hold call | | dc Dump config |"); + puts("| v re-inVite (release hold) +--------------------------+-------------------+"); + puts("| ] Select next dialog | Conference Command | |"); + puts("| [ Select previous dialog | cl List ports | |"); + puts("| x Xfer call | cc Connect port | |"); + puts("| # Send DTMF string | cd Disconnect port | |"); + puts("+------------------------------+--------------------------+-------------------+"); + puts("| q QUIT |"); + puts("+=============================================================================+"); +} + + +/* + * Input simple string + */ +static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len) +{ + char *p; + + printf("%s (empty to cancel): ", title); fflush(stdout); + fgets(buf, len, stdin); + + /* Remove trailing newlines. */ + for (p=buf; ; ++p) { + if (*p=='\r' || *p=='\n') *p='\0'; + else if (!*p) break; + } + + if (!*buf) + return PJ_FALSE; + + return PJ_TRUE; +} + + +#define NO_NB -2 +struct input_result +{ + int nb_result; + char *uri_result; +}; + + +/* + * Input URL. + */ +static void ui_input_url(const char *title, char *buf, int len, + struct input_result *result) +{ + result->nb_result = NO_NB; + result->uri_result = NULL; + + print_buddy_list(); + + printf("Choices:\n" + " 0 For current dialog.\n" + " -1 All %d buddies in buddy list\n" + " [1 -%2d] Select from buddy list\n" + " URL An URL\n" + " <Enter> Empty input (or 'q') to cancel\n" + , pjsua.buddy_cnt, pjsua.buddy_cnt); + printf("%s: ", title); + + fflush(stdout); + fgets(buf, len, stdin); + len = strlen(buf); + + /* Left trim */ + while (isspace(*buf)) { + ++buf; + --len; + } + + /* Remove trailing newlines */ + while (len && (buf[len-1] == '\r' || buf[len-1] == '\n')) + buf[--len] = '\0'; + + if (len == 0 || buf[0]=='q') + return; + + if (isdigit(*buf) || *buf=='-') { + + int i; + + if (*buf=='-') + i = 1; + else + i = 0; + + for (; i<len; ++i) { + if (!isdigit(buf[i])) { + puts("Invalid input"); + return; + } + } + + result->nb_result = atoi(buf); + + if (result->nb_result > 0 && result->nb_result <= (int)pjsua.buddy_cnt) { + --result->nb_result; + return; + } + if (result->nb_result == -1) + return; + + puts("Invalid input"); + result->nb_result = NO_NB; + return; + + } else { + pj_status_t status; + + if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Invalid URL", status); + return; + } + + result->uri_result = buf; + } +} + +static void conf_list(void) +{ + unsigned i, count; + pjmedia_conf_port_info info[PJSUA_MAX_CALLS]; + + printf("Conference ports:\n"); + + count = PJ_ARRAY_SIZE(info); + pjmedia_conf_get_ports_info(pjsua.mconf, &count, info); + for (i=0; i<count; ++i) { + char txlist[PJSUA_MAX_CALLS*4+10]; + int j; + pjmedia_conf_port_info *port_info = &info[i]; + + txlist[0] = '\0'; + for (j=0; j<pjsua.max_calls+PJSUA_CONF_MORE_PORTS; ++j) { + char s[10]; + if (port_info->listener[j]) { + pj_sprintf(s, "#%d ", j); + pj_ansi_strcat(txlist, s); + } + } + printf("Port #%02d %20.*s transmitting to: %s\n", + port_info->slot, + (int)port_info->name.slen, + port_info->name.ptr, + txlist); + + } + puts(""); +} + + +static void ui_console_main(void) +{ + char menuin[10]; + char buf[128]; + int i, count; + char *uri; + struct input_result result; + + keystroke_help(); + + for (;;) { + + printf(">>> "); + fflush(stdout); + + fgets(menuin, sizeof(menuin), stdin); + + switch (menuin[0]) { + + case 'm': + /* Make call! : */ + printf("(You currently have %d calls)\n", pjsua.call_cnt); + + ui_input_url("Make call", buf, sizeof(buf), &result); + if (result.nb_result != NO_NB) { + if (result.nb_result == -1) + puts("You can't do that with make call!"); + else + pjsua_make_call( current_acc, + pjsua.buddies[result.nb_result].uri.ptr, + NULL); + } else if (result.uri_result) + pjsua_make_call( current_acc, result.uri_result, NULL); + + break; + + case 'M': + /* Make multiple calls! : */ + printf("(You currently have %d calls)\n", pjsua.call_cnt); + + ui_input_url("Make call", buf, sizeof(buf), &result); + if (result.nb_result != NO_NB) { + if (result.nb_result == -1) { + puts("You can't do that with make call!"); + continue; + } + uri = pjsua.buddies[result.nb_result].uri.ptr; + } else { + uri = result.uri_result; + } + + if (!simple_input("Number of calls", menuin, sizeof(menuin))) + continue; + + count = atoi(menuin); + if (count < 1) + continue; + + for (i=0; i<atoi(menuin); ++i) { + pj_status_t status; + + status = pjsua_make_call(current_acc, uri, NULL); + if (status != PJ_SUCCESS) + break; + } + break; + + case 'a': + + if (current_call == -1 || + pjsua.calls[current_call].inv->role != PJSIP_ROLE_UAS || + pjsua.calls[current_call].inv->state >= PJSIP_INV_STATE_CONNECTING) + { + puts("No pending incoming call"); + fflush(stdout); + continue; + + } else { + pj_status_t status; + pjsip_tx_data *tdata; + + if (!simple_input("Answer with code (100-699)", buf, sizeof(buf))) + continue; + + if (atoi(buf) < 100) + continue; + + /* + * Must check again! + * Call may have been disconnected while we're waiting for + * keyboard input. + */ + if (current_call == -1) { + puts("Call has been disconnected"); + fflush(stdout); + continue; + } + + status = pjsip_inv_answer(pjsua.calls[current_call].inv, + atoi(buf), + NULL, NULL, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_inv_send_msg(pjsua.calls[current_call].inv, + tdata, NULL); + + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Unable to create/send response", + status); + } + + break; + + + case 'h': + + if (current_call == -1) { + puts("No current call"); + fflush(stdout); + continue; + + } else { + pjsua_call_hangup(current_call, PJSIP_SC_DECLINE); + } + break; + + case ']': + case '[': + /* + * Cycle next/prev dialog. + */ + if (menuin[0] == ']') { + find_next_call(); + + } else { + find_prev_call(); + } + + if (current_call != -1) { + char url[PJSIP_MAX_URL_SIZE]; + int len; + const pjsip_uri *u; + + u = pjsua.calls[current_call].inv->dlg->remote.info->uri; + len = pjsip_uri_print(0, u, url, sizeof(url)-1); + if (len < 1) { + pj_ansi_strcpy(url, "<uri is too long>"); + } else { + url[len] = '\0'; + } + + PJ_LOG(3,(THIS_FILE,"Current dialog: %s", url)); + + } else { + PJ_LOG(3,(THIS_FILE,"No current dialog")); + } + break; + + case 'H': + /* + * Hold call. + */ + if (current_call != -1) { + + pjsua_call_set_hold(current_call); + + } else { + PJ_LOG(3,(THIS_FILE, "No current call")); + } + break; + + case 'v': + /* + * Send re-INVITE (to release hold, etc). + */ + if (current_call != -1) { + + pjsua_call_reinvite(current_call); + + } else { + PJ_LOG(3,(THIS_FILE, "No current call")); + } + break; + + case 'x': + /* + * Transfer call. + */ + if (current_call == -1) { + + PJ_LOG(3,(THIS_FILE, "No current call")); + + } else { + int call = current_call; + + ui_input_url("Transfer to URL", buf, sizeof(buf), &result); + + /* Check if call is still there. */ + + if (call != current_call) { + puts("Call has been disconnected"); + continue; + } + + if (result.nb_result != NO_NB) { + if (result.nb_result == -1) + puts("You can't do that with transfer call!"); + else + pjsua_call_xfer( current_call, + pjsua.buddies[result.nb_result].uri.ptr); + + } else if (result.uri_result) { + pjsua_call_xfer( current_call, result.uri_result); + } + } + break; + + case '#': + /* + * Send DTMF strings. + */ + if (current_call == -1) { + + PJ_LOG(3,(THIS_FILE, "No current call")); + + } else if (pjsua.calls[current_call].session == NULL) { + + PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); + + } else { + pj_str_t digits; + int call = current_call; + pj_status_t status; + + if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, + sizeof(buf))) + { + break; + } + + if (call != current_call) { + puts("Call has been disconnected"); + continue; + } + + digits = pj_str(buf); + status = pjmedia_session_dial_dtmf(pjsua.calls[current_call].session, 0, + &digits); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send DTMF", status); + } else { + puts("DTMF digits enqueued for transmission"); + } + } + break; + + case 's': + case 'u': + /* + * Subscribe/unsubscribe presence. + */ + ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result); + if (result.nb_result != NO_NB) { + if (result.nb_result == -1) { + int i; + for (i=0; i<pjsua.buddy_cnt; ++i) + pjsua.buddies[i].monitor = (menuin[0]=='s'); + } else { + pjsua.buddies[result.nb_result].monitor = (menuin[0]=='s'); + } + + pjsua_pres_refresh(current_acc); + + } else if (result.uri_result) { + puts("Sorry, can only subscribe to buddy's presence, " + "not arbitrary URL (for now)"); + } + + break; + + case 'r': + switch (menuin[1]) { + case 'r': + /* + * Re-Register. + */ + pjsua_regc_update(current_acc, PJ_TRUE); + break; + case 'u': + /* + * Unregister + */ + pjsua_regc_update(current_acc, PJ_FALSE); + break; + } + break; + + case 't': + pjsua.acc[current_acc].online_status = + !pjsua.acc[current_acc].online_status; + pjsua_pres_refresh(current_acc); + break; + + case 'c': + switch (menuin[1]) { + case 'l': + conf_list(); + break; + case 'c': + case 'd': + { + char src_port[10], dst_port[10]; + pj_status_t status; + const char *src_title, *dst_title; + + conf_list(); + + src_title = (menuin[1]=='c'? + "Connect src port #": + "Disconnect src port #"); + dst_title = (menuin[1]=='c'? + "To dst port #": + "From dst port #"); + + if (!simple_input(src_title, src_port, sizeof(src_port))) + break; + + if (!simple_input(dst_title, dst_port, sizeof(dst_port))) + break; + + if (menuin[1]=='c') { + status = pjmedia_conf_connect_port(pjsua.mconf, + atoi(src_port), + atoi(dst_port)); + } else { + status = pjmedia_conf_disconnect_port(pjsua.mconf, + atoi(src_port), + atoi(dst_port)); + } + if (status == PJ_SUCCESS) { + puts("Success"); + } else { + puts("ERROR!!"); + } + } + break; + } + break; + + case 'd': + if (menuin[1] == 'c') { + char settings[2000]; + int len; + + len = pjsua_dump_settings(settings, sizeof(settings)); + if (len < 1) + PJ_LOG(3,(THIS_FILE, "Error: not enough buffer")); + else + PJ_LOG(3,(THIS_FILE, + "Dumping configuration (%d bytes):\n%s\n", + len, settings)); + } else { + pjsua_dump(); + } + break; + + case 'q': + goto on_exit; + + default: + keystroke_help(); + break; + } + } + +on_exit: + ; +} + + +/***************************************************************************** + * 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 console_on_rx_msg(pjsip_rx_data *rdata) +{ + PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n" + "%s\n" + "--end msg--", + rdata->msg_info.len, + pjsip_rx_data_get_info(rdata), + rdata->pkt_info.src_name, + rdata->pkt_info.src_port, + 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 console_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:%d:\n" + "%s\n" + "--end msg--", + (tdata->buf.cur - tdata->buf.start), + pjsip_tx_data_get_info(tdata), + tdata->tp_info.dst_name, + tdata->tp_info.dst_port, + tdata->buf.start)); + + /* Always return success, otherwise message will not get sent! */ + return PJ_SUCCESS; +} + +/* The module instance. */ +static pjsip_module console_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() */ + &console_on_rx_msg, /* on_rx_request() */ + &console_on_rx_msg, /* on_rx_response() */ + &console_on_tx_msg, /* on_tx_request. */ + &console_on_tx_msg, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + + +/***************************************************************************** + * Console application custom logging: + */ + + +static FILE *log_file; + + +static void app_log_writer(int level, const char *buffer, int len) +{ + /* Write to both stdout and file. */ + + if (level <= pjsua.app_log_level) + pj_log_write(level, buffer, len); + + if (log_file) { + fwrite(buffer, len, 1, log_file); + fflush(log_file); + } +} + + +void app_logging_init(void) +{ + /* Redirect log function to ours */ + + pj_log_set_log_func( &app_log_writer ); + + /* If output log file is desired, create the file: */ + + if (pjsua.log_filename) + log_file = fopen(pjsua.log_filename, "wt"); +} + + +void app_logging_shutdown(void) +{ + /* Close logging file, if any: */ + + if (log_file) { + fclose(log_file); + log_file = NULL; + } +} + +/***************************************************************************** + * Error display: + */ + +/* + * Display error message for the specified error code. + */ +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 [code=%d]", title, errmsg, status)); +} + + + + +/***************************************************************************** + * main(): + */ +int main(int argc, char *argv[]) +{ + + /* Init default settings. */ + pjsua_default(); + + + /* Initialize pjsua (to create pool etc). + */ + if (pjsua_init() != PJ_SUCCESS) + return 1; + + + /* Parse command line arguments: */ + if (pjsua_parse_args(argc, argv) != PJ_SUCCESS) + return 1; + + + /* Init logging: */ + app_logging_init(); + + + /* Register message logger to print incoming and outgoing + * messages. + */ + pjsip_endpt_register_module(pjsua.endpt, &console_msg_logger); + + + /* Start pjsua! */ + if (pjsua_start() != PJ_SUCCESS) { + + pjsua_destroy(); + return 1; + } + + + /* Sleep for a while, let any messages get printed to console: */ + pj_thread_sleep(500); + + + /* Start UI console main loop: */ + ui_console_main(); + + + /* Destroy pjsua: */ + pjsua_destroy(); + + + /* Close logging: */ + app_logging_shutdown(); + + + /* Exit... */ + + return 0; +} + |