| /* $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, NULL, 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, NULL, 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, NULL, NULL); |
| |
| // TODO: Need to delete dialog |
| |
| } else { |
| status = pjsip_inv_send_msg(inv, response); |
| 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); |
| 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; |
| |
| PJ_UNUSED_ARG(ht); |
| PJ_UNUSED_ARG(e); |
| |
| 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); |
| 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; |
| |
| PJ_UNUSED_ARG(e); |
| |
| 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, &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; |
| } |
| |
| |