
/*
 * Copyright (c) 2010-2019 Belledonne Communications SARL.
 *
 * This file is part of Liblinphone.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include "linphone/lpconfig.h"
#include "linphone/wrapper_utils.h"
#include "mediastreamer2/mediastream.h"
#include "core/core-p.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_SIGHANDLER_T
#include <signal.h>
#endif /*HAVE_SIGHANDLER_T*/

#include <string.h>
#if !defined(_WIN32_WCE)
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#if _MSC_VER
#include <io.h>
#else
#include <unistd.h>
#endif
#include <fcntl.h>
#endif /*_WIN32_WCE*/

#undef snprintf
#include <mediastreamer2/stun.h>

#include <math.h>
#if _MSC_VER
#define snprintf _snprintf
#define popen _popen
#define pclose _pclose
#endif

#include "nat/stun-client.h"
#include "utils/payload-type-handler.h"

#include "c-wrapper/c-wrapper.h"

// TODO: From coreapi. Remove me later.
#include "private.h"

#include "conference/session/media-session-p.h"
#include "call/call-p.h"

void linphone_core_update_allocated_audio_bandwidth(LinphoneCore *lc){
	const bctbx_list_t *elem;
	int maxbw=LinphonePrivate::PayloadTypeHandler::getMinBandwidth(linphone_core_get_download_bandwidth(lc),
					linphone_core_get_upload_bandwidth(lc));
	int max_codec_bitrate=0;

	for(elem=linphone_core_get_audio_codecs(lc);elem!=NULL;elem=elem->next){
		PayloadType *pt=(PayloadType*)elem->data;
		if (payload_type_enabled(pt)){
			int pt_bitrate=LinphonePrivate::PayloadTypeHandler::getAudioPayloadTypeBandwidth(pt,maxbw);
			if (max_codec_bitrate==0) {
				max_codec_bitrate=pt_bitrate;
			}else if (max_codec_bitrate<pt_bitrate){
				max_codec_bitrate=pt_bitrate;
			}
		}
	}
	if (max_codec_bitrate) {
		lc->audio_bw=max_codec_bitrate;
	}
}

bool_t linphone_core_is_payload_type_usable_for_bandwidth(const LinphoneCore *lc, const PayloadType *pt, int bandwidth_limit){
	double codec_band;
	const int video_enablement_limit = 99;
	bool_t ret=FALSE;

	switch (pt->type){
		case PAYLOAD_AUDIO_CONTINUOUS:
		case PAYLOAD_AUDIO_PACKETIZED:
			codec_band=LinphonePrivate::PayloadTypeHandler::getAudioPayloadTypeBandwidth(pt,bandwidth_limit);
			ret=LinphonePrivate::PayloadTypeHandler::bandwidthIsGreater(bandwidth_limit,(int)codec_band);
			/*ms_message("Payload %s: codec_bandwidth=%g, bandwidth_limit=%i",pt->mime_type,codec_band,bandwidth_limit);*/
			break;
		case PAYLOAD_VIDEO:
			if (bandwidth_limit<=0 || bandwidth_limit >= video_enablement_limit) {/* infinite or greater than video_enablement_limit*/
				ret=TRUE;
			}
			else ret=FALSE;
			break;
		case PAYLOAD_TEXT:
			ret=TRUE;
			break;
	}
	return ret;
}

bool_t lp_spawn_command_line_sync(const char *command, char **result,int *command_ret){
#if !defined(_WIN32_WCE) && !defined(LINPHONE_WINDOWS_UNIVERSAL)
#ifndef ENABLE_MICROSOFT_STORE_APP
	FILE *f=popen(command,"r");
	if (f!=NULL){
		int err;
		*result=reinterpret_cast<char *>(ms_malloc(4096));
		err=(int)fread(*result,1,4096-1,f);
		if (err<0){
			ms_warning("Error reading command output:%s",strerror(errno));
			ms_free(result);
			return FALSE;
		}
		(*result)[err]=0;
		err=pclose(f);
		if (command_ret!=NULL) *command_ret=err;
		return TRUE;
	}
#endif  // ENABLE_MICROSOFT_STORE_APP
#endif /*_WIN32_WCE*/
	return FALSE;
}

int linphone_parse_host_port(const char *input, char *host, size_t hostlen, int *port){
	char tmphost[NI_MAXHOST]={0};

	if ((sscanf(input, "[%64[^]]]:%d", tmphost, port) == 2) || (sscanf(input, "[%64[^]]]", tmphost) == 1)) {

	} else {
		const char *p1 = strchr(input, ':');
		const char *p2 = strrchr(input, ':');
		if (p1 && p2 && (p1 != p2)) {/* an ipv6 address without port*/
			strncpy(tmphost, input, sizeof(tmphost) - 1);
		} else if (sscanf(input, "%[^:]:%d", tmphost, port) != 2) {
			/*no port*/
			strncpy(tmphost, input, sizeof(tmphost) - 1);
		}
	}
	strncpy(host,tmphost,hostlen);
	return 0;
}

int parse_hostname_to_addr(const char *server, struct sockaddr_storage *ss, socklen_t *socklen, int default_port){
	struct addrinfo hints,*res=NULL;
	char port[6];
	char host[NI_MAXHOST];
	int port_int=default_port;
	int ret;

	linphone_parse_host_port(server,host,sizeof(host),&port_int);

	snprintf(port, sizeof(port), "%d", port_int);
	memset(&hints,0,sizeof(hints));
	hints.ai_family=AF_UNSPEC;
	hints.ai_socktype=SOCK_DGRAM;
	hints.ai_protocol=IPPROTO_UDP;
	ret=getaddrinfo(host,port,&hints,&res);
	if (ret!=0){
		ms_error("getaddrinfo() failed for %s:%s : %s",host,port,gai_strerror(ret));
		return -1;
	}
	if (!res) return -1;
	memcpy(ss,res->ai_addr,(size_t)res->ai_addrlen);
	*socklen=(socklen_t)res->ai_addrlen;
	freeaddrinfo(res);
	return 0;
}

/* this functions runs a simple stun test and return the number of milliseconds to complete the tests, or -1 if the test were failed.*/
int linphone_run_stun_tests(LinphoneCore *lc, int audioPort, int videoPort, int textPort,
	char *audioCandidateAddr, int *audioCandidatePort, char *videoCandidateAddr, int *videoCandidatePort, char *textCandidateAddr, int *textCandidatePort) {
	LinphonePrivate::StunClient *client = new LinphonePrivate::StunClient(L_GET_CPP_PTR_FROM_C_OBJECT(lc));
	int ret = client->run(audioPort, videoPort, textPort);
	strncpy(audioCandidateAddr, client->getAudioCandidate().address.c_str(), LINPHONE_IPADDR_SIZE);
	*audioCandidatePort = client->getAudioCandidate().port;
	strncpy(videoCandidateAddr, client->getVideoCandidate().address.c_str(), LINPHONE_IPADDR_SIZE);
	*videoCandidatePort = client->getVideoCandidate().port;
	strncpy(textCandidateAddr, client->getTextCandidate().address.c_str(), LINPHONE_IPADDR_SIZE);
	*textCandidatePort = client->getTextCandidate().port;
	delete client;
	return ret;
}

int linphone_core_get_edge_bw(LinphoneCore *lc){
	int edge_bw=lp_config_get_int(lc->config,"net","edge_bw",20);
	return edge_bw;
}

int linphone_core_get_edge_ptime(LinphoneCore *lc){
	int edge_ptime=lp_config_get_int(lc->config,"net","edge_ptime",100);
	return edge_ptime;
}

void linphone_core_resolve_stun_server(LinphoneCore *lc) {
	auto proxies = linphone_core_get_proxy_config_list(lc);
	for (auto item = proxies; item; item = bctbx_list_next(item)) {
		auto proxy = reinterpret_cast<LinphoneProxyConfig *>(bctbx_list_get_data(item));
		auto policy = linphone_proxy_config_get_nat_policy(proxy);
		if (policy)
			linphone_nat_policy_resolve_stun_server(policy);
	}
	if (lc->nat_policy)
		linphone_nat_policy_resolve_stun_server(lc->nat_policy);
}

const struct addrinfo *linphone_core_get_stun_server_addrinfo(LinphoneCore *lc){
	if (lc->nat_policy != NULL) {
		return linphone_nat_policy_get_stun_server_addrinfo(lc->nat_policy);
	} else {
		ms_error("linphone_core_get_stun_server_addrinfo(): called without nat_policy, this should not happen.");
	}
	return NULL;
}

void linphone_core_enable_forced_ice_relay(LinphoneCore *lc, bool_t enable) {
	lc->forced_ice_relay = enable;
}

void linphone_core_enable_short_turn_refresh(LinphoneCore *lc, bool_t enable) {
	lc->short_turn_refresh = enable;
}

const char *linphone_ice_state_to_string(LinphoneIceState state){
	switch(state){
		case LinphoneIceStateFailed:
			return "IceStateFailed";
		case LinphoneIceStateHostConnection:
			return "IceStateHostConnection";
		case LinphoneIceStateInProgress:
			return "IceStateInProgress";
		case LinphoneIceStateNotActivated:
			return "IceStateNotActivated";
		case LinphoneIceStateReflexiveConnection:
			return "IceStateReflexiveConnection";
		case LinphoneIceStateRelayConnection:
			return "IceStateRelayConnection";
	}
	return "invalid";
}

bool_t linphone_core_media_description_contains_video_stream(const SalMediaDescription *md){
	int i;

	for (i = 0; md && i < md->nb_streams; i++) {
		if (md->streams[i].type == SalVideo && md->streams[i].rtp_port!=0)
			return TRUE;
	}
	return FALSE;
}

unsigned int linphone_core_get_audio_features(LinphoneCore *lc){
	unsigned int ret=0;
	const char *features=lp_config_get_string(lc->config,"sound","features",NULL);
	if (features){
		char tmp[256]={0};
		char name[256];
		char *p,*n;
		strncpy(tmp,features,sizeof(tmp)-1);
		for(p=tmp;*p!='\0';p++){
			if (*p==' ') continue;
			n=strchr(p,'|');
			if (n) *n='\0';
			sscanf(p,"%s",name);
			ms_message("Found audio feature %s",name);
			if (strcasecmp(name,"PLC")==0) ret|=AUDIO_STREAM_FEATURE_PLC;
			else if (strcasecmp(name,"EC")==0) ret|=AUDIO_STREAM_FEATURE_EC;
			else if (strcasecmp(name,"EQUALIZER")==0) ret|=AUDIO_STREAM_FEATURE_EQUALIZER;
			else if (strcasecmp(name,"VOL_SND")==0) ret|=AUDIO_STREAM_FEATURE_VOL_SND;
			else if (strcasecmp(name,"VOL_RCV")==0) ret|=AUDIO_STREAM_FEATURE_VOL_RCV;
			else if (strcasecmp(name,"DTMF")==0) ret|=AUDIO_STREAM_FEATURE_DTMF;
			else if (strcasecmp(name,"DTMF_ECHO")==0) ret|=AUDIO_STREAM_FEATURE_DTMF_ECHO;
			else if (strcasecmp(name,"MIXED_RECORDING")==0) ret|=AUDIO_STREAM_FEATURE_MIXED_RECORDING;
			else if (strcasecmp(name,"LOCAL_PLAYING")==0) ret|=AUDIO_STREAM_FEATURE_LOCAL_PLAYING;
			else if (strcasecmp(name,"REMOTE_PLAYING")==0) ret|=AUDIO_STREAM_FEATURE_REMOTE_PLAYING;
			else if (strcasecmp(name,"ALL")==0) ret|=AUDIO_STREAM_FEATURE_ALL;
			else if (strcasecmp(name,"NONE")==0) ret=0;
			else ms_error("Unsupported audio feature %s requested in config file.",name);
			if (!n) break;
			p=n;
		}
	}else ret=AUDIO_STREAM_FEATURE_ALL;

	if (ret==AUDIO_STREAM_FEATURE_ALL){
		/*since call recording is specified before creation of the stream in linphonecore,
		* it will be requested on demand. It is not necessary to include it all the time*/
		ret&=(unsigned int) ~AUDIO_STREAM_FEATURE_MIXED_RECORDING;
	}
	return ret;
}

bool_t linphone_core_tone_indications_enabled(LinphoneCore*lc){
	return !!lp_config_get_int(lc->config,"sound","tone_indications",1);
}

int linphone_core_get_local_ip_for(int type, const char *dest, char *result){
	if (dest && dest[0] == '\0') dest = NULL; /*If null, bctbx_get_local_ip_for() will use an arbitrary public address instead.*/
	return bctbx_get_local_ip_for(type, dest, 5060, result, LINPHONE_IPADDR_SIZE);
}

void linphone_core_get_local_ip(LinphoneCore *lc, int af, const char *dest, char *result) {
	if (af == AF_UNSPEC) {
		if (linphone_core_ipv6_enabled(lc)) {
			bool_t has_ipv6 = linphone_core_get_local_ip_for(AF_INET6, dest, result) == 0;
			if (strcmp(result, "::1") != 0)
				return; /*this machine has real ipv6 connectivity*/
			if ((linphone_core_get_local_ip_for(AF_INET, dest, result) == 0) && (strcmp(result, "127.0.0.1") != 0))
				return; /*this machine has only ipv4 connectivity*/
			if (has_ipv6) {
				/*this machine has only local loopback for both ipv4 and ipv6, so prefer ipv6*/
				strncpy(result, "::1", LINPHONE_IPADDR_SIZE);
				return;
			}
		}
		/*in all other cases use IPv4*/
		af = AF_INET;
	}
	linphone_core_get_local_ip_for(af, dest, result);
}

SalReason linphone_reason_to_sal(LinphoneReason reason){
	switch(reason){
		case LinphoneReasonNone:
			return SalReasonNone;
		case LinphoneReasonNoResponse:
			return SalReasonRequestTimeout;
		case LinphoneReasonForbidden:
			return SalReasonForbidden;
		case LinphoneReasonDeclined:
			return SalReasonDeclined;
		case LinphoneReasonNotFound:
			return SalReasonNotFound;
		case LinphoneReasonTemporarilyUnavailable:
			return SalReasonTemporarilyUnavailable;
		case LinphoneReasonBusy:
			return SalReasonBusy;
		case LinphoneReasonNotAcceptable:
			return SalReasonNotAcceptable;
		case LinphoneReasonIOError:
			return SalReasonServiceUnavailable;
		case LinphoneReasonDoNotDisturb:
			return SalReasonDoNotDisturb;
		case LinphoneReasonUnauthorized:
			return SalReasonUnauthorized;
		case LinphoneReasonUnsupportedContent:
			return SalReasonUnsupportedContent;
		case LinphoneReasonNoMatch:
			return SalReasonNoMatch;
		case LinphoneReasonMovedPermanently:
			return SalReasonMovedPermanently;
		case LinphoneReasonGone:
			return SalReasonGone;
		case LinphoneReasonAddressIncomplete:
			return SalReasonAddressIncomplete;
		case LinphoneReasonNotImplemented:
			return SalReasonNotImplemented;
		case LinphoneReasonBadGateway:
			return SalReasonBadGateway;
		case LinphoneReasonSessionIntervalTooSmall:
			return SalReasonSessionIntervalTooSmall;
		case LinphoneReasonServerTimeout:
			return SalReasonServerTimeout;
		case LinphoneReasonNotAnswered:
			return SalReasonRequestTimeout;
		case LinphoneReasonUnknown:
			return SalReasonUnknown;
	}
	return SalReasonUnknown;
}

LinphoneReason linphone_reason_from_sal(SalReason r){
	LinphoneReason ret=LinphoneReasonNone;
	switch(r){
		case SalReasonNone:
			ret=LinphoneReasonNone;
			break;
		case SalReasonIOError:
			ret=LinphoneReasonIOError;
			break;
		case SalReasonUnknown:
		case SalReasonInternalError:
			ret=LinphoneReasonUnknown;
			break;
		case SalReasonBusy:
			ret=LinphoneReasonBusy;
			break;
		case SalReasonDeclined:
			ret=LinphoneReasonDeclined;
			break;
		case SalReasonDoNotDisturb:
			ret=LinphoneReasonDoNotDisturb;
			break;
		case SalReasonForbidden:
			ret=LinphoneReasonBadCredentials;
			break;
		case SalReasonNotAcceptable:
			ret=LinphoneReasonNotAcceptable;
			break;
		case SalReasonNotFound:
			ret=LinphoneReasonNotFound;
			break;
		case SalReasonRedirect:
			ret=LinphoneReasonNone;
			break;
		case SalReasonTemporarilyUnavailable:
			ret=LinphoneReasonTemporarilyUnavailable;
			break;
		case SalReasonServiceUnavailable:
			ret=LinphoneReasonIOError;
			break;
		case SalReasonRequestPending:
			ret=LinphoneReasonTemporarilyUnavailable; /*might not be exactly the perfect matching, but better than LinphoneReasonNone*/
			break;
		case SalReasonUnauthorized:
			ret=LinphoneReasonUnauthorized;
			break;
		case SalReasonUnsupportedContent:
			ret=LinphoneReasonUnsupportedContent;
		break;
		case SalReasonNoMatch:
			ret=LinphoneReasonNoMatch;
		break;
		case SalReasonRequestTimeout:
			ret=LinphoneReasonNotAnswered;
		break;
		case SalReasonMovedPermanently:
			ret=LinphoneReasonMovedPermanently;
		break;
		case SalReasonGone:
			ret=LinphoneReasonGone;
		break;
		case SalReasonAddressIncomplete:
			ret=LinphoneReasonAddressIncomplete;
		break;
		case SalReasonNotImplemented:
			ret=LinphoneReasonNotImplemented;
		break;
		case SalReasonBadGateway:
			ret=LinphoneReasonBadGateway;
		break;
		case SalReasonSessionIntervalTooSmall:
			ret=LinphoneReasonSessionIntervalTooSmall;
		break;
		case SalReasonServerTimeout:
			ret=LinphoneReasonServerTimeout;
		break;
	}
	return ret;
}

/**
 * Set the name of the mediastreamer2 filter to be used for rendering video.
 * This is for advanced users of the library, mainly to workaround hardware/driver bugs.
 * @ingroup media_parameters
**/
void linphone_core_set_video_display_filter(LinphoneCore *lc, const char *filter_name){
	lp_config_set_string(lc->config,"video", "displaytype", filter_name);
}

const char *linphone_core_get_video_display_filter(LinphoneCore *lc){
	return lp_config_get_string(lc->config, "video","displaytype", NULL);
}

const char *linphone_core_get_default_video_display_filter(LinphoneCore *lc) {
	return ms_factory_get_default_video_renderer(lc->factory);
}

bool_t linphone_core_is_media_filter_supported(LinphoneCore *lc, const char *filtername) {
	return ms_factory_lookup_filter_by_name(lc->factory, filtername) != NULL;
}

void linphone_core_set_echo_canceller_filter_name(LinphoneCore *lc, const char *filtername) {
	lp_config_set_string(lc->config, "sound", "ec_filter", filtername);
	if (filtername != NULL) {
		ms_factory_set_echo_canceller_filter_name(lc->factory, filtername);
	}
}

const char * linphone_core_get_echo_canceller_filter_name(const LinphoneCore *lc) {
	return lp_config_get_string(lc->config, "sound", "ec_filter", NULL);
}

/**
 * Queue a task into the main loop. The data pointer must remain valid until the task is completed.
 * task_fun must return BELLE_SIP_STOP when job is finished.
**/
void linphone_core_queue_task(LinphoneCore *lc, belle_sip_source_func_t task_fun, void *data, const char *task_description){
	belle_sip_source_t *s=lc->sal->createTimer(task_fun,data, 20, task_description);
	belle_sip_object_unref(s);
}

static int get_unique_transport(LinphoneCore *lc, LinphoneTransportType *type, int *port){
	LinphoneSipTransports tp;
	linphone_core_get_sip_transports(lc,&tp);
	if (tp.tcp_port==0 && tp.tls_port==0 && tp.udp_port!=0){
		*type=LinphoneTransportUdp;
		*port=tp.udp_port;
		return 0;
	}else if (tp.tcp_port==0 && tp.udp_port==0 && tp.tls_port!=0){
		*type=LinphoneTransportTls;
		*port=tp.tls_port;
		return 0;
	}else if (tp.tcp_port!=0 && tp.udp_port==0 && tp.tls_port==0){
		*type=LinphoneTransportTcp;
		*port=tp.tcp_port;
		return 0;
	}
	return -1;
}

static void linphone_core_migrate_proxy_config(LinphoneCore *lc, LinphoneTransportType type){
	const bctbx_list_t *elem;
	for(elem=linphone_core_get_proxy_config_list(lc);elem!=NULL;elem=elem->next){
		LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)elem->data;
		const char *proxy=linphone_proxy_config_get_addr(cfg);
		const char *route=linphone_proxy_config_get_route(cfg);
		LinphoneAddress *proxy_addr=linphone_address_new(proxy);
		LinphoneAddress *route_addr=NULL;
		char *tmp;
		if (route) route_addr=linphone_address_new(route);
		if (proxy_addr){
			linphone_address_set_transport(proxy_addr,type);
			tmp=linphone_address_as_string(proxy_addr);
			linphone_proxy_config_set_server_addr(cfg,tmp);
			ms_free(tmp);
			linphone_address_unref(proxy_addr);
		}
		if (route_addr){
			linphone_address_set_transport(route_addr,type);
			tmp=linphone_address_as_string(route_addr);
			linphone_proxy_config_set_route(cfg,tmp);
			ms_free(tmp);
			linphone_address_unref(route_addr);
		}
	}
}

LinphoneStatus linphone_core_migrate_to_multi_transport(LinphoneCore *lc){
	if (!lp_config_get_int(lc->config,"sip","multi_transport_migration_done",0)){
		LinphoneTransportType tpt;
		int port;
		if (get_unique_transport(lc,&tpt,&port)==0){
			LinphoneSipTransports newtp={0};
			if (lp_config_get_int(lc->config,"sip","sip_random_port",0))
				port=-1;
			ms_message("Core is using a single SIP transport, migrating proxy config and enabling multi-transport.");
			linphone_core_migrate_proxy_config(lc,tpt);
			newtp.udp_port=port;
			newtp.tcp_port=port;
			newtp.tls_port=LC_SIP_TRANSPORT_RANDOM;
			lp_config_set_string(lc->config, "sip","sip_random_port",NULL); /*remove*/
			linphone_core_set_sip_transports(lc,&newtp);
		}
		lp_config_set_int(lc->config,"sip","multi_transport_migration_done",1);
		return 1;
	}
	return 0;
}

LinphoneToneDescription * linphone_tone_description_new(LinphoneReason reason, LinphoneToneID id, const char *audiofile){
	LinphoneToneDescription *obj=ms_new0(LinphoneToneDescription,1);
	obj->reason=reason;
	obj->toneid=id;
	obj->audiofile=audiofile ? ms_strdup(audiofile) : NULL;
	return obj;
}

void linphone_tone_description_destroy(LinphoneToneDescription *obj){
	if (obj->audiofile) ms_free(obj->audiofile);
	ms_free(obj);
}

void linphone_core_set_call_error_tone(LinphoneCore *lc, LinphoneReason reason, const char *audiofile){
	L_GET_PRIVATE_FROM_C_OBJECT(lc)->getToneManager()->setTone(reason, LinphoneToneUndefined, audiofile);
}

void linphone_core_set_tone(LinphoneCore *lc, LinphoneToneID id, const char *audiofile){
	L_GET_PRIVATE_FROM_C_OBJECT(lc)->getToneManager()->setTone(LinphoneReasonNone, id, audiofile);
}

const MSCryptoSuite * linphone_core_get_srtp_crypto_suites(LinphoneCore *lc){
	const char *config= lp_config_get_string(lc->config, "sip", "srtp_crypto_suites", "AES_CM_128_HMAC_SHA1_80, AES_CM_128_HMAC_SHA1_32, AES_256_CM_HMAC_SHA1_80, AES_256_CM_HMAC_SHA1_32");
	char *tmp=ms_strdup(config);

	char *sep;
	char *pos;
	char *nextpos;
	char *params;
	unsigned long found=0;
	MSCryptoSuite *result=NULL;
	pos=tmp;
	do{
		sep=strchr(pos,',');
		if (!sep) {
			sep=pos+strlen(pos);
			nextpos=NULL;
		}else {
			*sep='\0';
			nextpos=sep+1;
		}
		while(*pos==' ') ++pos; /*strip leading spaces*/
		params=strchr(pos,' '); /*look for params that arrive after crypto suite name*/
		if (params){
			while(*params==' ') ++params; /*strip parameters leading space*/
		}
		if (sep-pos>0){
			MSCryptoSuiteNameParams np;
			MSCryptoSuite suite;
			np.name=pos;
			np.params=params;
			suite=ms_crypto_suite_build_from_name_params(&np);
			if (suite!=MS_CRYPTO_SUITE_INVALID){
				result=reinterpret_cast<MSCryptoSuite *>(ms_realloc(result,(found+2)*sizeof(MSCryptoSuite)));
				result[found]=suite;
				result[found+1]=MS_CRYPTO_SUITE_INVALID;
				found++;
				ms_message("Configured srtp crypto suite: %s %s",np.name,np.params ? np.params : "");
			}
		}
		pos=nextpos;
	}while(pos);
	ms_free(tmp);
	if (lc->rtp_conf.srtp_suites){
		ms_free(lc->rtp_conf.srtp_suites);
		lc->rtp_conf.srtp_suites=NULL;
	}
	lc->rtp_conf.srtp_suites=result;
	return result;
}

static char * seperate_string_list(char **str) {
	char *ret;

	if (str == NULL) return NULL;
	if (*str == NULL) return NULL;
	if (**str == '\0') return NULL;

	ret = *str;
	for ( ; **str!='\0' && **str!=' ' && **str!=','; (*str)++);
	if (**str == '\0') {
		return ret;
	} else {
		**str = '\0';
		do { (*str)++; } while (**str!='\0' && (**str==' ' || **str==','));
		return ret;
	}
}

MsZrtpCryptoTypesCount linphone_core_get_zrtp_key_agreement_suites(LinphoneCore *lc, MSZrtpKeyAgreement keyAgreements[MS_MAX_ZRTP_CRYPTO_TYPES]){
	char * zrtpConfig = (char*)lp_config_get_string(lc->config, "sip", "zrtp_key_agreements_suites", NULL);
	MsZrtpCryptoTypesCount key_agreements_count = 0;
	char * entry, * origPtr;
	if (zrtpConfig == NULL) {
	        return 0;
	}

	origPtr = ms_strdup(zrtpConfig);
	zrtpConfig = origPtr;
	while ((entry = seperate_string_list(&zrtpConfig))) {
		const MSZrtpKeyAgreement agreement = ms_zrtp_key_agreement_from_string(entry);
		if (agreement != MS_ZRTP_KEY_AGREEMENT_INVALID) {
			ms_message("Configured zrtp key agreement: '%s'", ms_zrtp_key_agreement_to_string(agreement));
			keyAgreements[key_agreements_count++] = agreement;
		}
	}

	ms_free(origPtr);
	return key_agreements_count;
}

MsZrtpCryptoTypesCount linphone_core_get_zrtp_cipher_suites(LinphoneCore *lc, MSZrtpCipher ciphers[MS_MAX_ZRTP_CRYPTO_TYPES]){
	char * zrtpConfig = (char*)lp_config_get_string(lc->config, "sip", "zrtp_cipher_suites", NULL);
	MsZrtpCryptoTypesCount cipher_count = 0;
	char * entry, * origPtr;
	if (zrtpConfig == NULL) {
	        return 0;
	}

	origPtr = ms_strdup(zrtpConfig);
	zrtpConfig = origPtr;
	while ((entry = seperate_string_list(&zrtpConfig))) {
		const MSZrtpCipher cipher = ms_zrtp_cipher_from_string(entry);
		if (cipher != MS_ZRTP_CIPHER_INVALID) {
			ms_message("Configured zrtp cipher: '%s'", ms_zrtp_cipher_to_string(cipher));
			ciphers[cipher_count++] = cipher;
		}
	}

	ms_free(origPtr);
	return cipher_count;
}

MsZrtpCryptoTypesCount linphone_core_get_zrtp_hash_suites(LinphoneCore *lc, MSZrtpHash hashes[MS_MAX_ZRTP_CRYPTO_TYPES]){
	char * zrtpConfig = (char*)lp_config_get_string(lc->config, "sip", "zrtp_hash_suites", NULL);
	MsZrtpCryptoTypesCount hash_count = 0;
	char * entry, * origPtr;
	if (zrtpConfig == NULL) {
        	return 0;
	}

	origPtr = ms_strdup(zrtpConfig);
	zrtpConfig = origPtr;
	while ((entry = seperate_string_list(&zrtpConfig))) {
		const MSZrtpHash hash = ms_zrtp_hash_from_string(entry);
		if (hash != MS_ZRTP_HASH_INVALID) {
			ms_message("Configured zrtp hash: '%s'", ms_zrtp_hash_to_string(hash));
			hashes[hash_count++] = hash;
		}
	}

	ms_free(origPtr);
	return hash_count;
}

MsZrtpCryptoTypesCount linphone_core_get_zrtp_auth_suites(LinphoneCore *lc, MSZrtpAuthTag authTags[MS_MAX_ZRTP_CRYPTO_TYPES]){
	char * zrtpConfig = (char*)lp_config_get_string(lc->config, "sip", "zrtp_auth_suites", NULL);
	MsZrtpCryptoTypesCount auth_tag_count = 0;
	char * entry, * origPtr;
	if (zrtpConfig == NULL) {
		return 0;
	}

	origPtr = ms_strdup(zrtpConfig);
	zrtpConfig = origPtr;
	while ((entry = seperate_string_list(&zrtpConfig))) {
		const MSZrtpAuthTag authTag = ms_zrtp_auth_tag_from_string(entry);
		if (authTag != MS_ZRTP_AUTHTAG_INVALID) {
			ms_message("Configured zrtp auth tag: '%s'", ms_zrtp_auth_tag_to_string(authTag));
			authTags[auth_tag_count++] = authTag;
		}
	}

	ms_free(origPtr);
	return auth_tag_count;
}

MsZrtpCryptoTypesCount linphone_core_get_zrtp_sas_suites(LinphoneCore *lc, MSZrtpSasType sasTypes[MS_MAX_ZRTP_CRYPTO_TYPES]){
	char * zrtpConfig = (char*)lp_config_get_string(lc->config, "sip", "zrtp_sas_suites", NULL);
	MsZrtpCryptoTypesCount sas_count = 0;
	char * entry, * origPtr;
	if (zrtpConfig == NULL) {
	        return 0;
	}

	origPtr = ms_strdup(zrtpConfig);
	zrtpConfig = origPtr;
	while ((entry = seperate_string_list(&zrtpConfig))) {
		const MSZrtpSasType type = ms_zrtp_sas_type_from_string(entry);
		if (type != MS_ZRTP_SAS_INVALID) {
			ms_message("Configured zrtp SAS type: '%s'", ms_zrtp_sas_type_to_string(type));
			sasTypes[sas_count++] = type;
		}
	}

	ms_free(origPtr);
	return sas_count;
}

const char ** linphone_core_get_supported_file_formats(LinphoneCore *core){
	static const char *mkv="mkv";
	static const char *wav="wav";
	if (core->supported_formats==NULL){
		core->supported_formats=reinterpret_cast<const char **>(ms_malloc0(3*sizeof(char*)));
		core->supported_formats[0]=wav;
        if (ms_factory_lookup_filter_by_id(core->factory,MS_MKV_RECORDER_ID)){
			core->supported_formats[1]=mkv;
		}
	}
	return core->supported_formats;
}

bctbx_list_t * linphone_core_get_supported_file_formats_list(LinphoneCore *core){
	bctbx_list_t *file_formats = NULL;
	file_formats = bctbx_list_append(file_formats, ms_strdup("wav"));
	if (ms_factory_lookup_filter_by_id(core->factory,MS_MKV_RECORDER_ID)){
		file_formats = bctbx_list_append(file_formats, ms_strdup("mkv"));
	}
	return file_formats;
}

bool_t linphone_core_file_format_supported(LinphoneCore *lc, const char *fmt){
	const char **formats=linphone_core_get_supported_file_formats(lc);
	for(;*formats!=NULL;++formats){
		if (strcasecmp(*formats,fmt)==0) return TRUE;
	}
	return FALSE;
}

bool_t linphone_core_symmetric_rtp_enabled(LinphoneCore*lc){
	/* Clients don't really need rtp symmetric, unless they have a public IP address and want
	 * to interoperate with natted client. This case is not frequent with client apps.
	 */
	return !!lp_config_get_int(lc->config,"rtp","symmetric",0);
}

LinphoneStatus linphone_core_set_network_simulator_params(LinphoneCore *lc, const OrtpNetworkSimulatorParams *params){
	if (params!=&lc->net_conf.netsim_params)
		lc->net_conf.netsim_params=*params;
	
	/* update all running streams. */
	const bctbx_list_t *calls = linphone_core_get_calls(lc);
	for (const bctbx_list_t *elem = calls; elem != NULL; elem = elem->next){
		LinphoneCall *call = (LinphoneCall*) elem->data;
		std::shared_ptr<LinphonePrivate::MediaSession> ms = std::dynamic_pointer_cast<LinphonePrivate::MediaSession>(L_GET_PRIVATE_FROM_C_OBJECT(call)->getActiveSession());
		if (ms){
			L_GET_PRIVATE(ms)->getStreamsGroup().forEach<LinphonePrivate::MS2Stream>([params](LinphonePrivate::MS2Stream* ms2s){
			MediaStream *stream = ms2s->getMediaStream();
			if (stream && stream->sessions.rtp_session){
				rtp_session_enable_network_simulation(stream->sessions.rtp_session, params);
			}
			});
		}
	}
	return 0;
}

const OrtpNetworkSimulatorParams *linphone_core_get_network_simulator_params(const LinphoneCore *lc){
	return &lc->net_conf.netsim_params;
}

static const char *_tunnel_mode_str[3] = { "disable", "enable", "auto" };

LinphoneTunnelMode linphone_tunnel_mode_from_string(const char *string) {
	if(string != NULL) {
		int i;
		for(i=0; i<3 && strcmp(string, _tunnel_mode_str[i]) != 0; i++);
		if(i<3) {
			return (LinphoneTunnelMode)i;
		} else {
			ms_error("Invalid tunnel mode '%s'", string);
			return LinphoneTunnelModeDisable;
		}
	} else {
		return LinphoneTunnelModeDisable;
	}
}

const char *linphone_tunnel_mode_to_string(LinphoneTunnelMode mode) {
	switch(mode){
		case LinphoneTunnelModeAuto:
			return "auto";
		case LinphoneTunnelModeDisable:
			return "disable";
		case LinphoneTunnelModeEnable:
			return "enable";
	}
	return "invalid";
}


typedef struct Hook{
	LinphoneCoreIterateHook fun;
	void *data;
}Hook;

void linphone_task_list_init(LinphoneTaskList *t){
	t->hooks = NULL;
}

static Hook *hook_new(LinphoneCoreIterateHook hook, void *hook_data){
	Hook *h=ms_new0(Hook,1);
	h->fun=hook;
	h->data=hook_data;
	return h;
}

static void hook_invoke(Hook *h){
	h->fun(h->data);
}

void linphone_task_list_add(LinphoneTaskList *t, LinphoneCoreIterateHook hook, void *hook_data){
	t->hooks = bctbx_list_append(t->hooks,hook_new(hook,hook_data));
}

void linphone_task_list_remove(LinphoneTaskList *t, LinphoneCoreIterateHook hook, void *hook_data){
	bctbx_list_t *elem;
	for(elem=t->hooks;elem!=NULL;elem=elem->next){
		Hook *h=(Hook*)elem->data;
		if (h->fun==hook && h->data==hook_data){
			t->hooks = bctbx_list_erase_link(t->hooks,elem);
			ms_free(h);
			return;
		}
	}
	ms_error("linphone_task_list_remove(): No such hook found.");
}

void linphone_task_list_run(LinphoneTaskList *t){
	bctbx_list_for_each(t->hooks,(void (*)(void*))hook_invoke);
}

void linphone_task_list_free(LinphoneTaskList *t){
	t->hooks = bctbx_list_free_with_data(t->hooks, (void (*)(void*))ms_free);
}

void linphone_core_report_call_log(LinphoneCore *lc, LinphoneCallLog *call_log){
	bool_t call_logs_sqlite_db_found = FALSE;

	// TODO: This is a workaround that has to be removed ASAP
	// Do not add calls made to the conference factory in the history
	const char *conference_factory_uri = nullptr;
	LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(lc, call_log->to);
	if (proxy)
		conference_factory_uri = linphone_proxy_config_get_conference_factory_uri(proxy);
	if (conference_factory_uri) {
		LinphoneAddress *conference_factory_addr = linphone_address_new(conference_factory_uri);
		if (linphone_address_weak_equal(call_log->to, conference_factory_addr)) {
			linphone_address_unref(conference_factory_addr);
			return;
		}
		linphone_address_unref(conference_factory_addr);
	}
	const char *usernameFrom = linphone_address_get_username(call_log->from);
	const char *usernameTo = linphone_address_get_username(call_log->to);
	if ((usernameFrom && (strstr(usernameFrom, "chatroom-") == usernameFrom))
		|| (usernameTo && (strstr(usernameTo, "chatroom-") == usernameTo))
	)
		return;
	// End of workaround

	if (lc->logs_db) {
		call_logs_sqlite_db_found = TRUE;
		linphone_core_store_call_log(lc, call_log);
	}

	if (!call_logs_sqlite_db_found) {
		lc->call_logs=bctbx_list_prepend(lc->call_logs,linphone_call_log_ref(call_log));
		if (bctbx_list_size(lc->call_logs)>(size_t)lc->max_call_logs){
			bctbx_list_t *elem,*prevelem=NULL;
			/*find the last element*/
			for(elem=lc->call_logs;elem!=NULL;elem=elem->next){
				prevelem = elem;
			}
			elem = prevelem;
			linphone_call_log_unref((LinphoneCallLog*)elem->data);
			lc->call_logs = bctbx_list_erase_link(lc->call_logs,elem);
		}
		call_logs_write_to_config_file(lc);
	}

	linphone_core_notify_call_log_updated(lc,call_log);
}

void linphone_core_report_early_failed_call(LinphoneCore *lc, LinphoneCallDir dir, LinphoneAddress *from, LinphoneAddress *to, LinphoneErrorInfo *ei, const char *cid){
	LinphoneCallLog *l = linphone_call_log_new(dir, from, to);
	l->error_info = ei;
	l->call_id = cid? bctbx_strdup(cid) : NULL;
	l->status = LinphoneCallEarlyAborted;
	linphone_core_report_call_log(lc, l);
	linphone_call_log_unref(l);
}

/* Functions to mainpulate the LinphoneRange structure */

BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneRange);

BELLE_SIP_INSTANCIATE_VPTR(LinphoneRange, belle_sip_object_t,
	NULL, // destroy
	NULL, // clone
	NULL, // marshal
	FALSE
);

LinphoneRange *linphone_range_new() {
	LinphoneRange *range = belle_sip_object_new(LinphoneRange);
	range->min = 0;
	range->max = 0;
	return range;
}

LinphoneRange* linphone_range_ref(LinphoneRange* range) {
	return (LinphoneRange*) belle_sip_object_ref(range);
}

void linphone_range_unref(LinphoneRange* range) {
	belle_sip_object_unref(range);
}

void *linphone_range_get_user_data(const LinphoneRange *range) {
	return range->user_data;
}

void linphone_range_set_user_data(LinphoneRange *range, void *data) {
	range->user_data = data;
}

int linphone_range_get_min(const LinphoneRange *range) {
	return range->min;
}

int linphone_range_get_max(const LinphoneRange *range) {
	return range->max;
}

void linphone_range_set_min(LinphoneRange *range, int min) {
	range->min = min;
}

void linphone_range_set_max(LinphoneRange *range, int max) {
	range->max = max;
}



LinphoneHeaders * linphone_headers_ref(LinphoneHeaders *obj){
	sal_custom_header_ref((SalCustomHeader*)obj);
	return obj;
}


void linphone_headers_unref(LinphoneHeaders *obj){
	sal_custom_header_unref((SalCustomHeader*)obj);
}


const char* linphone_headers_get_value(LinphoneHeaders *obj, const char *header_name){
	return sal_custom_header_find((SalCustomHeader*)obj, header_name);
}

void linphone_headers_add(LinphoneHeaders *obj, const char *name, const char *value){
	sal_custom_header_append((SalCustomHeader*)obj, name, value);
}

void linphone_headers_remove(LinphoneHeaders *obj, const char *name){
	sal_custom_header_remove((SalCustomHeader*)obj, name);
}
