/* * Copyright (C) 2009 Mamadou Diop. * * Contact: Mamadou Diop * * This file is part of Open Source Doubango Framework. * * DOUBANGO 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 3 of the License, or * (at your option) any later version. * * DOUBANGO 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 DOUBANGO. * */ #include "common.h" #include "invite.h" #include "message.h" #include "options.h" #include "publish.h" #include "register.h" #include "subscribe.h" #include #include #include /* === default values === */ #define DEFAULT_REALM "open-ims.test" #define DEFAULT_IMPI "bob@"DEFAULT_REALM #define DEFAULT_IMPU "sip:bob@"DEFAULT_REALM #ifndef DEFAULT_LOCAL_IP //# ifdef ANDROID /* On the emulator */ //# define DEFAULT_LOCAL_IP "10.0.2.15" //# define DEFAULT_LOCAL_IP "192.168.0.14" //# else # define DEFAULT_LOCAL_IP TNET_SOCKET_HOST_ANY //# endif #endif extern ctx_t* ctx; int stack_callback(const tsip_event_t *sipevent); int session_handle_event(const tsip_event_t *sipevent); int session_tostring(const session_t* session); /* our SIP callback function */ int stack_callback(const tsip_event_t *_event) { int ret = 0; if(!_event) { /* should never happen ...but who know? */ TSK_DEBUG_WARN("Null SIP event."); return -1; } #if 0 tsk_safeobj_lock(ctx); #endif switch(_event->type) { case tsip_event_register: { /* REGISTER */ ret = register_handle_event(_event); break; } case tsip_event_invite: { /* INVITE */ ret = invite_handle_event(_event); break; } case tsip_event_message: { /* MESSAGE */ ret = message_handle_event(_event); break; } case tsip_event_options: { /* OPTIONS */ ret = options_handle_event(_event); break; } case tsip_event_publish: { /* PUBLISH */ ret = publish_handle_event(_event); break; } case tsip_event_subscribe: { /* SUBSCRIBE */ ret = subscribe_handle_event(_event); break; } case tsip_event_dialog: { /* Common to all dialogs */ ret = session_handle_event(_event); break; } case tsip_event_stack: { switch(_event->code) { case tsip_event_code_stack_started: TSK_DEBUG_INFO("Stack started"); break; case tsip_event_code_stack_stopped: TSK_DEBUG_INFO("Stack stopped"); break; case tsip_event_code_stack_failed_to_start: TSK_DEBUG_INFO("Stack failed to start"); break; case tsip_event_code_stack_failed_to_stop: TSK_DEBUG_INFO("Stack failed to stop"); break; } break; } default: { /* Unsupported */ TSK_DEBUG_WARN("%d not supported as SIP event.", _event->type); ret = -3; break; } } #if 0 tsk_safeobj_unlock(ctx); #endif return ret; } /* ================================================================== ========================== Context ================================= */ ctx_t* ctx_create() { return tsk_object_new(ctx_def_t); } static tsk_object_t* ctx_ctor(tsk_object_t * self, va_list * app) { ctx_t *ctx = self; if(ctx) { /* stack */ ctx->stack = tsip_stack_create(stack_callback, DEFAULT_REALM, DEFAULT_IMPI, DEFAULT_IMPU, /* Mandatory parameters */ TSIP_STACK_SET_LOCAL_IP(DEFAULT_LOCAL_IP), /* local IP */ TSIP_STACK_SET_NULL() /* Mandatory */); /* SIP Sessions */ ctx->sessions = tsk_list_create(); /* user's parameters */ ctx->params = tsk_list_create(); /* init internal mutex */ tsk_safeobj_init(ctx); } return self; } static tsk_object_t* ctx_dtor(tsk_object_t * self) { ctx_t *ctx = self; if(ctx) { /* Stop the stack (as sessions are alive, you will continue to receive callbacks)*/ tsip_stack_stop(ctx->stack); /* sessions : should be freed before the stack as explained on the Programmer's Guide * As all dialogs have been hanged up, the list should be empty ...but who know?*/ TSK_OBJECT_SAFE_FREE(ctx->sessions); /* Destroy the stack */ TSK_OBJECT_SAFE_FREE(ctx->stack); /* Identity */ TSK_FREE(ctx->identity.display_name); TSK_FREE(ctx->identity.impu); TSK_FREE(ctx->identity.preferred); TSK_FREE(ctx->identity.impi); TSK_FREE(ctx->identity.password); /* Network */ TSK_FREE(ctx->network.local_ip); TSK_FREE(ctx->network.proxy_cscf); TSK_FREE(ctx->network.proxy_cscf_trans); TSK_FREE(ctx->network.realm); /* Security */ TSK_FREE(ctx->security.operator_id); /* Params */ TSK_OBJECT_SAFE_FREE(ctx->params); /* deinit internal mutex */ tsk_safeobj_deinit(ctx); } return self; } static const tsk_object_def_t ctx_def_s = { sizeof(ctx_t), ctx_ctor, ctx_dtor, tsk_null, }; const tsk_object_def_t *ctx_def_t = &ctx_def_s; /* ================================================================== ========================== Stack ================================= */ int stack_dump() { const tsk_list_item_t* item; tsk_list_foreach(item, ctx->sessions) { session_tostring(item->data); } return 0; } int stack_config(const opts_L_t* opts) { const tsk_list_item_t* item; const opt_t* opt; int ret = 0; tsk_param_t* param; tsk_bool_t pcscf_changed = tsk_false; tsk_bool_t stun_done = tsk_false; if(!opts) { return -1; } tsk_list_foreach(item, opts) { opt = item->data; /* Stack-level option */ if(opt->lv != lv_none && opt->lv != lv_stack) { continue; } switch(opt->type) { case opt_amf: { break; } case opt_dhcpv4: case opt_dhcpv6: { tsip_stack_set(ctx->stack, TSIP_STACK_SET_DISCOVERY_DHCP(tsk_true), TSIP_STACK_SET_NULL()); break; } case opt_dname: { break; } case opt_dns_naptr: { tsip_stack_set(ctx->stack, TSIP_STACK_SET_DISCOVERY_NAPTR(tsk_true), TSIP_STACK_SET_NULL()); break; } case opt_header: { if((param = tsk_params_parse_param(opt->value, tsk_strlen(opt->value)))) { ret = tsip_stack_set(ctx->stack, TSIP_STACK_SET_HEADER(param->name, param->value), TSIP_STACK_SET_NULL()); TSK_OBJECT_SAFE_FREE(param); } break; } case opt_impi: { tsk_strupdate(&ctx->identity.impi, opt->value); ret = tsip_stack_set(ctx->stack, TSIP_STACK_SET_IMPI(ctx->identity.impi), TSIP_STACK_SET_NULL()); break; } case opt_impu: { tsk_strupdate(&ctx->identity.impu, opt->value); ret = tsip_stack_set(ctx->stack, TSIP_STACK_SET_IMPU(ctx->identity.impu), TSIP_STACK_SET_NULL()); break; } case opt_ipv6: { pcscf_changed = tsk_true; ctx->network.ipv6 = tsk_true; break; } case opt_local_ip: { ret = tsip_stack_set(ctx->stack, TSIP_STACK_SET_LOCAL_IP(opt->value), TSIP_STACK_SET_NULL()); break; } case opt_local_port: { unsigned port = (unsigned)atoi(opt->value); ret = tsip_stack_set(ctx->stack, TSIP_STACK_SET_LOCAL_PORT(port), TSIP_STACK_SET_NULL()); break; } case opt_opid: { break; } case opt_password: { ret = tsip_stack_set(ctx->stack, TSIP_STACK_SET_PASSWORD(opt->value), TSIP_STACK_SET_NULL()); break; } case opt_pcscf_ip: { pcscf_changed = tsk_true; tsk_strupdate(&ctx->network.proxy_cscf, opt->value); break; } case opt_pcscf_port: { pcscf_changed = tsk_true; ctx->network.proxy_cscf_port = atoi(opt->value); break; } case opt_pcscf_trans: { pcscf_changed = tsk_true; tsk_strupdate(&ctx->network.proxy_cscf_trans, opt->value); break; } case opt_realm: { ret = tsip_stack_set(ctx->stack, TSIP_STACK_SET_REALM(opt->value), TSIP_STACK_SET_NULL()); break; } case opt_sigcomp_id: { /* add compartment */ ret = tsip_stack_set(ctx->stack, TSIP_STACK_SET_SIGCOMP_NEW_COMPARTMENT(opt->value), TSIP_STACK_SET_NULL()); break; } case opt_stun_ip: case opt_stun_pwd: case opt_stun_port: case opt_stun_usr: { if(!stun_done) { const opt_t* _opt; const char* ip = tsk_null, *usr = tsk_null, *pwd = tsk_null; unsigned port = 0; if((_opt = opt_get_by_type(opts, opt_stun_ip))) { ip = _opt->value; } if((_opt = opt_get_by_type(opts, opt_stun_port))) { port = atoi(_opt->value); } if((_opt = opt_get_by_type(opts, opt_stun_usr))) { usr = _opt->value; } if((_opt = opt_get_by_type(opts, opt_stun_pwd))) { pwd = _opt->value; } if(ip && port) { tsip_stack_set(ctx->stack, TSIP_STACK_SET_STUN_SERVER(ip, port), TSIP_STACK_SET_NULL()); } if(usr) { tsip_stack_set(ctx->stack, TSIP_STACK_SET_STUN_CRED(usr, pwd), TSIP_STACK_SET_NULL()); } stun_done = tsk_true; } break; } }/* switch */ } /* foreach */ /* whether Proxy-CSCF config has changed */ if(pcscf_changed) { ret = tsip_stack_set(ctx->stack, TSIP_STACK_SET_PROXY_CSCF(ctx->network.proxy_cscf, ctx->network.proxy_cscf_port, ctx->network.proxy_cscf_trans, ctx->network.ipv6 ? "ipv6" : "ipv4"), TSIP_STACK_SET_NULL()); } return ret; } int stack_run(const opts_L_t* opts) { if(!ctx->stack) { TSK_DEBUG_ERROR("Stack is Null."); return -1; } else { return tsip_stack_start(ctx->stack); } } /* ================================================================== ========================== Session ================================= */ /* Find SIP session by id */ int pred_find_session_by_id(const tsk_list_item_t *item, const void* id) { const session_t* session; if(item && item->data) { session = item->data; return (int)(tsip_ssession_get_id(session->handle) - *((tsip_ssession_id_t*)id)); } return -1; } session_t* session_create(session_type_t type, tsip_ssession_handle_t* handle) { session_t* session = tsk_object_new(session_def_t, type, handle); if(!session) { TSK_DEBUG_ERROR("Failed to create new SIP session"); return tsk_null; } switch(type) { case st_invite: { /* Enable all features (QoS, Session timers, SigComp, ...) */ tsip_ssession_set(session->handle, TSIP_SSESSION_SET_USERDATA(session), /*=== MEDIA */ TSIP_SSESSION_SET_MEDIA( // 100rel TSIP_MSESSION_SET_100rel(tsk_false), // Session timers TSIP_MSESSION_SET_TIMERS(3600, "uac"), // QoS TSIP_MSESSION_SET_QOS(tmedia_qos_stype_segmented, tmedia_qos_strength_optional), // close media params TSIP_MSESSION_SET_NULL() ), TSIP_SSESSION_SET_NULL()); break; } default: break; } return session; } const session_t* session_get_by_sid(const sessions_L_t* sessions, tsip_ssession_id_t sid) { const tsk_list_item_t* item; if((item = tsk_list_find_item_by_pred(sessions, pred_find_session_by_id, &sid))) { return item->data; } else { return tsk_null; } } int session_tostring(const session_t* session) { //char* temp = tsk_null; printf("== Session: "); if(session) { /* Session Id */ printf("sid=%llu", tsip_ssession_get_id(session->handle)); /* Type */ printf(" type="); switch(session->type) { case st_invite: printf("INVITE"); break; case st_message: printf("MESSAGE"); break; case st_publish: printf("PUBLISH"); break; case st_register: printf("REGISTER"); break; case st_subscribe: printf("SUBSCRIBE"); break; default: printf("(null)"); break; } /* From */ printf(" from=%s", session->from ? session->from : ctx->identity.impu); /* From */ printf(" to=%s", session->to ? session->to : ctx->identity.impu); } else { printf("(invalid)"); } printf("\n"); return -1; } /* handle events -common to all sessions */ int session_handle_event(const tsip_event_t *_event) { const session_t* session; /* Find associated session */ if(!(session = session_get_by_sid(ctx->sessions, tsip_ssession_get_id(_event->ss)))) { /* Silentky ignore */ return 0; } switch(_event->code) { /* === 7xx ==> errors === */ case tsip_event_code_dialog_transport_error: case tsip_event_code_dialog_global_error: case tsip_event_code_dialog_message_error: /* do not guess that the dialog is terminated, wait for "tsip_event_code_dialog_terminated" event */ break; /* === 8xx ==> success === */ case tsip_event_code_dialog_request_incoming: case tsip_event_code_dialog_request_cancelled: case tsip_event_code_dialog_request_sent: break; /* === 9xx ==> Informational === */ case tsip_event_code_dialog_terminated: { /* we no longer need the session * -> remove and destroy the session */ TSK_DEBUG_INFO("Dialog Terminated --> %s", _event->phrase); tsk_list_remove_item_by_data(ctx->sessions, session); break; } case tsip_event_code_dialog_connected: ((session_t*)session)->connected = tsk_true; break; case tsip_event_code_dialog_terminating: break; } return 0; } /* handle commands -common to all sessions */ const session_t* session_handle_cmd(cmd_type_t cmd, const opts_L_t* opts) { const session_t* session = tsk_null; const opt_t* opt; const tsk_list_item_t* item; tsk_param_t* param; int ret = 0; /* Check if there is a session with is Id */ if((opt = opt_get_by_type(opts, opt_sid))) { tsip_ssession_id_t sid = atoi(opt->value); session = session_get_by_sid(ctx->sessions, sid); } #define TYPE_FROM_CMD(_CMD) \ ((_CMD==cmd_audio || _CMD==cmd_video || _CMD==cmd_audiovideo || _CMD==cmd_file || _CMD==cmd_large_message) ? st_invite : \ ((_CMD==cmd_message || _CMD==cmd_sms) ? st_message : \ (_CMD==cmd_options ? st_options : \ (_CMD==cmd_publish ? st_publish : \ (_CMD==cmd_register ? st_register : \ (_CMD==cmd_subscribe ? st_subscribe : st_none)))))) /* === Command === */ switch(cmd) { case cmd_audio: case cmd_video: case cmd_audiovideo: case cmd_file: case cmd_large_message: case cmd_message: case cmd_sms: case cmd_options: case cmd_publish: case cmd_register: case cmd_subscribe: { if(!session) { /* Create "client-side-session" */ session_t* _session; if((_session = session_client_create(TYPE_FROM_CMD(cmd))) && (session = _session)) { tsk_list_push_back_data(ctx->sessions, (void**)&_session); } } break; } default: { if(session) { /* hold, resume, refer, update, ...all in-dialog commands */ break; } else { TSK_DEBUG_WARN("Session handling: Cannot handle this command [%d]", cmd); goto bail; } } } /* switch */ if(!session) { TSK_DEBUG_ERROR("SIP Session is Null"); goto bail; } /* === User Options === */ tsk_list_foreach(item, opts) { opt = item->data; /* Session-level option? */ if(opt->lv != lv_none && opt->lv != lv_session) { continue; } switch(opt->type) { case opt_caps: { if(!tsk_strnullORempty(opt->value)) { if((param = tsk_params_parse_param(opt->value, tsk_strlen(opt->value)))) { ret = tsip_ssession_set(session->handle, TSIP_SSESSION_SET_CAPS(param->name, param->value), TSIP_SSESSION_SET_NULL()); TSK_OBJECT_SAFE_FREE(param); } } break; } case opt_expires: { if(!tsk_strnullORempty(opt->value)) { unsigned expires = atoi(opt->value); ret = tsip_ssession_set(session->handle, TSIP_SSESSION_SET_EXPIRES(expires), TSIP_SSESSION_SET_NULL()); } break; } case opt_from: { /* You should use TSIP_SSESSION_SET_OPTION(TSIP_SSESSION_OPTION_FROM, value) instead of TSIP_SSESSION_SET_HEADER() to set the destination URI. */ break; } case opt_header: { if((param = tsk_params_parse_param(opt->value, tsk_strlen(opt->value)))) { ret = tsip_ssession_set(session->handle, TSIP_SSESSION_SET_HEADER(param->name, param->value), TSIP_SSESSION_SET_NULL()); TSK_OBJECT_SAFE_FREE(param); } break; } case opt_payload: { /* Will be handled by the caller */ break; } case opt_silent: { /* valueless option */ ret = tsip_ssession_set(session->handle, TSIP_SSESSION_SET_SILENT_HANGUP(tsk_true), TSIP_SSESSION_SET_NULL()); break; } case opt_sigcomp_id: { /* sigcomp-id */ ret = tsip_ssession_set(session->handle, TSIP_SSESSION_SET_SIGCOMP_COMPARTMENT(opt->value), TSIP_SSESSION_SET_NULL()); break; } case opt_to: { /* You should use TSIP_SSESSION_SET_OPTION(TSIP_SSESSION_OPTION_TO, value) instead of TSIP_SSESSION_SET_HEADER() to set the destination URI. */ if((cmd != cmd_sms) && (cmd != cmd_ect) && !tsk_strnullORempty(opt->value)) { /* SMS will use SMSC Address as Request URI */ ret = tsip_ssession_set(session->handle, TSIP_SSESSION_SET_TO(opt->value), TSIP_SSESSION_SET_NULL()); } break; } default: { /* will be handled by the caller */ break; } } } /* foreach */ bail: return session; } int session_hangup(tsip_ssession_id_t sid) { const session_t* session; if((session = session_get_by_sid(ctx->sessions, sid))) { switch(session->type) { case st_invite: tsip_api_invite_send_bye(session->handle, /* You can add your parameters */ TSIP_ACTION_SET_NULL()); break; case st_message: break; case st_publish: tsip_api_publish_send_unpublish(session->handle, /* You can add your parameters */ TSIP_ACTION_SET_NULL()); break; case st_register: tsip_api_register_send_unregister(session->handle, /* You can add your parameters */ TSIP_ACTION_SET_NULL()); break; case st_subscribe: tsip_api_subscribe_send_unsubscribe(session->handle, /* You can add your parameters */ TSIP_ACTION_SET_NULL()); break; default: TSK_DEBUG_WARN("Cannot hangup session with this type [%d]", session->type); return -2; } return 0; } else { TSK_DEBUG_WARN("Failed to find session with sid=%llu", sid); return -1; } } static tsk_object_t* session_ctor(tsk_object_t * self, va_list * app) { session_t *session = self; if(session) { session->type = va_arg(*app, session_type_t); if((session->handle = va_arg(*app, tsip_ssession_handle_t*))) { int ret; /* "server-side-session" */ if((ret = tsip_ssession_take_ownership(session->handle))) { TSK_DEBUG_ERROR("Failed to take ownership [%d]", ret); } } else { /* "client-side-session" */ session->handle = tsip_ssession_create(ctx->stack, TSIP_SSESSION_SET_NULL()); } } return self; } static tsk_object_t* session_dtor(tsk_object_t * self) { session_t *session = self; if(session) { TSK_OBJECT_SAFE_FREE(session->handle); TSK_FREE(session->to); TSK_FREE(session->from); } return self; } static int session_cmp(const tsk_object_t *_ss1, const tsk_object_t *_ss2) { const session_t *ss1 = _ss1; const session_t *ss2 = _ss2; if(ss1 && ss1) { if(ss1->handle && ss2->handle) { return tsk_object_cmp(ss1->handle, ss2->handle); } else { return (ss2 - ss1); } } else if(!ss1 && !ss2) { return 0; } else { return -1; } } static const tsk_object_def_t session_def_s = { sizeof(session_t), session_ctor, session_dtor, session_cmp, }; const tsk_object_def_t *session_def_t = &session_def_s; tsip_action_handle_t* action_get_config(const opts_L_t* opts) { const opt_t* opt; const tsk_list_item_t* item; tsip_action_handle_t* action_config = tsk_null; tsk_param_t* param; if(TSK_LIST_IS_EMPTY(opts)) { return tsk_null; } tsk_list_foreach(item, opts) { opt = item->data; /* action level? */ if(opt->lv != lv_action) { continue; } /* create new action */ if(!action_config && !(action_config = tsip_action_create(tsip_atype_config, TSIP_ACTION_SET_NULL()))) { break; } switch(opt->type) { case opt_header: { if((param = tsk_params_parse_param(opt->value, tsk_strlen(opt->value)))) { tsip_action_set(action_config, TSIP_ACTION_SET_HEADER(param->name, param->value), TSIP_ACTION_SET_NULL()); TSK_OBJECT_SAFE_FREE(param); } break; } case opt_payload: { tsip_action_set(action_config, TSIP_ACTION_SET_PAYLOAD(opt->value, tsk_strlen(opt->value)), TSIP_ACTION_SET_NULL()); break; } default: { break; } } } return action_config; }