/* * OpenMRCP - Open Source Media Resource Control Protocol Stack * Copyright (C) 2007, Cepstral LLC * * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * Author(s): * Arsen Chaloyan * * Contributor(s): * */ typedef struct mrcp_client_sofia_agent_t mrcp_client_sofia_agent_t; #define NUA_MAGIC_T mrcp_client_sofia_agent_t typedef struct mrcp_client_sofia_session_t mrcp_client_sofia_session_t; #define NUA_HMAGIC_T mrcp_client_sofia_session_t #include #ifdef _MSC_VER #undef strncasecmp #undef strcasecmp #endif #include #include #include #include #include "mrcp_client_signaling_agent.h" #include "apt_producer_task.h" struct mrcp_client_sofia_agent_t { mrcp_signaling_agent_t *signaling_agent; apt_producer_task_t *producer_task; su_root_t *root; nua_t *nua; apr_pool_t *pool; }; struct mrcp_client_sofia_session_t { mrcp_signaling_channel_t *channel; su_home_t *home; nua_handle_t *nh; mrcp_status_t initiating; }; typedef enum { MRCP_SOFIA_EVENT_AGENT_PROCESS, MRCP_SOFIA_EVENT_AGENT_STARTED, MRCP_SOFIA_EVENT_AGENT_TERMINATED } mrcp_sofia_event_id; typedef struct mrcp_sofia_event_t mrcp_sofia_event_t; struct mrcp_sofia_event_t { mrcp_sofia_event_id event_id; nua_event_t nua_event; int status; char const *phrase; nua_t *nua; mrcp_client_sofia_agent_t *sofia_agent; nua_handle_t *nh; mrcp_client_sofia_session_t *sofia_session; sip_t const *sip; tagi_t *tags; }; #define MRCP_CLIENT_SOFIA_AGENT "OpenMRCP Sofia-SIP" static mrcp_status_t mrcp_sofia_agent_destroy(mrcp_module_t *module); static mrcp_module_state_t mrcp_sofia_agent_open(mrcp_module_t *module); static mrcp_module_state_t mrcp_sofia_agent_close(mrcp_module_t *module); static mrcp_status_t mrcp_sofia_agent_signal_handler(mrcp_module_t *module, apt_task_msg_t *msg); static const mrcp_module_method_set_t module_method_set = { mrcp_sofia_agent_destroy, mrcp_sofia_agent_open, mrcp_sofia_agent_close, mrcp_sofia_agent_signal_handler }; static mrcp_status_t mrcp_sofia_resource_discover(mrcp_signaling_agent_t *agent); static mrcp_signaling_channel_t* mrcp_sofia_session_create(mrcp_signaling_agent_t *agent, apr_pool_t *pool); static const mrcp_signaling_agent_method_set_t signaling_agent_method_set = { mrcp_sofia_resource_discover, mrcp_sofia_session_create, }; static mrcp_status_t mrcp_sofia_session_offer(mrcp_signaling_channel_t *channel); static mrcp_status_t mrcp_sofia_session_terminate(mrcp_signaling_channel_t *channel); static mrcp_status_t mrcp_sofia_session_destroy(mrcp_signaling_channel_t *channel); static const mrcp_signaling_channel_method_set_t signaling_channel_method_set = { mrcp_sofia_session_offer, NULL, /*answer*/ mrcp_sofia_session_terminate, mrcp_sofia_session_destroy }; size_t mrcp_sdp_offer_generate_by_mrcp_descriptor(char *buffer, size_t size, mrcp_descriptor_t *descriptor); mrcp_status_t mrcp_descriptor_generate_by_sdp_session(mrcp_descriptor_t *descriptor, sdp_session_t *sdp, apr_pool_t *pool); static APR_INLINE mrcp_client_sofia_agent_t* mrcp_sofia_agent_get(mrcp_module_t *module) { return ((mrcp_signaling_agent_t*)module)->object; } static mrcp_status_t mrcp_client_sofia_on_call_ready(mrcp_sofia_event_t *sofia_event) { const char *local_sdp_str = NULL, *remote_sdp_str = NULL; sdp_parser_t *parser = NULL; sdp_session_t *sdp = NULL; mrcp_signaling_channel_t *channel = sofia_event->sofia_session->channel; tl_gets(sofia_event->tags, SOATAG_LOCAL_SDP_STR_REF(local_sdp_str), SOATAG_REMOTE_SDP_STR_REF(remote_sdp_str), TAG_END()); if(remote_sdp_str) { apt_log(APT_PRIO_INFO,"Remote SDP\n[%s]\n", remote_sdp_str); parser = sdp_parse(sofia_event->sofia_session->home,remote_sdp_str,(int)strlen(remote_sdp_str),0); sdp = sdp_session(parser); mrcp_descriptor_generate_by_sdp_session(&channel->remote_descriptor,sdp,channel->pool); } sofia_event->sofia_session->initiating = MRCP_STATUS_FAILURE; channel->event_set->on_answer(channel); if(parser) { sdp_parser_free(parser); } return MRCP_STATUS_SUCCESS; } static mrcp_status_t mrcp_sofia_on_state_changed(mrcp_sofia_event_t *sofia_event) { int ss_state = nua_callstate_init; tl_gets(sofia_event->tags, NUTAG_CALLSTATE_REF(ss_state), TAG_END()); apt_log(APT_PRIO_NOTICE,"SIP Call State [%s]\n", nua_callstate_name(ss_state)); switch(ss_state) { case nua_callstate_ready: mrcp_client_sofia_on_call_ready(sofia_event); break; case nua_callstate_terminated: sofia_event->sofia_session->channel->event_set->on_terminate(sofia_event->sofia_session->channel); break; } return MRCP_STATUS_SUCCESS; } static mrcp_status_t mrcp_sofia_on_resource_discover(mrcp_sofia_event_t *sofia_event) { const char *remote_sdp_str = NULL; tl_gets(sofia_event->tags, SOATAG_REMOTE_SDP_STR_REF(remote_sdp_str), TAG_END()); mrcp_descriptor_init(&sofia_event->sofia_agent->signaling_agent->server_capabilities); if(remote_sdp_str) { apt_log(APT_PRIO_INFO,"Remote SDP\n[%s]\n", remote_sdp_str); } sofia_event->sofia_agent->signaling_agent->event_set->on_resource_discover(sofia_event->sofia_agent->signaling_agent); if(sofia_event->nh) { nua_handle_destroy(sofia_event->nh); sofia_event->nh = NULL; } return MRCP_STATUS_SUCCESS; } static mrcp_signaling_channel_t* mrcp_sofia_session_create(mrcp_signaling_agent_t *agent, apr_pool_t *pool) { mrcp_client_sofia_agent_t *sofia_agent = mrcp_client_signaling_agent_object_get(agent); mrcp_client_sofia_session_t *sofia_session = apr_palloc(pool,sizeof(mrcp_client_sofia_session_t)); sofia_session->home = su_home_new(sizeof(*sofia_session->home)); sofia_session->channel = mrcp_client_signaling_channel_create(agent,&signaling_channel_method_set,sofia_session,pool); sofia_session->initiating = MRCP_STATUS_FAILURE; sofia_session->nh = nua_handle(sofia_agent->nua,sofia_session, SIPTAG_TO_STR(agent->sip_to_str), SIPTAG_FROM_STR(agent->sip_from_str), SIPTAG_CONTACT_STR(agent->sip_contact_str), TAG_END()); return sofia_session->channel; } static mrcp_status_t mrcp_sofia_session_destroy(mrcp_signaling_channel_t *channel) { mrcp_client_sofia_session_t *sofia_session = mrcp_client_signaling_channel_object_get(channel); if(sofia_session) { if(sofia_session->nh) { nua_handle_bind(sofia_session->nh, NULL); nua_handle_destroy(sofia_session->nh); } if(sofia_session->home) { su_home_unref(sofia_session->home); sofia_session->home = NULL; } } return MRCP_STATUS_SUCCESS; } static mrcp_status_t mrcp_sofia_session_offer(mrcp_signaling_channel_t *channel) { char sdp_str[2048]; char *local_sdp_str = NULL; mrcp_signaling_agent_t *agent = mrcp_client_signaling_channel_agent_get(channel); mrcp_client_sofia_agent_t *sofia_agent = mrcp_client_signaling_agent_object_get(agent); mrcp_client_sofia_session_t *sofia_session = mrcp_client_signaling_channel_object_get(channel); if(!sofia_agent || !sofia_session || !sofia_session->nh) { return MRCP_STATUS_FAILURE; } if(mrcp_sdp_offer_generate_by_mrcp_descriptor(sdp_str,sizeof(sdp_str),&channel->local_descriptor) > 0) { local_sdp_str = sdp_str; apt_log(APT_PRIO_INFO,"Local SDP\n[%s]\n", local_sdp_str); } sofia_session->initiating = MRCP_STATUS_SUCCESS; nua_invite(sofia_session->nh, TAG_IF(local_sdp_str,SOATAG_USER_SDP_STR(local_sdp_str)), TAG_END()); return MRCP_STATUS_SUCCESS; } static mrcp_status_t mrcp_sofia_session_terminate(mrcp_signaling_channel_t *channel) { mrcp_client_sofia_session_t *sofia_session = mrcp_client_signaling_channel_object_get(channel); if(!sofia_session || !sofia_session->nh) { return MRCP_STATUS_FAILURE; } if(sofia_session->initiating == MRCP_STATUS_SUCCESS) { nua_cancel(sofia_session->nh,TAG_END()); } else { nua_bye(sofia_session->nh,TAG_END()); } return MRCP_STATUS_SUCCESS; } static mrcp_status_t mrcp_sofia_resource_discover(mrcp_signaling_agent_t *agent) { mrcp_client_sofia_agent_t *sofia_agent = mrcp_client_signaling_agent_object_get(agent); nua_handle_t *nh = nua_handle(sofia_agent->nua,NULL, SIPTAG_TO_STR(agent->sip_to_str), SIPTAG_FROM_STR(agent->sip_from_str), SIPTAG_CONTACT_STR(agent->sip_contact_str), TAG_END()); nua_options(nh,TAG_END()); return MRCP_STATUS_SUCCESS; } static mrcp_status_t mrcp_sofia_agent_signal_handler(mrcp_module_t *module, apt_task_msg_t *msg) { mrcp_sofia_event_t *sofia_event = (mrcp_sofia_event_t*)msg->data; if(!sofia_event) { return MRCP_STATUS_FAILURE; } if(sofia_event->event_id == MRCP_SOFIA_EVENT_AGENT_TERMINATED) { if(module->event_set && module->event_set->on_close) { module->event_set->on_close(module); } return MRCP_STATUS_SUCCESS; } if(!sofia_event->sofia_agent) { return MRCP_STATUS_FAILURE; } apt_log(APT_PRIO_DEBUG,"Process SIP Event [%s] Status %d %s\n", nua_event_name(sofia_event->nua_event), sofia_event->status, sofia_event->phrase); switch(sofia_event->nua_event) { case nua_i_state: mrcp_sofia_on_state_changed(sofia_event); break; case nua_r_options: mrcp_sofia_on_resource_discover(sofia_event); break; default: break; } return MRCP_STATUS_SUCCESS; } static void mrcp_sofia_agent_on_terminate_requested(void *data) { mrcp_client_sofia_agent_t *sofia_agent = data; apt_log(APT_PRIO_DEBUG,"Sofia SIP Terminate Requested\n"); while(!sofia_agent->nua) { /* wait for nua to start to be able to terminate */ apt_log(APT_PRIO_DEBUG,"Wait for NUA to Start\n"); apt_task_delay(500); } if(sofia_agent->nua) { apt_log(APT_PRIO_DEBUG,"Send Shutdown Signal to NUA\n"); nua_shutdown(sofia_agent->nua); } } static void mrcp_sofia_agent_on_terminate_complete(void *data) { mrcp_client_sofia_agent_t *sofia_agent = data; apt_task_msg_t *task_msg; task_msg = apt_producer_task_msg_get(sofia_agent->producer_task); if(task_msg) { mrcp_sofia_event_t *sofia_event = (mrcp_sofia_event_t*)task_msg->data; sofia_event->event_id = MRCP_SOFIA_EVENT_AGENT_TERMINATED; mrcp_client_signaling_agent_signal(sofia_agent->signaling_agent,task_msg); } } /* This callback will be called by SIP stack to process incoming events */ static void mrcp_sofia_event_callback(nua_event_t nua_event, int status, char const *phrase, nua_t *nua, mrcp_client_sofia_agent_t *sofia_agent, nua_handle_t *nh, mrcp_client_sofia_session_t *sofia_session, sip_t const *sip, tagi_t tags[]) { mrcp_status_t mrcp_process = MRCP_STATUS_FAILURE; apt_log(APT_PRIO_INFO,"Recieve SIP Event [%s] Status %d %s\n",nua_event_name(nua_event),status,phrase); switch(nua_event) { case nua_i_state: case nua_r_options: mrcp_process = MRCP_STATUS_SUCCESS; break; case nua_r_shutdown: /* break main loop of sofia thread */ su_root_break(sofia_agent->root); break; default: break; } if(mrcp_process == MRCP_STATUS_SUCCESS) { apt_task_msg_t *task_msg = apt_producer_task_msg_get(sofia_agent->producer_task); mrcp_sofia_event_t *sofia_event = (mrcp_sofia_event_t*)task_msg->data; sofia_event->event_id = MRCP_SOFIA_EVENT_AGENT_PROCESS; sofia_event->nua_event = nua_event; sofia_event->status = status; sofia_event->phrase = phrase; sofia_event->nua = nua; sofia_event->sofia_agent = sofia_agent; sofia_event->nh = nh; sofia_event->sofia_session = sofia_session; sofia_event->sip = sip; sofia_event->tags = tags; mrcp_client_signaling_agent_signal(sofia_agent->signaling_agent,task_msg); } } static void mrcp_sofia_main_loop(void *data) { char sip_bind_url[128]; mrcp_client_sofia_agent_t *sofia_agent = data; if(!sofia_agent) { return; } /* Initialize Sofia-SIP library and create event loop */ su_init(); sofia_agent->root = su_root_create(NULL); /* Create a user agent instance. The stack will call the 'event_callback()' * callback when events such as succesful registration to network, * an incoming call, etc, occur. */ sprintf(sip_bind_url,"sip:%s:%d","0.0.0.0",sofia_agent->signaling_agent->client_sip_port); sofia_agent->nua = nua_create(sofia_agent->root, /* Event loop */ mrcp_sofia_event_callback, /* Callback for processing events */ sofia_agent, /* Additional data to pass to callback */ NUTAG_URL(sip_bind_url), /* Address to bind to */ TAG_END()); /* Last tag should always finish the sequence */ if(sofia_agent->nua) { nua_set_params(sofia_agent->nua, NUTAG_AUTOANSWER(0), NUTAG_APPL_METHOD("OPTIONS"), SIPTAG_USER_AGENT_STR(MRCP_CLIENT_SOFIA_AGENT), TAG_END()); /* Run event loop */ su_root_run(sofia_agent->root); /* Destroy allocated resources */ nua_destroy(sofia_agent->nua); } su_root_destroy(sofia_agent->root); su_deinit(); } static mrcp_module_state_t mrcp_sofia_agent_open(mrcp_module_t *module) { mrcp_client_sofia_agent_t *sofia_agent = mrcp_sofia_agent_get(module); apt_log(APT_PRIO_INFO,"Open Sofia SIP Client Agent\n"); apt_producer_task_start(sofia_agent->producer_task); return MODULE_STATE_OPEN_INPROGRESS; } static mrcp_module_state_t mrcp_sofia_agent_close(mrcp_module_t *module) { mrcp_client_sofia_agent_t *sofia_agent = mrcp_sofia_agent_get(module); apt_log(APT_PRIO_INFO,"Close Sofia SIP Client Agent\n"); apt_producer_task_terminate(sofia_agent->producer_task,MRCP_STATUS_FAILURE); return MODULE_STATE_CLOSE_INPROGRESS; } static void mrcp_sofia_agent_config(mrcp_client_sofia_agent_t *sofia_agent, const char *client_ip, unsigned short client_port, const char *server_ip, unsigned short server_port, const char *resource_location) { char sip_str[128]; if(client_ip) { sofia_agent->signaling_agent->client_ip = apr_pstrdup(sofia_agent->pool,client_ip); } if(server_ip) { sofia_agent->signaling_agent->server_ip = apr_pstrdup(sofia_agent->pool,server_ip); } if(resource_location) { sofia_agent->signaling_agent->resource_location = apr_pstrdup(sofia_agent->pool,resource_location); } sofia_agent->signaling_agent->client_sip_port = client_port; sofia_agent->signaling_agent->server_sip_port = server_port; sprintf(sip_str,"sip:%s:%d",sofia_agent->signaling_agent->client_ip,sofia_agent->signaling_agent->client_sip_port); sofia_agent->signaling_agent->sip_contact_str = apr_pstrdup(sofia_agent->pool,sip_str); sprintf(sip_str,"sip:%s",sofia_agent->signaling_agent->client_ip); sofia_agent->signaling_agent->sip_from_str = apr_pstrdup(sofia_agent->pool,sip_str); if(sofia_agent->signaling_agent->resource_location && sofia_agent->signaling_agent->resource_location != '\0') { sprintf(sip_str,"sip:%s@%s:%d", sofia_agent->signaling_agent->resource_location, sofia_agent->signaling_agent->server_ip, sofia_agent->signaling_agent->server_sip_port); } else { sprintf(sip_str,"sip:%s:%d",sofia_agent->signaling_agent->server_ip,sofia_agent->signaling_agent->server_sip_port); } sofia_agent->signaling_agent->sip_to_str = apr_pstrdup(sofia_agent->pool,sip_str); } static mrcp_status_t mrcp_sofia_agent_destroy(mrcp_module_t *module) { mrcp_client_sofia_agent_t *sofia_agent = mrcp_sofia_agent_get(module); apt_log(APT_PRIO_NOTICE,"Destroy Sofia SIP Client Agent\n"); apt_producer_task_destroy(sofia_agent->producer_task); sofia_agent->pool = NULL; return MRCP_STATUS_SUCCESS; } mrcp_signaling_agent_t* mrcp_client_sofia_agent_create(const char *client_ip, unsigned short client_port, const char *server_ip, unsigned short server_port, const char *resource_location, apr_pool_t *pool) { apt_task_msg_pool_t *msg_pool; mrcp_client_sofia_agent_t *sofia_agent = apr_palloc(pool,sizeof(mrcp_client_sofia_agent_t)); mrcp_signaling_agent_t *agent = mrcp_client_signaling_agent_create( &signaling_agent_method_set, &module_method_set, sofia_agent, pool); apt_log(APT_PRIO_NOTICE,"Create Sofia SIP Client Agent %s:%hu -> %s:%hu\n",client_ip,client_port,server_ip,server_port); sofia_agent->pool = pool; sofia_agent->signaling_agent = agent; sofia_agent->root = NULL; sofia_agent->nua = NULL; mrcp_sofia_agent_config(sofia_agent,client_ip,client_port,server_ip,server_port,resource_location); msg_pool = apt_task_msg_pool_create_waitable_static(sizeof(mrcp_sofia_event_t),pool); sofia_agent->producer_task = apt_producer_task_create(sofia_agent,mrcp_sofia_main_loop,msg_pool,pool); if(sofia_agent->producer_task) { apt_task_t *task = apt_producer_task_get(sofia_agent->producer_task); apt_task_event_handler_set(task,TASK_STATE_TERMINATE_REQUESTED,sofia_agent,mrcp_sofia_agent_on_terminate_requested); apt_task_event_handler_set(task,TASK_STATE_TERMINATE_COMPLETED,sofia_agent,mrcp_sofia_agent_on_terminate_complete); } return sofia_agent->signaling_agent; }