/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of mediastreamer2
 * (see https://gitlab.linphone.org/BC/public/mediastreamer2).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#if !defined(_WIN32) && !defined(_WIN32_WCE)
#ifdef __APPLE__
#include <sys/types.h>
#endif
#include <netdb.h>
#include <sys/socket.h>
#endif

#include <inttypes.h>

#include "mediastreamer2/ice.h"
#include "mediastreamer2/stun.h"
#include "ortp/ortp.h"
#include <bctoolbox/defs.h>
#include <bctoolbox/port.h>

#define ICE_MAX_NB_CANDIDATES 32
#define ICE_MAX_NB_CANDIDATE_PAIRS 128

#define ICE_MIN_COMPONENTID 1
#define ICE_MAX_COMPONENTID 256
#define ICE_INVALID_COMPONENTID 0
#define ICE_MAX_UFRAG_LEN 256
#define ICE_MAX_PWD_LEN 256
#define ICE_DEFAULT_TA_DURATION 40            /* In milliseconds */
#define ICE_DEFAULT_RTO_DURATION 200          /* In milliseconds */
#define ICE_DEFAULT_KEEPALIVE_TIMEOUT 15      /* In seconds */
#define ICE_GATHERING_CANDIDATES_TIMEOUT 3500 /* In milliseconds */
#define ICE_NOMINATION_DELAY 1000             /* In milliseconds */
#define ICE_MAX_RETRANSMISSIONS 7
#define ICE_MAX_RETRANSMISSIONS_FOR_NOMINATIONS 5
#define ICE_MAX_STUN_REQUEST_RETRANSMISSIONS 7

typedef struct _TransportAddress_ComponentID {
	const IceTransportAddress *ta;
	uint16_t componentID;
} TransportAddress_ComponentID;

typedef struct _Type_ComponentID {
	IceCandidateType type;
	uint16_t componentID;
} Type_ComponentID;

typedef struct _Type_Family_ComponentID {
	IceCandidateType type;
	int family;
	uint16_t componentID;
} Type_Family_ComponentID;

typedef struct _Foundation_Pair_Priority_ComponentID {
	const IcePairFoundation *foundation;
	IceCandidatePair *pair;
	uint64_t priority;
	uint16_t componentID;
} Foundation_Pair_Priority_ComponentID;

typedef struct _CheckList_RtpSession {
	IceCheckList *cl;
	const RtpSession *rtp_session;
} CheckList_RtpSession;

typedef struct _CheckList_RtpSession_Time {
	IceCheckList *cl;
	const RtpSession *rtp_session;
	MSTimeSpec time;
} CheckList_RtpSession_Time;

typedef struct _CheckList_Bool {
	IceCheckList *cl;
	bool_t result;
} CheckList_Bool;

typedef struct _CheckList_MSListPtr {
	const IceCheckList *cl;
	bctbx_list_t **list;
} CheckList_MSListPtr;

typedef struct _LocalCandidate_RemoteCandidate {
	IceCandidate *local;
	IceCandidate *remote;
} LocalCandidate_RemoteCandidate;

typedef struct _TransportAddresses {
	IceTransportAddress **rtp_taddr;
	IceTransportAddress **rtcp_taddr;
} TransportAddresses;

typedef struct _Time_Bool {
	MSTimeSpec time;
	bool_t result;
} Time_Bool;

typedef struct _Session_Index {
	IceSession *session;
	int index;
} Session_Index;

typedef struct _LosingRemoteCandidate_InProgress_Failed {
	const IceCandidate *losing_remote_candidate;
	bool_t in_progress_candidates;
	bool_t failed_candidates;
} LosingRemoteCandidate_InProgress_Failed;

typedef struct _ComponentID_Family {
	uint16_t componentID;
	int family;
} ComponentID_Family;

static MSTimeSpec ice_current_time(void);
static MSTimeSpec ice_add_ms(MSTimeSpec orig, uint32_t ms);
static int32_t ice_compare_time(MSTimeSpec ts1, MSTimeSpec ts2);
static void transactionID2string(const UInt96 *tr_id, char *tr_id_str);
static IceStunServerRequest *ice_stun_server_request_new(IceCheckList *cl,
                                                         MSTurnContext *turn_context,
                                                         RtpTransport *rtptp,
                                                         int family,
                                                         const char *srcaddr,
                                                         int srcport,
                                                         uint16_t stun_method);
static void ice_stun_server_request_transaction_free(IceStunServerRequestTransaction *transaction);
static void ice_stun_server_request_free(IceStunServerRequest *request);
static IceStunServerRequestTransaction *
ice_send_stun_server_request(IceStunServerRequest *request, const struct sockaddr *server, socklen_t addrlen);
static void ice_check_list_deallocate_rtp_turn_candidate(IceCheckList *cl);
static void ice_check_list_deallocate_rtcp_turn_candidate(IceCheckList *cl);
static void ice_check_list_deallocate_turn_candidates(IceCheckList *cl);
static int ice_compare_transport_addresses(const IceTransportAddress *ta1, const IceTransportAddress *ta2);
static int ice_compare_pair_priorities(const IceCandidatePair *p1, const IceCandidatePair *p2);
static int ice_compare_pairs(const IceCandidatePair *p1, const IceCandidatePair *p2);
static int ice_compare_candidates(const IceCandidate *c1, const IceCandidate *c2);
static int ice_find_host_candidate(const IceCandidate *candidate, const ComponentID_Family *cf);
static int ice_find_candidate_from_type_and_componentID(const IceCandidate *candidate, const Type_ComponentID *tc);
static int ice_find_candidate_from_type_family_and_componentID(const IceCandidate *candidate,
                                                               const Type_Family_ComponentID *tc);
static void ice_create_turn_permissions(IceCheckList *cl);
static int ice_find_nominated_valid_pair_from_componentID(const IceValidCandidatePair *valid_pair,
                                                          const uint16_t *componentID);
static int ice_find_selected_valid_pair_from_componentID(const IceValidCandidatePair *valid_pair,
                                                         const uint16_t *componentID);
static void ice_find_selected_valid_pair_for_componentID(const uint16_t *componentID, CheckList_Bool *cb);
static int ice_find_pair_in_valid_list(IceValidCandidatePair *valid_pair, IceCandidatePair *pair);
static void ice_pair_set_state(IceCandidatePair *pair, IceCandidatePairState state);
static void ice_compute_candidate_foundation(IceCandidate *candidate, IceCheckList *cl);
static void ice_set_credentials(char **ufrag, char **pwd, const char *ufrag_str, const char *pwd_str);
static void ice_conclude_processing(IceCheckList *cl, RtpSession *rtp_session, bool_t nomination_delay_expired);
static void ice_check_list_stop_gathering(IceCheckList *cl);
static void ice_check_list_remove_stun_server_request(IceCheckList *cl, UInt96 *tr_id);
static IceStunServerRequest *ice_check_list_get_stun_server_request(IceCheckList *cl, UInt96 *tr_id);
static void ice_transport_address_to_printable_ip_address(const IceTransportAddress *taddr,
                                                          char *printable_ip,
                                                          size_t printable_ip_size);
static void ice_stun_server_request_add_transaction(IceStunServerRequest *request,
                                                    IceStunServerRequestTransaction *transaction);
static void ice_check_list_perform_nominations(IceCheckList *cl, bool_t nomination_delay_expired);
static void ice_dump_candidate(const IceCandidate *candidate, const char *const prefix);
static void ice_dump_valid_pair(const IceValidCandidatePair *valid_pair, int *i);
static bool_t ice_has_componentID(const bctbx_list_t *list, uint16_t componentID);
#if 0
static int ice_session_connectivity_checks_duration(IceSession *session);
#endif

/******************************************************************************
 * CONSTANTS DEFINITIONS                                                      *
 *****************************************************************************/

static const char *const role_values[] = {
    "Controlling", /* IR_Controlling */
    "Controlled",  /* IR_Controlled */
};

static const char *const candidate_type_values[] = {
    "host",  /* ICT_HostCandidate */
    "srflx", /* ICT_ServerReflexiveCandidate */
    "prflx", /* ICT_PeerReflexiveCandidate */
    "relay"  /* ICT_RelayedCandidate */
};

/**
 * ICE candidate type preference values as recommended in 4.1.1.2.
 */
static const uint8_t type_preference_values[] = {
    126, /* ICT_HostCandidate */
    100, /* ICT_ServerReflexiveCandidate */
    110, /* ICT_PeerReflexiveCandidate */
    0    /* ICT_RelayedCandidate */
};

static const char *const candidate_pair_state_values[] = {
    "Waiting",     /* ICP_Waiting */
    "In-Progress", /* ICP_InProgress */
    "Succeeded",   /* ICP_Succeeded */
    "Failed",      /* ICP_Failed */
    "Frozen"       /* ICP_Frozen */
};

/******************************************************************************
 * SESSION INITIALISATION AND DEINITIALISATION                                *
 *****************************************************************************/

static uint64_t generate_tie_breaker(void) {
	return (((uint64_t)bctbx_random()) << 32) | (((uint64_t)bctbx_random()) & 0xffffffff);
}

static char *generate_ufrag(void) {
	return ms_strdup_printf("%08x", (int)bctbx_random());
}

static char *generate_pwd(void) {
	return ms_strdup_printf("%08x%08x%08x", (int)bctbx_random(), (int)bctbx_random(), (int)bctbx_random());
}

static void ice_session_init(IceSession *session) {
	session->state = IS_Stopped;
	session->role = IR_Controlling;
	session->tie_breaker = generate_tie_breaker();
	session->ta = ICE_DEFAULT_TA_DURATION;
	session->keepalive_timeout = ICE_DEFAULT_KEEPALIVE_TIMEOUT;
	session->max_connectivity_checks = ICE_MAX_NB_CANDIDATE_PAIRS;
	session->local_ufrag = generate_ufrag();
	session->local_pwd = generate_pwd();
	session->remote_ufrag = NULL;
	session->remote_pwd = NULL;
	session->send_event = FALSE;
	session->gathering_start_ts.tv_sec = session->gathering_start_ts.tv_nsec = -1;
	session->gathering_end_ts.tv_sec = session->gathering_end_ts.tv_nsec = -1;
	session->connectivity_checks_start_ts.tv_sec = session->connectivity_checks_start_ts.tv_nsec = -1;
	session->check_message_integrity = TRUE;
	session->default_candidates_prefer_ipv6 = TRUE;
	session->default_types[0] = ICT_RelayedCandidate;
	session->default_types[1] = ICT_ServerReflexiveCandidate;
	session->default_types[2] = ICT_HostCandidate;
	session->default_types[2] = ICT_CandidateInvalid;
}

IceSession *ice_session_new(void) {
	IceSession *session = ms_new0(IceSession, 1);
	if (session == NULL) {
		ms_error("ice: Memory allocation of ICE session failed");
		return NULL;
	}
	ice_session_init(session);
	return session;
}

void ice_session_destroy(IceSession *session) {
	int i;
	if (session != NULL) {
		for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
			if (session->streams[i] != NULL) {
				ice_check_list_destroy(session->streams[i]);
				session->streams[i] = NULL;
			}
		}
		if (session->local_ufrag) ms_free(session->local_ufrag);
		if (session->local_pwd) ms_free(session->local_pwd);
		if (session->remote_ufrag) ms_free(session->remote_ufrag);
		if (session->remote_pwd) ms_free(session->remote_pwd);
		ms_free(session);
	}
}

void ice_session_set_default_candidates_types(IceSession *session, const IceCandidateType types[ICT_CandidateTypeMax]) {
	memcpy(session->default_types, types, sizeof(session->default_types));
}

void ice_session_set_default_candidates_ip_version(IceSession *session, bool_t ipv6_preferred) {
	session->default_candidates_prefer_ipv6 = ipv6_preferred;
}

void ice_session_enable_message_integrity_check(IceSession *session, bool_t enable) {
	session->check_message_integrity = enable;
}

/******************************************************************************
 * CHECK LIST INITIALISATION AND DEINITIALISATION                             *
 *****************************************************************************/

static void ice_check_list_init(IceCheckList *cl) {
	cl->session = NULL;
	cl->rtp_session = NULL;
	cl->remote_ufrag = cl->remote_pwd = NULL;
	cl->stun_server_requests = NULL;
	cl->local_candidates = cl->remote_candidates = cl->pairs = cl->losing_pairs = cl->triggered_checks_queue =
	    cl->check_list = cl->valid_list = cl->transaction_list = NULL;
	cl->local_componentIDs = cl->remote_componentIDs = cl->foundations = NULL;
	cl->state = ICL_Running;
	cl->foundation_generator = 1;
	cl->mismatch = FALSE;
	cl->gathering_candidates = FALSE;
	cl->gathering_finished = FALSE;
	cl->nomination_delay_running = FALSE;
	cl->ta_time = ice_current_time();
	memset(&cl->keepalive_time, 0, sizeof(cl->keepalive_time));
	memset(&cl->gathering_start_time, 0, sizeof(cl->gathering_start_time));
	memset(&cl->nomination_delay_start_time, 0, sizeof(cl->nomination_delay_start_time));
}

IceCheckList *ice_check_list_new(void) {
	IceCheckList *cl = ms_new0(IceCheckList, 1);
	if (cl == NULL) {
		ms_error("ice_check_list_new: Memory allocation failed");
		return NULL;
	}
	ice_check_list_init(cl);
	return cl;
}

static void ice_compute_pair_priority(IceCandidatePair *pair, const IceRole *role) {
	/* Use formula defined in 5.7.2 to compute pair priority. */
	uint64_t G = 0;
	uint64_t D = 0;

	switch (*role) {
		case IR_Controlling:
			G = pair->local->priority;
			D = pair->remote->priority;
			break;
		case IR_Controlled:
			G = pair->remote->priority;
			D = pair->local->priority;
			break;
	}
	pair->priority = (MIN(G, D) << 32) | (MAX(G, D) << 1) | (G > D ? 1 : 0);
}

static IceCandidatePair *ice_pair_new(IceCheckList *cl, IceCandidate *local_candidate, IceCandidate *remote_candidate) {
	IceCandidatePair *pair = ms_new0(IceCandidatePair, 1);
	pair->local = local_candidate;
	pair->remote = remote_candidate;
	pair->state = ICP_Frozen;
	pair->is_default = FALSE;
	pair->is_nominated = FALSE;
	pair->use_candidate = FALSE;
	if ((pair->local->is_default == TRUE) && (pair->remote->is_default == TRUE)) pair->is_default = TRUE;
	else pair->is_default = FALSE;
	pair->rto = ICE_DEFAULT_RTO_DURATION;
	pair->retransmissions = 0;
	pair->role = cl->session->role;
	ice_compute_pair_priority(pair, &cl->session->role);
	pair->retry_with_dummy_message_integrity = !cl->session->check_message_integrity;
	return pair;
}

static void ice_free_transaction(IceTransaction *transaction) {
	ms_free(transaction);
}

static void ice_free_pair_foundation(IcePairFoundation *foundation) {
	ms_free(foundation);
}

static void ice_free_valid_pair(IceValidCandidatePair *valid_pair) {
	ms_free(valid_pair);
}

static void ice_free_candidate_pair(IceCandidatePair *pair, IceCheckList *cl) {
	bctbx_list_t *elem;
	while ((elem = bctbx_list_find(cl->check_list, pair)) != NULL) {
		cl->check_list = bctbx_list_remove(cl->check_list, pair);
	}
	while ((elem = bctbx_list_find_custom(cl->valid_list, (bctbx_compare_func)ice_find_pair_in_valid_list, pair)) !=
	       NULL) {
		ice_free_valid_pair(elem->data);
		cl->valid_list = bctbx_list_erase_link(cl->valid_list, elem);
	}
	ms_free(pair);
}

static void ice_free_candidate(IceCandidate *candidate) {
	ms_free(candidate);
}

static void ice_check_list_destroy_turn_contexts(IceCheckList *cl) {
	ice_check_list_deallocate_turn_candidates(cl);
	if (cl->rtp_turn_context != NULL) {
		ms_turn_context_destroy(cl->rtp_turn_context);
		cl->rtp_turn_context = NULL;
	}
	if (cl->rtcp_turn_context != NULL) {
		ms_turn_context_destroy(cl->rtcp_turn_context);
		cl->rtcp_turn_context = NULL;
	}
}

void ice_check_list_destroy(IceCheckList *cl) {
	ice_check_list_destroy_turn_contexts(cl);
	if (cl->remote_ufrag) ms_free(cl->remote_ufrag);
	if (cl->remote_pwd) ms_free(cl->remote_pwd);
	bctbx_list_for_each(cl->stun_server_requests, (void (*)(void *))ice_stun_server_request_free);
	bctbx_list_for_each(cl->transaction_list, (void (*)(void *))ice_free_transaction);
	bctbx_list_for_each(cl->foundations, (void (*)(void *))ice_free_pair_foundation);
	bctbx_list_for_each2(cl->pairs, (void (*)(void *, void *))ice_free_candidate_pair, cl);
	bctbx_list_for_each(cl->valid_list, (void (*)(void *))ice_free_valid_pair);
	bctbx_list_for_each(cl->remote_candidates, (void (*)(void *))ice_free_candidate);
	bctbx_list_for_each(cl->local_candidates, (void (*)(void *))ice_free_candidate);
	bctbx_list_free(cl->stun_server_requests);
	bctbx_list_free(cl->transaction_list);
	bctbx_list_free(cl->foundations);
	bctbx_list_free(cl->local_componentIDs);
	bctbx_list_free(cl->remote_componentIDs);
	bctbx_list_free(cl->valid_list);
	bctbx_list_free(cl->check_list);
	bctbx_list_free(cl->triggered_checks_queue);
	bctbx_list_free(cl->losing_pairs);
	bctbx_list_free(cl->pairs);
	bctbx_list_free(cl->remote_candidates);
	bctbx_list_free(cl->local_candidates);
	memset(cl, 0, sizeof(IceCheckList));
	ms_free(cl);
}

/******************************************************************************
 * CANDIDATE ACCESSORS                                                        *
 *****************************************************************************/

const char *ice_candidate_type(const IceCandidate *candidate) {
	return candidate_type_values[candidate->type];
}

bool_t ice_candidate_is_relay(const IceCandidate *candidate) {
	if (candidate->type == ICT_RelayedCandidate) return TRUE;
	if (candidate->base && candidate->base->type == ICT_RelayedCandidate) {
		/* case where the TURN server is itself behind a firewall.
		 A peer reflexive candidate is then discovered thanks to the binding response received from the remote.
		 This peer reflexive candidate has the relay candidate as base.*/
		return TRUE;
	}
	return FALSE;
}

/******************************************************************************
 * CANDIDATE PAIR ACCESSORS                                                   *
 *****************************************************************************/

static void ice_pair_set_state(IceCandidatePair *pair, IceCandidatePairState state) {
	if (pair->state != state) {
		pair->state = state;
	}
}

/******************************************************************************
 * CHECK LIST ACCESSORS                                                       *
 *****************************************************************************/

IceCheckListState ice_check_list_state(const IceCheckList *cl) {
	return cl->state;
}

static IceCheckList *ice_find_check_list_from_state(const IceSession *session, IceCheckListState state) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] && ice_check_list_state(session->streams[i]) == state) return session->streams[i];
	}
	return NULL;
}

void ice_check_list_set_state(IceCheckList *cl, IceCheckListState state) {
	if (cl->state != state) {
		cl->state = state;
		if (ice_find_check_list_from_state(cl->session, ICL_Running) == NULL) {
			if (ice_find_check_list_from_state(cl->session, ICL_Failed) != NULL) {
				/* Set the state of the session to Failed if at least one check list is in the Failed state. */
				cl->session->state = IS_Failed;
			} else {
				/* All the check lists are in the Completed state, set the state of the session to Completed. */
				cl->session->state = IS_Completed;
			}
		}
	}
}

void ice_check_list_set_rtp_session(IceCheckList *cl, RtpSession *rtp_session) {
	cl->rtp_session = rtp_session;
}

const char *ice_check_list_local_ufrag(const IceCheckList *cl) {
	/* Do not handle media specific ufrag for the moment, so use the session local ufrag. */
	return cl->session->local_ufrag;
}

const char *ice_check_list_local_pwd(const IceCheckList *cl) {
	/* Do not handle media specific pwd for the moment, so use the session local pwd. */
	return cl->session->local_pwd;
}

const char *ice_check_list_remote_ufrag(const IceCheckList *cl) {
	if (cl->remote_ufrag) return cl->remote_ufrag;
	else return cl->session->remote_ufrag;
}

const char *ice_check_list_remote_pwd(const IceCheckList *cl) {
	if (cl->remote_pwd) return cl->remote_pwd;
	else return cl->session->remote_pwd;
}

static int ice_find_default_local_candidate(const IceCandidate *candidate, const uint16_t *componentID) {
	return !((candidate->componentID == *componentID) && (candidate->is_default == TRUE));
}

bool_t ice_check_list_remote_credentials_changed(IceCheckList *cl, const char *ufrag, const char *pwd) {
	const char *old_ufrag;
	const char *old_pwd;
	if ((cl->remote_ufrag == NULL) || (cl->remote_pwd == NULL)) {
		if (cl->remote_ufrag == NULL) old_ufrag = cl->session->remote_ufrag;
		else old_ufrag = cl->remote_ufrag;
		if (old_ufrag && strcmp(ufrag, old_ufrag) != 0) return TRUE;
		if (cl->remote_pwd == NULL) old_pwd = cl->session->remote_pwd;
		else old_pwd = cl->remote_pwd;
		if (old_pwd && strcmp(pwd, old_pwd) != 0) return TRUE;
		return FALSE;
	}
	if (strcmp(ufrag, cl->remote_ufrag) != 0) return TRUE;
	if (strcmp(pwd, cl->remote_pwd) != 0) return TRUE;
	return FALSE;
}

void ice_check_list_set_remote_credentials(IceCheckList *cl, const char *ufrag, const char *pwd) {
	ice_set_credentials(&cl->remote_ufrag, &cl->remote_pwd, ufrag, pwd);
}

const char *ice_check_list_get_remote_ufrag(const IceCheckList *cl) {
	return cl->remote_ufrag;
}

const char *ice_check_list_get_remote_pwd(const IceCheckList *cl) {
	return cl->remote_pwd;
}

bool_t ice_check_list_default_local_candidate(const IceCheckList *cl,
                                              IceCandidate **rtp_candidate,
                                              IceCandidate **rtcp_candidate) {
	uint16_t componentID;
	bctbx_list_t *elem;

	if (rtp_candidate != NULL) {
		componentID = ICE_RTP_COMPONENT_ID;
		elem = bctbx_list_find_custom(cl->local_candidates, (bctbx_compare_func)ice_find_default_local_candidate,
		                              &componentID);
		if (elem == NULL) return FALSE;
		*rtp_candidate = (IceCandidate *)elem->data;
	}
	if (rtcp_candidate != NULL) {
		if (ice_has_componentID(cl->local_componentIDs, ICE_RTCP_COMPONENT_ID)) {
			componentID = ICE_RTCP_COMPONENT_ID;
			elem = bctbx_list_find_custom(cl->local_candidates, (bctbx_compare_func)ice_find_default_local_candidate,
			                              &componentID);
			if (elem == NULL) return FALSE;
			else *rtcp_candidate = (IceCandidate *)elem->data;
		} else *rtcp_candidate = NULL;
	}

	return TRUE;
}

bool_t ice_check_list_selected_valid_local_candidate(const IceCheckList *cl,
                                                     IceCandidate **rtp_candidate,
                                                     IceCandidate **rtcp_candidate) {
	IceValidCandidatePair *valid_pair = NULL;
	uint16_t componentID;
	bctbx_list_t *elem;

	if (rtp_candidate != NULL) {
		componentID = ICE_RTP_COMPONENT_ID;
		elem = bctbx_list_find_custom(cl->valid_list, (bctbx_compare_func)ice_find_selected_valid_pair_from_componentID,
		                              &componentID);
		if (elem == NULL) {
			ms_warning("No selected valid RTP local candidate.");
			return FALSE;
		}
		valid_pair = (IceValidCandidatePair *)elem->data;
		*rtp_candidate = valid_pair->valid->local;
	}
	if (rtcp_candidate != NULL) {
		if (ice_has_componentID(cl->local_componentIDs, ICE_RTCP_COMPONENT_ID)) {
			componentID = ICE_RTCP_COMPONENT_ID;
			elem = bctbx_list_find_custom(
			    cl->valid_list, (bctbx_compare_func)ice_find_selected_valid_pair_from_componentID, &componentID);
			if (elem == NULL) return FALSE;
			valid_pair = (IceValidCandidatePair *)elem->data;
			*rtcp_candidate = valid_pair->valid->local;
		} else *rtcp_candidate = NULL;
	}
	return TRUE;
}

bool_t ice_check_list_selected_valid_local_base_candidate(const IceCheckList *cl,
                                                          IceCandidate **rtp_candidate,
                                                          IceCandidate **rtcp_candidate) {
	IceValidCandidatePair *valid_pair = NULL;
	uint16_t componentID;
	bctbx_list_t *elem;

	if (rtp_candidate != NULL) {
		componentID = 1;
		elem = bctbx_list_find_custom(cl->valid_list, (bctbx_compare_func)ice_find_selected_valid_pair_from_componentID,
		                              &componentID);
		if (elem == NULL) return FALSE;
		valid_pair = (IceValidCandidatePair *)elem->data;
		*rtp_candidate = valid_pair->generated_from->local;
		if (*rtp_candidate == NULL) *rtp_candidate = valid_pair->valid->local;
	}
	if (rtcp_candidate != NULL) {
		if (rtp_session_rtcp_mux_enabled(cl->rtp_session)) {
			componentID = 1;
		} else {
			componentID = 2;
		}
		elem = bctbx_list_find_custom(cl->valid_list, (bctbx_compare_func)ice_find_selected_valid_pair_from_componentID,
		                              &componentID);
		if (elem == NULL) return FALSE;
		valid_pair = (IceValidCandidatePair *)elem->data;
		*rtcp_candidate = valid_pair->generated_from->local;
		if (*rtcp_candidate == NULL) *rtcp_candidate = valid_pair->valid->local;
	}
	return TRUE;
}
bool_t ice_check_list_selected_valid_remote_candidate(const IceCheckList *cl,
                                                      IceCandidate **rtp_candidate,
                                                      IceCandidate **rtcp_candidate) {
	IceValidCandidatePair *valid_pair = NULL;
	uint16_t componentID;
	bctbx_list_t *elem;

	if (rtp_candidate != NULL) {
		componentID = 1;
		elem = bctbx_list_find_custom(cl->valid_list, (bctbx_compare_func)ice_find_selected_valid_pair_from_componentID,
		                              &componentID);
		if (elem == NULL) {
			ms_error("There are no selected valid remote candidates for RTP.");
			return FALSE;
		}
		valid_pair = (IceValidCandidatePair *)elem->data;
		*rtp_candidate = valid_pair->valid->remote;
	}
	if (rtcp_candidate != NULL) {
		if (rtp_session_rtcp_mux_enabled(cl->rtp_session)) {
			*rtcp_candidate = NULL;
		} else {
			componentID = 2;
			elem = bctbx_list_find_custom(
			    cl->valid_list, (bctbx_compare_func)ice_find_selected_valid_pair_from_componentID, &componentID);
			if (elem == NULL) {
				ms_error("Rtcp-mux is not used but there are no selected valid remote candidates for RTCP.");
				return FALSE;
			}
			valid_pair = (IceValidCandidatePair *)elem->data;
			*rtcp_candidate = valid_pair->valid->remote;
		}
	}
	return TRUE;
}

static int ice_find_host_pair_identical_to_reflexive_pair(const IceCandidatePair *p1, const IceCandidatePair *p2) {
	return !((ice_compare_transport_addresses(&p1->local->taddr, &p2->local->taddr) == 0) &&
	         (p1->local->componentID == p2->local->componentID) &&
	         (ice_compare_transport_addresses(&p1->remote->taddr, &p2->remote->taddr) == 0) &&
	         (p1->remote->componentID == p2->remote->componentID) && (p1->remote->type == ICT_HostCandidate));
}

IceCandidateType ice_check_list_selected_valid_candidate_type(const IceCheckList *cl) {
	IceCandidatePair *pair = NULL;
	IceCandidateType type = ICT_RelayedCandidate;
	bctbx_list_t *elem;
	uint16_t componentID = 1;

	elem = bctbx_list_find_custom(cl->valid_list, (bctbx_compare_func)ice_find_selected_valid_pair_from_componentID,
	                              &componentID);
	if (elem == NULL) return type;
	pair = ((IceValidCandidatePair *)elem->data)->valid;
	if (ice_candidate_is_relay(pair->local)) return ICT_RelayedCandidate;
	type = pair->remote->type;
	/**
	 * If the pair is reflexive, check if there is a pair with the same addresses and componentID that is of host type
	 * to report host connection instead of reflexive connection. This might happen if the ICE checks discover
	 * reflexives candidates before the signaling layer has communicated the host candidates to the other peer.
	 */
	if ((type == ICT_ServerReflexiveCandidate) || (type == ICT_PeerReflexiveCandidate)) {
		elem =
		    bctbx_list_find_custom(cl->pairs, (bctbx_compare_func)ice_find_host_pair_identical_to_reflexive_pair, pair);
		if (elem != NULL) {
			type = ((IceCandidatePair *)elem->data)->remote->type;
		}
	}
	return type;
}

void ice_check_list_check_completed(IceCheckList *cl) {
	CheckList_Bool cb;

	if (cl->state != ICL_Completed) {
		cb.cl = cl;
		cb.result = TRUE;
		bctbx_list_for_each2(cl->local_componentIDs,
		                     (void (*)(void *, void *))ice_find_selected_valid_pair_for_componentID, &cb);
		if (cb.result == TRUE) {
			ice_check_list_set_state(cl, ICL_Completed);
		}
	}
}

static void ice_check_list_queue_triggered_check(IceCheckList *cl, IceCandidatePair *pair) {
	bctbx_list_t *elem = bctbx_list_find(cl->triggered_checks_queue, pair);
	if (elem != NULL) {
		/* The pair is already in the triggered checks queue, do not add it again. */
	} else {
		cl->triggered_checks_queue = bctbx_list_append(cl->triggered_checks_queue, pair);
	}
}

static IceCandidatePair *ice_check_list_pop_triggered_check(IceCheckList *cl) {
	IceCandidatePair *pair;

	if (bctbx_list_size(cl->triggered_checks_queue) == 0) return NULL;
	pair = bctbx_list_nth_data(cl->triggered_checks_queue, 0);
	if (pair != NULL) {
		/* Remove the first element in the triggered checks queue. */
		cl->triggered_checks_queue = bctbx_list_erase_link(cl->triggered_checks_queue, cl->triggered_checks_queue);
	}
	return pair;
}

static int ice_find_non_frozen_pair(const IceCandidatePair *pair, BCTBX_UNUSED(const void *dummy)) {
	return (pair->state == ICP_Frozen);
}

static bool_t ice_check_list_is_frozen(const IceCheckList *cl) {
	bctbx_list_t *elem = bctbx_list_find_custom(cl->check_list, (bctbx_compare_func)ice_find_non_frozen_pair, NULL);
	return (elem == NULL);
}

bool_t ice_check_list_is_mismatch(const IceCheckList *cl) {
	return cl->mismatch;
}

/******************************************************************************
 * SESSION ACCESSORS                                                          *
 *****************************************************************************/

IceCheckList *ice_session_check_list(const IceSession *session, int n) {
	if (n >= ICE_SESSION_MAX_CHECK_LISTS) return NULL;
	return session->streams[n];
}

const char *ice_session_local_ufrag(const IceSession *session) {
	return session->local_ufrag;
}

const char *ice_session_local_pwd(const IceSession *session) {
	return session->local_pwd;
}

const char *ice_session_remote_ufrag(const IceSession *session) {
	return session->remote_ufrag;
}

const char *ice_session_remote_pwd(const IceSession *session) {
	return session->remote_pwd;
}

static void ice_check_list_compute_pair_priorities(IceCheckList *cl) {
	bctbx_list_for_each2(cl->pairs, (void (*)(void *, void *))ice_compute_pair_priority, &cl->session->role);
}

static void ice_session_compute_pair_priorities(IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) ice_check_list_compute_pair_priorities(session->streams[i]);
	}
}

IceSessionState ice_session_state(const IceSession *session) {
	return session->state;
}

IceRole ice_session_role(const IceSession *session) {
	return session->role;
}

void ice_session_set_role(IceSession *session, IceRole role) {
	if (session->role != role) {
		/* Compute new candidate pair priorities if the role changes. */
		session->role = role;
		ice_session_compute_pair_priorities(session);
	}
}

void ice_session_set_local_credentials(IceSession *session, const char *ufrag, const char *pwd) {
	ice_set_credentials(&session->local_ufrag, &session->local_pwd, ufrag, pwd);
}

bool_t ice_session_remote_credentials_changed(IceSession *session, const char *ufrag, const char *pwd) {
	if ((session->remote_ufrag == NULL) || (session->remote_pwd == NULL)) return TRUE;
	if (strlen(ufrag) != strlen(session->remote_ufrag) || (strcmp(ufrag, session->remote_ufrag) != 0)) return TRUE;
	if (strlen(pwd) != strlen(session->remote_pwd) || (strcmp(pwd, session->remote_pwd) != 0)) return TRUE;
	return FALSE;
}

void ice_session_set_remote_credentials(IceSession *session, const char *ufrag, const char *pwd) {
	ice_set_credentials(&session->remote_ufrag, &session->remote_pwd, ufrag, pwd);
}

void ice_session_set_max_connectivity_checks(IceSession *session, uint8_t max_connectivity_checks) {
	session->max_connectivity_checks = max_connectivity_checks;
}

void ice_session_set_keepalive_timeout(IceSession *session, uint8_t timeout) {
	if (timeout < ICE_DEFAULT_KEEPALIVE_TIMEOUT) timeout = ICE_DEFAULT_KEEPALIVE_TIMEOUT;
	session->keepalive_timeout = timeout;
}

/******************************************************************************
 * SESSION HANDLING                                                           *
 *****************************************************************************/

int ice_session_nb_check_lists(IceSession *session) {
	int i;
	int nb = 0;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) nb++;
	}
	return nb;
}

bool_t ice_session_has_completed_check_list(const IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if ((session->streams[i] != NULL) && (ice_check_list_state(session->streams[i]) == ICL_Completed)) return TRUE;
	}
	return FALSE;
}

void ice_session_add_check_list(IceSession *session, IceCheckList *cl, unsigned int idx) {
	if (idx >= ICE_SESSION_MAX_CHECK_LISTS) {
		ms_error("ice_session_add_check_list: Wrong idx parameter");
		return;
	}
	if (session->streams[idx] != NULL) {
		ms_error("ice_session_add_check_list: Existing check list at index %u, remove it first", idx);
		return;
	}
	session->streams[idx] = cl;
	cl->session = session;
	if (cl->state == ICL_Running) {
		session->state = IS_Running;
	}
}

void ice_session_remove_check_list(IceSession *session, IceCheckList *cl) {
	int i;
	bool_t keep_session_state = FALSE;

	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if ((session->streams[i] != NULL) && (session->streams[i] == cl)) {
			ice_check_list_destroy(cl);
			session->streams[i] = NULL;
			break;
		}
	}

	// If all remaining check lists have completed set the session state to completed
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if ((session->streams[i] != NULL) && (ice_check_list_state(session->streams[i]) != ICL_Completed)) {
			keep_session_state = TRUE;
		}
	}
	if (!keep_session_state) {
		session->state = IS_Completed;
	}
}

void ice_session_remove_check_list_from_idx(IceSession *session, unsigned int idx) {
	if (idx >= ICE_SESSION_MAX_CHECK_LISTS) {
		ms_error("ice_session_remove_check_list_from_idx: Wrong idx parameter");
		return;
	}
	if (session->streams[idx] != NULL) {
		ice_check_list_destroy(session->streams[idx]);
		session->streams[idx] = NULL;
	}
}

static int ice_find_default_candidate_from_componentID(const IceCandidate *candidate, const uint16_t *componentID) {
	return !((candidate->is_default == TRUE) && (candidate->componentID == *componentID));
}

static void ice_find_default_remote_candidate_for_componentID(const uint16_t *componentID, IceCheckList *cl) {
	bctbx_list_t *elem = bctbx_list_find_custom(
	    cl->remote_candidates, (bctbx_compare_func)ice_find_default_candidate_from_componentID, componentID);
	if (elem == NULL) {
		ms_error("ICE mismatch for checklist [%p], due to default remote candidate not found for component ID [%i]", cl,
		         (int)*componentID);
		cl->mismatch = TRUE;
		cl->state = ICL_Failed;
	}
}

static void ice_check_list_check_mismatch(IceCheckList *cl) {
	bctbx_list_for_each2(cl->remote_componentIDs,
	                     (void (*)(void *, void *))ice_find_default_remote_candidate_for_componentID, cl);
}

void ice_session_check_mismatch(IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) ice_check_list_check_mismatch(session->streams[i]);
	}
}

/******************************************************************************
 * CANDIDATES GATHERING                                                       *
 *****************************************************************************/

bool_t ice_check_list_candidates_gathered(const IceCheckList *cl) {
	return cl->gathering_finished;
}

bool_t ice_session_candidates_gathered(const IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if ((session->streams[i] != NULL) && (ice_check_list_candidates_gathered(session->streams[i]) != TRUE))
			return FALSE;
	}
	return TRUE;
}

static bool_t ice_check_list_gathering_needed(const IceCheckList *cl) {
	if (cl->gathering_finished == FALSE) return TRUE;
	return FALSE;
}

static void ice_check_list_add_stun_server_request(IceCheckList *cl, IceStunServerRequest *request) {
	cl->stun_server_requests = bctbx_list_append(cl->stun_server_requests, request);
}

static bool_t ice_check_list_gather_candidates(IceCheckList *cl, Session_Index *si) {
	IceStunServerRequest *request;
	RtpTransport *rtptp = NULL;
	MSTimeSpec curtime = ice_current_time();
	char source_addr_str[64];
	int source_port = 0;

	if ((cl->rtp_session != NULL) && (cl->gathering_candidates == FALSE) && (cl->state != ICL_Completed) &&
	    (ice_check_list_candidates_gathered(cl) == FALSE)) {
		cl->gathering_candidates = TRUE;
		cl->gathering_start_time = curtime;
		rtp_session_get_transports(cl->rtp_session, &rtptp, NULL);
		if (rtptp) {
			struct sockaddr *sa = (struct sockaddr *)&cl->rtp_session->rtp.gs.loc_addr;
			if (cl->session->turn_enabled) {
				/* Define the RTP endpoint that will perform STUN encapsulation/decapsulation for TURN data */
				meta_rtp_transport_set_endpoint(rtptp, ms_turn_context_create_endpoint(cl->rtp_turn_context));
				ms_turn_context_set_server_addr(cl->rtp_turn_context, (struct sockaddr *)&cl->session->ss,
				                                cl->session->ss_len);

				// Start turn tcp client now if needed
				if (cl->rtp_turn_context->transport != MS_TURN_CONTEXT_TRANSPORT_UDP) {
					if (!cl->rtp_turn_context->turn_tcp_client) {
						cl->rtp_turn_context->turn_tcp_client = ms_turn_tcp_client_new(
						    cl->rtp_turn_context, cl->rtp_turn_context->transport == MS_TURN_CONTEXT_TRANSPORT_TLS,
						    cl->rtp_turn_context->root_certificate);
					}
					ms_turn_tcp_client_connect(cl->rtp_turn_context->turn_tcp_client);
				}
			}
			memset(source_addr_str, 0, sizeof(source_addr_str));
			bctbx_sockaddr_to_ip_address(sa, cl->rtp_session->rtp.gs.loc_addrlen, source_addr_str,
			                             sizeof(source_addr_str), &source_port);
			request = ice_stun_server_request_new(
			    cl, cl->rtp_turn_context, rtptp, sa->sa_family, source_addr_str, source_port,
			    cl->session->turn_enabled ? MS_TURN_METHOD_ALLOCATE : MS_STUN_METHOD_BINDING);
			if (!request) goto error;
			request->gathering = TRUE;
			if (si->index == 0) {
				IceStunServerRequestTransaction *transaction = NULL;
				request->next_transmission_time = ice_add_ms(curtime, ICE_DEFAULT_RTO_DURATION);
				if (cl->session->turn_enabled)
					ms_turn_context_set_state(cl->rtp_turn_context, MS_TURN_CONTEXT_STATE_CREATING_ALLOCATION);
				transaction =
				    ice_send_stun_server_request(request, (struct sockaddr *)&cl->session->ss, cl->session->ss_len);
				ice_stun_server_request_add_transaction(request, transaction);
			} else {
				request->next_transmission_time = ice_add_ms(curtime, 2 * si->index * ICE_DEFAULT_TA_DURATION);
			}
			ice_check_list_add_stun_server_request(cl, request);
		} else {
			ms_error("ice: no rtp socket found for session [%p]", cl->rtp_session);
		}
		rtptp = NULL;
		rtp_session_get_transports(cl->rtp_session, NULL, &rtptp);
		if (!rtp_session_rtcp_mux_enabled(cl->rtp_session) && rtptp) {
			struct sockaddr *sa = (struct sockaddr *)&cl->rtp_session->rtcp.gs.loc_addr;
			if (cl->session->turn_enabled) {
				/* Define the RTP endpoint that will perform STUN encapsulation/decapsulation for TURN data */
				meta_rtp_transport_set_endpoint(rtptp, ms_turn_context_create_endpoint(cl->rtcp_turn_context));
				ms_turn_context_set_server_addr(cl->rtcp_turn_context, (struct sockaddr *)&cl->session->ss,
				                                cl->session->ss_len);

				// Start turn tcp client now if needed
				if (cl->rtcp_turn_context->transport != MS_TURN_CONTEXT_TRANSPORT_UDP) {
					if (!cl->rtcp_turn_context->turn_tcp_client) {
						cl->rtcp_turn_context->turn_tcp_client = ms_turn_tcp_client_new(
						    cl->rtcp_turn_context, cl->rtcp_turn_context->transport == MS_TURN_CONTEXT_TRANSPORT_TLS,
						    cl->rtcp_turn_context->root_certificate);
					}
					ms_turn_tcp_client_connect(cl->rtcp_turn_context->turn_tcp_client);
				}
			}
			memset(source_addr_str, 0, sizeof(source_addr_str));
			bctbx_sockaddr_to_ip_address(sa, cl->rtp_session->rtcp.gs.loc_addrlen, source_addr_str,
			                             sizeof(source_addr_str), &source_port);
			request = ice_stun_server_request_new(
			    cl, cl->rtcp_turn_context, rtptp, sa->sa_family, source_addr_str, source_port,
			    cl->session->turn_enabled ? MS_TURN_METHOD_ALLOCATE : MS_STUN_METHOD_BINDING);
			if (!request) goto error;
			request->gathering = TRUE;
			request->next_transmission_time =
			    ice_add_ms(curtime, 2 * si->index * ICE_DEFAULT_TA_DURATION + ICE_DEFAULT_TA_DURATION);
			ice_check_list_add_stun_server_request(cl, request);
			if (cl->session->turn_enabled)
				ms_turn_context_set_state(cl->rtcp_turn_context, MS_TURN_CONTEXT_STATE_CREATING_ALLOCATION);
		} else {
			ms_message("ice: no rtcp socket for session [%p]", cl->rtp_session);
		}
		si->index++;
	} else {
		if (cl->gathering_candidates == FALSE) {
			ms_message("ice: candidate gathering skipped for rtp session [%p] with check list [%p] in state [%s]",
			           cl->rtp_session, cl, ice_check_list_state_to_string(cl->state));
		}
	}

	return cl->gathering_candidates;

error:
	cl->gathering_candidates = FALSE;
	return cl->gathering_candidates;
}

static void ice_check_list_deallocate_turn_candidate(IceCheckList *cl,
                                                     MSTurnContext *turn_context,
                                                     RtpTransport *rtptp,
                                                     OrtpStream *stream) {
	IceStunServerRequest *request = NULL;
	IceStunServerRequestTransaction *transaction = NULL;
	char source_addr_str[64];
	int source_port = 0;

	if ((turn_context != NULL) &&
	    (ms_turn_context_get_state(turn_context) >= MS_TURN_CONTEXT_STATE_ALLOCATION_CREATED)) {
		ms_turn_context_set_lifetime(turn_context, 0);
		if (rtptp) {
			struct sockaddr *sa = (struct sockaddr *)&stream->loc_addr;
			memset(source_addr_str, 0, sizeof(source_addr_str));
			bctbx_sockaddr_to_ip_address(sa, stream->loc_addrlen, source_addr_str, sizeof(source_addr_str),
			                             &source_port);
			ms_message("ice: about to deallocate turn candidate for %s:%i", source_addr_str, source_port);
			request = ice_stun_server_request_new(cl, turn_context, rtptp, sa->sa_family, source_addr_str, source_port,
			                                      MS_TURN_METHOD_REFRESH);
			transaction =
			    ice_send_stun_server_request(request, (struct sockaddr *)&cl->session->ss, cl->session->ss_len);
			if (transaction != NULL) ice_stun_server_request_transaction_free(transaction);
			ice_stun_server_request_free(request);
			meta_rtp_transport_set_endpoint(rtptp, NULL); /*endpoint is later freed*/
		} else {
			ms_error("ice: no rtp socket found for session [%p]", cl->rtp_session);
		}
	}
}

static void ice_check_list_deallocate_rtp_turn_candidate(IceCheckList *cl) {
	RtpTransport *rtptp = NULL;
	rtp_session_get_transports(cl->rtp_session, &rtptp, NULL);
	ice_check_list_deallocate_turn_candidate(cl, cl->rtp_turn_context, rtptp, &cl->rtp_session->rtp.gs);
}

static void ice_check_list_deallocate_rtcp_turn_candidate(IceCheckList *cl) {
	RtpTransport *rtptp = NULL;
	rtp_session_get_transports(cl->rtp_session, NULL, &rtptp);
	ice_check_list_deallocate_turn_candidate(cl, cl->rtcp_turn_context, rtptp, &cl->rtp_session->rtcp.gs);
}

static void ice_check_list_deallocate_turn_candidates(IceCheckList *cl) {
	ice_check_list_deallocate_rtp_turn_candidate(cl);
	ice_check_list_deallocate_rtcp_turn_candidate(cl);
}

static bool_t ice_session_gathering_needed(const IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if ((session->streams[i] != NULL) && (ice_check_list_gathering_needed(session->streams[i]) == TRUE))
			return TRUE;
	}
	return FALSE;
}

static IceCheckList *ice_session_first_check_list(const IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) return session->streams[i];
	}
	return NULL;
}

bool_t ice_session_gather_candidates(IceSession *session, const struct sockaddr *ss, socklen_t ss_len) {
	Session_Index si;
	OrtpEvent *ev;
	int i;
	bool_t gathering_in_progress = FALSE;

	memcpy(&session->ss, ss, ss_len);
	session->ss_len = ss_len;
	si.session = session;
	si.index = 0;
	ms_get_cur_time(&session->gathering_start_ts);
	if (ice_session_gathering_needed(session) == TRUE) {
		for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
			if (session->streams[i] != NULL) {
				bool_t cl_gathering_in_progress = ice_check_list_gather_candidates(session->streams[i], &si);
				if (cl_gathering_in_progress == TRUE) gathering_in_progress = TRUE;
			}
		}
	} else {
		/* Notify end of gathering since it has already been done. */
		ev = ortp_event_new(ORTP_EVENT_ICE_GATHERING_FINISHED);
		ortp_event_get_data(ev)->info.ice_processing_successful = TRUE;
		session->gathering_end_ts = session->gathering_start_ts;
		rtp_session_dispatch_event(ice_session_first_check_list(session)->rtp_session, ev);
	}

	return gathering_in_progress;
}

int ice_session_gathering_duration(IceSession *session) {
	if ((session->gathering_start_ts.tv_sec == -1) || (session->gathering_end_ts.tv_sec == -1)) return -1;
	return (int)(((session->gathering_end_ts.tv_sec - session->gathering_start_ts.tv_sec) * 1000.0) +
	             ((session->gathering_end_ts.tv_nsec - session->gathering_start_ts.tv_nsec) / 1000000.0));
}

#if 0
int ice_session_connectivity_checks_duration(IceSession *session) {
	MSTimeSpec current_ts;
	if (session->connectivity_checks_start_ts.tv_sec == -1) return -1;
	ms_get_cur_time(&current_ts);
	return (int)(((current_ts.tv_sec - session->connectivity_checks_start_ts.tv_sec) * 1000.0)
		+ ((current_ts.tv_nsec - session->connectivity_checks_start_ts.tv_nsec) / 1000000.0));
}
#endif

void ice_session_enable_forced_relay(IceSession *session, bool_t enable) {
	session->forced_relay = enable;
}

void ice_session_enable_short_turn_refresh(IceSession *session, bool_t enable) {
	session->short_turn_refresh = enable;
}

static void ice_check_list_create_turn_contexts(IceCheckList *cl) {
	if (!cl->rtp_turn_context) {
		cl->rtp_turn_context = ms_turn_context_new(MS_TURN_CONTEXT_TYPE_RTP, cl->rtp_session);
	}
	if (!cl->rtcp_turn_context) {
		cl->rtcp_turn_context = ms_turn_context_new(MS_TURN_CONTEXT_TYPE_RTCP, cl->rtp_session);
	}
}

void ice_session_enable_turn(IceSession *session, bool_t enable) {
	int i;
	session->turn_enabled = enable;
	if (!enable) return;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) ice_check_list_create_turn_contexts(session->streams[i]);
	}
}

void ice_session_set_turn_transport(IceSession *session, const char *transport) {
	int i;

	if (!session->turn_enabled) return;

	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) {
			if (session->streams[i]->rtp_turn_context) {
				ms_turn_context_set_transport(session->streams[i]->rtp_turn_context,
				                              ms_turn_get_transport_from_string(transport));
			}

			if (session->streams[i]->rtcp_turn_context) {
				ms_turn_context_set_transport(session->streams[i]->rtcp_turn_context,
				                              ms_turn_get_transport_from_string(transport));
			}
		}
	}
}

void ice_session_set_turn_root_certificate(IceSession *session, const char *root_certificate) {
	int i;

	if (!session->turn_enabled) return;

	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) {
			if (session->streams[i]->rtp_turn_context) {
				ms_turn_context_set_root_certificate(session->streams[i]->rtp_turn_context, root_certificate);
			}

			if (session->streams[i]->rtcp_turn_context) {
				ms_turn_context_set_root_certificate(session->streams[i]->rtcp_turn_context, root_certificate);
			}
		}
	}
}

void ice_session_set_turn_cn(IceSession *session, const char *cn) {
	int i;

	if (!session->turn_enabled) return;

	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) {
			if (session->streams[i]->rtp_turn_context) {
				ms_turn_context_set_cn(session->streams[i]->rtp_turn_context, cn);
			}

			if (session->streams[i]->rtcp_turn_context) {
				ms_turn_context_set_cn(session->streams[i]->rtcp_turn_context, cn);
			}
		}
	}
}

void ice_session_set_stun_auth_requested_cb(IceSession *session, MSStunAuthRequestedCb cb, void *userdata) {
	session->stun_auth_requested_cb = cb;
	session->stun_auth_requested_userdata = userdata;
}

static void ice_transaction_sum_gathering_round_trip_time(const IceStunServerRequestTransaction *transaction,
                                                          IceStunRequestRoundTripTime *rtt) {
	if ((transaction->response_time.tv_sec != 0) && (transaction->response_time.tv_nsec != 0)) {
		rtt->nb_responses++;
		rtt->sum += ice_compare_time(transaction->response_time, transaction->request_time);
	}
}

static void ice_stun_server_check_sum_gathering_round_trip_time(const IceStunServerRequest *request,
                                                                IceStunRequestRoundTripTime *rtt) {
	if (request->gathering == TRUE) {
		bctbx_list_for_each2(request->transactions,
		                     (void (*)(void *, void *))ice_transaction_sum_gathering_round_trip_time, rtt);
	}
}

static void ice_check_list_sum_gathering_round_trip_times(IceCheckList *cl) {
	bctbx_list_for_each2(cl->stun_server_requests,
	                     (void (*)(void *, void *))ice_stun_server_check_sum_gathering_round_trip_time, &cl->rtt);
}

int ice_session_average_gathering_round_trip_time(IceSession *session) {
	IceStunRequestRoundTripTime rtt;
	int i;

	if ((session->gathering_start_ts.tv_sec == -1) || (session->gathering_end_ts.tv_sec == -1)) return -1;
	memset(&rtt, 0, sizeof(rtt));
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) {
			rtt.nb_responses += session->streams[i]->rtt.nb_responses;
			rtt.sum += session->streams[i]->rtt.sum;
		}
	}
	if (rtt.nb_responses == 0) return -1;
	return (rtt.sum / rtt.nb_responses);
}

/******************************************************************************
 * CANDIDATES SELECTION                                                       *
 *****************************************************************************/

static void ice_unselect_valid_pair(IceValidCandidatePair *valid_pair) {
	valid_pair->selected = FALSE;
}

static void ice_check_list_select_candidates(IceCheckList *cl) {
	IceValidCandidatePair *valid_pair = NULL;
	uint16_t componentID;
	bctbx_list_t *elem;

	if (cl->state != ICL_Completed) return;

	bctbx_list_for_each(cl->valid_list, (void (*)(void *))ice_unselect_valid_pair);
	for (componentID = 1; componentID <= 2; componentID++) {
		elem = bctbx_list_find_custom(cl->valid_list,
		                              (bctbx_compare_func)ice_find_nominated_valid_pair_from_componentID, &componentID);
		if (elem == NULL) continue;
		valid_pair = (IceValidCandidatePair *)elem->data;
		valid_pair->selected = TRUE;
	}
}

/*
 * This function sets the supplied valid_pair as selected, but insuring that the previously selected valid pair for that
 * componentID is unselected.
 */
static void ice_check_list_set_selected_valid_pair(IceCheckList *cl, IceValidCandidatePair *valid_pair) {
	const MSList *elem;
	for (elem = cl->valid_list; elem != NULL; elem = elem->next) {
		IceValidCandidatePair *valid_pair_it = (IceValidCandidatePair *)elem->data;
		if (valid_pair_it->selected && valid_pair_it != valid_pair &&
		    valid_pair_it->valid->local->componentID == valid_pair->valid->local->componentID) {
			valid_pair_it->selected = FALSE;
		}
	}
	valid_pair->selected = TRUE;
}

void ice_session_select_candidates(IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) ice_check_list_select_candidates(session->streams[i]);
	}
}

/******************************************************************************
 * TRANSACTION HANDLING                                                       *
 *****************************************************************************/

static IceTransaction *ice_create_transaction(IceCheckList *cl, IceCandidatePair *pair, const UInt96 tr_id) {
	IceTransaction *transaction = ms_new0(IceTransaction, 1);
	transaction->pair = pair;
	transaction->transactionID = tr_id;
	cl->transaction_list = bctbx_list_prepend(cl->transaction_list, transaction);
	return transaction;
}

static int ice_find_transaction_from_pair(const IceTransaction *transaction, const IceCandidatePair *pair) {
	return !((transaction->pair == pair) && !transaction->canceled);
}

static IceTransaction *ice_find_transaction(const IceCheckList *cl, const IceCandidatePair *pair) {
	bctbx_list_t *elem =
	    bctbx_list_find_custom(cl->transaction_list, (bctbx_compare_func)ice_find_transaction_from_pair, pair);
	if (elem == NULL) return NULL;
	return (IceTransaction *)elem->data;
}

/******************************************************************************
 * STUN PACKETS HANDLING                                                      *
 *****************************************************************************/

static IceStunServerRequestTransaction *ice_stun_server_request_transaction_new(UInt96 transactionID) {
	IceStunServerRequestTransaction *transaction = ms_new0(IceStunServerRequestTransaction, 1);
	transaction->request_time = ice_current_time();
	transaction->transactionID = transactionID;
	return transaction;
}

static IceStunServerRequest *ice_stun_server_request_new(IceCheckList *cl,
                                                         MSTurnContext *turn_context,
                                                         RtpTransport *rtptp,
                                                         int family,
                                                         const char *srcaddr,
                                                         int srcport,
                                                         uint16_t stun_method) {
	IceStunServerRequest *request = (IceStunServerRequest *)ms_new0(IceStunServerRequest, 1);
	request->cl = cl;
	request->turn_context = turn_context;
	request->rtptp = rtptp;
	request->source_ai = bctbx_ip_address_to_addrinfo(family, SOCK_DGRAM, srcaddr, srcport);
	if (request->source_ai == NULL) {
		ms_error("ice_stun_server_request_new(): source address not defined");
		ms_free(request);
		return NULL;
	}
	request->stun_method = stun_method;
	return request;
}

static void ice_stun_server_request_add_transaction(IceStunServerRequest *request,
                                                    IceStunServerRequestTransaction *transaction) {
	if (transaction != NULL) {
		request->transactions = bctbx_list_append(request->transactions, transaction);
	}
}

static void ice_stun_server_request_transaction_free(IceStunServerRequestTransaction *transaction) {
	ms_free(transaction);
}

static void ice_stun_server_request_free(IceStunServerRequest *request) {
	bctbx_list_for_each(request->transactions, (void (*)(void *))ice_stun_server_request_transaction_free);
	bctbx_list_free(request->transactions);
	if (request->source_ai != NULL) bctbx_freeaddrinfo(request->source_ai);
	ms_free(request);
}

static int ice_send_message_to_socket(RtpTransport *rtptp,
                                      char *buf,
                                      size_t len,
                                      const struct sockaddr *from,
                                      BCTBX_UNUSED(socklen_t fromlen),
                                      const struct sockaddr *to,
                                      socklen_t tolen) {
	mblk_t *m = rtp_session_create_packet_raw((const uint8_t *)buf, len);
	int err;
	struct addrinfo *v6ai = NULL;

	if (from != NULL) ortp_sockaddr_to_recvaddr(from, &m->recv_addr);
	if ((rtptp->session->rtp.gs.sockfamily == AF_INET6) && (to->sa_family == AF_INET)) {
		char to_addr_str[64];
		int to_port = 0;
		memset(to_addr_str, 0, sizeof(to_addr_str));
		bctbx_sockaddr_to_ip_address(to, tolen, to_addr_str, sizeof(to_addr_str), &to_port);
		v6ai = bctbx_ip_address_to_addrinfo(AF_INET6, SOCK_DGRAM, to_addr_str, to_port);
		to = v6ai->ai_addr;
		tolen = (socklen_t)v6ai->ai_addrlen;
	}
	err = meta_rtp_transport_modifier_inject_packet_to_send_to(rtptp, NULL, m, 0, to, tolen);
	freemsg(m);
	if (v6ai) bctbx_freeaddrinfo(v6ai);
	return err;
}

static int
ice_send_message_to_stun_addr(RtpTransport *rtpt, char *buff, size_t len, MSStunAddress *source, MSStunAddress *dest) {
	struct sockaddr_storage source_addr;
	struct sockaddr_storage dest_addr;
	socklen_t source_addrlen = sizeof(source_addr);
	socklen_t dest_addrlen = sizeof(dest_addr);
	memset(&source_addr, 0, source_addrlen);
	memset(&dest_addr, 0, dest_addrlen);
	ms_stun_address_to_sockaddr(source, (struct sockaddr *)&source_addr, &source_addrlen);
	ms_stun_address_to_sockaddr(dest, (struct sockaddr *)&dest_addr, &dest_addrlen);
	return ice_send_message_to_socket(rtpt, buff, len, (struct sockaddr *)&source_addr, source_addrlen,
	                                  (struct sockaddr *)&dest_addr, dest_addrlen);
}

static IceStunServerRequestTransaction *ice_send_stun_request(RtpTransport *rtptp,
                                                              const struct sockaddr *source,
                                                              socklen_t sourcelen,
                                                              const struct sockaddr *server,
                                                              BCTBX_UNUSED(socklen_t addrlen),
                                                              MSStunMessage *msg,
                                                              const char *request_type) {
	IceStunServerRequestTransaction *transaction = NULL;
	char *buf = NULL;
	size_t len;
	char source_addr_str[64];
	char dest_addr_str[64];
	char tr_id_str[25];
	struct sockaddr_storage server_addr;
	socklen_t server_addrlen = sizeof(server_addr);

	len = ms_stun_message_encode(msg, &buf);
	if (len > 0) {
		transaction = ice_stun_server_request_transaction_new(ms_stun_message_get_tr_id(msg));
		transactionID2string(&transaction->transactionID, tr_id_str);
		memset(&server_addr, 0, server_addrlen);
		bctbx_sockaddr_ipv6_to_ipv4(server, (struct sockaddr *)&server_addr, &server_addrlen);
		memset(source_addr_str, 0, sizeof(source_addr_str));
		memset(dest_addr_str, 0, sizeof(dest_addr_str));
		bctbx_sockaddr_to_printable_ip_address((struct sockaddr *)source, sourcelen, source_addr_str,
		                                       sizeof(source_addr_str));
		bctbx_sockaddr_to_printable_ip_address((struct sockaddr *)&server_addr, server_addrlen, dest_addr_str,
		                                       sizeof(dest_addr_str));
		ms_message("ice: Send %s: %s --> %s [%s]", request_type, source_addr_str, dest_addr_str, tr_id_str);
		ice_send_message_to_socket(rtptp, buf, len, source, sourcelen, (struct sockaddr *)&server_addr, server_addrlen);
	} else {
		ms_error("ice: encoding %s [%s] failed", request_type, tr_id_str);
	}
	if (buf != NULL) ms_free(buf);
	return transaction;
}

static IceStunServerRequestTransaction *
ice_send_stun_server_binding_request(IceStunServerRequest *request, const struct sockaddr *server, socklen_t addrlen) {
	IceStunServerRequestTransaction *transaction = NULL;
	MSStunMessage *msg = ms_stun_binding_request_create();
	transaction =
	    ice_send_stun_request(request->rtptp, request->source_ai->ai_addr, (socklen_t)request->source_ai->ai_addrlen,
	                          server, addrlen, msg, "STUN binding request");
	ms_stun_message_destroy(msg);
	return transaction;
}

static int
ice_parse_stun_server_response(const MSStunMessage *msg, MSStunAddress *srflx_address, MSStunAddress *relay_address) {
	const MSStunAddress *stunaddr = ms_stun_message_get_xor_mapped_address(msg);
	if (stunaddr == NULL) stunaddr = ms_stun_message_get_mapped_address(msg);
	if (stunaddr == NULL) return -1;
	*srflx_address = *stunaddr;
	stunaddr = ms_stun_message_get_xor_relayed_address(msg);
	if (stunaddr != NULL) *relay_address = *stunaddr;
	return 0;
}

static void stun_message_fill_authentication_from_turn_context(MSStunMessage *msg, const MSTurnContext *turn_context) {
	ms_stun_message_set_realm(msg, ms_turn_context_get_realm(turn_context));
	ms_stun_message_set_nonce(msg, ms_turn_context_get_nonce(turn_context));
	ms_stun_message_set_username(msg, ms_turn_context_get_username(turn_context));
	ms_stun_message_set_password(msg, ms_turn_context_get_password(turn_context));
	ms_stun_message_set_ha1(msg, ms_turn_context_get_ha1(turn_context));
	if (ms_turn_context_get_password(turn_context) || ms_turn_context_get_ha1(turn_context))
		ms_stun_message_enable_message_integrity(msg, TRUE);
}

static IceStunServerRequestTransaction *
ice_send_turn_server_allocate_request(IceStunServerRequest *request, const struct sockaddr *server, socklen_t addrlen) {
	IceStunServerRequestTransaction *transaction = NULL;
	MSStunMessage *msg = ms_turn_allocate_request_create();
	stun_message_fill_authentication_from_turn_context(msg, request->turn_context);
	request->stun_method = ms_stun_message_get_method(msg);
	if (request->requested_address_family != 0)
		ms_stun_message_set_requested_address_family(msg, request->requested_address_family);
	transaction =
	    ice_send_stun_request(request->rtptp, request->source_ai->ai_addr, (socklen_t)request->source_ai->ai_addrlen,
	                          server, addrlen, msg, "TURN allocate request");
	ms_stun_message_destroy(msg);
	return transaction;
}

static IceStunServerRequestTransaction *
ice_send_turn_server_refresh_request(IceStunServerRequest *request, const struct sockaddr *server, socklen_t addrlen) {
	IceStunServerRequestTransaction *transaction = NULL;
	if (ms_turn_context_get_state(request->turn_context) != MS_TURN_CONTEXT_STATE_IDLE) {
		MSStunMessage *msg = ms_turn_refresh_request_create(ms_turn_context_get_lifetime(request->turn_context));
		stun_message_fill_authentication_from_turn_context(msg, request->turn_context);
		request->stun_method = ms_stun_message_get_method(msg);
		transaction = ice_send_stun_request(request->rtptp, request->source_ai->ai_addr,
		                                    (socklen_t)request->source_ai->ai_addrlen, server, addrlen, msg,
		                                    "TURN refresh request");
		ms_stun_message_destroy(msg);
	}
	return transaction;
}

static IceStunServerRequestTransaction *ice_send_turn_server_create_permission_request(IceStunServerRequest *request,
                                                                                       const struct sockaddr *server,
                                                                                       socklen_t addrlen) {
	IceStunServerRequestTransaction *transaction = NULL;
	MSStunMessage *msg = ms_turn_create_permission_request_create(request->peer_address);
	stun_message_fill_authentication_from_turn_context(msg, request->turn_context);
	request->stun_method = ms_stun_message_get_method(msg);
	transaction =
	    ice_send_stun_request(request->rtptp, request->source_ai->ai_addr, (socklen_t)request->source_ai->ai_addrlen,
	                          server, addrlen, msg, "TURN create permission request");
	ms_stun_message_destroy(msg);
	return transaction;
}

static IceStunServerRequestTransaction *ice_send_turn_server_channel_bind_request(IceStunServerRequest *request,
                                                                                  const struct sockaddr *server,
                                                                                  socklen_t addrlen) {
	IceStunServerRequestTransaction *transaction = NULL;
	MSStunMessage *msg = ms_turn_channel_bind_request_create(request->peer_address, request->channel_number);
	stun_message_fill_authentication_from_turn_context(msg, request->turn_context);
	request->stun_method = ms_stun_message_get_method(msg);
	transaction =
	    ice_send_stun_request(request->rtptp, request->source_ai->ai_addr, (socklen_t)request->source_ai->ai_addrlen,
	                          server, addrlen, msg, "TURN channel bind request");
	ms_stun_message_destroy(msg);
	return transaction;
}

static IceStunServerRequestTransaction *
ice_send_stun_server_request(IceStunServerRequest *request, const struct sockaddr *server, socklen_t addrlen) {
	IceStunServerRequestTransaction *transaction = NULL;
	switch (request->stun_method) {
		case MS_STUN_METHOD_BINDING:
			transaction = ice_send_stun_server_binding_request(request, server, addrlen);
			break;
		case MS_TURN_METHOD_ALLOCATE:
			transaction = ice_send_turn_server_allocate_request(request, server, addrlen);
			break;
		case MS_TURN_METHOD_CREATE_PERMISSION:
			transaction = ice_send_turn_server_create_permission_request(request, server, addrlen);
			break;
		case MS_TURN_METHOD_REFRESH:
			transaction = ice_send_turn_server_refresh_request(request, server, addrlen);
			break;
		case MS_TURN_METHOD_CHANNEL_BIND:
			transaction = ice_send_turn_server_channel_bind_request(request, server, addrlen);
			break;
		default:
			break;
	}
	return transaction;
}

/* Send a STUN binding request for ICE connectivity checks according to 7.1.2. */
static void ice_send_binding_request(IceCheckList *cl, IceCandidatePair *pair, const RtpSession *rtp_session) {
	MSStunMessage *msg;
	MSStunAddress source;
	MSStunAddress dest;
	char *username;
	IceTransaction *transaction;
	char *buf = NULL;
	size_t len;
	RtpTransport *rtptp;
	char local_addr_str[64];
	char remote_addr_str[64];
	char tr_id_str[25];

	transaction = ice_find_transaction(cl, pair);

	if (pair->state == ICP_InProgress) {
		if (transaction == NULL) {
			ms_error("ice: No transaction found for InProgress pair");
			return;
		}
		/* This is a retransmission: update the number of retransmissions, the retransmission timer value, and the
		 * transmission time. */
		pair->retransmissions++;
		if (cl->session->role == IR_Controlling && pair->use_candidate && !pair->nomination_failing &&
		    pair->retransmissions > ICE_MAX_RETRANSMISSIONS_FOR_NOMINATIONS) {
			/*The nomination process is abnormally long: possibly the nat association has been accidentally closed by
			 * the media stream. Nominate an alternate pair if possible*/
			pair->nomination_failing = TRUE;
			cl->nomination_in_progress = FALSE;
			ice_check_list_perform_nominations(cl, FALSE);
			/*Despite we've started a new nomination, we continue the retransmissions for that pair, in case a response
			 * is finally received.*/
		}

		if (pair->retransmissions > ICE_MAX_RETRANSMISSIONS) {
			/* Too much retransmissions, stop sending connectivity checks for this pair. */
			ice_pair_set_state(pair, ICP_Failed);
			return;
		}
		pair->rto = pair->rto << 1;
	}
	pair->transmission_time = ice_current_time();

	if (pair->local->componentID == 1) {
		rtp_session_get_transports(rtp_session, &rtptp, NULL);
	} else if (pair->local->componentID == 2) {
		rtp_session_get_transports(rtp_session, NULL, &rtptp);
	} else return;

	source = ms_ip_address_to_stun_address(pair->local->taddr.family, SOCK_DGRAM, pair->local->taddr.ip,
	                                       pair->local->taddr.port);
	dest = ms_ip_address_to_stun_address(pair->remote->taddr.family, SOCK_DGRAM, pair->remote->taddr.ip,
	                                     pair->remote->taddr.port);
	msg = ms_stun_binding_request_create();
	username = ms_strdup_printf("%s:%s", ice_check_list_remote_ufrag(cl), ice_check_list_local_ufrag(cl));
	ms_stun_message_set_username(msg, username);
	ms_free(username);
	ms_stun_message_set_password(msg, ice_check_list_remote_pwd(cl));
	ms_stun_message_enable_message_integrity(msg, TRUE);
	ms_stun_message_enable_fingerprint(msg, TRUE);

	/* Set the PRIORITY attribute as defined in 7.1.2.1. */
	ms_stun_message_set_priority(msg, (pair->local->priority & 0x00ffffff) |
	                                      (type_preference_values[ICT_PeerReflexiveCandidate] << 24));

	/* Include the USE-CANDIDATE attribute if the pair is nominated and the agent has the controlling role, as defined
	 * in 7.1.2.1. */
	if ((cl->session->role == IR_Controlling) && (pair->use_candidate == TRUE)) {
		ms_stun_message_enable_use_candidate(msg, TRUE);
	}

	/* Include the ICE-CONTROLLING or ICE-CONTROLLED attribute depending on the role of the agent, as defined
	 * in 7.1.2.2. */
	switch (cl->session->role) {
		case IR_Controlling:
			ms_stun_message_set_ice_controlling(msg, cl->session->tie_breaker);
			break;
		case IR_Controlled:
			ms_stun_message_set_ice_controlled(msg, cl->session->tie_breaker);
			break;
	}

	/* Keep the same transaction ID for retransmission. */
	if (pair->state == ICP_InProgress) {
		ms_stun_message_set_tr_id(msg, transaction->transactionID);
	} else {
		transaction = ice_create_transaction(cl, pair, ms_stun_message_get_tr_id(msg));
	}

	/* For backward compatibility */
	ms_stun_message_enable_dummy_message_integrity(msg, pair->use_dummy_hmac);

	len = ms_stun_message_encode(msg, &buf);
	if (len > 0) {
		memset(local_addr_str, 0, sizeof(local_addr_str));
		memset(remote_addr_str, 0, sizeof(remote_addr_str));
		transactionID2string(&transaction->transactionID, tr_id_str);
		ice_transport_address_to_printable_ip_address(&pair->local->taddr, local_addr_str, sizeof(local_addr_str));
		ice_transport_address_to_printable_ip_address(&pair->remote->taddr, remote_addr_str, sizeof(remote_addr_str));
		if (pair->state == ICP_InProgress) {
			ms_message("ice: Retransmit (%d) binding request for pair %p: %s:%s --> %s:%s [%s]", pair->retransmissions,
			           pair, local_addr_str, candidate_type_values[pair->local->type], remote_addr_str,
			           candidate_type_values[pair->remote->type], tr_id_str);
		} else {
			ms_message("ice: Send binding request for %s pair %p: %s:%s --> %s:%s [%s] (flags:%s)",
			           candidate_pair_state_values[pair->state], pair, local_addr_str,
			           candidate_type_values[pair->local->type], remote_addr_str,
			           candidate_type_values[pair->remote->type], tr_id_str,
			           pair->use_candidate ? "use-candidate" : "none");
		}

		if ((cl->session->forced_relay == TRUE) && (pair->remote->type != ICT_RelayedCandidate) &&
		    (pair->local->type != ICT_RelayedCandidate)) {
			ms_message("ice: Forced relay, did not send binding request for %s pair %p: %s:%s --> %s:%s [%s]",
			           candidate_pair_state_values[pair->state], pair, local_addr_str,
			           candidate_type_values[pair->local->type], remote_addr_str,
			           candidate_type_values[pair->remote->type], tr_id_str);
		} else {
			ice_send_message_to_stun_addr(rtptp, buf, len, &source, &dest);
		}

		if (pair->state != ICP_InProgress) {
			/* First transmission of the request, initialize the retransmission timer. */
			pair->rto = ICE_DEFAULT_RTO_DURATION;
			pair->retransmissions = 0;
			/* Save the role of the agent. */
			pair->role = cl->session->role;
			/* Change the state of the pair. */
			ice_pair_set_state(pair, ICP_InProgress);
		}
	}
	if (buf != NULL) ms_free(buf);
	ms_stun_message_destroy(msg);
}

static int ice_get_componentID_from_rtp_session(const OrtpEventData *evt_data) {
	if (evt_data->info.socket_type == OrtpRTPSocket) {
		return 1;
	} else if (evt_data->info.socket_type == OrtpRTCPSocket) {
		return 2;
	}
	return -1;
}

static int
ice_get_transport_from_rtp_session(const RtpSession *rtp_session, const OrtpEventData *evt_data, RtpTransport **rtptp) {
	if (evt_data->info.socket_type == OrtpRTPSocket) {
		rtp_session_get_transports(rtp_session, rtptp, NULL);
		return 0;
	} else if (evt_data->info.socket_type == OrtpRTCPSocket) {
		rtp_session_get_transports(rtp_session, NULL, rtptp);
		return 0;
	}
	return -1;
}

static int ice_get_transport_from_rtp_session_and_componentID(const RtpSession *rtp_session,
                                                              int componentID,
                                                              RtpTransport **rtptp) {
	if (componentID == 1) {
		rtp_session_get_transports(rtp_session, rtptp, NULL);
		return 0;
	} else if (componentID == 2) {
		rtp_session_get_transports(rtp_session, NULL, rtptp);
		return 0;
	} else return -1;
}

static int ice_get_ortp_stream_from_rtp_session_and_componentID(const RtpSession *rtp_session,
                                                                int componentID,
                                                                const OrtpStream **stream) {
	if (componentID == 1) {
		*stream = &rtp_session->rtp.gs;
		return 0;
	} else if (componentID == 2) {
		*stream = &rtp_session->rtcp.gs;
		return 0;
	} else return -1;
}

static MSTurnContext *ice_get_turn_context_from_check_list(const IceCheckList *cl, const OrtpEventData *evt_data) {
	if (evt_data->info.socket_type == OrtpRTPSocket) {
		return cl->rtp_turn_context;
	} else if (evt_data->info.socket_type == OrtpRTCPSocket) {
		return cl->rtcp_turn_context;
	} else return NULL;
}

static MSTurnContext *ice_get_turn_context_from_check_list_componentID(const IceCheckList *cl, uint16_t componentID) {
	if (componentID == 1) {
		return cl->rtp_turn_context;
	} else if (componentID == 2) {
		return cl->rtcp_turn_context;
	} else return NULL;
}

static void ice_send_binding_response(IceCheckList *cl,
                                      const RtpSession *rtp_session,
                                      const OrtpEventData *evt_data,
                                      const MSStunMessage *msg,
                                      const MSStunAddress *dest) {
	MSStunMessage *response;
	char *buf = NULL;
	char *username;
	size_t len;
	RtpTransport *rtptp = NULL;
	struct sockaddr_storage dest_addr;
	socklen_t dest_addrlen = sizeof(dest_addr);
	struct sockaddr_storage source_addr;
	socklen_t source_addrlen = sizeof(source_addr);
	char dest_addr_str[256];
	char source_addr_str[256];
	char tr_id_str[25];
	UInt96 tr_id;

	ice_get_transport_from_rtp_session(rtp_session, evt_data, &rtptp);
	if (!rtptp) return;

	memset(&dest_addr, 0, dest_addrlen);
	memset(&source_addr, 0, source_addrlen);

	/* Create the binding response, copying the transaction ID from the request. */
	tr_id = ms_stun_message_get_tr_id(msg);
	response = ms_stun_binding_success_response_create();
	ms_stun_message_set_tr_id(response, tr_id);
	ms_stun_message_enable_message_integrity(response, TRUE);
	ms_stun_message_enable_fingerprint(response, TRUE);
	/* For backward compatibility */
	ms_stun_message_enable_dummy_message_integrity(response, ms_stun_message_dummy_message_integrity_enabled(msg));

	/* Add username for message integrity */
	username = ms_strdup_printf("%s:%s", ice_check_list_local_ufrag(cl), ice_check_list_remote_ufrag(cl));
	ms_stun_message_set_username(response, username);
	ms_free(username);
	if (ms_stun_message_dummy_message_integrity_enabled(msg) && !cl->session->check_message_integrity) {
		/* Legacy case, include username for backward compatibility */
	} else {
		ms_stun_message_include_username_attribute(response, FALSE);
	}

	/* Add password for message integrity */
	ms_stun_message_set_password(response, ice_check_list_local_pwd(cl));

	/* Add the mapped address to the response. */
	ms_stun_message_set_xor_mapped_address(response, *dest);

	len = ms_stun_message_encode(response, &buf);
	if (len > 0) {
		memset(dest_addr_str, 0, sizeof(dest_addr_str));
		memset(source_addr_str, 0, sizeof(source_addr_str));
		transactionID2string(&tr_id, tr_id_str);
		ms_stun_address_to_sockaddr(dest, (struct sockaddr *)&dest_addr, &dest_addrlen);
		bctbx_sockaddr_to_printable_ip_address((struct sockaddr *)&dest_addr, dest_addrlen, dest_addr_str,
		                                       sizeof(dest_addr_str));
		ortp_recvaddr_to_sockaddr(&evt_data->packet->recv_addr, (struct sockaddr *)&source_addr, &source_addrlen);
		bctbx_sockaddr_ipv6_to_ipv4((struct sockaddr *)&source_addr, (struct sockaddr *)&source_addr, &source_addrlen);
		bctbx_sockaddr_to_printable_ip_address((struct sockaddr *)&source_addr, source_addrlen, source_addr_str,
		                                       sizeof(source_addr_str));
		ms_message("ice: Send binding response: %s --> %s [%s]", source_addr_str, dest_addr_str, tr_id_str);
		ice_send_message_to_socket(rtptp, buf, len, (struct sockaddr *)&source_addr, source_addrlen,
		                           (struct sockaddr *)&dest_addr, dest_addrlen);
	}
	if (buf != NULL) ms_free(buf);
	ms_stun_message_destroy(response);
}

static void ice_send_error_response(const RtpSession *rtp_session,
                                    const OrtpEventData *evt_data,
                                    const MSStunMessage *msg,
                                    const MSStunAddress *dest,
                                    uint16_t error_num,
                                    const char *error_msg) {
	MSStunMessage *response;
	char *buf = NULL;
	size_t len;
	RtpTransport *rtptp;
	struct sockaddr_storage dest_addr;
	socklen_t dest_addrlen = sizeof(dest_addr);
	struct sockaddr_storage source_addr;
	socklen_t source_addrlen = sizeof(source_addr);
	char dest_addr_str[256];
	char source_addr_str[256];
	char tr_id_str[25];
	UInt96 tr_id;

	memset(&dest_addr, 0, dest_addrlen);
	memset(&source_addr, 0, source_addrlen);
	ice_get_transport_from_rtp_session(rtp_session, evt_data, &rtptp);
	if (!rtptp) return;

	/* Create the error response, copying the transaction ID from the request. */
	tr_id = ms_stun_message_get_tr_id(msg);
	response = ms_stun_binding_error_response_create();
	ms_stun_message_enable_fingerprint(response, TRUE);
	ms_stun_message_set_error_code(response, error_num, error_msg);

	len = ms_stun_message_encode(response, &buf);
	if (len > 0) {
		memset(dest_addr_str, 0, sizeof(dest_addr_str));
		memset(source_addr_str, 0, sizeof(source_addr_str));
		transactionID2string(&tr_id, tr_id_str);
		ms_stun_address_to_sockaddr(dest, (struct sockaddr *)&dest_addr, &dest_addrlen);
		bctbx_sockaddr_to_printable_ip_address((struct sockaddr *)&dest_addr, dest_addrlen, dest_addr_str,
		                                       sizeof(dest_addr_str));
		ortp_recvaddr_to_sockaddr(&evt_data->packet->recv_addr, (struct sockaddr *)&source_addr, &source_addrlen);
		bctbx_sockaddr_ipv6_to_ipv4((struct sockaddr *)&source_addr, (struct sockaddr *)&source_addr, &source_addrlen);
		bctbx_sockaddr_to_printable_ip_address((struct sockaddr *)&source_addr, source_addrlen, source_addr_str,
		                                       sizeof(source_addr_str));
		ms_message("ice: Send error response: %s --> %s [%s]", source_addr_str, dest_addr_str, tr_id_str);
		ice_send_message_to_socket(rtptp, buf, len, (struct sockaddr *)&source_addr, source_addrlen,
		                           (struct sockaddr *)&dest_addr, dest_addrlen);
	}
	if (buf != NULL) ms_free(buf);
	ms_stun_message_destroy(response);
}

static void ice_send_indication(const IceCandidatePair *pair, const RtpSession *rtp_session) {
	MSStunMessage *indication;
	MSStunAddress source;
	MSStunAddress dest;
	char *buf = NULL;
	size_t len;
	RtpTransport *rtptp;
	char local_addr_str[64];
	char remote_addr_str[64];

	if (pair->local->componentID == 1) {
		rtp_session_get_transports(rtp_session, &rtptp, NULL);
	} else if (pair->local->componentID == 2) {
		rtp_session_get_transports(rtp_session, NULL, &rtptp);
	} else return;
	source = ms_ip_address_to_stun_address(pair->local->taddr.family, SOCK_DGRAM, pair->local->taddr.ip,
	                                       pair->local->taddr.port);
	dest = ms_ip_address_to_stun_address(pair->remote->taddr.family, SOCK_DGRAM, pair->remote->taddr.ip,
	                                     pair->remote->taddr.port);
	indication = ms_stun_binding_indication_create();
	ms_stun_message_enable_fingerprint(indication, TRUE);
	/* For backward compatibility */
	ms_stun_message_enable_dummy_message_integrity(indication, pair->use_dummy_hmac);

	len = ms_stun_message_encode(indication, &buf);
	if (len > 0) {
		memset(local_addr_str, 0, sizeof(local_addr_str));
		memset(remote_addr_str, 0, sizeof(remote_addr_str));
		ice_transport_address_to_printable_ip_address(&pair->local->taddr, local_addr_str, sizeof(local_addr_str));
		ice_transport_address_to_printable_ip_address(&pair->remote->taddr, remote_addr_str, sizeof(remote_addr_str));
		ms_message("ice: Send indication for pair %p: %s:%s --> %s:%s", pair, local_addr_str,
		           candidate_type_values[pair->local->type], remote_addr_str,
		           candidate_type_values[pair->remote->type]);
		ice_send_message_to_stun_addr(rtptp, buf, len, &source, &dest);
	}
	if (buf != NULL) ms_free(buf);
	ms_free(indication);
}

static void ice_send_keepalive_packet_for_componentID(const uint16_t *componentID, const CheckList_RtpSession *cr) {
	bctbx_list_t *elem = bctbx_list_find_custom(
	    cr->cl->valid_list, (bctbx_compare_func)ice_find_selected_valid_pair_from_componentID, componentID);
	if (elem != NULL) {
		IceValidCandidatePair *valid_pair = (IceValidCandidatePair *)elem->data;
		ice_send_indication(valid_pair->valid, cr->rtp_session);
	}
}

static void
ice_check_keep_alive_on_valid_pair(IceValidCandidatePair *valid, RtpSession *rtp_session, const MSTimeSpec *curtime) {
	if (ice_compare_time(*curtime, valid->last_keepalive) >= 3000) {
		ice_send_indication(valid->valid, rtp_session);
		valid->last_keepalive = *curtime;
	}
}

static void ice_send_keepalive_packets(IceCheckList *cl, RtpSession *rtp_session) {
	CheckList_RtpSession cr;
	if (cl->state == ICL_Completed) {
		cr.cl = cl;
		cr.rtp_session = rtp_session;
		bctbx_list_for_each2(cl->local_componentIDs,
		                     (void (*)(void *, void *))ice_send_keepalive_packet_for_componentID, &cr);
	} else if (cl->state == ICL_Running) {
		bctbx_list_t *elem;
		MSTimeSpec curtime;
		ms_get_cur_time(&curtime);
		/*refresh pairs on the valid list, to keep them alive until conclusion*/
		for (elem = cl->valid_list; elem != NULL; elem = elem->next) {
			IceValidCandidatePair *valid = (IceValidCandidatePair *)elem->data;
			ice_check_keep_alive_on_valid_pair(valid, rtp_session, &curtime);
		}
	}
}

static int ice_find_candidate_from_transport_address(const IceCandidate *candidate, const IceTransportAddress *taddr) {
	return ice_compare_transport_addresses(&candidate->taddr, taddr);
}

static int ice_find_candidate_from_transport_address_and_componentID(const IceCandidate *candidate,
                                                                     const TransportAddress_ComponentID *taci) {
	return !((candidate->componentID == taci->componentID) &&
	         (ice_compare_transport_addresses(&candidate->taddr, taci->ta) == 0));
}

static int ice_find_candidate_from_ip_address(const IceCandidate *candidate, const char *ipaddr) {
	return strcmp(candidate->taddr.ip, ipaddr);
}

/* Check that the mandatory attributes of a connectivity check binding request are present. */
static int ice_check_received_binding_request_attributes(const RtpSession *rtp_session,
                                                         const OrtpEventData *evt_data,
                                                         const MSStunMessage *msg,
                                                         const MSStunAddress *remote_addr) {
	if (!ms_stun_message_message_integrity_enabled(msg)) {
		ms_warning("ice: Received binding request missing MESSAGE-INTEGRITY attribute");
		ice_send_error_response(rtp_session, evt_data, msg, remote_addr, MS_STUN_ERROR_CODE_BAD_REQUEST,
		                        "Missing MESSAGE-INTEGRITY attribute");
		return -1;
	}
	if (ms_stun_message_get_username(msg) == NULL) {
		ms_warning("ice: Received binding request missing USERNAME attribute");
		ice_send_error_response(rtp_session, evt_data, msg, remote_addr, MS_STUN_ERROR_CODE_BAD_REQUEST,
		                        "Missing USERNAME attribute");
		return -1;
	}
	if (!ms_stun_message_fingerprint_enabled(msg)) {
		ms_warning("ice: Received binding request missing FINGERPRINT attribute");
		ice_send_error_response(rtp_session, evt_data, msg, remote_addr, MS_STUN_ERROR_CODE_BAD_REQUEST,
		                        "Missing FINGERPRINT attribute");
		return -1;
	}
	if (!ms_stun_message_has_priority(msg)) {
		ms_warning("ice: Received binding request missing PRIORITY attribute");
		ice_send_error_response(rtp_session, evt_data, msg, remote_addr, MS_STUN_ERROR_CODE_BAD_REQUEST,
		                        "Missing PRIORITY attribute");
		return -1;
	}
	if (!ms_stun_message_has_ice_controlling(msg) && !ms_stun_message_has_ice_controlled(msg)) {
		ms_warning("ice: Received binding request missing ICE-CONTROLLING or ICE-CONTROLLED attribute");
		ice_send_error_response(rtp_session, evt_data, msg, remote_addr, MS_STUN_ERROR_CODE_BAD_REQUEST,
		                        "Missing ICE-CONTROLLING or ICE-CONTROLLED attribute");
		return -1;
	}
	return 0;
}

static int ice_check_received_binding_request_integrity(const IceCheckList *cl,
                                                        const RtpSession *rtp_session,
                                                        const OrtpEventData *evt_data,
                                                        const MSStunMessage *msg,
                                                        const MSStunAddress *remote_addr) {
	char *hmac;
	mblk_t *mp = evt_data->packet;
	int ret = 0;

	/* Check the message integrity: first remove length of fingerprint... */
	char *lenpos = (char *)mp->b_rptr + sizeof(uint16_t);
	uint16_t newlen = htons(ms_stun_message_get_length(msg) - 8);

	memcpy(lenpos, &newlen, sizeof(uint16_t));
	hmac = ms_stun_calculate_integrity_short_term((char *)mp->b_rptr, (size_t)(mp->b_wptr - mp->b_rptr - 24 - 8),
	                                              ice_check_list_local_pwd(cl));
	/* ... and then restore the length with fingerprint. */
	newlen = htons(ms_stun_message_get_length(msg));
	memcpy(lenpos, &newlen, sizeof(uint16_t));
	if (strcmp(ms_stun_message_get_message_integrity(msg), hmac) != 0) {
		ms_error("ice: Wrong MESSAGE-INTEGRITY in received binding request");
		if (!cl->session->check_message_integrity && ms_stun_message_dummy_message_integrity_enabled(msg)) {
			ms_message("ice: skipping message integrity check for cl [%p]", cl);
		} else {
			ice_send_error_response(rtp_session, evt_data, msg, remote_addr, MS_STUN_ERROR_CODE_UNAUTHORIZED,
			                        "Wrong MESSAGE-INTEGRITY attribute");
			ret = -1;
		}
	}
	ms_free(hmac);
	return ret;
}

static int ice_check_received_binding_request_username(const IceCheckList *cl,
                                                       const RtpSession *rtp_session,
                                                       const OrtpEventData *evt_data,
                                                       const MSStunMessage *msg,
                                                       const MSStunAddress *remote_addr) {
	const char *username = ms_stun_message_get_username(msg);
	char *colon = strchr(username, ':');
	if ((colon == NULL) || (strncmp(username, ice_check_list_local_ufrag(cl), colon - username) != 0)) {
		ms_error("ice: Wrong USERNAME attribute (colon=%p)", colon);
		ice_send_error_response(rtp_session, evt_data, msg, remote_addr, MS_STUN_ERROR_CODE_UNAUTHORIZED,
		                        "Wrong USERNAME attribute");
		return -1;
	}
	return 0;
}

static int ice_check_received_binding_request_role_conflict(const IceCheckList *cl,
                                                            const RtpSession *rtp_session,
                                                            const OrtpEventData *evt_data,
                                                            const MSStunMessage *msg,
                                                            const MSStunAddress *remote_addr) {
	/* Detect and repair role conflicts according to 7.2.1.1. */
	if ((cl->session->role == IR_Controlling) && (ms_stun_message_has_ice_controlling(msg))) {
		ms_warning("ice: Role conflict, both agents are CONTROLLING");
		if (cl->session->tie_breaker >= ms_stun_message_get_ice_controlling(msg)) {
			ice_send_error_response(rtp_session, evt_data, msg, remote_addr, MS_ICE_ERROR_CODE_ROLE_CONFLICT,
			                        "Role Conflict");
			return -1;
		} else {
			ms_message("ice: Switch to the CONTROLLED role");
			ice_session_set_role(cl->session, IR_Controlled);
		}
	} else if ((cl->session->role == IR_Controlled) && (ms_stun_message_has_ice_controlled(msg))) {
		ms_warning("ice: Role conflict, both agents are CONTROLLED");
		if (cl->session->tie_breaker >= ms_stun_message_get_ice_controlled(msg)) {
			ms_message("ice: Switch to the CONTROLLING role");
			ice_session_set_role(cl->session, IR_Controlling);
		} else {
			ice_send_error_response(rtp_session, evt_data, msg, remote_addr, MS_ICE_ERROR_CODE_ROLE_CONFLICT,
			                        "Role Conflict");
			return -1;
		}
	}
	return 0;
}

static void
ice_fill_transport_address_from_sockaddr(IceTransportAddress *taddr, struct sockaddr *addr, socklen_t addrlen) {
	bctbx_sockaddr_to_ip_address(addr, addrlen, taddr->ip, sizeof(taddr->ip), &taddr->port);
	taddr->family = addr->sa_family;
}

static void ice_fill_transport_address_from_stun_address(IceTransportAddress *taddr, const MSStunAddress *stun_addr) {
	struct sockaddr_storage addr;
	socklen_t addrlen = sizeof(addr);
	memset(&addr, 0, addrlen);
	ms_stun_address_to_sockaddr(stun_addr, (struct sockaddr *)&addr, &addrlen);
	ice_fill_transport_address_from_sockaddr(taddr, (struct sockaddr *)&addr, addrlen);
}

static MSStunAddress ice_transport_address_to_stun_address(IceTransportAddress *taddr) {
	return ms_ip_address_to_stun_address(taddr->family, SOCK_DGRAM, taddr->ip, taddr->port);
}

static void ice_transport_address_to_printable_ip_address(const IceTransportAddress *taddr,
                                                          char *printable_ip,
                                                          size_t printable_ip_size) {
	struct addrinfo *ai;
	if (taddr == NULL) {
		*printable_ip = '\0';
	} else {
		ai = bctbx_ip_address_to_addrinfo(taddr->family, SOCK_DGRAM, taddr->ip, taddr->port);
		if (ai) {
			bctbx_addrinfo_to_printable_ip_address(ai, printable_ip, printable_ip_size);
			bctbx_freeaddrinfo(ai);
		} else {
			*printable_ip = '\0';
		}
	}
}

static int ice_find_candidate_from_foundation(const IceCandidate *candidate, const char *foundation) {
	return !((strlen(candidate->foundation) == strlen(foundation)) && (strcmp(candidate->foundation, foundation) == 0));
}

static void ice_generate_arbitrary_foundation(char *foundation, int len, bctbx_list_t *list) {
	uint64_t r;
	bctbx_list_t *elem;

	do {
		r = (((uint64_t)bctbx_random()) << 32) | (((uint64_t)bctbx_random()) & 0xffffffff);
		snprintf(foundation, len, "%" PRIx64, r);
		elem = bctbx_list_find_custom(list, (bctbx_compare_func)ice_find_candidate_from_foundation, foundation);
	} while (elem != NULL);
}

static IceCandidate *ice_learn_peer_reflexive_candidate(IceCheckList *cl,
                                                        const OrtpEventData *evt_data,
                                                        const MSStunMessage *msg,
                                                        const IceTransportAddress *taddr) {
	char foundation[32];
	IceCandidate *candidate = NULL;
	bctbx_list_t *elem;
	int componentID;
	TransportAddress_ComponentID taci;

	componentID = ice_get_componentID_from_rtp_session(evt_data);
	if (componentID < 0) return NULL;

	taci.ta = taddr;
	taci.componentID = componentID;
	elem = bctbx_list_find_custom(cl->remote_candidates,
	                              (bctbx_compare_func)ice_find_candidate_from_transport_address_and_componentID, &taci);
	if (elem == NULL) {
		ms_message("ice: Learned peer reflexive candidate %s:%d for componentID %d", taddr->ip, taddr->port,
		           componentID);
		/* Add peer reflexive candidate to the remote candidates list. */
		memset(foundation, '\0', sizeof(foundation));
		ice_generate_arbitrary_foundation(foundation, sizeof(foundation), cl->remote_candidates);
		candidate = ice_add_remote_candidate(cl, "prflx", taddr->family, taddr->ip, taddr->port, componentID,
		                                     ms_stun_message_get_priority(msg), foundation, FALSE);
	}
	return candidate;
}

static int ice_find_pair_from_candidates(const IceCandidatePair *pair,
                                         const LocalCandidate_RemoteCandidate *candidates) {
	return !((pair->local == candidates->local) && (pair->remote == candidates->remote));
}

/* Trigger checks as defined in 7.2.1.4. */
static IceCandidatePair *ice_trigger_connectivity_check_on_binding_request(IceCheckList *cl,
                                                                           BCTBX_UNUSED(const RtpSession *rtp_session),
                                                                           const OrtpEventData *evt_data,
                                                                           IceCandidate *prflx_candidate,
                                                                           const IceTransportAddress *remote_taddr) {
	IceTransportAddress local_taddr;
	LocalCandidate_RemoteCandidate candidates;
	bctbx_list_t *elem;
	IceCandidatePair *pair = NULL;
	struct sockaddr_storage recv_addr;
	socklen_t recv_addrlen = sizeof(recv_addr);
	char addr_str[64];

	memset(&recv_addr, 0, recv_addrlen);
	memset(addr_str, 0, sizeof(addr_str));
	ortp_recvaddr_to_sockaddr(&evt_data->packet->recv_addr, (struct sockaddr *)&recv_addr, &recv_addrlen);
	bctbx_sockaddr_ipv6_to_ipv4((struct sockaddr *)&recv_addr, (struct sockaddr *)&recv_addr, &recv_addrlen);
	ice_fill_transport_address_from_sockaddr(&local_taddr, (struct sockaddr *)&recv_addr, recv_addrlen);
	elem = bctbx_list_find_custom(cl->local_candidates, (bctbx_compare_func)ice_find_candidate_from_transport_address,
	                              &local_taddr);
	if (elem == NULL) {
		ice_transport_address_to_printable_ip_address(&local_taddr, addr_str, sizeof(addr_str));
		ms_error("ice: Local candidate %s not found!", addr_str);
		return NULL;
	}
	candidates.local = (IceCandidate *)elem->data;
	if (prflx_candidate != NULL) {
		candidates.remote = prflx_candidate;
	} else {
		TransportAddress_ComponentID taci;
		taci.componentID = candidates.local->componentID;
		taci.ta = remote_taddr;
		elem = bctbx_list_find_custom(cl->remote_candidates,
		                              (bctbx_compare_func)ice_find_candidate_from_transport_address_and_componentID,
		                              &taci);
		if (elem == NULL) {
			ice_transport_address_to_printable_ip_address(remote_taddr, addr_str, sizeof(addr_str));
			ms_error("ice: Remote candidate %s not found!", addr_str);
			return NULL;
		}
		candidates.remote = (IceCandidate *)elem->data;
	}
	elem = bctbx_list_find_custom(cl->check_list, (bctbx_compare_func)ice_find_pair_from_candidates, &candidates);
	if (elem == NULL) {
		/* The pair is not in the check list yet. */
		ms_message("ice: Add new candidate pair [%p - %p] in the check list", candidates.local, candidates.remote);
		/* Check if the pair is in the list of pairs even if it is not in the check list. */
		elem = bctbx_list_find_custom(cl->pairs, (bctbx_compare_func)ice_find_pair_from_candidates, &candidates);
		if (elem == NULL) {
			pair = ice_pair_new(cl, candidates.local, candidates.remote);
			cl->pairs = bctbx_list_append(cl->pairs, pair);
		} else {
			pair = (IceCandidatePair *)elem->data;
		}
		elem = bctbx_list_find(cl->check_list, pair);
		if (elem == NULL) {
			cl->check_list =
			    bctbx_list_insert_sorted(cl->check_list, pair, (bctbx_compare_func)ice_compare_pair_priorities);
		}
		/* Set the state of the pair to Waiting and trigger a check. */
		ice_pair_set_state(pair, ICP_Waiting);
		ice_check_list_queue_triggered_check(cl, pair);
	} else {
		/* The pair has been found in the check list. */
		pair = (IceCandidatePair *)elem->data;
		switch (pair->state) {
			case ICP_Waiting:
			case ICP_Frozen:
			case ICP_Failed:
				ice_pair_set_state(pair, ICP_Waiting);
				ice_check_list_queue_triggered_check(cl, pair);
				break;
			case ICP_InProgress:
				ms_message("ice: we are receiving a STUN request on pair %p, for which an outgoing STUN transaction is "
				           "running.",
				           pair);

				if (!pair->has_canceled_transaction) {
					/*cancel the transaction, but this may happen only once.*/
					IceTransaction *tr = ice_find_transaction(cl, pair);
					if (tr) {
						ms_message("ice: transaction is canceled, a new binding request sent.");
						tr->canceled = TRUE;
						/*and queue a new triggered check*/
						ice_pair_set_state(pair, ICP_Waiting);
						ice_check_list_queue_triggered_check(cl, pair);
						pair->has_canceled_transaction = TRUE;
					}
				}
				break;
			case ICP_Succeeded:
				/* Nothing to be done. */
				break;
		}
	}
	return pair;
}

static IceCandidatePair *ice_lookup_possible_valid_pair(const IceCheckList *cl, IceCandidatePair *pair) {
	const bctbx_list_t *elem;

	for (elem = cl->valid_list; elem != NULL; elem = elem->next) {
		IceValidCandidatePair *valid = (IceValidCandidatePair *)elem->data;
		if (pair == valid->valid) {
			return pair;
		}
		if (valid->generated_from == pair) {
			ms_message("ice: found the reflexive candidate corresponding to the candidate pair on which the binding "
			           "request was received.");
			return valid->valid; /*return the peer reflexive candidate pair*/
		}
	}
	return NULL;
}

/* Update the nominated flag of a candidate pair according to 7.2.1.5. */
static void
ice_update_nominated_flag_on_binding_request(const IceCheckList *cl, const MSStunMessage *msg, IceCandidatePair *pair) {
	if (ms_stun_message_use_candidate_enabled(msg) && (cl->session->role == IR_Controlled)) {
		IceCandidatePair *valid = ice_lookup_possible_valid_pair(cl, pair);

		switch (pair->state) {
			case ICP_Succeeded:
				if (valid) {
					valid->is_nominated = TRUE;
					ms_message("ice: receiving a binding request with use-candidate flag on succeeded pair");
				} else {
					ms_warning("ice: receiving a binding request with use-candidate flag on succeeded pair that is not "
					           "in the valid list.");
					pair->is_nominated = TRUE;
				}
				break;
			case ICP_Waiting:
			case ICP_Frozen:
			case ICP_InProgress:
				/*Normally valid should be null if go here*/
				ms_message("ice: receiving a binding request with nominated flag on non-succeeded pair");
				pair->nomination_pending = TRUE; /*We cannot accept the nomination immediately. We will wait for our
				    pair to complete its bind requests, and then the pair will be officially nominated*/
				break;
			case ICP_Failed:
				ms_error(
				    "ice: receiving a binding request with nominated flag on failed pair. This should not happen.");
				break;
		}
	}
	// ice_dump_valid_list(cl);
}

static void ice_handle_received_binding_request(IceCheckList *cl,
                                                RtpSession *rtp_session,
                                                const OrtpEventData *evt_data,
                                                const MSStunMessage *msg,
                                                const MSStunAddress *remote_addr) {
	IceTransportAddress taddr;
	IceCandidate *prflx_candidate;
	IceCandidatePair *pair;

	if (ice_check_received_binding_request_attributes(rtp_session, evt_data, msg, remote_addr) < 0) return;
	if (ice_check_received_binding_request_integrity(cl, rtp_session, evt_data, msg, remote_addr) < 0) return;
	if (ice_check_received_binding_request_username(cl, rtp_session, evt_data, msg, remote_addr) < 0) return;
	if (ice_check_received_binding_request_role_conflict(cl, rtp_session, evt_data, msg, remote_addr) < 0) return;

	ice_fill_transport_address_from_stun_address(&taddr, remote_addr);
	prflx_candidate = ice_learn_peer_reflexive_candidate(cl, evt_data, msg, &taddr);
	pair = ice_trigger_connectivity_check_on_binding_request(cl, rtp_session, evt_data, prflx_candidate, &taddr);
	if (pair != NULL) ice_update_nominated_flag_on_binding_request(cl, msg, pair);
	ice_send_binding_response(cl, rtp_session, evt_data, msg, remote_addr);
	ice_conclude_processing(cl, rtp_session, FALSE);
}

static int ice_find_stun_server_request(const IceStunServerRequest *request, const RtpTransport *rtptp) {
	return !(request->rtptp == rtptp);
}

static IceCheckList *ice_find_check_list_gathering_candidates(const IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if ((session->streams[i] != NULL) && (session->streams[i]->gathering_candidates == TRUE))
			return session->streams[i];
	}
	return NULL;
}

static int ice_find_pair_from_transactionID(const IceTransaction *transaction, const UInt96 *transactionID) {
	return memcmp(&transaction->transactionID, transactionID, sizeof(transaction->transactionID));
}

static int ice_check_received_binding_response_addresses(BCTBX_UNUSED(const RtpSession *rtp_session),
                                                         const OrtpEventData *evt_data,
                                                         IceCandidatePair *pair,
                                                         MSStunAddress *remote_addr) {
	struct sockaddr_storage recv_addr;
	socklen_t recv_addrlen = sizeof(recv_addr);
	MSStunAddress recv_stun_addr;
	MSStunAddress pair_remote_stun_addr;
	MSStunAddress pair_local_stun_addr;

	pair_remote_stun_addr = ms_ip_address_to_stun_address(pair->remote->taddr.family, SOCK_DGRAM,
	                                                      pair->remote->taddr.ip, pair->remote->taddr.port);
	pair_local_stun_addr = ms_ip_address_to_stun_address(pair->local->taddr.family, SOCK_DGRAM, pair->local->taddr.ip,
	                                                     pair->local->taddr.port);
	memset(&recv_addr, 0, recv_addrlen);
	ortp_recvaddr_to_sockaddr(&evt_data->packet->recv_addr, (struct sockaddr *)&recv_addr, &recv_addrlen);
	bctbx_sockaddr_ipv6_to_ipv4((struct sockaddr *)&recv_addr, (struct sockaddr *)&recv_addr, &recv_addrlen);
	ms_sockaddr_to_stun_address((struct sockaddr *)&recv_addr, &recv_stun_addr);
	if (ms_compare_stun_addresses(remote_addr, &pair_remote_stun_addr) ||
	    ms_compare_stun_addresses(&recv_stun_addr, &pair_local_stun_addr)) {
		/* Non-symmetric addresses, set the state of the pair to Failed as defined in 7.1.3.1. */
		ms_warning("ice: Non symmetric addresses, set state of pair %p to Failed", pair);
		ice_pair_set_state(pair, ICP_Failed);
		return -1;
	}
	return 0;
}

static int ice_check_received_binding_response_attributes(const MSStunMessage *msg,
                                                          BCTBX_UNUSED(const MSStunAddress *remote_addr),
                                                          bool_t check_integrity) {
	if (!ms_stun_message_message_integrity_enabled(msg)) {
		ms_warning("ice: Received binding response missing MESSAGE-INTEGRITY attribute");
		if (check_integrity) return -1;
	}
	if (!ms_stun_message_fingerprint_enabled(msg)) {
		ms_warning("ice: Received binding response missing FINGERPRINT attribute");
		return -1;
	}
	if (ms_stun_message_get_xor_mapped_address(msg) == NULL) {
		ms_warning("ice: Received binding response missing XOR-MAPPED-ADDRESS attribute");
		return -1;
	}
	return 0;
}

static IceCandidate *
ice_discover_peer_reflexive_candidate(IceCheckList *cl, const IceCandidatePair *pair, const MSStunMessage *msg) {
	IceTransportAddress taddr;
	const MSStunAddress *xor_mapped_address;
	IceCandidate *candidate = NULL;
	bctbx_list_t *elem;
	char taddr_str[64];
	TransportAddress_ComponentID taci;

	memset(&taddr, 0, sizeof(taddr));
	xor_mapped_address = ms_stun_message_get_xor_mapped_address(msg);
	ice_fill_transport_address_from_stun_address(&taddr, xor_mapped_address);
	taci.componentID = pair->local->componentID;
	taci.ta = &taddr;
	elem = bctbx_list_find_custom(cl->local_candidates,
	                              (bctbx_compare_func)ice_find_candidate_from_transport_address_and_componentID, &taci);
	if (elem == NULL) {
		memset(taddr_str, 0, sizeof(taddr_str));
		ice_transport_address_to_printable_ip_address(&taddr, taddr_str, sizeof(taddr_str));
		ms_message("ice: Discovered peer reflexive candidate %s for componentID %d", taddr_str,
		           pair->local->componentID);
		/* Add peer reflexive candidate to the local candidates list. */
		candidate = ice_add_local_candidate(cl, "prflx", taddr.family, taddr.ip, taddr.port, pair->local->componentID,
		                                    pair->local);
		ice_compute_candidate_foundation(candidate, cl);
	} else {
		candidate = (IceCandidate *)elem->data;
	}
	return candidate;
}

static int ice_compare_valid_pair_priorities(const IceValidCandidatePair *vp1, const IceValidCandidatePair *vp2) {
	return ice_compare_pair_priorities(vp1->valid, vp2->valid);
}

static int ice_find_valid_pair(const IceValidCandidatePair *vp1, const IceValidCandidatePair *vp2) {
	return ice_compare_pairs(vp1->valid, vp2->valid) || ice_compare_pairs(vp1->generated_from, vp2->generated_from);
}

/* Construct a valid ICE candidate pair as defined in 7.1.3.2.2. */
static IceCandidatePair *ice_construct_valid_pair(IceCheckList *cl,
                                                  RtpSession *rtp_session,
                                                  BCTBX_UNUSED(const OrtpEventData *evt_data),
                                                  IceCandidate *candidate,
                                                  IceCandidatePair *succeeded_pair) {
	LocalCandidate_RemoteCandidate candidates;
	IceCandidatePair *pair = NULL;
	IceValidCandidatePair *valid_pair;
	bctbx_list_t *elem;
	OrtpEvent *ev;
	char local_addr_str[64];
	char remote_addr_str[64];

	candidates.local = candidate;
	candidates.remote = succeeded_pair->remote;
	elem = bctbx_list_find_custom(cl->check_list, (bctbx_compare_func)ice_find_pair_from_candidates, &candidates);
	if (elem == NULL) {
		/* The candidate pair is not a known candidate pair, compute its priority and add it to the valid list. */
		pair = ice_pair_new(cl, candidates.local, candidates.remote);
		cl->pairs = bctbx_list_append(cl->pairs, pair);
	} else {
		/* The candidate pair is already in the check list, add it to the valid list. */
		pair = (IceCandidatePair *)elem->data;
	}
	valid_pair = ms_new0(IceValidCandidatePair, 1);
	valid_pair->valid = pair;
	valid_pair->generated_from = succeeded_pair;
	ms_get_cur_time(&valid_pair->last_keepalive); /*initialize the origin of keepalives*/
	valid_pair->selected = FALSE;
	memset(local_addr_str, 0, sizeof(local_addr_str));
	memset(remote_addr_str, 0, sizeof(remote_addr_str));
	ice_transport_address_to_printable_ip_address(&pair->local->taddr, local_addr_str, sizeof(local_addr_str));
	ice_transport_address_to_printable_ip_address(&pair->remote->taddr, remote_addr_str, sizeof(remote_addr_str));
	elem = bctbx_list_find_custom(cl->valid_list, (bctbx_compare_func)ice_find_valid_pair, valid_pair);
	if (elem == NULL) {
		if (pair->is_default) {
			OrtpEvent *ev;
			ms_message("ice: succeeded pair with the local default candidate.");
			/* Notify the application that a pair using the default local candidate was verified, which is helpful to
			 * know that stream should be now working. */
			ev = ortp_event_new(ORTP_EVENT_ICE_CHECK_LIST_DEFAULT_CANDIDATE_VERIFIED);
			rtp_session_dispatch_event(cl->rtp_session, ev);
		}
		cl->valid_list =
		    bctbx_list_insert_sorted(cl->valid_list, valid_pair, (bctbx_compare_func)ice_compare_valid_pair_priorities);
		ms_message("ice: Added pair %p to the valid list: %s:%s --> %s:%s", pair, local_addr_str,
		           candidate_type_values[pair->local->type], remote_addr_str,
		           candidate_type_values[pair->remote->type]);
		// ice_dump_valid_list(cl);
		elem = bctbx_list_find_custom(cl->losing_pairs, (bctbx_compare_func)ice_find_pair_from_candidates, &candidates);
		if (elem != NULL) {
			cl->losing_pairs = bctbx_list_erase_link(cl->losing_pairs, elem);
			/* Select the losing pair that has just become a valid pair. */
			ice_check_list_set_selected_valid_pair(cl, valid_pair);
			if (ice_session_nb_losing_pairs(cl->session) == 0) {
				/* Notify the application that the checks for losing pairs have completed. The answer can now be sent.
				 */
				ice_check_list_set_state(cl, ICL_Completed);
				ev = ortp_event_new(ORTP_EVENT_ICE_LOSING_PAIRS_COMPLETED);
				ortp_event_get_data(ev)->info.ice_processing_successful = TRUE;
				rtp_session_dispatch_event(rtp_session, ev);
			}
		}
		return pair;
	} else {
		ms_message("ice: Pair already in the valid list: %s:%s --> %s:%s", local_addr_str,
		           candidate_type_values[pair->local->type], remote_addr_str,
		           candidate_type_values[pair->remote->type]);
		ms_free(valid_pair);
		return ((IceValidCandidatePair *)(elem->data))->valid;
	}
}

static int ice_compare_pair_foundations(const IceCandidatePair *p1, const IceCandidatePair *p2) {
	return !((strlen(p1->local->foundation) == strlen(p2->local->foundation)) &&
	         (strcmp(p1->local->foundation, p2->local->foundation) == 0) &&
	         ((strlen(p1->remote->foundation) == strlen(p2->remote->foundation)) &&
	          (strcmp(p1->remote->foundation, p2->remote->foundation) == 0)));
}

static void ice_change_state_of_frozen_pairs_to_waiting(IceCandidatePair *pair,
                                                        const IceCandidatePair *succeeded_pair) {
	if ((pair != succeeded_pair) && (pair->state == ICP_Frozen) &&
	    (ice_compare_pair_foundations(pair, succeeded_pair) == 0)) {
		ms_message("ice: Change state of pair %p from Frozen to Waiting", pair);
		ice_pair_set_state(pair, ICP_Waiting);
	}
}

/* Update the pair states according to 7.1.3.2.3. */
static void ice_update_pair_states_on_binding_response(IceCheckList *cl, IceCandidatePair *pair) {
	/* Set the state of the pair that generated the check to Succeeded. */
	ice_pair_set_state(pair, ICP_Succeeded);

	/* Change the state of all Frozen pairs with the same foundation to Waiting. */
	bctbx_list_for_each2(cl->check_list, (void (*)(void *, void *))ice_change_state_of_frozen_pairs_to_waiting, pair);
}

/* Update the nominated flag of a candidate pair according to 7.1.3.2.4. */
static void ice_update_nominated_flag_on_binding_response(const IceCheckList *cl,
                                                          IceCandidatePair *valid_pair,
                                                          IceCandidatePair *succeeded_pair) {
	switch (cl->session->role) {
		case IR_Controlling:
			if (succeeded_pair->use_candidate == TRUE) {
				valid_pair->nomination_failing = FALSE;
				valid_pair->is_nominated = TRUE;
				// ice_dump_valid_list(cl);
			}
			break;
		case IR_Controlled:
			if (succeeded_pair->nomination_pending) {
				valid_pair->is_nominated = TRUE;
				succeeded_pair->nomination_pending = FALSE;
			}
			break;
	}
}

static int ice_compare_transactionIDs(const IceStunServerRequestTransaction *transaction, const UInt96 *tr_id2) {
	const UInt96 *tr_id1 = &transaction->transactionID;
	return memcmp(tr_id1, tr_id2, sizeof(UInt96));
}

static int ice_find_non_responded_gathering_stun_server_request(const IceStunServerRequest *request,
                                                                BCTBX_UNUSED(const void *dummy)) {
	return (request->gathering == FALSE) || (request->responded == TRUE);
}

static void ice_allow_turn_peer_address(IceCheckList *cl, int componentID, MSStunAddress *peer_address) {
	MSTurnContext *turn_context = ice_get_turn_context_from_check_list_componentID(cl, componentID);
	ms_turn_context_allow_peer_address(turn_context, peer_address);
}

static void ice_schedule_turn_allocation_refresh(IceCheckList *cl, int componentID, uint32_t lifetime) {
	char source_addr_str[64];
	int source_port = 0;
	MSTurnContext *turn_context;
	IceStunServerRequest *request;
	RtpTransport *rtptp = NULL;
	const OrtpStream *stream = NULL;
	struct sockaddr *sa;
	uint32_t ms = (uint32_t)((lifetime * .9f) * 1000); /* 90% of the lifetime */

	turn_context = ice_get_turn_context_from_check_list_componentID(cl, componentID);
	ice_get_transport_from_rtp_session_and_componentID(cl->rtp_session, componentID, &rtptp);
	ice_get_ortp_stream_from_rtp_session_and_componentID(cl->rtp_session, componentID, &stream);
	sa = (struct sockaddr *)&stream->loc_addr;
	memset(source_addr_str, 0, sizeof(source_addr_str));
	bctbx_sockaddr_to_ip_address(sa, stream->loc_addrlen, source_addr_str, sizeof(source_addr_str), &source_port);
	request = ice_stun_server_request_new(cl, turn_context, rtptp, sa->sa_family, source_addr_str, source_port,
	                                      MS_TURN_METHOD_REFRESH);
	if (!request) return;
	if (cl->session->short_turn_refresh == TRUE) ms = 5000; /* 5 seconds */
	request->next_transmission_time = ice_add_ms(ice_current_time(), ms);
	ice_check_list_add_stun_server_request(cl, request);
}

static void ice_schedule_turn_permission_refresh(IceCheckList *cl, int componentID, MSStunAddress peer_address) {
	char source_addr_str[64];
	int source_port = 0;
	MSTurnContext *turn_context;
	IceStunServerRequest *request;
	RtpTransport *rtptp = NULL;
	const OrtpStream *stream = NULL;
	struct sockaddr *sa;
	uint32_t ms = 240000; /* 4 minutes */

	turn_context = ice_get_turn_context_from_check_list_componentID(cl, componentID);
	ice_get_transport_from_rtp_session_and_componentID(cl->rtp_session, componentID, &rtptp);
	ice_get_ortp_stream_from_rtp_session_and_componentID(cl->rtp_session, componentID, &stream);
	sa = (struct sockaddr *)&stream->loc_addr;
	memset(source_addr_str, 0, sizeof(source_addr_str));
	bctbx_sockaddr_to_ip_address(sa, stream->loc_addrlen, source_addr_str, sizeof(source_addr_str), &source_port);
	request = ice_stun_server_request_new(cl, turn_context, rtptp, sa->sa_family, source_addr_str, source_port,
	                                      MS_TURN_METHOD_CREATE_PERMISSION);
	if (!request) return;
	request->peer_address = peer_address;
	if (cl->session->short_turn_refresh == TRUE) ms = 5000; /* 5 seconds */
	request->next_transmission_time = ice_add_ms(ice_current_time(), ms);
	ice_check_list_add_stun_server_request(cl, request);
}

static void ice_schedule_turn_channel_bind_refresh(IceCheckList *cl,
                                                   int componentID,
                                                   uint16_t channel_number,
                                                   MSStunAddress peer_address) {
	char source_addr_str[64];
	int source_port = 0;
	MSTurnContext *turn_context;
	IceStunServerRequest *request;
	RtpTransport *rtptp = NULL;
	const OrtpStream *stream = NULL;
	struct sockaddr *sa;
	uint32_t ms = 540000; /* 9 minutes */

	turn_context = ice_get_turn_context_from_check_list_componentID(cl, componentID);
	ice_get_transport_from_rtp_session_and_componentID(cl->rtp_session, componentID, &rtptp);
	ice_get_ortp_stream_from_rtp_session_and_componentID(cl->rtp_session, componentID, &stream);
	sa = (struct sockaddr *)&stream->loc_addr;
	memset(source_addr_str, 0, sizeof(source_addr_str));
	bctbx_sockaddr_to_ip_address(sa, stream->loc_addrlen, source_addr_str, sizeof(source_addr_str), &source_port);
	request = ice_stun_server_request_new(cl, turn_context, rtptp, sa->sa_family, source_addr_str, source_port,
	                                      MS_TURN_METHOD_CHANNEL_BIND);
	if (!request) return;
	request->channel_number = channel_number;
	request->peer_address = peer_address;
	if (cl->session->short_turn_refresh == TRUE) ms = 5000; /* 5 seconds */
	request->next_transmission_time = ice_add_ms(ice_current_time(), ms);
	ice_check_list_add_stun_server_request(cl, request);
}

static bool_t ice_handle_received_turn_allocate_success_response(IceCheckList *cl,
                                                                 RtpSession *rtp_session,
                                                                 const OrtpEventData *evt_data,
                                                                 const MSStunMessage *msg,
                                                                 const MSStunAddress *remote_addr) {
	bctbx_list_t *base_elem;
	IceCandidate *base_candidate;
	OrtpEvent *ev;
	bool_t stun_server_response = FALSE;
	struct sockaddr *servaddr = (struct sockaddr *)&cl->session->ss;
	socklen_t servaddrlen = 0;
	UInt96 tr_id = ms_stun_message_get_tr_id(msg);
	MSStunAddress serv_stun_addr;
	MSStunAddress srflx_addr;
	MSStunAddress relay_addr;
	char srflx_addr_str[64];
	char relay_addr_str[64];
	int srflx_port = 0;
	int relay_port = 0;
	int componentID;

	bctbx_sockaddr_ipv6_to_ipv4(servaddr, servaddr, &servaddrlen);
	ms_sockaddr_to_stun_address(servaddr, &serv_stun_addr);
	if (!ms_compare_stun_addresses(remote_addr, &serv_stun_addr)) {
		IceStunServerRequest *request = ice_check_list_get_stun_server_request(cl, &tr_id);
		if (request != NULL) {
			componentID = ice_get_componentID_from_rtp_session(evt_data);
			memset(&srflx_addr, 0, sizeof(srflx_addr));
			memset(&relay_addr, 0, sizeof(relay_addr));
			if ((componentID > 0) && (ice_parse_stun_server_response(msg, &srflx_addr, &relay_addr) >= 0)) {
				IceTransportAddress taddr;
				struct sockaddr_storage recv_addr;
				socklen_t recv_addr_len = sizeof(recv_addr);
				TransportAddress_ComponentID taci;

				memset(&taddr, 0, sizeof(taddr));

				ortp_recvaddr_to_sockaddr(&evt_data->packet->recv_addr, (struct sockaddr *)&recv_addr, &recv_addr_len);
				bctbx_sockaddr_ipv6_to_ipv4((struct sockaddr *)&recv_addr, (struct sockaddr *)&recv_addr,
				                            &recv_addr_len);
				ice_fill_transport_address_from_sockaddr(&taddr, (struct sockaddr *)&recv_addr, recv_addr_len);
				taci.componentID = componentID;
				taci.ta = &taddr;
				base_elem = bctbx_list_find_custom(
				    cl->local_candidates, (bctbx_compare_func)ice_find_candidate_from_transport_address_and_componentID,
				    &taci);

				if (cl->rtp_turn_context &&
				    ms_turn_context_get_transport(cl->rtp_turn_context) != MS_TURN_CONTEXT_TRANSPORT_UDP) {
					/* fallback code for TCP/TLS, where recv_addr is not needed */
					ComponentID_Family cf = {componentID, AF_INET};
					if (srflx_addr.family == MS_STUN_ADDR_FAMILY_IPV6) cf.family = AF_INET6;
					base_elem =
					    bctbx_list_find_custom(cl->local_candidates, (bctbx_compare_func)ice_find_host_candidate, &cf);
					if ((base_elem == NULL) && (cf.family == AF_INET)) {
						/* Handle NAT64 case where the local candidate is IPv6 but the reflexive candidate returned by
						 * STUN is IPv4. */
						cf.family = AF_INET6;
						base_elem = bctbx_list_find_custom(cl->local_candidates,
						                                   (bctbx_compare_func)ice_find_host_candidate, &cf);
					}
				}

				if (base_elem != NULL) {
					base_candidate = (IceCandidate *)base_elem->data;
					memset(srflx_addr_str, 0, sizeof(srflx_addr));
					ms_stun_address_to_ip_address(&srflx_addr, srflx_addr_str, sizeof(srflx_addr_str), &srflx_port);
					if (srflx_port != 0) {
						ice_add_local_candidate(cl, "srflx", ms_stun_family_to_af(srflx_addr.family), srflx_addr_str,
						                        srflx_port, componentID, base_candidate);
						ms_stun_address_to_printable_ip_address(&srflx_addr, srflx_addr_str, sizeof(srflx_addr_str));
						ms_message("ice: Add candidate obtained by STUN/TURN: %s:srflx", srflx_addr_str);

						if (cl->session->turn_enabled) {
							request->turn_context->stats.nb_successful_allocate++;
							ice_schedule_turn_allocation_refresh(cl, componentID, ms_stun_message_get_lifetime(msg));
						}
						if (relay_addr.family != 0) {
							memset(relay_addr_str, 0, sizeof(relay_addr_str));
							ms_stun_address_to_ip_address(&relay_addr, relay_addr_str, sizeof(relay_addr_str),
							                              &relay_port);
							if (relay_port != 0) {
								if (cl->session->turn_enabled) {
									ms_turn_context_set_allocated_relay_addr(request->turn_context, relay_addr);
								}
								ice_add_local_candidate(cl, "relay", ms_stun_family_to_af(relay_addr.family),
								                        relay_addr_str, relay_port, componentID, base_candidate);
								ms_stun_address_to_printable_ip_address(&relay_addr, relay_addr_str,
								                                        sizeof(relay_addr_str));
								ms_message("ice: Add candidate obtained by STUN/TURN: %s:relay", relay_addr_str);
							}
						}
					}
				} else {
					ms_error("Received allocate response through an unknown local interface.");
				}
				request->responded = TRUE;
				if (cl->session->turn_enabled) {
					ms_turn_context_set_state(request->turn_context, MS_TURN_CONTEXT_STATE_ALLOCATION_CREATED);
					if (ms_stun_message_has_lifetime(msg))
						ms_turn_context_set_lifetime(request->turn_context, ms_stun_message_get_lifetime(msg));
				}
			}
			stun_server_response = TRUE;
		}

		if (bctbx_list_find_custom(cl->stun_server_requests,
		                           (bctbx_compare_func)ice_find_non_responded_gathering_stun_server_request,
		                           NULL) == NULL) {
			ice_check_list_stop_gathering(cl);
			ms_message("ice: Finished candidates gathering for check list %p", cl);
			ice_dump_candidates(cl);
			if (ice_find_check_list_gathering_candidates(cl->session) == NULL) {
				/* Notify the application when there is no longer any check list gathering candidates. */
				ev = ortp_event_new(ORTP_EVENT_ICE_GATHERING_FINISHED);
				ortp_event_get_data(ev)->info.ice_processing_successful = TRUE;
				cl->session->gathering_end_ts = evt_data->ts;
				rtp_session_dispatch_event(rtp_session, ev);
			}
		}
	}

	return stun_server_response;
}

static void ice_handle_received_create_permission_success_response(IceCheckList *cl,
                                                                   BCTBX_UNUSED(RtpSession *rtp_session),
                                                                   const OrtpEventData *evt_data,
                                                                   const MSStunMessage *msg,
                                                                   BCTBX_UNUSED(const MSStunAddress *remote_addr)) {
	int componentID = ice_get_componentID_from_rtp_session(evt_data);
	UInt96 tr_id = ms_stun_message_get_tr_id(msg);
	IceStunServerRequest *request = ice_check_list_get_stun_server_request(cl, &tr_id);
	if (request != NULL) {
		MSStunAddress peer_address = request->peer_address;
		ice_check_list_remove_stun_server_request(cl, &tr_id);
		ice_allow_turn_peer_address(cl, componentID, &peer_address);
		ice_schedule_turn_permission_refresh(cl, componentID, peer_address);
	}
}

static void ice_handle_received_turn_refresh_success_response(IceCheckList *cl,
                                                              BCTBX_UNUSED(RtpSession *rtp_session),
                                                              const OrtpEventData *evt_data,
                                                              const MSStunMessage *msg,
                                                              BCTBX_UNUSED(const MSStunAddress *remote_addr)) {
	int componentID = ice_get_componentID_from_rtp_session(evt_data);
	MSTurnContext *context = ice_get_turn_context_from_check_list_componentID(cl, componentID);
	UInt96 tr_id = ms_stun_message_get_tr_id(msg);
	ice_check_list_remove_stun_server_request(cl, &tr_id);
	if (ms_turn_context_get_lifetime(context) == 0) {
		/* TURN deallocation success */
		ms_turn_context_set_state(context, MS_TURN_CONTEXT_STATE_IDLE);
	} else {
		ice_schedule_turn_allocation_refresh(cl, componentID, ms_stun_message_get_lifetime(msg));
		context->stats.nb_successful_refresh++;
	}
}

static void ice_handle_received_turn_channel_bind_success_response(IceCheckList *cl,
                                                                   BCTBX_UNUSED(RtpSession *rtp_session),
                                                                   const OrtpEventData *evt_data,
                                                                   const MSStunMessage *msg,
                                                                   BCTBX_UNUSED(const MSStunAddress *remote_addr)) {
	int componentID = ice_get_componentID_from_rtp_session(evt_data);
	UInt96 tr_id = ms_stun_message_get_tr_id(msg);
	IceStunServerRequest *request = ice_check_list_get_stun_server_request(cl, &tr_id);
	if (request != NULL) {
		uint16_t channel_number = request->channel_number;
		MSStunAddress peer_address = request->peer_address;
		MSTurnContext *context = ice_get_turn_context_from_check_list_componentID(cl, componentID);
		ms_turn_context_set_state(context, MS_TURN_CONTEXT_STATE_CHANNEL_BOUND);
		ice_check_list_remove_stun_server_request(cl, &tr_id);
		ice_schedule_turn_channel_bind_refresh(cl, componentID, channel_number, peer_address);
	}
}

static void ice_handle_received_binding_response(IceCheckList *cl,
                                                 RtpSession *rtp_session,
                                                 const OrtpEventData *evt_data,
                                                 const MSStunMessage *msg,
                                                 MSStunAddress *remote_addr) {
	IceCandidatePair *succeeded_pair;
	IceCandidatePair *valid_pair;
	IceCandidate *candidate;
	bctbx_list_t *elem;
	IceTransaction *tr;
	UInt96 tr_id = ms_stun_message_get_tr_id(msg);
	char tr_id_str[25];

	transactionID2string(&tr_id, tr_id_str);

	if (cl->gathering_candidates == TRUE) {
		if (ice_handle_received_turn_allocate_success_response(cl, rtp_session, evt_data, msg, remote_addr) == TRUE)
			return;
	}

	elem = bctbx_list_find_custom(cl->transaction_list, (bctbx_compare_func)ice_find_pair_from_transactionID, &tr_id);
	if (elem == NULL) {
		/* We received an a binding response concerning an unknown binding request, ignore it... */
		ms_warning("ice: Received a binding response for an unknown transaction ID: %s", tr_id_str);
		return;
	}
	tr = (IceTransaction *)elem->data;
	if (tr->canceled) {
		/* We received an binding response concerning a canceled binding request transaction*/
		ms_message("ice: Received a binding response for a cancelled transaction ID: %s", tr_id_str);
		/* It has to be processed anyway. According to 7.3.1.4 , cancellation just stop retransmission and do not
		 * consider the lack of response as a failure.*/
	}

	succeeded_pair = (IceCandidatePair *)((IceTransaction *)elem->data)->pair;
	if (ice_check_received_binding_response_addresses(rtp_session, evt_data, succeeded_pair, remote_addr) < 0) return;
	if (ice_check_received_binding_response_attributes(msg, remote_addr, cl->session->check_message_integrity) < 0)
		return;

	candidate = ice_discover_peer_reflexive_candidate(cl, succeeded_pair, msg);
	valid_pair = ice_construct_valid_pair(cl, rtp_session, evt_data, candidate, succeeded_pair);
	ice_update_pair_states_on_binding_response(cl, succeeded_pair);

	ice_update_nominated_flag_on_binding_response(cl, valid_pair, succeeded_pair);
	ice_conclude_processing(cl, rtp_session, FALSE);
}

static void ice_handle_stun_server_error_response(IceCheckList *cl,
                                                  RtpSession *rtp_session,
                                                  const OrtpEventData *evt_data,
                                                  const MSStunMessage *msg) {
	bctbx_list_t *elem;
	RtpTransport *rtptp = NULL;
	char *reason = NULL;
	uint16_t number = ms_stun_message_get_error_code(msg, &reason);

	ice_get_transport_from_rtp_session(rtp_session, evt_data, &rtptp);
	elem = bctbx_list_find_custom(cl->stun_server_requests, (bctbx_compare_func)ice_find_stun_server_request, rtptp);
	if (elem != NULL) {
		IceStunServerRequest *request = (IceStunServerRequest *)elem->data;
		if ((request != NULL) && (number == 401) && (cl->session->stun_auth_requested_cb != NULL)) {
			const char *username = NULL;
			const char *password = NULL;
			const char *ha1 = NULL;
			const char *realm = ms_stun_message_get_realm(msg);
			const char *nonce = ms_stun_message_get_nonce(msg);
			cl->session->stun_auth_requested_cb(cl->session->stun_auth_requested_userdata, realm, nonce, &username,
			                                    &password, &ha1);
			if ((username != NULL) && (cl->session->turn_enabled)) {
				IceStunServerRequestTransaction *transaction = NULL;
				MSTurnContext *turn_context = ice_get_turn_context_from_check_list(cl, evt_data);
				ms_turn_context_set_realm(turn_context, realm);
				ms_turn_context_set_nonce(turn_context, nonce);
				ms_turn_context_set_username(turn_context, username);
				ms_turn_context_set_password(turn_context, password);
				ms_turn_context_set_ha1(turn_context, ha1);
				request->next_transmission_time = ice_add_ms(ice_current_time(), ICE_DEFAULT_RTO_DURATION);
				transaction = ice_send_turn_server_allocate_request(request, (struct sockaddr *)&cl->session->ss,
				                                                    cl->session->ss_len);
				ice_stun_server_request_add_transaction(request, transaction);
			}
		}
	}
}

static void ice_handle_received_error_response(IceCheckList *cl,
                                               RtpSession *rtp_session,
                                               const OrtpEventData *evt_data,
                                               const MSStunMessage *msg) {
	IceCandidatePair *pair;
	char local_addr_str[64];
	char remote_addr_str[64];

	if (cl->gathering_candidates == TRUE) {
		ice_handle_stun_server_error_response(cl, rtp_session, evt_data, msg);
	} else {
		UInt96 tr_id = ms_stun_message_get_tr_id(msg);
		bctbx_list_t *elem =
		    bctbx_list_find_custom(cl->transaction_list, (bctbx_compare_func)ice_find_pair_from_transactionID, &tr_id);
		if (elem == NULL) {
			/* We received an error response concerning an unknown binding request, ignore it... */
			return;
		}

		pair = (IceCandidatePair *)((IceTransaction *)elem->data)->pair;
		if (ms_stun_message_has_error_code(msg) &&
		    (ms_stun_message_get_error_code(msg, NULL) == MS_STUN_ERROR_CODE_UNAUTHORIZED) &&
		    pair->retry_with_dummy_message_integrity) {
			ms_warning("ice pair [%p], retry skipping message integrity for compatibility with older version", pair);
			pair->retry_with_dummy_message_integrity = FALSE;
			pair->use_dummy_hmac = TRUE;
			return;

		} else {
			ice_pair_set_state(pair, ICP_Failed);
			memset(local_addr_str, 0, sizeof(local_addr_str));
			memset(remote_addr_str, 0, sizeof(remote_addr_str));
			ice_transport_address_to_printable_ip_address(&pair->local->taddr, local_addr_str, sizeof(local_addr_str));
			ice_transport_address_to_printable_ip_address(&pair->remote->taddr, remote_addr_str,
			                                              sizeof(remote_addr_str));
			ms_message("ice: Error response, set state to Failed for pair %p: %s:%s --> %s:%s", pair, local_addr_str,
			           candidate_type_values[pair->local->type], remote_addr_str,
			           candidate_type_values[pair->remote->type]);
		}
		if (ms_stun_message_has_error_code(msg) &&
		    (ms_stun_message_get_error_code(msg, NULL) == MS_ICE_ERROR_CODE_ROLE_CONFLICT)) {
			/* Handle error 487 (Role Conflict) according to 7.1.3.1. */
			switch (pair->role) {
				case IR_Controlling:
					ms_message("ice: Switch to the CONTROLLED role");
					ice_session_set_role(cl->session, IR_Controlled);
					break;
				case IR_Controlled:
					ms_message("ice: Switch to the CONTROLLING role");
					ice_session_set_role(cl->session, IR_Controlling);
					break;
			}

			/* Set the state of the pair to Waiting and trigger a check. */
			ice_pair_set_state(pair, ICP_Waiting);
			ice_check_list_queue_triggered_check(cl, pair);
		}

		ice_conclude_processing(cl, rtp_session, FALSE);
	}
}

static int ice_find_stun_server_request_transaction(IceStunServerRequest *request, UInt96 *tr_id) {
	return (bctbx_list_find_custom(request->transactions, (bctbx_compare_func)ice_compare_transactionIDs, tr_id) ==
	        NULL);
}

static int ice_compare_stun_server_requests_to_remove(IceStunServerRequest *request) {
	return request->to_remove == FALSE;
}

static void ice_check_list_remove_stun_server_request(IceCheckList *cl, UInt96 *tr_id) {
	bctbx_list_t *elem = cl->stun_server_requests;
	while (elem != NULL) {
		elem = bctbx_list_find_custom(cl->stun_server_requests,
		                              (bctbx_compare_func)ice_find_stun_server_request_transaction, tr_id);
		if (elem != NULL) {
			IceStunServerRequest *request = (IceStunServerRequest *)elem->data;
			ice_stun_server_request_free(request);
			cl->stun_server_requests = bctbx_list_erase_link(cl->stun_server_requests, elem);
		}
	}
}

static IceStunServerRequest *ice_check_list_get_stun_server_request(IceCheckList *cl, UInt96 *tr_id) {
	bctbx_list_t *elem = bctbx_list_find_custom(cl->stun_server_requests,
	                                            (bctbx_compare_func)ice_find_stun_server_request_transaction, tr_id);
	if (elem == NULL) return NULL;
	return (IceStunServerRequest *)elem->data;
}

static void ice_set_transaction_response_time(IceCheckList *cl, UInt96 *tr_id, MSTimeSpec response_time) {
	IceStunServerRequest *request;
	IceStunServerRequestTransaction *transaction;
	bctbx_list_t *elem = bctbx_list_find_custom(cl->stun_server_requests,
	                                            (bctbx_compare_func)ice_find_stun_server_request_transaction, tr_id);
	if (elem == NULL) return;
	request = (IceStunServerRequest *)elem->data;
	elem = bctbx_list_find_custom(request->transactions, (bctbx_compare_func)ice_compare_transactionIDs, tr_id);
	if (elem == NULL) return;
	transaction = (IceStunServerRequestTransaction *)elem->data;
	transaction->response_time = response_time;
}

void ice_handle_stun_packet(IceCheckList *cl, RtpSession *rtp_session, const OrtpEventData *evt_data) {
	MSStunMessage *msg;
	MSStunAddress source_stun_addr;
	struct sockaddr_storage recv_addr;
	socklen_t recv_addrlen = sizeof(recv_addr);
	char source_addr_str[64];
	char recv_addr_str[64];
	mblk_t *mp = evt_data->packet;
	char tr_id_str[25];
	UInt96 tr_id;

	if (cl->session == NULL) return;

	msg = ms_stun_message_create_from_buffer_parsing(mp->b_rptr, (ssize_t)(mp->b_wptr - mp->b_rptr));
	if (msg == NULL) {
		ms_warning("ice: Received invalid STUN packet");
		return;
	}
	memset(source_addr_str, 0, sizeof(source_addr_str));
	memset(recv_addr_str, 0, sizeof(recv_addr_str));
	tr_id = ms_stun_message_get_tr_id(msg);
	transactionID2string(&tr_id, tr_id_str);
	memset(&recv_addr, 0, recv_addrlen);
	ortp_recvaddr_to_sockaddr(&evt_data->packet->recv_addr, (struct sockaddr *)&recv_addr, &recv_addrlen);
	bctbx_sockaddr_ipv6_to_ipv4((struct sockaddr *)&recv_addr, (struct sockaddr *)&recv_addr, &recv_addrlen);
	bctbx_sockaddr_to_printable_ip_address((struct sockaddr *)&recv_addr, recv_addrlen, recv_addr_str,
	                                       sizeof(recv_addr_str));
	bctbx_sockaddr_ipv6_to_ipv4((struct sockaddr *)&evt_data->source_addr, (struct sockaddr *)&evt_data->source_addr,
	                            &recv_addrlen);
	ms_sockaddr_to_stun_address((struct sockaddr *)&evt_data->source_addr, &source_stun_addr);
	ms_stun_address_to_printable_ip_address(&source_stun_addr, source_addr_str, sizeof(source_addr_str));

	if (ms_stun_message_is_request(msg)) {
		ms_message("ice: Recv binding request: %s <-- %s [%s] (flags:%s)", recv_addr_str, source_addr_str, tr_id_str,
		           ms_stun_message_use_candidate_enabled(msg) ? "use-candidate" : "none");
		ice_handle_received_binding_request(cl, rtp_session, evt_data, msg, &source_stun_addr);
	} else if (ms_stun_message_is_success_response(msg)) {
		ice_set_transaction_response_time(cl, &tr_id, evt_data->ts);
		switch (ms_stun_message_get_method(msg)) {
			case MS_STUN_METHOD_BINDING:
				ms_message("ice: Recv binding response: %s <-- %s [%s]", recv_addr_str, source_addr_str, tr_id_str);
				ice_handle_received_binding_response(cl, rtp_session, evt_data, msg, &source_stun_addr);
				break;
			case MS_TURN_METHOD_ALLOCATE:
				ms_message("ice: Recv TURN allocate success response: %s <-- %s [%s]", recv_addr_str, source_addr_str,
				           tr_id_str);
				ice_handle_received_turn_allocate_success_response(cl, rtp_session, evt_data, msg, &source_stun_addr);
				break;
			case MS_TURN_METHOD_CREATE_PERMISSION:
				ms_message("ice: Recv TURN create permission success response: %s <-- %s [%s]", recv_addr_str,
				           source_addr_str, tr_id_str);
				ice_handle_received_create_permission_success_response(cl, rtp_session, evt_data, msg,
				                                                       &source_stun_addr);
				break;
			case MS_TURN_METHOD_REFRESH:
				ms_message("ice: Recv TURN refresh success response: %s <-- %s [%s]", recv_addr_str, source_addr_str,
				           tr_id_str);
				ice_handle_received_turn_refresh_success_response(cl, rtp_session, evt_data, msg, &source_stun_addr);
				break;
			case MS_TURN_METHOD_CHANNEL_BIND:
				ms_message("ice: Recv TURN channel bind success response: %s <-- %s [%s]", recv_addr_str,
				           source_addr_str, tr_id_str);
				ice_handle_received_turn_channel_bind_success_response(cl, rtp_session, evt_data, msg,
				                                                       &source_stun_addr);
				break;
			default:
				ms_warning("ice: Recv unknown STUN success response: %s <-- %s [%s]", recv_addr_str, source_addr_str,
				           tr_id_str);
				break;
		}

	} else if (ms_stun_message_is_error_response(msg)) {
		ice_set_transaction_response_time(cl, &tr_id, evt_data->ts);
		ms_message("ice: Recv error response: %s <-- %s [%s]", recv_addr_str, source_addr_str, tr_id_str);
		ice_handle_received_error_response(cl, rtp_session, evt_data, msg);
	} else if (ms_stun_message_is_indication(msg)) {
		ms_message("ice: Recv indication: %s <-- %s [%s]", recv_addr_str, source_addr_str, tr_id_str);
	} else {
		ms_warning("ice: STUN message type not handled");
	}

	ms_stun_message_destroy(msg);
}

/******************************************************************************
 * ADD CANDIDATES                                                             *
 *****************************************************************************/

static void ice_compute_candidate_priority(IceCandidate *candidate) {
	// TODO: Handle local preferences for multihomed hosts.
	uint32_t type_preference = type_preference_values[candidate->type];
	uint32_t local_preference = 65535; /* Value recommended for non-multihomed hosts in 4.1.2.1 */
	uint32_t af_preference = candidate->taddr.family == AF_INET6 ? 1 << 7 : 0;
	candidate->priority =
	    (type_preference << 24) | (local_preference << 8) | af_preference | (128 - candidate->componentID);
}

static IceCandidate *ice_candidate_new(const char *type, int family, const char *ip, int port, uint16_t componentID) {
	IceCandidate *candidate;
	IceCandidateType candidate_type;

	if (strcmp(type, "host") == 0) {
		candidate_type = ICT_HostCandidate;
	} else if (strcmp(type, "srflx") == 0) {
		candidate_type = ICT_ServerReflexiveCandidate;
	} else if (strcmp(type, "prflx") == 0) {
		candidate_type = ICT_PeerReflexiveCandidate;
	} else if (strcmp(type, "relay") == 0) {
		candidate_type = ICT_RelayedCandidate;
	} else {
		ms_error("ice: Invalid candidate type");
		return NULL;
	}

	candidate = ms_new0(IceCandidate, 1);
	strncpy(candidate->taddr.ip, ip, sizeof(candidate->taddr.ip));
	candidate->taddr.port = port;
	candidate->taddr.family = family;
	candidate->type = candidate_type;
	candidate->componentID = componentID;
	candidate->is_default = FALSE;

	switch (candidate->type) {
		case ICT_HostCandidate:
			candidate->base = candidate;
			break;
		default:
			candidate->base = NULL;
			break;
	}
	ice_compute_candidate_priority(candidate);
	return candidate;
}

static int ice_find_componentID(const uint16_t *cid1, const uint16_t *cid2) {
	return !(*cid1 == *cid2);
}

static void ice_add_componentID(bctbx_list_t **list, uint16_t *componentID) {
	bctbx_list_t *elem = bctbx_list_find_custom(*list, (bctbx_compare_func)ice_find_componentID, componentID);
	if (elem == NULL) {
		*list = bctbx_list_append(*list, componentID);
	}
}

static void ice_remove_componentID(bctbx_list_t **list, uint16_t componentID) {
	*list = bctbx_list_remove_custom(*list, (bctbx_compare_func)ice_find_componentID, &componentID);
}

static bool_t ice_has_componentID(const bctbx_list_t *list, uint16_t componentID) {
	const bctbx_list_t *elem = bctbx_list_find_custom(list, (bctbx_compare_func)ice_find_componentID, &componentID);
	return elem != NULL;
}

IceCandidate *ice_add_local_candidate(IceCheckList *cl,
                                      const char *type,
                                      int family,
                                      const char *ip,
                                      int port,
                                      uint16_t componentID,
                                      IceCandidate *base) {
	bctbx_list_t *elem;
	IceCandidate *candidate;

	if (bctbx_list_size(cl->local_candidates) >= ICE_MAX_NB_CANDIDATES) {
		ms_error("ice: Candidate list limited to %d candidates", ICE_MAX_NB_CANDIDATES);
		return NULL;
	}

	candidate = ice_candidate_new(type, family, ip, port, componentID);
	if (candidate->base == NULL) candidate->base = base;

	elem = bctbx_list_find_custom(cl->local_candidates, (bctbx_compare_func)ice_compare_candidates, candidate);
	if (elem != NULL) {
		/* This candidate is already in the list, do not add it again. */
		ms_free(candidate);
		return NULL;
	}

	ice_add_componentID(&cl->local_componentIDs, &candidate->componentID);
	cl->local_candidates = bctbx_list_append(cl->local_candidates, candidate);

	return candidate;
}

static void ice_create_turn_permissions(IceCheckList *cl) {
	bctbx_list_t *remote_candidate_it;
	if (!cl->session->turn_enabled) return;

	for (remote_candidate_it = cl->remote_candidates; remote_candidate_it != NULL;
	     remote_candidate_it = remote_candidate_it->next) {
		IceCandidate *candidate = (IceCandidate *)remote_candidate_it->data;
		Type_Family_ComponentID tfci = {ICT_RelayedCandidate, candidate->taddr.family, candidate->componentID};
		bctbx_list_t *elem = bctbx_list_find_custom(
		    cl->local_candidates, (bctbx_compare_func)ice_find_candidate_from_type_family_and_componentID, &tfci);
		if (elem != NULL) {
			IceStunServerRequest *request;
			IceStunServerRequestTransaction *transaction;
			IceCandidate *local_candidate = (IceCandidate *)elem->data;
			RtpTransport *rtptp = NULL;
			if (!local_candidate->base) {
				ms_error("ice_create_turn_permissions(): Local relay candidate has no base !");
				continue;
			}
			ice_get_transport_from_rtp_session_and_componentID(cl->rtp_session, candidate->componentID, &rtptp);
			if (rtptp != NULL) {
				MSStunAddress peer_address =
				    ms_ip_address_to_stun_address(candidate->taddr.family, SOCK_DGRAM, candidate->taddr.ip, 3478);
				if (peer_address.family == MS_STUN_ADDR_FAMILY_IPV6) peer_address.ip.v6.port = 0;
				else peer_address.ip.v4.port = 0;
				request = ice_stun_server_request_new(
				    cl, ice_get_turn_context_from_check_list_componentID(cl, candidate->componentID), rtptp,
				    local_candidate->base->taddr.family, local_candidate->base->taddr.ip,
				    local_candidate->base->taddr.port, MS_TURN_METHOD_CREATE_PERMISSION);
				if (request) {
					request->peer_address = peer_address;
					request->next_transmission_time = ice_add_ms(ice_current_time(), ICE_DEFAULT_RTO_DURATION);
					transaction =
					    ice_send_stun_server_request(request, (struct sockaddr *)&cl->session->ss, cl->session->ss_len);
					ice_stun_server_request_add_transaction(request, transaction);
					ice_check_list_add_stun_server_request(cl, request);
				} else {
					ms_error("IceCheckList[%p]: could not build turn request.", cl);
				}
			} else ms_error("ice_create_turn_permissions(): No RTP transport");
		} else {
			ms_message("IceCheckList[%p]: no relay candidate to reach %s", cl, candidate->taddr.ip);
		}
	}
}

IceCandidate *ice_add_remote_candidate(IceCheckList *cl,
                                       const char *type,
                                       int family,
                                       const char *ip,
                                       int port,
                                       uint16_t componentID,
                                       uint32_t priority,
                                       const char *const foundation,
                                       bool_t is_default) {
	bctbx_list_t *elem;
	IceCandidate *candidate;

	if (bctbx_list_size(cl->local_candidates) >= ICE_MAX_NB_CANDIDATES) {
		ms_error("ice: Candidate list limited to %d candidates", ICE_MAX_NB_CANDIDATES);
		return NULL;
	}

	candidate = ice_candidate_new(type, family, ip, port, componentID);
	/* If the priority is 0, compute it. It is used for debugging purpose in mediastream to set priorities of remote
	 * candidates. */
	if (priority != 0) candidate->priority = priority;

	elem = bctbx_list_find_custom(cl->remote_candidates, (bctbx_compare_func)ice_compare_candidates, candidate);
	if (elem != NULL) {
		/* This candidate is already in the list, do not add it again. */
		ms_free(candidate);
		return NULL;
	}

	strncpy(candidate->foundation, foundation, sizeof(candidate->foundation) - 1);
	candidate->is_default = is_default;
	ice_add_componentID(&cl->remote_componentIDs, &candidate->componentID);
	cl->remote_candidates = bctbx_list_append(cl->remote_candidates, candidate);

	return candidate;
}

/******************************************************************************
 * LOSING PAIRS HANDLING                                                      *
 *****************************************************************************/

static int ice_find_pair_in_valid_list(IceValidCandidatePair *valid_pair, IceCandidatePair *pair) {
	return !((ice_compare_transport_addresses(&valid_pair->valid->local->taddr, &pair->local->taddr) == 0) &&
	         (valid_pair->valid->local->componentID == pair->local->componentID) &&
	         (ice_compare_transport_addresses(&valid_pair->valid->remote->taddr, &pair->remote->taddr) == 0) &&
	         (valid_pair->valid->remote->componentID == pair->remote->componentID));
}

static void ice_check_if_losing_pair_should_cause_restart(const IceCandidatePair *pair,
                                                          LosingRemoteCandidate_InProgress_Failed *lif) {
	if (ice_compare_candidates(pair->remote, lif->losing_remote_candidate) == 0) {
		if (pair->state == ICP_InProgress) lif->in_progress_candidates = TRUE;
		if (pair->state == ICP_Failed) lif->failed_candidates = TRUE;
	}
}

void ice_add_losing_pair(IceCheckList *cl,
                         uint16_t componentID,
                         int local_family,
                         const char *local_addr,
                         int local_port,
                         int remote_family,
                         const char *remote_addr,
                         int remote_port) {
	IceTransportAddress taddr;
	TransportAddress_ComponentID taci;
	Type_ComponentID tc;
	bctbx_list_t *elem;
	bctbx_list_t *srflx_elem = NULL;
	LocalCandidate_RemoteCandidate lr;
	IceCandidatePair *pair;
	IceValidCandidatePair *valid_pair;
	bool_t added_missing_relay_candidate = FALSE;
	char taddr_str[64];

	memset(taddr_str, 0, sizeof(taddr_str));
	snprintf(taddr.ip, sizeof(taddr.ip), "%s", local_addr);

	/* Search for the local candidates that matches componentId, local_addr, and local_port as they are provided in the
	 * received remote-candidate attribute. */

	taddr.port = local_port;
	taddr.family = local_family;
	taci.componentID = componentID;
	taci.ta = &taddr;
	elem = bctbx_list_find_custom(cl->local_candidates,
	                              (bctbx_compare_func)ice_find_candidate_from_transport_address_and_componentID, &taci);
	if (elem == NULL) {
		// Workaround to detect if the local candidate that has not been found has been added by the proxy server.
		// If that is the case, add it to the local candidates now.
		elem = bctbx_list_find_custom(cl->remote_candidates, (bctbx_compare_func)ice_find_candidate_from_ip_address,
		                              local_addr);
		if (elem != NULL) {
			tc.componentID = componentID;
			tc.type = ICT_ServerReflexiveCandidate;
			srflx_elem = bctbx_list_find_custom(cl->remote_candidates,
			                                    (bctbx_compare_func)ice_find_candidate_from_type_and_componentID, &tc);
		}
		ice_transport_address_to_printable_ip_address(&taddr, taddr_str, sizeof(taddr_str));
		if (srflx_elem != NULL) {
			ms_message("ice: Add missing local candidate %s:relay", taddr_str);
			added_missing_relay_candidate = TRUE;
			lr.local = ice_add_local_candidate(cl, "relay", local_family, local_addr, local_port, componentID,
			                                   srflx_elem->data);
			ice_compute_candidate_foundation(lr.local, cl);
		} else {
			ms_warning("ice: Local candidate %s should have been found", taddr_str);
			return;
		}
	} else {
		lr.local = (IceCandidate *)elem->data;
	}
	snprintf(taddr.ip, sizeof(taddr.ip), "%s", remote_addr);
	taddr.port = remote_port;
	taddr.family = remote_family;
	taci.componentID = componentID;
	taci.ta = &taddr;
	elem = bctbx_list_find_custom(cl->remote_candidates,
	                              (bctbx_compare_func)ice_find_candidate_from_transport_address_and_componentID, &taci);
	if (elem == NULL) {
		ice_transport_address_to_printable_ip_address(&taddr, taddr_str, sizeof(taddr_str));
		ms_warning("ice: Remote candidate %s should have been found", taddr_str);
		return;
	}
	lr.remote = (IceCandidate *)elem->data;
	if (added_missing_relay_candidate == TRUE) {
		/* If we just added a missing relay candidate, also add the candidate pair. */
		pair = ice_pair_new(cl, lr.local, lr.remote);
		cl->pairs = bctbx_list_append(cl->pairs, pair);
	}
	elem = bctbx_list_find_custom(cl->pairs, (bctbx_compare_func)ice_find_pair_from_candidates, &lr);
	if (elem == NULL) {
		if (added_missing_relay_candidate == FALSE) {
			/* Candidate pair has not been created but the candidates exist.
			It must be that the local candidate is a reflexive or relayed candidate.
			Therefore create this pair and use it. */
			pair = ice_pair_new(cl, lr.local, lr.remote);
			cl->pairs = bctbx_list_append(cl->pairs, pair);
		} else return;
	} else {
		pair = (IceCandidatePair *)elem->data;
	}
	elem = bctbx_list_find_custom(cl->valid_list, (bctbx_compare_func)ice_find_pair_in_valid_list, pair);
	if (elem == NULL) {
		LosingRemoteCandidate_InProgress_Failed lif;
		/* The pair has not been found in the valid list, therefore it is a losing pair. */
		lif.losing_remote_candidate = pair->remote;
		lif.failed_candidates = FALSE;
		lif.in_progress_candidates = FALSE;
		bctbx_list_for_each2(cl->check_list, (void (*)(void *, void *))ice_check_if_losing_pair_should_cause_restart,
		                     &lif);
		if ((lif.in_progress_candidates == FALSE) && (lif.failed_candidates == TRUE)) {
			/* A network failure, such as a network partition or serious packet loss has most likely occured, restart
			 * ICE after some delay. */
			ms_warning("ice: ICE restart is needed!");
			cl->session->event_time = ice_add_ms(ice_current_time(), 1000);
			cl->session->event_value = ORTP_EVENT_ICE_RESTART_NEEDED;
			cl->session->send_event = TRUE;
		} else if (lif.in_progress_candidates == TRUE) {
			/* Wait for the in progress checks to complete. */
			ms_message("ice: Added losing pair, wait for InProgress checks to complete");
			elem = bctbx_list_find(cl->losing_pairs, pair);
			if (elem == NULL) {
				cl->losing_pairs = bctbx_list_append(cl->losing_pairs, pair);
			}
		}
	} else {
		valid_pair = (IceValidCandidatePair *)elem->data;
		ice_check_list_set_selected_valid_pair(cl, valid_pair);
		ms_message("ice: Select losing valid pair: cl=%p, componentID=%u, local_addr=%s, local_port=%d, "
		           "remote_addr=%s, remote_port=%d",
		           cl, componentID, local_addr, local_port, remote_addr, remote_port);
	}
}

static int ice_check_list_nb_losing_pairs(const IceCheckList *cl) {
	return (int)bctbx_list_size(cl->losing_pairs);
}

int ice_session_nb_losing_pairs(const IceSession *session) {
	int i;
	int nb_losing_pairs = 0;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) nb_losing_pairs += ice_check_list_nb_losing_pairs(session->streams[i]);
	}
	return nb_losing_pairs;
}

/******************************************************************************
 * COMPUTE CANDIDATES FOUNDATIONS                                             *
 *****************************************************************************/

static int ice_find_candidate_with_same_foundation(const IceCandidate *c1, const IceCandidate *c2) {
	if ((c1 != c2) && c1->base && c2->base && (c1->type == c2->type) &&
	    (strlen(c1->base->taddr.ip) == strlen(c2->base->taddr.ip)) &&
	    (strcmp(c1->base->taddr.ip, c2->base->taddr.ip) == 0))
		return 0;
	else return 1;
}

static void ice_compute_candidate_foundation(IceCandidate *candidate, IceCheckList *cl) {
	bctbx_list_t *l = bctbx_list_find_custom(cl->local_candidates,
	                                         (bctbx_compare_func)ice_find_candidate_with_same_foundation, candidate);
	if (l != NULL) {
		/* We found a candidate that should have the same foundation, so copy it from this candidate. */
		IceCandidate *other_candidate = (IceCandidate *)l->data;
		if (strlen(other_candidate->foundation) > 0) {
			strncpy(candidate->foundation, other_candidate->foundation, sizeof(candidate->foundation) - 1);
			return;
		}
		/* If the foundation of the other candidate is empty we need to assign a new one, so continue. */
	}

	/* No candidate that should have the same foundation has been found, assign a new one. */
	snprintf(candidate->foundation, sizeof(candidate->foundation) - 1, "%u", cl->foundation_generator);
	cl->foundation_generator++;
}

static void ice_check_list_compute_candidates_foundations(IceCheckList *cl) {
	if (cl->state == ICL_Running) {
		bctbx_list_for_each2(cl->local_candidates, (void (*)(void *, void *))ice_compute_candidate_foundation, cl);
	}
}

void ice_session_compute_candidates_foundations(IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) ice_check_list_compute_candidates_foundations(session->streams[i]);
	}
}

/******************************************************************************
 * ELIMINATE REDUNDANT CANDIDATES                                             *
 *****************************************************************************/

static int ice_find_redundant_candidate(const IceCandidate *c1, const IceCandidate *c2) {
	if (c1 == c2) return 1;
	return !(!ice_compare_transport_addresses(&c1->taddr, &c2->taddr) && (c1->base == c2->base));
}

static void ice_check_list_eliminate_redundant_candidates(IceCheckList *cl) {
	bctbx_list_t *elem;
	bctbx_list_t *other_elem;
	IceCandidate *candidate;
	IceCandidate *other_candidate;
	bool_t elem_removed;

	if (cl->state == ICL_Running) {
		do {
			elem_removed = FALSE;
			/* Do not use bctbx_list_for_each2() here, we may remove list elements. */
			for (elem = cl->local_candidates; elem != NULL; elem = elem->next) {
				candidate = (IceCandidate *)elem->data;
				other_elem = bctbx_list_find_custom(cl->local_candidates,
				                                    (bctbx_compare_func)ice_find_redundant_candidate, candidate);
				if (other_elem != NULL) {
					other_candidate = (IceCandidate *)other_elem->data;
					if (other_candidate->priority < candidate->priority) {
						ice_free_candidate(other_candidate);
						cl->local_candidates = bctbx_list_erase_link(cl->local_candidates, other_elem);
					} else {
						ice_free_candidate(candidate);
						cl->local_candidates = bctbx_list_erase_link(cl->local_candidates, elem);
					}
					elem_removed = TRUE;
					break;
				}
			}
		} while (elem_removed);
	}
}

void ice_session_eliminate_redundant_candidates(IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) ice_check_list_eliminate_redundant_candidates(session->streams[i]);
	}
}

/******************************************************************************
 * CHOOSE DEFAULT CANDIDATES                                                  *
 *****************************************************************************/

static int ice_find_candidate_from_type_and_componentID(const IceCandidate *candidate, const Type_ComponentID *tc) {
	return !((candidate->type == tc->type) && (candidate->componentID == tc->componentID));
}

static int ice_find_candidate_from_type_family_and_componentID(const IceCandidate *candidate,
                                                               const Type_Family_ComponentID *tc) {
	return !((candidate->type == tc->type) && (candidate->componentID == tc->componentID) &&
	         candidate->taddr.family == tc->family);
}

static void ice_choose_local_or_remote_default_candidates(IceCheckList *cl, bctbx_list_t *list) {
	Type_Family_ComponentID tc;
	bctbx_list_t *l;
	int i, k;

	/* Choose the default candidate for each componentID as defined in 4.1.4. */
	for (i = ICE_MIN_COMPONENTID; i <= ICE_MAX_COMPONENTID; i++) {
		IceCandidate *candidate = NULL;

		tc.componentID = i;
		l = NULL;
		for (k = 0; k < ICT_CandidateTypeMax && cl->session->default_types[k] != ICT_CandidateInvalid; ++k) {
			IceCandidate *inet_candidate = NULL;
			IceCandidate *inet6_candidate = NULL;
			tc.type = cl->session->default_types[k];

			tc.family = AF_INET;
			l = bctbx_list_find_custom(list, (bctbx_compare_func)ice_find_candidate_from_type_family_and_componentID,
			                           &tc);
			inet_candidate = l ? (IceCandidate *)l->data : NULL;

			tc.family = AF_INET6;
			l = bctbx_list_find_custom(list, (bctbx_compare_func)ice_find_candidate_from_type_family_and_componentID,
			                           &tc);
			inet6_candidate = l ? (IceCandidate *)l->data : NULL;

			if (inet6_candidate && cl->session->default_candidates_prefer_ipv6) {
				candidate = inet6_candidate;
			} else if (inet_candidate && !cl->session->default_candidates_prefer_ipv6) {
				candidate = inet_candidate;
			} else if (inet_candidate) {
				candidate = inet_candidate;
			} else {
				candidate = inet6_candidate;
			}
			if (candidate) break;
		}
		if (candidate) {
			candidate->is_default = TRUE;
			if (cl->session->turn_enabled) {
				ms_turn_context_set_force_rtp_sending_via_relay(ice_get_turn_context_from_check_list_componentID(cl, i),
				                                                candidate->type == ICT_RelayedCandidate);
			}
		}
	}
}

static void ice_check_list_choose_default_candidates(IceCheckList *cl) {
	if (cl->state == ICL_Running) {
		ice_choose_local_or_remote_default_candidates(cl, cl->local_candidates);
	}
}

void ice_session_choose_default_candidates(IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) ice_check_list_choose_default_candidates(session->streams[i]);
	}
}

static void ice_check_list_choose_default_remote_candidates(IceCheckList *cl) {
	ice_choose_local_or_remote_default_candidates(cl, cl->remote_candidates);
}

void ice_session_choose_default_remote_candidates(IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) ice_check_list_choose_default_remote_candidates(session->streams[i]);
	}
}

/******************************************************************************
 * FORM CANDIDATES PAIRS                                                      *
 *****************************************************************************/

static int ice_compare_pair_priorities(const IceCandidatePair *p1, const IceCandidatePair *p2) {
	return (p1->priority < p2->priority);
}

/* Form candidate pairs, compute their priorities and sort them by decreasing priorities according to 5.7.1 and 5.7.2.
 */
static void ice_form_candidate_pairs(IceCheckList *cl) {
	bctbx_list_t *local_list = cl->local_candidates;
	bctbx_list_t *remote_list;
	IceCandidatePair *pair;
	IceCandidate *local_candidate;
	IceCandidate *remote_candidate;

	while (local_list != NULL) {
		remote_list = cl->remote_candidates;
		while (remote_list != NULL) {
			local_candidate = (IceCandidate *)local_list->data;
			remote_candidate = (IceCandidate *)remote_list->data;
			if ((local_candidate->componentID == remote_candidate->componentID) &&
			    (local_candidate->taddr.family == remote_candidate->taddr.family)) {
				pair = ice_pair_new(cl, local_candidate, remote_candidate);
				cl->pairs = bctbx_list_append(cl->pairs, pair);
			}
			remote_list = bctbx_list_next(remote_list);
		}
		local_list = bctbx_list_next(local_list);
	}
}

static void ice_replace_srflx_by_base_in_pair(IceCandidatePair *pair) {
	/* Replace local server reflexive candidates by their bases. */
	if (pair->local->type == ICT_ServerReflexiveCandidate) {
		pair->local = pair->local->base;
	}
}

static int ice_compare_transport_addresses(const IceTransportAddress *ta1, const IceTransportAddress *ta2) {
	return !((ta1->family == ta2->family) && (ta1->port == ta2->port) && (strcmp(ta1->ip, ta2->ip) == 0));
}

static int ice_compare_candidates(const IceCandidate *c1, const IceCandidate *c2) {
	return !((c1->type == c2->type) && (ice_compare_transport_addresses(&c1->taddr, &c2->taddr) == 0) &&
	         (c1->componentID == c2->componentID) && (c1->priority == c2->priority));
}

static int ice_compare_pairs(const IceCandidatePair *p1, const IceCandidatePair *p2) {
	return !((ice_compare_candidates(p1->local, p2->local) == 0) &&
	         (ice_compare_candidates(p1->remote, p2->remote) == 0));
}

static int ice_prune_duplicate_pair(IceCandidatePair *pair, bctbx_list_t **pairs, IceCheckList *cl) {
	bctbx_list_t *other_pair = bctbx_list_find_custom(*pairs, (bctbx_compare_func)ice_compare_pairs, pair);
	if (other_pair != NULL) {
		IceCandidatePair *other_candidate_pair = (IceCandidatePair *)other_pair->data;
		if (other_candidate_pair->priority > pair->priority) {
			/* Found duplicate with higher priority so prune current pair. */
			*pairs = bctbx_list_remove(*pairs, pair);
			ice_free_candidate_pair(pair, cl);
			return 1;
		}
	}
	return 0;
}

static void ice_create_check_list(IceCandidatePair *pair, IceCheckList *cl) {
	cl->check_list = bctbx_list_insert_sorted(cl->check_list, pair, (bctbx_compare_func)ice_compare_pair_priorities);
}

/* Prune pairs according to 5.7.3. */
static void ice_prune_candidate_pairs(IceCheckList *cl) {
	bctbx_list_t *list;
	bctbx_list_t *next;
	bctbx_list_t *prev;
	int nb_pairs;
	int nb_pairs_to_remove;
	int i;

	bctbx_list_for_each(cl->pairs, (void (*)(void *))ice_replace_srflx_by_base_in_pair);
	/* Do not use bctbx_list_for_each2() here, because ice_prune_duplicate_pair() can remove list elements. */
	for (list = cl->pairs; list != NULL; list = list->next) {
		next = list->next;
		if (ice_prune_duplicate_pair(list->data, &cl->pairs, cl)) {
			if (next && next->prev) list = next->prev;
			else break; /* The end of the list has been reached, prevent accessing a wrong list->next */
		}
	}

	/* Create the check list. */
	bctbx_list_free(cl->check_list);
	cl->check_list = NULL;
	bctbx_list_for_each2(cl->pairs, (void (*)(void *, void *))ice_create_check_list, cl);

	/* Limit the number of connectivity checks. */
	nb_pairs = (int)bctbx_list_size(cl->check_list);
	if (nb_pairs > cl->session->max_connectivity_checks) {
		nb_pairs_to_remove = nb_pairs - cl->session->max_connectivity_checks;
		list = cl->check_list;
		for (i = 0; i < (nb_pairs - 1); i++)
			list = bctbx_list_next(list);
		for (i = 0; i < nb_pairs_to_remove; i++) {
			cl->pairs = bctbx_list_remove(cl->pairs, list->data);
			prev = list->prev;
			ice_free_candidate_pair(list->data, cl); // this function remove list in cl too
			list = prev;
		}
	}
}

static int ice_find_pair_foundation(const IcePairFoundation *f1, const IcePairFoundation *f2) {
	return !((strlen(f1->local) == strlen(f2->local)) && (strcmp(f1->local, f2->local) == 0) &&
	         (strlen(f1->remote) == strlen(f2->remote)) && (strcmp(f1->remote, f2->remote) == 0));
}

static void ice_generate_pair_foundations_list(const IceCandidatePair *pair, bctbx_list_t **list) {
	IcePairFoundation foundation;
	IcePairFoundation *dyn_foundation;
	bctbx_list_t *elem;

	memset(&foundation, 0, sizeof(foundation));
	strncpy(foundation.local, pair->local->foundation, sizeof(foundation.local) - 1);
	strncpy(foundation.remote, pair->remote->foundation, sizeof(foundation.remote) - 1);

	elem = bctbx_list_find_custom(*list, (bctbx_compare_func)ice_find_pair_foundation, &foundation);
	if (elem == NULL) {
		dyn_foundation = ms_new0(IcePairFoundation, 1);
		memcpy(dyn_foundation, &foundation, sizeof(foundation));
		*list = bctbx_list_append(*list, dyn_foundation);
	}
}

static void ice_find_lowest_componentid_pair_with_specified_foundation(IceCandidatePair *pair,
                                                                       Foundation_Pair_Priority_ComponentID *fc) {
	if ((strlen(pair->local->foundation) == strlen(fc->foundation->local)) &&
	    (strcmp(pair->local->foundation, fc->foundation->local) == 0) &&
	    (strlen(pair->remote->foundation) == strlen(fc->foundation->remote)) &&
	    (strcmp(pair->remote->foundation, fc->foundation->remote) == 0) &&
	    ((fc->componentID == ICE_INVALID_COMPONENTID) ||
	     ((pair->local->componentID < fc->componentID) && (pair->priority > fc->priority)))) {
		fc->componentID = pair->local->componentID;
		fc->priority = pair->priority;
		fc->pair = pair;
	}
}

static void ice_set_lowest_componentid_pair_with_foundation_to_waiting_state(const IcePairFoundation *foundation,
                                                                             IceCheckList *cl) {
	Foundation_Pair_Priority_ComponentID fc;
	fc.foundation = foundation;
	fc.pair = NULL;
	fc.componentID = ICE_INVALID_COMPONENTID;
	fc.priority = 0;
	bctbx_list_for_each2(cl->check_list,
	                     (void (*)(void *, void *))ice_find_lowest_componentid_pair_with_specified_foundation, &fc);
	if (fc.pair != NULL) {
		/* Set the state of the pair to Waiting. */
		ice_pair_set_state(fc.pair, ICP_Waiting);
	}
}

/* Compute pairs states according to 5.7.4. */
static void ice_compute_pairs_states(IceCheckList *cl) {
	bctbx_list_for_each2(cl->foundations,
	                     (void (*)(void *, void *))ice_set_lowest_componentid_pair_with_foundation_to_waiting_state,
	                     cl);
}

static void ice_check_list_pair_candidates(IceCheckList *cl) {
	if (cl->state == ICL_Running) {
		cl->connectivity_checks_running = TRUE;
		ice_create_turn_permissions(cl);
		ms_message("ICE: connectivity checks are going to start for check list %p", cl);
		ice_form_candidate_pairs(cl);
		ice_prune_candidate_pairs(cl);
		/* Generate pair foundations list. */
		bctbx_list_for_each2(cl->check_list, (void (*)(void *, void *))ice_generate_pair_foundations_list,
		                     &cl->foundations);
	}
}

static void ice_session_pair_candidates(IceSession *session) {
	IceCheckList *cl = NULL;
	int i;

	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if ((session->streams[i] != NULL) && (ice_check_list_state(session->streams[i]) == ICL_Running)) {
			cl = session->streams[i];
			break;
		}
	}
	if (cl != NULL) {
		for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
			if (session->streams[i] != NULL) ice_check_list_pair_candidates(session->streams[i]);
		}
		ice_compute_pairs_states(cl);
		ice_dump_candidate_pairs_foundations(cl);
		ice_dump_candidate_pairs(cl);
		ice_dump_check_list(cl);
	}
}

void ice_session_start_connectivity_checks(IceSession *session) {
	ice_session_pair_candidates(session);
	session->state = IS_Running;
	ms_get_cur_time(&session->connectivity_checks_start_ts);
}

/******************************************************************************
 * CONCLUDE ICE PROCESSING                                                    *
 *****************************************************************************/

static int valid_pair_compare(IceValidCandidatePair *new_pair, IceValidCandidatePair *pair_in_list) {
	// ms_message("ice: priorities %lu  <>   %lu", (unsigned long)p1->generated_from->priority, (unsigned
	// long)p2->generated_from->priority);
	return pair_in_list->valid->priority > new_pair->valid->priority;
}

static bctbx_list_t *ice_get_valid_pairs_for_componentID(IceCheckList *cl, uint16_t componentID) {
	const bctbx_list_t *it;
	bctbx_list_t *ret = NULL;
	for (it = cl->valid_list; it != NULL; it = it->next) {
		IceValidCandidatePair *valid_pair = (IceValidCandidatePair *)it->data;
		if (valid_pair->valid->local->componentID == componentID) {
			ret = bctbx_list_insert_sorted(ret, valid_pair, (bctbx_compare_func)valid_pair_compare);
		}
	}
	return ret;
}

static void ice_pair_stop_retransmissions(IceCandidatePair *pair, IceCheckList *cl) {
	bctbx_list_t *elem;
	if (pair->state == ICP_InProgress) {
		ice_pair_set_state(pair, ICP_Failed);
		elem = bctbx_list_find(cl->triggered_checks_queue, pair);
		if (elem != NULL) {
			cl->triggered_checks_queue = bctbx_list_erase_link(cl->triggered_checks_queue, elem);
		}
	}
}

static void ice_check_list_stop_retransmissions(IceCheckList *cl) {
	bctbx_list_for_each2(cl->check_list, (void (*)(void *, void *))ice_pair_stop_retransmissions, cl);
}

static void ice_check_list_nominate(IceCheckList *cl, const bctbx_list_t *best_valid_list) {
	const bctbx_list_t *it;
	for (it = best_valid_list; it != NULL; it = it->next) {
		IceValidCandidatePair *valid_pair = (IceValidCandidatePair *)it->data;
		if (valid_pair->generated_from->use_candidate == FALSE) {
			valid_pair->generated_from->use_candidate = TRUE;
			ice_check_list_queue_triggered_check(cl, valid_pair->generated_from);
		}
	}
	cl->nomination_in_progress = TRUE;
}

static void ice_check_list_perform_nominations(IceCheckList *cl, bool_t nomination_delay_expired) {
	const bctbx_list_t *comp_it;
	bctbx_list_t *best_valid_list = NULL;
	bool_t concludable = TRUE;
	bool_t need_more_time = FALSE;
	int nominations_to_do = 0;

	if (cl->nomination_in_progress) return;

	for (comp_it = cl->local_componentIDs; comp_it != NULL; comp_it = comp_it->next) {
		IceValidCandidatePair *valid_pair;
		bctbx_list_t *valid_it = NULL;
		bctbx_list_t *valid_list = NULL;

		uint16_t componentID = *(const uint16_t *)comp_it->data;
		valid_list = ice_get_valid_pairs_for_componentID(cl, componentID);
		if (valid_list == NULL) {
			ms_message("ice_check_list_perform_nominations(cl=%p): no valid pairs yet for componentID %i", cl,
			           (int)componentID);
			concludable = FALSE;
			break;
		}
		/* Normally the first element in the list is the best one to nominate.
		 * However if nomination is failing, we'll nominate the next one and so one.
		 */
		for (valid_it = valid_list; valid_it != NULL; valid_it = valid_it->next) {
			valid_pair = (IceValidCandidatePair *)valid_it->data;
			if (valid_pair->generated_from->nomination_failing) {
				ms_message("ice_check_list_perform_nominations(): a nominated pair is not responding");
			} else break;
		}
		if (valid_it) {
			valid_pair = (IceValidCandidatePair *)valid_it->data;
			if (valid_pair->generated_from->remote->type == ICT_RelayedCandidate ||
			    valid_pair->generated_from->local->type == ICT_RelayedCandidate) {
				need_more_time = TRUE;
			}
			best_valid_list = bctbx_list_append(best_valid_list, valid_pair);
			if (valid_pair->generated_from->use_candidate == FALSE) nominations_to_do++;
		} else {
			ms_warning("ice_check_list_perform_nominations(cl=%p): no more pair to nominate for componentID %i", cl,
			           (int)componentID);
		}
		bctbx_list_free(valid_list);
	}
	if (concludable && nominations_to_do > 0) {
		ms_message("ice_check_list_perform_nominations(): check list is concludable.");
		if (need_more_time && !cl->nomination_delay_running) {
			ms_message("ice_check_list_perform_nominations(cl=%p): for a component, the best candidate is a relay one, "
			           "let's wait a bit before performing nomination",
			           cl);
			cl->nomination_delay_running = TRUE;
			cl->nomination_delay_start_time = ice_current_time();
		}
		if (nomination_delay_expired || need_more_time == FALSE) {
			ms_message("ice_check_list_perform_nominations(cl=%p): nominating the best valid pair for each component.",
			           cl);
			cl->nomination_delay_running = FALSE;
			ice_check_list_nominate(cl, best_valid_list);
		}
	}
	bctbx_list_free(best_valid_list);
}

static void ice_remove_waiting_and_frozen_pairs_from_list(bctbx_list_t **list, uint16_t componentID) {
	IceCandidatePair *pair;
	bctbx_list_t *elem;
	bctbx_list_t *next;

	for (elem = *list; elem != NULL; elem = elem->next) {
		pair = (IceCandidatePair *)elem->data;
		if (((pair->state == ICP_Waiting) || (pair->state == ICP_Frozen)) &&
		    (pair->local->componentID == componentID)) {
			next = elem->next;
			*list = bctbx_list_erase_link(*list, elem);
			if (next && next->prev) elem = next->prev;
			else break; /* The end of the list has been reached, prevent accessing a wrong list->next */
		}
	}
}

static void ice_conclude_waiting_frozen_and_inprogress_pairs(const IceValidCandidatePair *valid_pair,
                                                             IceCheckList *cl) {
	if (valid_pair->valid->is_nominated == TRUE) {
		bctbx_list_t *elem;
		ice_remove_waiting_and_frozen_pairs_from_list(&cl->check_list, valid_pair->valid->local->componentID);
		ice_remove_waiting_and_frozen_pairs_from_list(&cl->triggered_checks_queue,
		                                              valid_pair->valid->local->componentID);

		for (elem = cl->check_list; elem != NULL; elem = elem->next) {
			IceCandidatePair *pair = (IceCandidatePair *)elem->data;
			if ((pair->state == ICP_InProgress) &&
			    (pair->local->componentID == valid_pair->valid->local->componentID) &&
			    pair->priority < valid_pair->valid->priority) {
				/* Set the retransmission number to the max to stop retransmissions for this pair. */
				pair->retransmissions = ICE_MAX_RETRANSMISSIONS;
			}
		}
	}
}

#if 0
static int ice_find_use_candidate_valid_pair_from_componentID(const IceValidCandidatePair *valid_pair, const uint16_t *componentID)
{
	return !((valid_pair->generated_from->use_candidate == TRUE) && (valid_pair->generated_from->local->componentID == *componentID));
}
#endif

static int ice_find_nominated_valid_pair_from_componentID(const IceValidCandidatePair *valid_pair,
                                                          const uint16_t *componentID) {
	return !((valid_pair->valid->is_nominated == TRUE) && (valid_pair->valid->local->componentID == *componentID));
}

static void ice_find_nominated_valid_pair_for_componentID(const uint16_t *componentID, CheckList_Bool *cb) {
	bctbx_list_t *elem = bctbx_list_find_custom(
	    cb->cl->valid_list, (bctbx_compare_func)ice_find_nominated_valid_pair_from_componentID, componentID);
	if (elem == NULL) {
		/* This component ID is not present in the valid list. */
		cb->result = FALSE;
	}
}

static int ice_find_selected_valid_pair_from_componentID(const IceValidCandidatePair *valid_pair,
                                                         const uint16_t *componentID) {
	return !((valid_pair->selected == TRUE) && (valid_pair->valid->local->componentID == *componentID));
}

static void ice_find_selected_valid_pair_for_componentID(const uint16_t *componentID, CheckList_Bool *cb) {
	bctbx_list_t *elem = bctbx_list_find_custom(
	    cb->cl->valid_list, (bctbx_compare_func)ice_find_selected_valid_pair_from_componentID, componentID);
	if (elem == NULL) {
		/* This component ID is not present in the valid list. */
		cb->result = FALSE;
	}
}

#if 0
static int ice_find_not_failed_or_succeeded_pair(const IceCandidatePair *pair, const void *dummy)
{
	return !((pair->state != ICP_Failed) && (pair->state != ICP_Succeeded));
}

static void ice_check_all_pairs_in_failed_or_succeeded_state(const IceCandidatePair *pair, CheckList_Bool *cb)
{
	bctbx_list_t *elem = bctbx_list_find_custom(cb->cl->check_list, (bctbx_compare_func)ice_find_not_failed_or_succeeded_pair, NULL);
	if (elem != NULL) {
		cb->result = FALSE;
	}
}
#endif

static IceCheckList *ice_session_find_running_check_list(const IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if ((session->streams[i] != NULL) && (ice_check_list_state(session->streams[i]) == ICL_Running))
			return session->streams[i];
	}
	return NULL;
}

static IceCheckList *ice_session_find_unsuccessful_check_list(const IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if ((session->streams[i] != NULL) && (ice_check_list_state(session->streams[i]) != ICL_Completed))
			return session->streams[i];
	}
	return NULL;
}

static bool_t ice_session_contains_check_list(const IceSession *session, const IceCheckList *cl) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if ((session->streams[i] != NULL) && (session->streams[i] == cl)) return TRUE;
	}
	return FALSE;
}

static void ice_notify_session_processing_finished(IceCheckList *cl, BCTBX_UNUSED(RtpSession *rtp_session)) {
	IceCheckList *next_cl;
	if (ice_session_contains_check_list(cl->session, cl) == FALSE) {
		ms_error("ice: Could not find check list in the session");
		return;
	}
	next_cl = ice_session_find_running_check_list(cl->session);
	if (next_cl == NULL) {
		/* This was the last check list of the session. */
		if (ice_session_find_unsuccessful_check_list(cl->session) == NULL) {
			/* All the check lists of the session have completed successfully. */
			cl->session->state = IS_Completed;
		} else {
			/* Some check lists have failed, consider the session to be a failure. */
			cl->session->state = IS_Failed;
		}
		cl->session->event_time = ice_add_ms(ice_current_time(), 1000);
		cl->session->event_value = ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED;
		cl->session->send_event = TRUE;
	}
}

static void ice_check_list_create_turn_channel(IceCheckList *cl,
                                               RtpTransport *rtptp,
                                               struct sockaddr *local_addr,
                                               socklen_t local_addrlen,
                                               IceTransportAddress *remote_taddr,
                                               uint16_t componentID) {
	IceStunServerRequestTransaction *transaction;
	IceStunServerRequest *request;
	MSTurnContext *turn_context = ice_get_turn_context_from_check_list_componentID(cl, componentID);
	MSStunAddress peer_address = ice_transport_address_to_stun_address(remote_taddr);
	char local_ip[64];
	int local_port = 0;

	bctbx_sockaddr_to_ip_address(local_addr, local_addrlen, local_ip, sizeof(local_ip), &local_port);
	request = ice_stun_server_request_new(cl, turn_context, rtptp, local_addr->sa_family, local_ip, local_port,
	                                      MS_TURN_METHOD_CHANNEL_BIND);
	if (!request) return;
	request->peer_address = peer_address;
	request->channel_number = 0x4000 | componentID;
	ms_turn_context_set_channel_number(turn_context, request->channel_number);
	ms_turn_context_set_state(turn_context, MS_TURN_CONTEXT_STATE_BINDING_CHANNEL);
	request->next_transmission_time = ice_add_ms(ice_current_time(), ICE_DEFAULT_RTO_DURATION);
	transaction = ice_send_stun_server_request(request, (struct sockaddr *)&cl->session->ss, cl->session->ss_len);
	ice_stun_server_request_add_transaction(request, transaction);
	ice_check_list_add_stun_server_request(cl, request);
}

/* Conclude ICE processing as defined in 8.1. */
static void ice_conclude_processing(IceCheckList *cl, RtpSession *rtp_session, bool_t nomination_delay_expired) {
	CheckList_Bool cb;
	OrtpEvent *ev;
	int nb_losing_pairs = 0;
	IceCandidate *rtp_local_candidate = NULL;
	IceCandidate *rtcp_local_candidate = NULL;
	IceCandidate *rtp_remote_candidate = NULL;
	IceCandidate *rtcp_remote_candidate = NULL;
	RtpTransport *rtptp;

	if (cl->state == ICL_Running) {
		if (cl->session->role == IR_Controlling) {
			ice_check_list_perform_nominations(cl, nomination_delay_expired);
		}

		bctbx_list_for_each2(cl->valid_list, (void (*)(void *, void *))ice_conclude_waiting_frozen_and_inprogress_pairs,
		                     cl);

		cb.cl = cl;
		cb.result = TRUE;
		bctbx_list_for_each2(cl->local_componentIDs,
		                     (void (*)(void *, void *))ice_find_nominated_valid_pair_for_componentID, &cb);
		if (cb.result == TRUE) {
			nb_losing_pairs = ice_check_list_nb_losing_pairs(cl);
			if ((cl->state != ICL_Completed) && (nb_losing_pairs == 0)) {
				bool_t result;
				cl->state = ICL_Completed;
				cl->nomination_in_progress = FALSE;
				cl->nomination_delay_running = FALSE;
				ice_check_list_select_candidates(cl);
				ms_message("ice: Finished ICE check list [%p] processing successfully!", cl);
				cl->connectivity_checks_running = FALSE;
				ice_dump_valid_list(cl);
				/* Initialise keepalive time. */
				cl->keepalive_time = ice_current_time();
				/* Stop all running transactions (since nomination is finished).*/
				ice_check_list_stop_retransmissions(cl);
				result =
				    ice_check_list_selected_valid_remote_candidate(cl, &rtp_remote_candidate, &rtcp_remote_candidate);
				if (result == TRUE) {
					/*Switch the destination of the mediastream to the destination selected by ICE*/
					rtp_session_set_remote_addr_full(
					    rtp_session, rtp_remote_candidate->taddr.ip, rtp_remote_candidate->taddr.port,
					    rtcp_remote_candidate ? rtcp_remote_candidate->taddr.ip : rtp_remote_candidate->taddr.ip,
					    rtcp_remote_candidate ? rtcp_remote_candidate->taddr.port : rtp_remote_candidate->taddr.port);
					ice_check_list_selected_valid_local_base_candidate(cl, &rtp_local_candidate, &rtcp_local_candidate);
					if ((rtp_local_candidate || rtcp_local_candidate)) {
						/*Switch the source of the mediastream to the source selected by ICE.*/
						rtp_session_use_local_addr(rtp_session,
						                           (rtp_local_candidate ? rtp_local_candidate->taddr.ip : ""),
						                           (rtcp_local_candidate ? rtcp_local_candidate->taddr.ip : ""));
					}
					if (cl->session->turn_enabled) {
						rtp_local_candidate = NULL;
						rtcp_local_candidate = NULL;
						ice_check_list_selected_valid_local_candidate(cl, &rtp_local_candidate, &rtcp_local_candidate);
						if (rtp_local_candidate) {
							ms_turn_context_set_force_rtp_sending_via_relay(
							    ice_get_turn_context_from_check_list_componentID(cl, 1),
							    ice_candidate_is_relay(rtp_local_candidate));
							if (ice_candidate_is_relay(rtp_local_candidate)) {
								rtp_session_get_transports(cl->rtp_session, &rtptp, NULL);
								ice_check_list_create_turn_channel(
								    cl, rtptp, (struct sockaddr *)&cl->rtp_session->rtp.gs.loc_addr,
								    cl->rtp_session->rtp.gs.loc_addrlen, &rtp_remote_candidate->taddr, 1);
							} else {
								ice_check_list_deallocate_rtp_turn_candidate(cl);
							}
						}
						if (rtcp_local_candidate) {
							ms_turn_context_set_force_rtp_sending_via_relay(
							    ice_get_turn_context_from_check_list_componentID(cl, 2),
							    ice_candidate_is_relay(rtcp_local_candidate));
							if (ice_candidate_is_relay(rtcp_local_candidate) && rtcp_remote_candidate) {
								rtp_session_get_transports(cl->rtp_session, NULL, &rtptp);
								ice_check_list_create_turn_channel(
								    cl, rtptp, (struct sockaddr *)&cl->rtp_session->rtcp.gs.loc_addr,
								    cl->rtp_session->rtcp.gs.loc_addrlen, &rtcp_remote_candidate->taddr, 2);
							} else {
								ice_check_list_deallocate_rtcp_turn_candidate(cl);
							}
						}
					}
				} else {
					ms_error("Cannot get remote candidate for check list [%p]", cl);
				}
				/* Notify the application of the successful processing. */
				ev = ortp_event_new(ORTP_EVENT_ICE_CHECK_LIST_PROCESSING_FINISHED);
				ortp_event_get_data(ev)->info.ice_processing_successful = TRUE;
				rtp_session_dispatch_event(rtp_session, ev);
				ice_notify_session_processing_finished(cl, rtp_session);
			}
		}
	}
}

/******************************************************************************
 * RESTART ICE PROCESSING                                                     *
 *****************************************************************************/

static void ice_check_list_restart(IceCheckList *cl) {
	if (cl->remote_ufrag) ms_free(cl->remote_ufrag);
	if (cl->remote_pwd) ms_free(cl->remote_pwd);
	cl->remote_ufrag = cl->remote_pwd = NULL;
	rtp_session_use_local_addr(cl->rtp_session, "", ""); // Reset the sources of rtp_session
	bctbx_list_for_each(cl->stun_server_requests, (void (*)(void *))ice_stun_server_request_free);
	bctbx_list_for_each(cl->transaction_list, (void (*)(void *))ice_free_transaction);
	bctbx_list_for_each(cl->foundations, (void (*)(void *))ice_free_pair_foundation);
	bctbx_list_for_each2(cl->pairs, (void (*)(void *, void *))ice_free_candidate_pair, cl);
	bctbx_list_for_each(cl->valid_list, (void (*)(void *))ice_free_valid_pair);
	bctbx_list_for_each(cl->remote_candidates, (void (*)(void *))ice_free_candidate);
	bctbx_list_free(cl->stun_server_requests);
	bctbx_list_free(cl->transaction_list);
	bctbx_list_free(cl->foundations);
	bctbx_list_free(cl->remote_componentIDs);
	bctbx_list_free(cl->valid_list);
	bctbx_list_free(cl->check_list);
	bctbx_list_free(cl->triggered_checks_queue);
	bctbx_list_free(cl->losing_pairs);
	bctbx_list_free(cl->pairs);
	bctbx_list_free(cl->remote_candidates);
	cl->stun_server_requests = cl->foundations = cl->remote_componentIDs = NULL;
	cl->valid_list = cl->check_list = cl->triggered_checks_queue = cl->losing_pairs = cl->pairs =
	    cl->remote_candidates = cl->transaction_list = NULL;
	cl->state = ICL_Running;
	cl->mismatch = FALSE;
	cl->gathering_candidates = FALSE;
	cl->gathering_finished = FALSE;
	cl->nomination_delay_running = FALSE;
	cl->ta_time = ice_current_time();
	cl->nomination_in_progress = FALSE;
	memset(&cl->keepalive_time, 0, sizeof(cl->keepalive_time));
	memset(&cl->gathering_start_time, 0, sizeof(cl->gathering_start_time));
	memset(&cl->nomination_delay_start_time, 0, sizeof(cl->nomination_delay_start_time));
}

void ice_session_restart(IceSession *session, IceRole role) {
	int i;

	ms_warning("ICE session restart");
	if (session->local_ufrag) ms_free(session->local_ufrag);
	if (session->local_pwd) ms_free(session->local_pwd);
	if (session->remote_ufrag) ms_free(session->remote_ufrag);
	if (session->remote_pwd) ms_free(session->remote_pwd);

	session->state = IS_Stopped;
	session->tie_breaker = generate_tie_breaker();
	session->local_ufrag = generate_ufrag();
	session->local_pwd = generate_pwd();
	session->remote_ufrag = NULL;
	session->remote_pwd = NULL;
	memset(&session->event_time, 0, sizeof(session->event_time));
	session->send_event = FALSE;

	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) ice_check_list_restart(session->streams[i]);
	}
	ice_session_set_role(session, role);
}

void ice_session_reset(IceSession *session, IceRole role) {
	int i;

	ice_session_restart(session, role);
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		IceCheckList *cl = session->streams[i];
		if (cl != NULL) {
			cl->local_candidates =
			    bctbx_list_free_with_data(cl->local_candidates, (bctbx_list_free_func)ice_free_candidate);
			bctbx_list_free(cl->local_componentIDs);
			cl->local_componentIDs = NULL;
		}
	}
}

/******************************************************************************
 * GLOBAL PROCESS                                                             *
 *****************************************************************************/

static int ice_find_gathering_stun_server_request(const IceStunServerRequest *request) {
	return request->gathering == FALSE;
}

static void ice_remove_gathering_stun_server_requests(IceCheckList *cl) {
	bctbx_list_t *elem = cl->stun_server_requests;
	while (elem != NULL) {
		elem = bctbx_list_find_custom(cl->stun_server_requests,
		                              (bctbx_compare_func)ice_find_gathering_stun_server_request, NULL);

		if (elem != NULL) {
			IceStunServerRequest *request = (IceStunServerRequest *)elem->data;
			ice_stun_server_request_free(request);
			cl->stun_server_requests = bctbx_list_erase_link(cl->stun_server_requests, elem);
		}
	}
}

static void ice_check_list_stop_gathering(IceCheckList *cl) {
	cl->gathering_candidates = FALSE;
	cl->gathering_finished = TRUE;
	ice_check_list_sum_gathering_round_trip_times(cl);
	ice_remove_gathering_stun_server_requests(cl);
}

static bool_t ice_check_gathering_timeout(IceCheckList *cl, RtpSession *rtp_session, MSTimeSpec curtime) {
	OrtpEvent *ev;
	IceCheckList *cl_it;
	int i;
	bool_t timeout = FALSE;

	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		cl_it = cl->session->streams[i];
		if ((cl_it != NULL) && (cl_it->gathering_candidates == TRUE) &&
		    (ice_compare_time(curtime, cl_it->gathering_start_time) >= ICE_GATHERING_CANDIDATES_TIMEOUT)) {
			timeout = TRUE;
			break;
		}
	}
	if (timeout == TRUE) {
		for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
			if (cl_it->session->streams[i] != NULL) ice_check_list_stop_gathering(cl_it->session->streams[i]);
		}
		/* Notify the application that the gathering process has timed out. */
		ev = ortp_event_new(ORTP_EVENT_ICE_GATHERING_FINISHED);
		ortp_event_get_data(ev)->info.ice_processing_successful = FALSE;
		rtp_session_dispatch_event(rtp_session, ev);
	}
	return timeout;
}

static void ice_send_stun_server_requests(IceStunServerRequest *request, IceCheckList *cl) {

	IceStunServerRequestTransaction *transaction = NULL;
	MSTimeSpec curtime = ice_current_time();

	if ((request->responded == FALSE) && (ice_compare_time(curtime, request->next_transmission_time) >= 0)) {
		if (bctbx_list_size(request->transactions) < ICE_MAX_STUN_REQUEST_RETRANSMISSIONS) {
			request->next_transmission_time = ice_add_ms(curtime, ICE_DEFAULT_RTO_DURATION);
			transaction =
			    ice_send_stun_server_request(request, (struct sockaddr *)&cl->session->ss, cl->session->ss_len);
			if (transaction != NULL) {
				ice_stun_server_request_add_transaction(request, transaction);
			} else {
				request->to_remove = TRUE;
			}
		}
	}
}

static void ice_handle_connectivity_check_retransmission(IceCandidatePair *pair,
                                                         const CheckList_RtpSession_Time *params) {
	if (params->cl->nomination_in_progress && pair->use_candidate == FALSE)
		return; /*no need to retransmit anything during nomination*/
	if ((pair->state == ICP_InProgress) && (ice_compare_time(params->time, pair->transmission_time) >= pair->rto)) {
		ice_send_binding_request(params->cl, pair, params->rtp_session);
	}
}

static int ice_find_pair_from_state(const IceCandidatePair *pair, const IceCandidatePairState *state) {
	return !(pair->state == *state);
}

static void ice_check_retransmissions_pending(const IceCandidatePair *pair, bool_t *retransmissions_pending) {
	if ((pair->state == ICP_InProgress) && (pair->retransmissions <= ICE_MAX_RETRANSMISSIONS))
		*retransmissions_pending = TRUE;
}

static void
ice_check_list_retransmit_connectivity_checks(IceCheckList *cl, RtpSession *rtp_session, MSTimeSpec curtime) {
	CheckList_RtpSession_Time params;
	params.cl = cl;
	params.rtp_session = rtp_session;
	params.time = curtime;
	bctbx_list_for_each2(cl->check_list, (void (*)(void *, void *))ice_handle_connectivity_check_retransmission,
	                     &params);
}

static IceCandidatePair *ice_check_list_send_triggered_check(IceCheckList *cl, RtpSession *rtp_session) {
	IceCandidatePair *pair = ice_check_list_pop_triggered_check(cl);
	if (pair != NULL) {
		ice_send_binding_request(cl, pair, rtp_session);
	}
	return pair;
}

/* Schedule checks as defined in 5.8. */
void ice_check_list_process(IceCheckList *cl, RtpSession *rtp_session) {
	IceCandidatePairState state;
	IceCandidatePair *pair;
	bctbx_list_t *elem;
	MSTimeSpec curtime;
	bool_t retransmissions_pending = FALSE;

	if (cl->session == NULL) return;
	curtime = ice_current_time();

	/* Check for gathering timeout */
	if ((cl->gathering_candidates == TRUE) && ice_check_gathering_timeout(cl, rtp_session, curtime)) {
		ms_message("ice: Gathering timeout for checklist [%p]", cl);
	}

	/* Send STUN/TURN server requests (to gather candidates, create/refresh TURN permissions, refresh TURN allocations
	 * or bind TURN channels). */
	bctbx_list_for_each2(cl->stun_server_requests, (void (*)(void *, void *))ice_send_stun_server_requests, cl);

	cl->stun_server_requests = bctbx_list_remove_custom_with_data(
	    cl->stun_server_requests, (bctbx_compare_func)ice_compare_stun_server_requests_to_remove,
	    (bctbx_list_free_func)ice_stun_server_request_free, NULL);

	/* Send event if needed. */
	if ((cl->session->send_event == TRUE) && (ice_compare_time(curtime, cl->session->event_time) >= 0)) {
		OrtpEvent *ev;
		cl->session->send_event = FALSE;
		ev = ortp_event_new(cl->session->event_value);
		ortp_event_get_data(ev)->info.ice_processing_successful = (cl->session->state == IS_Completed);
		rtp_session_dispatch_event(rtp_session, ev);
	}

	if ((cl->session->state == IS_Stopped) || (cl->session->state == IS_Failed)) return;

	switch (cl->state) {
		case ICL_Completed:
			/* Handle keepalives when check list has completed - long periods */
			if (ice_compare_time(curtime, cl->keepalive_time) >= (cl->session->keepalive_timeout * 1000)) {
				ice_send_keepalive_packets(cl, rtp_session);
				cl->keepalive_time = curtime;
			}
			/* Check if some retransmissions are needed. */
			ice_check_list_retransmit_connectivity_checks(cl, rtp_session, curtime);
			if (ice_compare_time(curtime, cl->ta_time) < cl->session->ta) return;
			cl->ta_time = curtime;
			/* Send a triggered connectivity check if there is one. */
			if (ice_check_list_send_triggered_check(cl, rtp_session) != NULL) return;
			break;
		case ICL_Running:
			/* Handle keepalives when check list is running, sent only on succeeded pair, to keep them alive until we
			 * conclude. */
			ice_send_keepalive_packets(cl, rtp_session);

			/* Check nomination delay. */
			if ((cl->nomination_delay_running == TRUE) &&
			    (ice_compare_time(curtime, cl->nomination_delay_start_time) >= ICE_NOMINATION_DELAY)) {
				ms_message("ice: Nomination delay timeout, select the potential relayed candidate anyway.");
				ice_conclude_processing(cl, rtp_session, TRUE);
				if (cl->session->state == IS_Completed) return;
			}
			/* Check if some retransmissions are needed. */
			ice_check_list_retransmit_connectivity_checks(cl, rtp_session, curtime);
			if (ice_compare_time(curtime, cl->ta_time) < cl->session->ta) return;
			cl->ta_time = curtime;
			/* Send a triggered connectivity check if there is one. */
			if (ice_check_list_send_triggered_check(cl, rtp_session) != NULL) return;

			/* Send ordinary connectivity checks only when the check list is Running and active. */
			if (ice_check_list_is_frozen(cl)) {
				ice_compute_pairs_states(cl); /* Begin processing on this check list. */
			} else {
				/* Send an ordinary connectivity check for the pair in the Waiting state and with the highest priority
				 * if there is one. */
				state = ICP_Waiting;
				elem = bctbx_list_find_custom(cl->check_list, (bctbx_compare_func)ice_find_pair_from_state, &state);
				if (elem != NULL) {
					pair = (IceCandidatePair *)elem->data;
					ice_send_binding_request(cl, pair, rtp_session);
					return;
				}

				/* Send an ordinary connectivity check for the pair in the Frozen state and with the highest priority if
				 * there is one. */
				state = ICP_Frozen;
				elem = bctbx_list_find_custom(cl->check_list, (bctbx_compare_func)ice_find_pair_from_state, &state);
				if (elem != NULL) {
					pair = (IceCandidatePair *)elem->data;
					ice_send_binding_request(cl, pair, rtp_session);
					return;
				}

				/* Check if there are some retransmissions pending. */
				bctbx_list_for_each2(cl->check_list, (void (*)(void *, void *))ice_check_retransmissions_pending,
				                     &retransmissions_pending);
				if (retransmissions_pending == FALSE) {
					ms_message("ice: There is no connectivity check left to be sent and no retransmissions pending, "
					           "concluding checklist [%p]",
					           cl);
					ice_conclude_processing(cl, rtp_session, FALSE);
				}
			}
			break;
		case ICL_Failed:
			/* Nothing to be done. */
			break;
	}
}

/******************************************************************************
 * OTHER FUNCTIONS                                                            *
 *****************************************************************************/

static MSTimeSpec ice_current_time(void) {
	MSTimeSpec cur_time;
	ms_get_cur_time(&cur_time);
	return cur_time;
}

static MSTimeSpec ice_add_ms(MSTimeSpec orig, uint32_t ms) {
	if (ms == 0) return orig;
	orig.tv_sec += ms / 1000;
	orig.tv_nsec += (ms % 1000) * 1000000;
	return orig;
}

static int32_t ice_compare_time(MSTimeSpec ts1, MSTimeSpec ts2) {
	int32_t ms = (int32_t)((ts1.tv_sec - ts2.tv_sec) * 1000);
	ms += (int32_t)((ts1.tv_nsec - ts2.tv_nsec) / 1000000);
	return ms;
}

static void transactionID2string(const UInt96 *tr_id, char *tr_id_str) {
	int j, pos;

	for (j = 0, pos = 0; j < 12; j++) {
		pos += sprintf(&tr_id_str[pos], "%02x", ((unsigned char *)tr_id)[j]);
	}
	tr_id_str[pos] = '\0';
}

static void ice_set_credentials(char **ufrag, char **pwd, const char *ufrag_str, const char *pwd_str) {
	size_t len_ufrag = MIN(strlen(ufrag_str), ICE_MAX_UFRAG_LEN);
	size_t len_pwd = MIN(strlen(pwd_str), ICE_MAX_PWD_LEN);

	if (*ufrag) ms_free(*ufrag);
	if (*pwd) ms_free(*pwd);
	*ufrag = ms_strdup(ufrag_str);
	*pwd = ms_strdup(pwd_str);
	(*ufrag)[len_ufrag] = '\0';
	(*pwd)[len_pwd] = '\0';
}

static int ice_find_host_candidate(const IceCandidate *candidate, const ComponentID_Family *cf) {
	if ((candidate->type == ICT_HostCandidate) && (candidate->componentID == cf->componentID) &&
	    (candidate->taddr.family == cf->family))
		return 0;
	else return 1;
}

static void ice_set_base_for_srflx_candidate(IceCandidate *candidate, IceCandidate *base) {
	if ((candidate->type == ICT_ServerReflexiveCandidate) && (candidate->base == NULL) &&
	    (candidate->componentID == base->componentID) && candidate->taddr.family == base->taddr.family) {
		candidate->base = base;
	}
}

static void ice_set_base_for_srflx_candidate_with_componentID(uint16_t *componentID, IceCheckList *cl) {
	IceCandidate *base;
	ComponentID_Family cf = {*componentID, AF_INET};
	bctbx_list_t *elem = bctbx_list_find_custom(cl->local_candidates, (bctbx_compare_func)ice_find_host_candidate, &cf);
	if (elem != NULL) {
		base = (IceCandidate *)elem->data;
		bctbx_list_for_each2(cl->local_candidates, (void (*)(void *, void *))ice_set_base_for_srflx_candidate,
		                     (void *)base);
	}
	cf.componentID = *componentID;
	cf.family = AF_INET6;
	elem = bctbx_list_find_custom(cl->local_candidates, (bctbx_compare_func)ice_find_host_candidate, &cf);
	if (elem != NULL) {
		base = (IceCandidate *)elem->data;
		bctbx_list_for_each2(cl->local_candidates, (void (*)(void *, void *))ice_set_base_for_srflx_candidate,
		                     (void *)base);
	}
}

static void ice_check_list_set_base_for_srflx_candidates(IceCheckList *cl) {
	bctbx_list_for_each2(cl->local_componentIDs,
	                     (void (*)(void *, void *))ice_set_base_for_srflx_candidate_with_componentID, cl);
}

void ice_session_set_base_for_srflx_candidates(IceSession *session) {
	int i;
	for (i = 0; i < ICE_SESSION_MAX_CHECK_LISTS; i++) {
		if (session->streams[i] != NULL) ice_check_list_set_base_for_srflx_candidates(session->streams[i]);
	}
}

static int ice_find_candidate_with_componentID(const IceCandidate *candidate, const uint16_t *componentID) {
	if (candidate->componentID == *componentID) return 0;
	else return 1;
}

/* same as bctbx_list_remove() but without warning if the element is not found */
static bctbx_list_t *remove_from_list(bctbx_list_t *l, void *data) {
	bctbx_list_t *elem = bctbx_list_find(l, data);
	return elem ? bctbx_list_erase_link(l, elem) : l;
}

static void ice_remove_transaction_with_pair(IceCheckList *cl, IceCandidatePair *pair) {
	bctbx_list_t *elem;
	bctbx_list_t *next_elem;

	for (elem = cl->transaction_list; elem != NULL;) {
		IceTransaction *tr = (IceTransaction *)elem->data;
		next_elem = elem->next;
		if (tr->pair == pair) {
			ice_free_transaction(tr);
			cl->transaction_list = bctbx_list_erase_link(cl->transaction_list, elem);
		}
		elem = next_elem;
	}
}

static void ice_clean_rtcp_candidate_pairs(IceCheckList *cl) {
	bctbx_list_t *elem;
	bctbx_list_t *next_elem;

	for (elem = cl->pairs; elem != NULL;) {
		IceCandidatePair *pair = (IceCandidatePair *)elem->data;
		next_elem = elem->next;
		if (pair->local->componentID == ICE_RTCP_COMPONENT_ID) {
			/* remove possible transaction using this pair*/
			ice_remove_transaction_with_pair(cl, pair);
			/* remove pair from triggered check queue */
			cl->triggered_checks_queue = remove_from_list(cl->triggered_checks_queue, pair);
			/* remove from losing pair */
			cl->losing_pairs = remove_from_list(cl->losing_pairs, pair);
			/*
			 * ice_free_candidate_pair() will also remove pair from check list and valid list.
			 */
			ice_free_candidate_pair(pair, cl);
			cl->pairs = bctbx_list_erase_link(cl->pairs, elem);
		}
		elem = next_elem;
	}
}

void ice_check_list_remove_rtcp_candidates(IceCheckList *cl) {
	bctbx_list_t *elem;
	uint16_t rtcp_componentID = ICE_RTCP_COMPONENT_ID;

	ice_remove_componentID(&cl->local_componentIDs, rtcp_componentID);

	/* Remove pairs with rtcp component ID*/
	ice_clean_rtcp_candidate_pairs(cl);

	while ((elem = bctbx_list_find_custom(cl->local_candidates, (bctbx_compare_func)ice_find_candidate_with_componentID,
	                                      &rtcp_componentID)) != NULL) {
		IceCandidate *candidate = (IceCandidate *)elem->data;
		cl->local_candidates = bctbx_list_remove(cl->local_candidates, candidate);
		ice_free_candidate(candidate);
	}
	ice_remove_componentID(&cl->remote_componentIDs, rtcp_componentID);
	while (
	    (elem = bctbx_list_find_custom(cl->remote_candidates, (bctbx_compare_func)ice_find_candidate_with_componentID,
	                                   &rtcp_componentID)) != NULL) {
		IceCandidate *candidate = (IceCandidate *)elem->data;
		cl->remote_candidates = bctbx_list_remove(cl->remote_candidates, candidate);
		ice_free_candidate(candidate);
	}
}

/******************************************************************************
 * RESULT ACCESSORS                                                           *
 *****************************************************************************/

static void ice_get_valid_pair_for_componentID(const uint16_t *componentID, CheckList_MSListPtr *cm) {
	bctbx_list_t *elem = bctbx_list_find_custom(
	    cm->cl->valid_list, (bctbx_compare_func)ice_find_nominated_valid_pair_from_componentID, componentID);
	if (elem != NULL) {
		IceValidCandidatePair *valid_pair = (IceValidCandidatePair *)elem->data;
		*cm->list = bctbx_list_append(*cm->list, valid_pair->valid);
	}
}

static bctbx_list_t *ice_get_valid_pairs(const IceCheckList *cl) {
	CheckList_MSListPtr cm;
	bctbx_list_t *valid_pairs = NULL;

	cm.cl = cl;
	cm.list = &valid_pairs;
	bctbx_list_for_each2(cl->local_componentIDs, (void (*)(void *, void *))ice_get_valid_pair_for_componentID, &cm);
	return valid_pairs;
}

static void ice_get_remote_transport_addresses_from_valid_pair(const IceCandidatePair *pair,
                                                               TransportAddresses *taddrs) {
	if (pair->local->componentID == 1) {
		*(taddrs->rtp_taddr) = &pair->remote->taddr;
	} else if (pair->local->componentID == 2) {
		*(taddrs->rtcp_taddr) = &pair->remote->taddr;
	}
}

void ice_get_remote_transport_addresses_from_valid_pairs(const IceCheckList *cl,
                                                         IceTransportAddress **rtp_taddr,
                                                         IceTransportAddress **rtcp_taddr) {
	TransportAddresses taddrs;
	bctbx_list_t *ice_pairs = ice_get_valid_pairs(cl);
	taddrs.rtp_taddr = rtp_taddr;
	taddrs.rtcp_taddr = rtcp_taddr;
	bctbx_list_for_each2(ice_pairs, (void (*)(void *, void *))ice_get_remote_transport_addresses_from_valid_pair,
	                     &taddrs);
	bctbx_list_free(ice_pairs);
}

static void ice_get_local_transport_address_from_valid_pair(const IceCandidatePair *pair, TransportAddresses *taddrs) {
	if (pair->local->componentID == 1) {
		*(taddrs->rtp_taddr) = &pair->local->taddr;
	} else if (pair->local->componentID == 2) {
		*(taddrs->rtcp_taddr) = &pair->local->taddr;
	}
}

static void ice_get_local_transport_addresses_from_valid_pairs(const IceCheckList *cl,
                                                               IceTransportAddress **rtp_taddr,
                                                               IceTransportAddress **rtcp_taddr) {
	TransportAddresses taddrs;
	bctbx_list_t *ice_pairs = ice_get_valid_pairs(cl);
	taddrs.rtp_taddr = rtp_taddr;
	taddrs.rtcp_taddr = rtcp_taddr;
	bctbx_list_for_each2(ice_pairs, (void (*)(void *, void *))ice_get_local_transport_address_from_valid_pair, &taddrs);
	bctbx_list_free(ice_pairs);
}

void ice_check_list_print_route(const IceCheckList *cl, const char *message) {
	char local_rtp_addr[64], local_rtcp_addr[64];
	char remote_rtp_addr[64], remote_rtcp_addr[64];
	IceTransportAddress *local_rtp_taddr = NULL;
	IceTransportAddress *local_rtcp_taddr = NULL;
	IceTransportAddress *remote_rtp_taddr = NULL;
	IceTransportAddress *remote_rtcp_taddr = NULL;

	if (cl->state == ICL_Completed) {
		ice_get_local_transport_addresses_from_valid_pairs(cl, &local_rtp_taddr, &local_rtcp_taddr);
		ice_get_remote_transport_addresses_from_valid_pairs(cl, &remote_rtp_taddr, &remote_rtcp_taddr);
		ice_transport_address_to_printable_ip_address(local_rtp_taddr, local_rtp_addr, sizeof(local_rtp_addr));
		ice_transport_address_to_printable_ip_address(local_rtcp_taddr, local_rtcp_addr, sizeof(local_rtcp_addr));
		ice_transport_address_to_printable_ip_address(remote_rtp_taddr, remote_rtp_addr, sizeof(remote_rtp_addr));
		ice_transport_address_to_printable_ip_address(remote_rtcp_taddr, remote_rtcp_addr, sizeof(remote_rtcp_addr));
		ms_message("%s", message);
		ms_message("\tRTP: %s --> %s", local_rtp_addr, remote_rtp_addr);
		ms_message("\tRTCP: %s --> %s", local_rtcp_addr, remote_rtcp_addr);
	}
}

/******************************************************************************
 * DEBUG FUNCTIONS                                                            *
 *****************************************************************************/

void ice_dump_session(const IceSession *session) {
	if (session == NULL) return;
	ms_message("Session:\n"
	           "\trole=%s tie-breaker=%" PRIx64 "\n"
	           "\tlocal_ufrag=%s local_pwd=%s\n\tremote_ufrag=%s remote_pwd=%s",
	           role_values[session->role], session->tie_breaker, session->local_ufrag, session->local_pwd,
	           session->remote_ufrag, session->remote_pwd);
}

static void ice_dump_candidate(const IceCandidate *candidate, const char *const prefix) {
	ms_message("%s[%p]: %stype=%s ip=%s port=%u componentID=%d priority=%u foundation=%s base=%p", prefix, candidate,
	           ((candidate->is_default == TRUE) ? "* " : "  "), candidate_type_values[candidate->type],
	           candidate->taddr.ip, candidate->taddr.port, candidate->componentID, candidate->priority,
	           candidate->foundation, candidate->base);
}

void ice_dump_candidates(const IceCheckList *cl) {
	if (cl == NULL) return;
	ms_message("Local candidates:");
	bctbx_list_for_each2(cl->local_candidates, (void (*)(void *, void *))ice_dump_candidate, "\t");
	ms_message("Remote candidates:");
	bctbx_list_for_each2(cl->remote_candidates, (void (*)(void *, void *))ice_dump_candidate, "\t");
}

static void ice_dump_candidate_pair(const IceCandidatePair *pair, int *i) {
	ms_message("\t%d [%p]: %sstate=%s use=%d nominated=%d priority=%" PRIu64, *i, pair,
	           ((pair->is_default == TRUE) ? "* " : "  "), candidate_pair_state_values[pair->state],
	           pair->use_candidate, pair->is_nominated, pair->priority);
	ice_dump_candidate(pair->local, "\t\tLocal: ");
	ice_dump_candidate(pair->remote, "\t\tRemote: ");
	(*i)++;
}

void ice_dump_candidate_pairs(const IceCheckList *cl) {
	int i = 1;
	if (cl == NULL) return;
	ms_message("Candidate pairs:");
	bctbx_list_for_each2(cl->pairs, (void (*)(void *, void *))ice_dump_candidate_pair, (void *)&i);
}

static void ice_dump_candidate_pair_foundation(const IcePairFoundation *foundation) {
	ms_message("\t%s\t%s", foundation->local, foundation->remote);
}

void ice_dump_candidate_pairs_foundations(const IceCheckList *cl) {
	if (cl == NULL) return;
	ms_message("Candidate pairs foundations:");
	bctbx_list_for_each(cl->foundations, (void (*)(void *))ice_dump_candidate_pair_foundation);
}

static void ice_dump_valid_pair(const IceValidCandidatePair *valid_pair, int *i) {
	int j = *i;
	ice_dump_candidate_pair(valid_pair->valid, &j);
	if (valid_pair->selected) {
		ms_message("\t--> selected");
	}
	*i = j;
}

void ice_dump_valid_list(const IceCheckList *cl) {
	int i = 1;
	if (cl == NULL) return;
	ms_message("Valid list:");
	bctbx_list_for_each2(cl->valid_list, (void (*)(void *, void *))ice_dump_valid_pair, &i);
}

void ice_dump_check_list(const IceCheckList *cl) {
	int i = 1;
	if (cl == NULL) return;
	ms_message("Check list:");
	bctbx_list_for_each2(cl->check_list, (void (*)(void *, void *))ice_dump_candidate_pair, (void *)&i);
}

void ice_dump_triggered_checks_queue(const IceCheckList *cl) {
	int i = 1;
	if (cl == NULL) return;
	ms_message("Triggered checks queue:");
	bctbx_list_for_each2(cl->triggered_checks_queue, (void (*)(void *, void *))ice_dump_candidate_pair, (void *)&i);
}

static void ice_dump_componentID(const uint16_t *componentID) {
	ms_message("\t%u", *componentID);
}

void ice_dump_componentIDs(const IceCheckList *cl) {
	if (cl == NULL) return;
	ms_message("Component IDs:");
	bctbx_list_for_each(cl->local_componentIDs, (void (*)(void *))ice_dump_componentID);
}
const char *ice_check_list_state_to_string(const IceCheckListState state) {
	switch (state) {
		case ICL_Running:
			return "ICL_Running";
		case ICL_Completed:
			return "ICL_Completed";
		case ICL_Failed:
			return "ICL_Failed";
	}
	return "Invalid ICE state";
}
