doubango/tinyDEMO/common.c

864 lines
25 KiB
C
Executable File

/*
* Copyright (C) 2009 Mamadou Diop.
*
* Contact: Mamadou Diop <diopmamadou(at)doubango.org>
*
* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
/* === 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_SECAGREE_IPSEC_2("TCP", tsk_true),
TSIP_STACK_SET_IMS_AKA_OPERATOR_ID("22b3156098e11e177e93711d6cb0e688"),
TSIP_STACK_SET_IMS_AKA_AMF(0x8000),
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;
}