misc.c 38.8 KB
Newer Older
aymeric's avatar
aymeric committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

/*
linphone
Copyright (C) 2000  Simon MORLAT (simon.morlat@linphone.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 "private.h"
22
#include "lpconfig.h"
aymeric's avatar
aymeric committed
23 24 25
#include "mediastreamer2/mediastream.h"
#include <stdlib.h>
#include <stdio.h>
26
#ifdef HAVE_SIGHANDLER_T
aymeric's avatar
aymeric committed
27
#include <signal.h>
28
#endif /*HAVE_SIGHANDLER_T*/
Simon Morlat's avatar
Simon Morlat committed
29 30 31 32

#include <string.h>
#if !defined(_WIN32_WCE)
#include <errno.h>
aymeric's avatar
aymeric committed
33 34 35 36
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
Jehan Monnier's avatar
Jehan Monnier committed
37
#endif /*_WIN32_WCE*/
aymeric's avatar
aymeric committed
38

39
#undef snprintf
smorlat's avatar
smorlat committed
40
#include <ortp/stun.h>
aymeric's avatar
aymeric committed
41

42 43 44 45
#ifdef HAVE_GETIFADDRS
#include <net/if.h>
#include <ifaddrs.h>
#endif
46
#include <math.h>
47 48

#if !defined(WIN32)
aymeric's avatar
aymeric committed
49 50 51 52 53 54 55

static char lock_name[80];
static char lock_set=0;
/* put a lock file in /tmp. this is called when linphone runs as a daemon*/
int set_lock_file()
{
	FILE *lockfile;
56

aymeric's avatar
aymeric committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
	snprintf(lock_name,80,"/tmp/linphone.%i",getuid());
	lockfile=fopen(lock_name,"w");
	if (lockfile==NULL)
	{
		printf("Failed to create lock file.\n");
		return(-1);
	}
	fprintf(lockfile,"%i",getpid());
	fclose(lockfile);
	lock_set=1;
	return(0);
}

/* looks if there is a lock file. If presents return its content (the pid of the already running linphone), if not found, returns -1*/
int get_lock_file()
{
	int pid;
	FILE *lockfile;
75

aymeric's avatar
aymeric committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
	snprintf(lock_name,80,"/tmp/linphone.%i",getuid());
	lockfile=fopen(lock_name,"r");
	if (lockfile==NULL)
		return(-1);
	if (fscanf(lockfile,"%i",&pid)!=1){
		ms_warning("Could not read pid in lock file.");
		fclose(lockfile);
		return -1;
	}
	fclose(lockfile);
	return pid;
}

/* remove the lock file if it was set*/
int remove_lock_file()
{
	int err=0;
	if (lock_set)
	{
		err=unlink(lock_name);
		lock_set=0;
	}
	return(err);
}

#endif

char *int2str(int number)
{
	char *numstr=ms_malloc(10);
	snprintf(numstr,10,"%i",number);
	return numstr;
}

void check_sound_device(LinphoneCore *lc)
{
Simon Morlat's avatar
Simon Morlat committed
112
#ifdef _linux
Jehan Monnier's avatar
Jehan Monnier committed
113 114
	int fd=0;
	int len;
aymeric's avatar
aymeric committed
115 116 117 118 119 120 121
	int a;
	char *file=NULL;
	char *i810_audio=NULL;
	char *snd_pcm_oss=NULL;
	char *snd_mixer_oss=NULL;
	char *snd_pcm=NULL;
	fd=open("/proc/modules",O_RDONLY);
Simon Morlat's avatar
Simon Morlat committed
122

aymeric's avatar
aymeric committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
	if (fd>0){
		/* read the entire /proc/modules file and check if sound conf seems correct */
		/*a=fstat(fd,&statbuf);
		if (a<0) ms_warning("Can't stat /proc/modules:%s.",strerror(errno));
		len=statbuf.st_size;
		if (len==0) ms_warning("/proc/modules has zero size!");
		*/
		/***** fstat does not work on /proc/modules for unknown reason *****/
		len=6000;
		file=ms_malloc(len+1);
		a=read(fd,file,len);
		if (a<len) file=ms_realloc(file,a+1);
		file[a]='\0';
		i810_audio=strstr(file,"i810_audio");
		if (i810_audio!=NULL){
			/* I'm sorry i put this warning in comments because
			 * i don't use yet the right driver !! */
/*			lc->vtable.display_warning(lc,_("You are currently using the i810_audio driver.\nThis driver is buggy and so does not work with Linphone.\nWe suggest that you replace it by its equivalent ALSA driver,\neither with packages from your distribution, or by downloading\nALSA drivers at http://www.alsa-project.org."));*/
			goto end;
		}
		snd_pcm=strstr(file,"snd-pcm");
		if (snd_pcm!=NULL){
			snd_pcm_oss=strstr(file,"snd-pcm-oss");
			snd_mixer_oss=strstr(file,"snd-mixer-oss");
			if (snd_pcm_oss==NULL){
				lc->vtable.display_warning(lc,_("Your computer appears to be using ALSA sound drivers.\nThis is the best choice. However the pcm oss emulation module\nis missing and linphone needs it. Please execute\n'modprobe snd-pcm-oss' as root to load it."));
			}
			if (snd_mixer_oss==NULL){
				lc->vtable.display_warning(lc,_("Your computer appears to be using ALSA sound drivers.\nThis is the best choice. However the mixer oss emulation module\nis missing and linphone needs it. Please execute\n 'modprobe snd-mixer-oss' as root to load it."));
			}
		}
	}else {
Simon Morlat's avatar
Simon Morlat committed
155

aymeric's avatar
aymeric committed
156 157 158 159
		ms_warning("Could not open /proc/modules.");
	}
	/* now check general volume. Some user forget to rise it and then complain that linphone is
	not working */
160
	/* but some other users complain that linphone should not change levels...
aymeric's avatar
aymeric committed
161 162 163 164 165 166 167 168 169 170 171
	if (lc->sound_conf.sndcard!=NULL){
		a=snd_card_get_level(lc->sound_conf.sndcard,SND_CARD_LEVEL_GENERAL);
		if (a<50){
			ms_warning("General level is quite low (%i). Linphone rises it up for you.",a);
			snd_card_set_level(lc->sound_conf.sndcard,SND_CARD_LEVEL_GENERAL,80);
		}
	}
	*/
	end:
	if (file!=NULL) ms_free(file);
	if (fd>0) close(fd);
Simon Morlat's avatar
Simon Morlat committed
172
#endif
aymeric's avatar
aymeric committed
173 174 175 176 177 178
}

#define UDP_HDR_SZ 8
#define RTP_HDR_SZ 12
#define IP4_HDR_SZ 20   /*20 is the minimum, but there may be some options*/

179
static void payload_type_set_enable(PayloadType *pt,int value)
aymeric's avatar
aymeric committed
180 181
{
	if ((value)!=0) payload_type_set_flag(pt,PAYLOAD_TYPE_ENABLED); \
182
	else payload_type_unset_flag(pt,PAYLOAD_TYPE_ENABLED);
aymeric's avatar
aymeric committed
183 184
}

185
static bool_t payload_type_enabled(const PayloadType *pt) {
aymeric's avatar
aymeric committed
186 187 188
	return (((pt)->flags & PAYLOAD_TYPE_ENABLED)!=0);
}

189 190
bool_t linphone_core_payload_type_enabled(LinphoneCore *lc, const PayloadType *pt){
	if (ms_list_find(lc->codecs_conf.audio_codecs, (PayloadType*) pt) || ms_list_find(lc->codecs_conf.video_codecs, (PayloadType*)pt)){
191 192 193 194
		return payload_type_enabled(pt);
	}
	ms_error("Getting enablement status of codec not in audio or video list of PayloadType !");
	return FALSE;
aymeric's avatar
aymeric committed
195
}
196 197 198 199

int linphone_core_enable_payload_type(LinphoneCore *lc, PayloadType *pt, bool_t enabled){
	if (ms_list_find(lc->codecs_conf.audio_codecs,pt) || ms_list_find(lc->codecs_conf.video_codecs,pt)){
		payload_type_set_enable(pt,enabled);
200
		_linphone_core_codec_config_write(lc);
201 202 203 204
		return 0;
	}
	ms_error("Enabling codec not in audio or video list of PayloadType !");
	return -1;
aymeric's avatar
aymeric committed
205 206
}

207
int linphone_core_get_payload_type_number(LinphoneCore *lc, const PayloadType *pt){
208 209 210
       return payload_type_get_number(pt);
}

211 212 213
const char *linphone_core_get_payload_type_description(LinphoneCore *lc, PayloadType *pt){
	if (ms_filter_codec_supported(pt->mime_type)){
		MSFilterDesc *desc=ms_filter_get_encoder(pt->mime_type);
214 215 216 217 218
#ifdef ENABLE_NLS
		return dgettext("mediastreamer",desc->text);
#else
		return desc->text;
#endif
219 220
	}
	return NULL;
aymeric's avatar
aymeric committed
221 222
}

223

224 225 226 227 228
/*this function makes a special case for speex/8000.
This codec is variable bitrate. The 8kbit/s mode is interesting when having a low upload bandwidth, but its quality
is not very good. We 'd better use its 15kbt/s mode when we have enough bandwidth*/
static int get_codec_bitrate(LinphoneCore *lc, const PayloadType *pt){
	int upload_bw=linphone_core_get_upload_bandwidth(lc);
229
	if (bandwidth_is_greater(upload_bw,129) || (bandwidth_is_greater(upload_bw,33) && !linphone_core_video_enabled(lc)) ) {
230 231 232 233 234 235 236 237
		if (strcmp(pt->mime_type,"speex")==0 && pt->clock_rate==8000){
			return 15000;
		}
	}
	return pt->normal_bitrate;
}

static double get_audio_payload_bandwidth(LinphoneCore *lc, const PayloadType *pt){
aymeric's avatar
aymeric committed
238 239 240
	double npacket=50;
	double packet_size;
	int bitrate;
241 242
	bitrate=get_codec_bitrate(lc,pt);
	packet_size= (((double)bitrate)/(50*8))+UDP_HDR_SZ+RTP_HDR_SZ+IP4_HDR_SZ;
aymeric's avatar
aymeric committed
243 244 245
	return packet_size*8.0*npacket;
}

Simon Morlat's avatar
Simon Morlat committed
246
void linphone_core_update_allocated_audio_bandwidth_in_call(LinphoneCall *call, const PayloadType *pt){
247
	call->audio_bw=(int)(ceil(get_audio_payload_bandwidth(call->core,pt)/1000.0)); /*rounding codec bandwidth should be avoid, specially for AMR*/
Simon Morlat's avatar
Simon Morlat committed
248
	ms_message("Audio bandwidth for this call is %i",call->audio_bw);
aymeric's avatar
aymeric committed
249 250
}

251
void linphone_core_update_allocated_audio_bandwidth(LinphoneCore *lc){
aymeric's avatar
aymeric committed
252 253 254 255 256
	const MSList *elem;
	PayloadType *max=NULL;
	for(elem=linphone_core_get_audio_codecs(lc);elem!=NULL;elem=elem->next){
		PayloadType *pt=(PayloadType*)elem->data;
		if (payload_type_enabled(pt)){
257
			int pt_bitrate=get_codec_bitrate(lc,pt);
aymeric's avatar
aymeric committed
258
			if (max==NULL) max=pt;
259
			else if (max->normal_bitrate<pt_bitrate){
aymeric's avatar
aymeric committed
260 261 262 263 264
				max=pt;
			}
		}
	}
	if (max) {
Simon Morlat's avatar
Simon Morlat committed
265
		lc->audio_bw=(int)(get_audio_payload_bandwidth(lc,max)/1000.0);
aymeric's avatar
aymeric committed
266 267 268
	}
}

269
bool_t linphone_core_is_payload_type_usable_for_bandwidth(LinphoneCore *lc, PayloadType *pt,  int bandwidth_limit)
Simon Morlat's avatar
Simon Morlat committed
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
{
	double codec_band;
	bool_t ret=FALSE;
	
	switch (pt->type){
		case PAYLOAD_AUDIO_CONTINUOUS:
		case PAYLOAD_AUDIO_PACKETIZED:
			codec_band=get_audio_payload_bandwidth(lc,pt);
			ret=bandwidth_is_greater(bandwidth_limit*1000,codec_band);
			/*hack to avoid using uwb codecs when having low bitrate and video*/
			if (bandwidth_is_greater(199,bandwidth_limit)){
				if (linphone_core_video_enabled(lc) && pt->clock_rate>16000){
					ret=FALSE;
				}
			}
			//ms_message("Payload %s: %g",pt->mime_type,codec_band);
			break;
		case PAYLOAD_VIDEO:
			if (bandwidth_limit!=0) {/* infinite (-1) or strictly positive*/
				ret=TRUE;
			}
			else ret=FALSE;
			break;
	}
	return ret;
}

aymeric's avatar
aymeric committed
297 298 299 300
/* return TRUE if codec can be used with bandwidth, FALSE else*/
bool_t linphone_core_check_payload_type_usability(LinphoneCore *lc, PayloadType *pt)
{
	double codec_band;
Simon Morlat's avatar
Simon Morlat committed
301
	int allowed_bw,video_bw;
aymeric's avatar
aymeric committed
302
	bool_t ret=FALSE;
303 304

	linphone_core_update_allocated_audio_bandwidth(lc);
Simon Morlat's avatar
Simon Morlat committed
305
	allowed_bw=get_min_bandwidth(linphone_core_get_download_bandwidth(lc),
aymeric's avatar
aymeric committed
306
					linphone_core_get_upload_bandwidth(lc));
Simon Morlat's avatar
Simon Morlat committed
307 308 309 310
	if (allowed_bw==0) {
		allowed_bw=-1;
		video_bw=1500; /*around 1.5 Mbit/s*/
	}else
311
		video_bw=get_video_bandwidth(allowed_bw,lc->audio_bw);
aymeric's avatar
aymeric committed
312 313 314 315

	switch (pt->type){
		case PAYLOAD_AUDIO_CONTINUOUS:
		case PAYLOAD_AUDIO_PACKETIZED:
316
			codec_band=get_audio_payload_bandwidth(lc,pt);
Simon Morlat's avatar
Simon Morlat committed
317
			ret=bandwidth_is_greater(allowed_bw*1000,codec_band);
318
			/*hack to avoid using uwb codecs when having low bitrate and video*/
Simon Morlat's avatar
Simon Morlat committed
319
			if (bandwidth_is_greater(199,allowed_bw)){
320 321 322 323
				if (linphone_core_video_enabled(lc) && pt->clock_rate>16000){
					ret=FALSE;
				}
			}
aymeric's avatar
aymeric committed
324 325 326
			//ms_message("Payload %s: %g",pt->mime_type,codec_band);
			break;
		case PAYLOAD_VIDEO:
327
			if (video_bw>0){
Simon Morlat's avatar
Simon Morlat committed
328
				pt->normal_bitrate=video_bw*1000;
329 330
				ret=TRUE;
			}
aymeric's avatar
aymeric committed
331 332 333 334 335 336 337
			else ret=FALSE;
			break;
	}
	return ret;
}

bool_t lp_spawn_command_line_sync(const char *command, char **result,int *command_ret){
338
#if !defined(_WIN32_WCE)
aymeric's avatar
aymeric committed
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
	FILE *f=popen(command,"r");
	if (f!=NULL){
		int err;
		*result=ms_malloc(4096);
		err=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;
	}
354
#endif /*_WIN32_WCE*/
aymeric's avatar
aymeric committed
355 356 357
	return FALSE;
}

smorlat's avatar
smorlat committed
358 359 360 361 362 363 364 365 366 367 368 369 370 371
static ortp_socket_t create_socket(int local_port){
	struct sockaddr_in laddr;
	ortp_socket_t sock;
	int optval;
	sock=socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
	if (sock<0) {
		ms_error("Fail to create socket");
		return -1;
	}
	memset (&laddr,0,sizeof(laddr));
	laddr.sin_family=AF_INET;
	laddr.sin_addr.s_addr=INADDR_ANY;
	laddr.sin_port=htons(local_port);
	if (bind(sock,(struct sockaddr*)&laddr,sizeof(laddr))<0){
smorlat's avatar
smorlat committed
372
		ms_error("Bind socket to 0.0.0.0:%i failed: %s",local_port,getSocketError());
smorlat's avatar
smorlat committed
373 374 375 376 377 378 379 380 381 382 383 384
		close_socket(sock);
		return -1;
	}
	optval=1;
	if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR,
				(SOCKET_OPTION_VALUE)&optval, sizeof (optval))<0){
		ms_warning("Fail to set SO_REUSEADDR");
	}
	set_non_blocking_socket(sock);
	return sock;
}

smorlat's avatar
smorlat committed
385
static int sendStunRequest(int sock, const struct sockaddr *server, socklen_t addrlen, int id, bool_t changeAddr){
smorlat's avatar
smorlat committed
386 387 388 389 390 391 392 393 394
	char buf[STUN_MAX_MESSAGE_SIZE];
	int len = STUN_MAX_MESSAGE_SIZE;
	StunAtrString username;
   	StunAtrString password;
	StunMessage req;
	int err;
	memset(&req, 0, sizeof(StunMessage));
	memset(&username,0,sizeof(username));
	memset(&password,0,sizeof(password));
smorlat's avatar
smorlat committed
395
	stunBuildReqSimple( &req, &username, changeAddr , changeAddr , id);
aymeric's avatar
aymeric committed
396
	len = stunEncodeMessage( &req, buf, len, &password);
smorlat's avatar
smorlat committed
397 398 399 400 401 402 403 404 405 406 407 408
	if (len<=0){
		ms_error("Fail to encode stun message.");
		return -1;
	}
	err=sendto(sock,buf,len,0,server,addrlen);
	if (err<0){
		ms_error("sendto failed: %s",strerror(errno));
		return -1;
	}
	return 0;
}

409
int parse_hostname_to_addr(const char *server, struct sockaddr_storage *ss, socklen_t *socklen){
smorlat's avatar
smorlat committed
410
	struct addrinfo hints,*res=NULL;
411 412
	int family = PF_INET;
	int port_int = 3478;
smorlat's avatar
smorlat committed
413
	int ret;
414
	char port[6];
smorlat's avatar
smorlat committed
415
	char host[NI_MAXHOST];
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
	char *p1, *p2;
	if ((sscanf(server, "[%64[^]]]:%d", host, &port_int) == 2) || (sscanf(server, "[%64[^]]]", host) == 1)) {
		family = PF_INET6;
	} else {
		p1 = strchr(server, ':');
		p2 = strrchr(server, ':');
		if (p1 && p2 && (p1 != p2)) {
			family = PF_INET6;
			host[NI_MAXHOST-1]='\0';
			strncpy(host, server, sizeof(host) - 1);
		} else if (sscanf(server, "%[^:]:%d", host, &port_int) != 2) {
			host[NI_MAXHOST-1]='\0';
			strncpy(host, server, sizeof(host) - 1);
		}
	}
	snprintf(port, sizeof(port), "%d", port_int);
smorlat's avatar
smorlat committed
432
	memset(&hints,0,sizeof(hints));
433
	hints.ai_family=family;
smorlat's avatar
smorlat committed
434 435 436 437 438 439 440 441 442 443 444 445 446 447
	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,res->ai_addrlen);
	*socklen=res->ai_addrlen;
	freeaddrinfo(res);
	return 0;
}

smorlat's avatar
smorlat committed
448
static int recvStunResponse(ortp_socket_t sock, char *ipaddr, int *port, int *id){
smorlat's avatar
smorlat committed
449 450 451 452 453 454
	char buf[STUN_MAX_MESSAGE_SIZE];
   	int len = STUN_MAX_MESSAGE_SIZE;
	StunMessage resp;
	len=recv(sock,buf,len,0);
	if (len>0){
		struct in_addr ia;
aymeric's avatar
aymeric committed
455 456
		stunParseMessage(buf,len, &resp );
		*id=resp.msgHdr.tr_id.octet[0];
457 458 459 460 461 462 463
		if (resp.hasXorMappedAddress){
			*port = resp.xorMappedAddress.ipv4.port;
			ia.s_addr=htonl(resp.xorMappedAddress.ipv4.addr);
		}else if (resp.hasMappedAddress){
			*port = resp.mappedAddress.ipv4.port;
			ia.s_addr=htonl(resp.mappedAddress.ipv4.addr);
		}else return -1;
smorlat's avatar
smorlat committed
464 465 466 467 468
		strncpy(ipaddr,inet_ntoa(ia),LINPHONE_IPADDR_SIZE);
	}
	return len;
}

Simon Morlat's avatar
Simon Morlat committed
469
/* this functions runs a simple stun test and return the number of milliseconds to complete the tests, or -1 if the test were failed.*/
470
int linphone_core_run_stun_tests(LinphoneCore *lc, LinphoneCall *call){
smorlat's avatar
smorlat committed
471
	const char *server=linphone_core_get_stun_server(lc);
472 473
	StunCandidate *ac=&call->ac;
	StunCandidate *vc=&call->vc;
Simon Morlat's avatar
Simon Morlat committed
474
	
smorlat's avatar
smorlat committed
475 476
	if (lc->sip_conf.ipv6_enabled){
		ms_warning("stun support is not implemented for ipv6");
Simon Morlat's avatar
Simon Morlat committed
477
		return -1;
smorlat's avatar
smorlat committed
478 479 480 481 482
	}
	if (server!=NULL){
		struct sockaddr_storage ss;
		socklen_t ss_len;
		ortp_socket_t sock1=-1, sock2=-1;
483
		int loops=0;
smorlat's avatar
smorlat committed
484 485
		bool_t video_enabled=linphone_core_video_enabled(lc);
		bool_t got_audio,got_video;
smorlat's avatar
smorlat committed
486
		bool_t cone_audio=FALSE,cone_video=FALSE;
smorlat's avatar
smorlat committed
487
		struct timeval init,cur;
Simon Morlat's avatar
Simon Morlat committed
488 489
		double elapsed;
		int ret=0;
490
		
491
		if (parse_hostname_to_addr(server,&ss,&ss_len)<0){
smorlat's avatar
smorlat committed
492
			ms_error("Fail to parser stun server address: %s",server);
Simon Morlat's avatar
Simon Morlat committed
493
			return -1;
smorlat's avatar
smorlat committed
494 495 496
		}
		if (lc->vtable.display_status!=NULL)
			lc->vtable.display_status(lc,_("Stun lookup in progress..."));
497

smorlat's avatar
smorlat committed
498
		/*create the two audio and video RTP sockets, and send STUN message to our stun server */
499
		sock1=create_socket(call->audio_port);
Simon Morlat's avatar
Simon Morlat committed
500
		if (sock1==-1) return -1;
smorlat's avatar
smorlat committed
501
		if (video_enabled){
502
			sock2=create_socket(call->video_port);
Simon Morlat's avatar
Simon Morlat committed
503
			if (sock2==-1) return -1;
smorlat's avatar
smorlat committed
504
		}
smorlat's avatar
smorlat committed
505 506 507 508
		got_audio=FALSE;
		got_video=FALSE;
		gettimeofday(&init,NULL);
		do{
Simon Morlat's avatar
Simon Morlat committed
509
			
smorlat's avatar
smorlat committed
510
			int id;
511 512 513 514 515 516 517 518 519
			if (loops%20==0){
				ms_message("Sending stun requests...");
				sendStunRequest(sock1,(struct sockaddr*)&ss,ss_len,11,TRUE);
				sendStunRequest(sock1,(struct sockaddr*)&ss,ss_len,1,FALSE);
				if (sock2!=-1){
					sendStunRequest(sock2,(struct sockaddr*)&ss,ss_len,22,TRUE);
					sendStunRequest(sock2,(struct sockaddr*)&ss,ss_len,2,FALSE);
				}
			}
smorlat's avatar
smorlat committed
520 521 522 523 524 525
#ifdef WIN32
			Sleep(10);
#else
			usleep(10000);
#endif

526 527
			if (recvStunResponse(sock1,ac->addr,
						&ac->port,&id)>0){
smorlat's avatar
smorlat committed
528
				ms_message("STUN test result: local audio port maps to %s:%i",
529 530
						ac->addr,
						ac->port);
smorlat's avatar
smorlat committed
531 532
				if (id==11)
					cone_audio=TRUE;
smorlat's avatar
smorlat committed
533 534
				got_audio=TRUE;
			}
535 536
			if (recvStunResponse(sock2,vc->addr,
							&vc->port,&id)>0){
smorlat's avatar
smorlat committed
537
				ms_message("STUN test result: local video port maps to %s:%i",
538 539
					vc->addr,
					vc->port);
smorlat's avatar
smorlat committed
540 541
				if (id==22)
					cone_video=TRUE;
smorlat's avatar
smorlat committed
542
				got_video=TRUE;
smorlat's avatar
smorlat committed
543 544 545
			}
			gettimeofday(&cur,NULL);
			elapsed=((cur.tv_sec-init.tv_sec)*1000.0) +  ((cur.tv_usec-init.tv_usec)/1000.0);
546 547
			if (elapsed>2000)  {
				ms_message("Stun responses timeout, going ahead.");
Simon Morlat's avatar
Simon Morlat committed
548
				ret=-1;
549 550 551 552
				break;
			}
			loops++;
		}while(!(got_audio && (got_video||sock2==-1)  ) );
Simon Morlat's avatar
Simon Morlat committed
553
		if (ret==0) ret=(int)elapsed;
smorlat's avatar
smorlat committed
554 555
		if (!got_audio){
			ms_error("No stun server response for audio port.");
smorlat's avatar
smorlat committed
556 557
		}else{
			if (!cone_audio) {
558
				ms_message("NAT is symmetric for audio port");
smorlat's avatar
smorlat committed
559
			}
smorlat's avatar
smorlat committed
560
		}
561
		if (sock2!=-1){
smorlat's avatar
smorlat committed
562 563 564 565
			if (!got_video){
				ms_error("No stun server response for video port.");
			}else{
				if (!cone_video) {
566
					ms_message("NAT is symmetric for video port.");
smorlat's avatar
smorlat committed
567 568
				}
			}
smorlat's avatar
smorlat committed
569
		}
smorlat's avatar
smorlat committed
570
		close_socket(sock1);
571
		if (sock2!=-1) close_socket(sock2);
Simon Morlat's avatar
Simon Morlat committed
572
		return ret;
smorlat's avatar
smorlat committed
573
	}
Simon Morlat's avatar
Simon Morlat committed
574
	return -1;
smorlat's avatar
smorlat committed
575 576
}

577 578 579 580 581 582 583 584 585 586
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;
}

Simon Morlat's avatar
Simon Morlat committed
587
void linphone_core_adapt_to_network(LinphoneCore *lc, int ping_time_ms, LinphoneCallParams *params){
588
	if (ping_time_ms>0 && lp_config_get_int(lc->config,"net","activate_edge_workarounds",0)==1){
589
		ms_message("Stun server ping time is %i ms",ping_time_ms);
Simon Morlat's avatar
Simon Morlat committed
590 591 592
		int threshold=lp_config_get_int(lc->config,"net","edge_ping_time",500);
		
		if (ping_time_ms>threshold){
593
			/* we might be in a 2G network*/
jehan's avatar
jehan committed
594
			params->low_bandwidth=TRUE;
Simon Morlat's avatar
Simon Morlat committed
595 596
		}/*else use default settings */
	}
597 598 599 600 601
	if (params->low_bandwidth){
		params->up_bw=params->down_bw=linphone_core_get_edge_bw(lc);
		params->up_ptime=params->down_ptime=linphone_core_get_edge_ptime(lc);
		params->has_video=FALSE;
	}
Simon Morlat's avatar
Simon Morlat committed
602 603 604 605
}



606
int linphone_core_gather_ice_candidates(LinphoneCore *lc, LinphoneCall *call)
607
{
608
	char local_addr[64];
609 610
	struct sockaddr_storage ss;
	socklen_t ss_len;
611 612
	IceCheckList *audio_check_list;
	IceCheckList *video_check_list;
613 614
	const char *server = linphone_core_get_stun_server(lc);

615 616 617
	if ((server == NULL) || (call->ice_session == NULL)) return -1;
	audio_check_list = ice_session_check_list(call->ice_session, 0);
	video_check_list = ice_session_check_list(call->ice_session, 1);
618
	if (audio_check_list == NULL) return -1;
619

620 621
	if (lc->sip_conf.ipv6_enabled){
		ms_warning("stun support is not implemented for ipv6");
622
		return -1;
623 624 625 626
	}

	if (parse_hostname_to_addr(server, &ss, &ss_len) < 0) {
		ms_error("Fail to parser stun server address: %s", server);
627
		return -1;
628 629 630 631
	}
	if (lc->vtable.display_status != NULL)
		lc->vtable.display_status(lc, _("ICE local candidates gathering in progress..."));

632
	/* Gather local host candidates. */
633
	if (linphone_core_get_local_ip_for(AF_INET, server, local_addr) < 0) {
634
		ms_error("Fail to get local ip");
635
		return -1;
636
	}
637 638 639 640 641 642 643
	if ((ice_check_list_state(audio_check_list) != ICL_Completed) && (ice_check_list_candidates_gathered(audio_check_list) == FALSE)) {
		ice_add_local_candidate(audio_check_list, "host", local_addr, call->audio_port, 1, NULL);
		ice_add_local_candidate(audio_check_list, "host", local_addr, call->audio_port + 1, 2, NULL);
		call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateInProgress;
	}
	if (call->params.has_video && (video_check_list != NULL)
		&& (ice_check_list_state(video_check_list) != ICL_Completed) && (ice_check_list_candidates_gathered(video_check_list) == FALSE)) {
644 645
		ice_add_local_candidate(video_check_list, "host", local_addr, call->video_port, 1, NULL);
		ice_add_local_candidate(video_check_list, "host", local_addr, call->video_port + 1, 2, NULL);
646
		call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateInProgress;
647
	}
648

jehan's avatar
jehan committed
649
	ms_message("ICE: gathering candidate from [%s]",server);
650
	/* Gather local srflx candidates. */
651
	ice_session_gather_candidates(call->ice_session, ss, ss_len);
652
	return 0;
653 654
}

655 656 657 658
void linphone_core_update_ice_state_in_call_stats(LinphoneCall *call)
{
	IceCheckList *audio_check_list;
	IceCheckList *video_check_list;
Ghislain MARY's avatar
Ghislain MARY committed
659
	IceSessionState session_state;
660 661 662 663 664 665

	if (call->ice_session == NULL) return;
	audio_check_list = ice_session_check_list(call->ice_session, 0);
	video_check_list = ice_session_check_list(call->ice_session, 1);
	if (audio_check_list == NULL) return;

Ghislain MARY's avatar
Ghislain MARY committed
666 667
	session_state = ice_session_state(call->ice_session);
	if ((session_state == IS_Completed) || ((session_state == IS_Failed) && (ice_session_has_completed_check_list(call->ice_session) == TRUE))) {
668 669
		if (ice_check_list_state(audio_check_list) == ICL_Completed) {
			switch (ice_check_list_selected_valid_candidate_type(audio_check_list)) {
Ghislain MARY's avatar
Ghislain MARY committed
670
				case ICT_HostCandidate:
671
					call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateHostConnection;
Ghislain MARY's avatar
Ghislain MARY committed
672 673 674
					break;
				case ICT_ServerReflexiveCandidate:
				case ICT_PeerReflexiveCandidate:
675
					call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateReflexiveConnection;
Ghislain MARY's avatar
Ghislain MARY committed
676 677
					break;
				case ICT_RelayedCandidate:
678
					call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateRelayConnection;
Ghislain MARY's avatar
Ghislain MARY committed
679 680
					break;
			}
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
		} else {
			call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateFailed;
		}
		if (call->params.has_video && (video_check_list != NULL)) {
			if (ice_check_list_state(video_check_list) == ICL_Completed) {
				switch (ice_check_list_selected_valid_candidate_type(video_check_list)) {
					case ICT_HostCandidate:
						call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateHostConnection;
						break;
					case ICT_ServerReflexiveCandidate:
					case ICT_PeerReflexiveCandidate:
						call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateReflexiveConnection;
						break;
					case ICT_RelayedCandidate:
						call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateRelayConnection;
						break;
				}
			} else {
				call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateFailed;
			}
Ghislain MARY's avatar
Ghislain MARY committed
701
		}
702 703 704 705 706
	} else if (session_state == IS_Running) {
		call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateInProgress;
		if (call->params.has_video && (video_check_list != NULL)) {
			call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateInProgress;
		}
Ghislain MARY's avatar
Ghislain MARY committed
707 708 709 710 711
	} else {
		call->stats[LINPHONE_CALL_STATS_AUDIO].ice_state = LinphoneIceStateFailed;
		if (call->params.has_video && (video_check_list != NULL)) {
			call->stats[LINPHONE_CALL_STATS_VIDEO].ice_state = LinphoneIceStateFailed;
		}
712 713 714
	}
}

715 716
void linphone_core_update_local_media_description_from_ice(SalMediaDescription *desc, IceSession *session)
{
717
	const char *rtp_addr, *rtcp_addr;
718
	IceSessionState session_state = ice_session_state(session);
719
	int nb_candidates;
720
	int i, j;
721
	bool_t result;
722

723 724
	if (session_state == IS_Completed) {
		desc->ice_completed = TRUE;
Ghislain MARY's avatar
Ghislain MARY committed
725 726 727 728 729 730
		result = ice_check_list_selected_valid_local_candidate(ice_session_check_list(session, 0), &rtp_addr, NULL, NULL, NULL);
		if (result == TRUE) {
			strncpy(desc->addr, rtp_addr, sizeof(desc->addr));
		} else {
			ms_warning("If ICE has completed successfully, rtp_addr should be set!");
		}
731 732 733 734
	}
	else {
		desc->ice_completed = FALSE;
	}
735 736
	strncpy(desc->ice_pwd, ice_session_local_pwd(session), sizeof(desc->ice_pwd));
	strncpy(desc->ice_ufrag, ice_session_local_ufrag(session), sizeof(desc->ice_ufrag));
737
	for (i = 0; i < desc->n_active_streams; i++) {
738 739
		SalStreamDescription *stream = &desc->streams[i];
		IceCheckList *cl = ice_session_check_list(session, i);
740
		nb_candidates = 0;
741
		if (cl == NULL) continue;
742
		if (ice_check_list_state(cl) == ICL_Completed) {
743
			stream->ice_completed = TRUE;
744 745 746 747 748 749
			result = ice_check_list_selected_valid_local_candidate(ice_session_check_list(session, i), &rtp_addr, &stream->rtp_port, &rtcp_addr, &stream->rtcp_port);
		} else {
			stream->ice_completed = FALSE;
			result = ice_check_list_default_local_candidate(ice_session_check_list(session, i), &rtp_addr, &stream->rtp_port, &rtcp_addr, &stream->rtcp_port);
		}
		if (result == TRUE) {
750 751 752
			strncpy(stream->rtp_addr, rtp_addr, sizeof(stream->rtp_addr));
			strncpy(stream->rtcp_addr, rtcp_addr, sizeof(stream->rtcp_addr));
		} else {
753 754
			memset(stream->rtp_addr, 0, sizeof(stream->rtp_addr));
			memset(stream->rtcp_addr, 0, sizeof(stream->rtcp_addr));
755
		}
756 757 758 759 760 761 762 763
		if ((strlen(ice_check_list_local_pwd(cl)) != strlen(desc->ice_pwd)) || (strcmp(ice_check_list_local_pwd(cl), desc->ice_pwd)))
			strncpy(stream->ice_pwd, ice_check_list_local_pwd(cl), sizeof(stream->ice_pwd));
		else
			memset(stream->ice_pwd, 0, sizeof(stream->ice_pwd));
		if ((strlen(ice_check_list_local_ufrag(cl)) != strlen(desc->ice_ufrag)) || (strcmp(ice_check_list_local_ufrag(cl), desc->ice_ufrag)))
			strncpy(stream->ice_ufrag, ice_check_list_local_ufrag(cl), sizeof(stream->ice_ufrag));
		else
			memset(stream->ice_pwd, 0, sizeof(stream->ice_pwd));
764
		stream->ice_mismatch = ice_check_list_is_mismatch(cl);
765
		if ((ice_check_list_state(cl) == ICL_Running) || (ice_check_list_state(cl) == ICL_Completed)) {
766
			memset(stream->ice_candidates, 0, sizeof(stream->ice_candidates));
Ghislain MARY's avatar
Ghislain MARY committed
767
			for (j = 0; j < MIN(ms_list_size(cl->local_candidates), SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES); j++) {
768
				SalIceCandidate *sal_candidate = &stream->ice_candidates[nb_candidates];
769 770 771 772 773 774 775 776 777 778 779 780
				IceCandidate *ice_candidate = ms_list_nth_data(cl->local_candidates, j);
				const char *default_addr = NULL;
				int default_port = 0;
				if (ice_candidate->componentID == 1) {
					default_addr = stream->rtp_addr;
					default_port = stream->rtp_port;
				} else if (ice_candidate->componentID == 2) {
					default_addr = stream->rtcp_addr;
					default_port = stream->rtcp_port;
				} else continue;
				if (default_addr[0] == '\0') default_addr = desc->addr;
				/* Only include the candidates matching the default destination for each component of the stream if the state is Completed as specified in RFC5245 section 9.1.2.2. */
781
				if ((ice_check_list_state(cl) == ICL_Completed)
782 783 784 785 786 787 788 789 790 791 792 793
					&& !((ice_candidate->taddr.port == default_port) && (strlen(ice_candidate->taddr.ip) == strlen(default_addr)) && (strcmp(ice_candidate->taddr.ip, default_addr) == 0)))
					continue;
				strncpy(sal_candidate->foundation, ice_candidate->foundation, sizeof(sal_candidate->foundation));
				sal_candidate->componentID = ice_candidate->componentID;
				sal_candidate->priority = ice_candidate->priority;
				strncpy(sal_candidate->type, ice_candidate_type(ice_candidate), sizeof(sal_candidate->type));
				strncpy(sal_candidate->addr, ice_candidate->taddr.ip, sizeof(sal_candidate->addr));
				sal_candidate->port = ice_candidate->taddr.port;
				if ((ice_candidate->base != NULL) && (ice_candidate->base != ice_candidate)) {
					strncpy(sal_candidate->raddr, ice_candidate->base->taddr.ip, sizeof(sal_candidate->raddr));
					sal_candidate->rport = ice_candidate->base->taddr.port;
				}
794
				nb_candidates++;
795 796
			}
		}
797
		if ((ice_check_list_state(cl) == ICL_Completed) && (ice_session_role(session) == IR_Controlling)) {
798 799
			int rtp_port, rtcp_port;
			memset(stream->ice_remote_candidates, 0, sizeof(stream->ice_remote_candidates));
Ghislain MARY's avatar
Ghislain MARY committed
800
			if (ice_check_list_selected_valid_remote_candidate(cl, &rtp_addr, &rtp_port, &rtcp_addr, &rtcp_port) == TRUE) {
801 802 803 804 805 806 807
				strncpy(stream->ice_remote_candidates[0].addr, rtp_addr, sizeof(stream->ice_remote_candidates[0].addr));
				stream->ice_remote_candidates[0].port = rtp_port;
				strncpy(stream->ice_remote_candidates[1].addr, rtcp_addr, sizeof(stream->ice_remote_candidates[1].addr));
				stream->ice_remote_candidates[1].port = rtcp_port;
			} else {
				ms_error("ice: Selected valid remote candidates should be present if the check list is in the Completed state");
			}
808 809 810 811 812
		} else {
			for (j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) {
				stream->ice_remote_candidates[j].addr[0] = '\0';
				stream->ice_remote_candidates[j].port = 0;
			}
813 814 815 816
		}
	}
}

817 818 819 820 821 822 823 824 825 826 827 828
static void get_default_addr_and_port(uint16_t componentID, const SalMediaDescription *md, const SalStreamDescription *stream, const char **addr, int *port)
{
	if (componentID == 1) {
		*addr = stream->rtp_addr;
		*port = stream->rtp_port;
	} else if (componentID == 2) {
		*addr = stream->rtcp_addr;
		*port = stream->rtcp_port;
	} else return;
	if ((*addr)[0] == '\0') *addr = md->addr;
}

829 830
void linphone_core_update_ice_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md)
{
831 832
	bool_t ice_restarted = FALSE;

833 834
	if ((md->ice_pwd[0] != '\0') && (md->ice_ufrag[0] != '\0')) {
		int i, j;
835 836

		/* Check for ICE restart and set remote credentials. */
837 838 839 840
		if ((strcmp(md->addr, "0.0.0.0") == 0) || (strcmp(md->addr, "::0") == 0)) {
			ice_session_restart(call->ice_session);
			ice_restarted = TRUE;
		} else {
841
			for (i = 0; i < md->n_total_streams; i++) {
842 843 844 845 846 847 848 849 850
				const SalStreamDescription *stream = &md->streams[i];
				IceCheckList *cl = ice_session_check_list(call->ice_session, i);
				if (cl && (strcmp(stream->rtp_addr, "0.0.0.0") == 0)) {
					ice_session_restart(call->ice_session);
					ice_restarted = TRUE;
					break;
				}
			}
		}
851 852 853
		if ((ice_session_remote_ufrag(call->ice_session) == NULL) && (ice_session_remote_pwd(call->ice_session) == NULL)) {
			ice_session_set_remote_credentials(call->ice_session, md->ice_ufrag, md->ice_pwd);
		} else if (ice_session_remote_credentials_changed(call->ice_session, md->ice_ufrag, md->ice_pwd)) {
854 855 856 857
			if (ice_restarted == FALSE) {
				ice_session_restart(call->ice_session);
				ice_restarted = TRUE;
			}
858 859
			ice_session_set_remote_credentials(call->ice_session, md->ice_ufrag, md->ice_pwd);
		}
860
		for (i = 0; i < md->n_total_streams; i++) {
861 862 863 864
			const SalStreamDescription *stream = &md->streams[i];
			IceCheckList *cl = ice_session_check_list(call->ice_session, i);
			if (cl && (stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0')) {
				if (ice_check_list_remote_credentials_changed(cl, stream->ice_ufrag, stream->ice_pwd)) {
865 866 867 868
					if (ice_restarted == FALSE) {
						ice_session_restart(call->ice_session);
						ice_restarted = TRUE;
					}
869 870 871 872 873 874 875
					ice_session_set_remote_credentials(call->ice_session, md->ice_ufrag, md->ice_pwd);
					break;
				}
			}
		}

		/* Create ICE check lists if needed and parse ICE attributes. */
876
		for (i = 0; i < md->n_total_streams; i++) {
877 878
			const SalStreamDescription *stream = &md->streams[i];
			IceCheckList *cl = ice_session_check_list(call->ice_session, i);
879
			if ((cl == NULL) && (i < md->n_active_streams)) {
880 881 882 883
				cl = ice_check_list_new();
				ice_session_add_check_list(call->ice_session, cl);
				switch (stream->type) {
					case SalAudio:
Ghislain MARY's avatar
Ghislain MARY committed
884
						if (call->audiostream != NULL) call->audiostream->ms.ice_check_list = cl;
885 886
						break;
					case SalVideo:
Ghislain MARY's avatar
Ghislain MARY committed
887
						if (call->videostream != NULL) call->videostream->ms.ice_check_list = cl;
888 889 890 891 892
						break;
					default:
						break;
				}
			}
893
			if (stream->ice_mismatch == TRUE) {
894
				ice_check_list_set_state(cl, ICL_Failed);
895
			} else if (stream->rtp_port == 0) {
Ghislain MARY's avatar
Ghislain MARY committed
896
				ice_session_remove_check_list(call->ice_session, cl);
897 898 899 900 901 902 903 904 905
			} else {
				if ((stream->ice_pwd[0] != '\0') && (stream->ice_ufrag[0] != '\0'))
					ice_check_list_set_remote_credentials(cl, stream->ice_ufrag, stream->ice_pwd);
				for (j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_CANDIDATES; j++) {
					const SalIceCandidate *candidate = &stream->ice_candidates[j];
					bool_t default_candidate = FALSE;
					const char *addr = NULL;
					int port = 0;
					if (candidate->addr[0] == '\0') break;
906 907
					if ((candidate->componentID == 0) || (candidate->componentID > 2)) continue;
					get_default_addr_and_port(candidate->componentID, md, stream, &addr, &port);
908 909 910 911 912
					if (addr && (candidate->port == port) && (strlen(candidate->addr) == strlen(addr)) && (strcmp(candidate->addr, addr) == 0))
						default_candidate = TRUE;
					ice_add_remote_candidate(cl, candidate->type, candidate->addr, candidate->port, candidate->componentID,
						candidate->priority, candidate->foundation, default_candidate);
				}
913
				if (ice_restarted == FALSE) {
914
					bool_t losing_pairs_added = FALSE;
915 916 917 918 919 920 921
					for (j = 0; j < SAL_MEDIA_DESCRIPTION_MAX_ICE_REMOTE_CANDIDATES; j++) {
						const SalIceRemoteCandidate *candidate = &stream->ice_remote_candidates[j];
						const char *addr = NULL;
						int port = 0;
						int componentID = j + 1;
						if (candidate->addr[0] == '\0') break;
						get_default_addr_and_port(componentID, md, stream, &addr, &port);
922 923 924 925
						if (j == 0) {
							/* If we receive a re-invite and we finished ICE processing on our side, use the candidates given by the remote. */
							ice_check_list_unselect_valid_pairs(cl);
						}
926 927
						ice_add_losing_pair(cl, j + 1, candidate->addr, candidate->port, addr, port);
						losing_pairs_added = TRUE;
928
					}
929
					if (losing_pairs_added == TRUE) ice_check_list_check_completed(cl);
930
				}
931 932
			}
		}
933
		for (i = ice_session_nb_check_lists(call->ice_session); i > md->n_active_streams; i--) {
934 935
			ice_session_remove_check_list(call->ice_session, ice_session_check_list(call->ice_session, i - 1));
		}
936
		ice_session_check_mismatch(call->ice_session);
937 938 939 940
	} else {
		/* Response from remote does not contain mandatory ICE attributes, delete the session. */
		linphone_call_delete_ice_session(call);
		return;
941
	}
942
	if (ice_session_nb_check_lists(call->ice_session) == 0) {
943 944 945 946
		linphone_call_delete_ice_session(call);
	}
}

947 948 949 950
bool_t linphone_core_media_description_contains_video_stream(const SalMediaDescription *md)
{
	int i;

951 952
	for (i = 0; i < md->n_active_streams; i++) {
		if (md->streams[i].type == SalVideo)
953 954 955 956 957
			return TRUE;
	}
	return FALSE;
}

958 959 960 961 962 963 964 965 966 967 968 969
LinphoneCall * is_a_linphone_call(void *user_pointer){
	LinphoneCall *call=(LinphoneCall*)user_pointer;
	if (call==NULL) return NULL;
	return call->magic==linphone_call_magic ? call : NULL;
}

LinphoneProxyConfig * is_a_linphone_proxy_config(void *user_pointer){
	LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)user_pointer;
	if (cfg==NULL) return NULL;
	return cfg->magic==linphone_proxy_config_magic ? cfg : NULL;
}

970 971 972 973 974 975