#include "ice.h"

#include <glib.h>
#include <sys/time.h>
#include <unistd.h>

#include "str.h"
#include "call.h"
#include "helpers.h"
#include "log.h"
#include "obj.h"
#include "stun.h"
#include "poller.h"
#include "log_funcs.h"
#include "timerthread.h"
#include "call_interfaces.h"

#if __DEBUG
#define ICE_DEBUG 1
#else
#define ICE_DEBUG 0
#endif

#if ICE_DEBUG
#define __DBG(x...) ilogs(ice, LOG_DEBUG, x)
#else
#define __DBG(x...) ilogs(internals, LOG_DEBUG, x)
#endif

#define PAIR_FORMAT STR_FORMAT_M ":" STR_FORMAT_M ":%lu"
#define PAIR_FMT(p) 								\
			STR_FMT_M(&(p)->local_intf->ice_foundation),		\
			STR_FMT_M(&(p)->remote_candidate->foundation),		\
			(p)->remote_candidate->component_id

struct sdp_fragment {
	ng_buffer *ngbuf;
	int64_t received;
	sdp_streams_q streams;
	sdp_ng_flags flags;
};



static void __ice_agent_free(struct ice_agent *);
static void create_random_ice_string(call_t *call, str *s, int len);
static void __do_ice_checks(struct ice_agent *ag);
static struct ice_candidate_pair *__pair_lookup(struct ice_agent *, struct ice_candidate *cand,
		const struct local_intf *ifa);
static void __recalc_pair_prios(struct ice_agent *ag);
static void __role_change(struct ice_agent *ag, int new_controlling);
static void __get_complete_components(candidate_pair_q *out, struct ice_agent *ag, GTree *t, unsigned int);
static void __agent_schedule(struct ice_agent *ag, int64_t);
static void __agent_schedule_abs(struct ice_agent *ag, int64_t tv);
static void __agent_deschedule(struct ice_agent *ag);
static void __ice_agent_free_components(struct ice_agent *ag);
static void __agent_shutdown(struct ice_agent *ag);
static void ice_agents_timer_run(void *);



static uint64_t tie_breaker;

static struct timerthread ice_agents_timer_thread;

static const char ice_chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

const unsigned int ice_type_preferences[] = {
	[ICT_UNKNOWN]	= 0,
	[ICT_HOST]	= 126,
	[ICT_SRFLX]	= 100,
	[ICT_PRFLX]	= 110,
	[ICT_RELAY]	= 0,
};

const char * const ice_type_strings[] = {
	[ICT_UNKNOWN]	= "unknown",
	[ICT_HOST]	= "host",
	[ICT_SRFLX]	= "srflx",
	[ICT_PRFLX]	= "prflx",
	[ICT_RELAY]	= "relay",
};



TYPED_GHASHTABLE_LOOKUP_INSERT(fragments_ht, NULL, fragment_q_new)



static void ice_update_media_streams(struct call_monologue *ml, sdp_streams_q *streams,
		sdp_ng_flags *flags)
{
	for (__auto_type l = streams->head; l; l = l->next) {
		struct stream_params *sp = l->data;
		struct call_media *media = NULL;

		if (sp->media_id.len)
			media = t_hash_table_lookup(ml->media_ids, &sp->media_id);
		else if (sp->index > 0) {
			unsigned int arr_idx = sp->index - 1;
			if (arr_idx < ml->medias->len)
				media = ml->medias->pdata[arr_idx];
		}

		if (!media) {
			ilogs(ice, LOG_WARN, "No matching media for trickle ICE update found");
			continue;
		}

		if (!media->ice_agent) {
			ilogs(ice, LOG_WARN, "Media for trickle ICE update is not ICE-enabled");
			continue;
		}
		if (!MEDIA_ISSET(media, TRICKLE_ICE)) {
			ilogs(ice, LOG_WARN, "Media for trickle ICE update is not trickle-ICE-enabled");
			continue;
		}

		ice_update(media->ice_agent, sp, false);
	}
}


static void fragment_free(struct sdp_fragment *frag) {
	sdp_streams_clear(&frag->streams);
	call_ng_free_flags(&frag->flags);
	obj_put(frag->ngbuf);
	g_free(frag);
}
static void queue_sdp_fragment(ng_buffer *ngbuf, call_t *call, str *key, sdp_streams_q *streams, sdp_ng_flags *flags) {
	ilog(LOG_DEBUG, "Queuing up SDP fragment for " STR_FORMAT_M "/" STR_FORMAT_M,
			STR_FMT_M(&flags->call_id), STR_FMT_M(&flags->from_tag));

	struct sdp_fragment *frag = g_new0(__typeof(*frag), 1);
	frag->received = rtpe_now;
	frag->ngbuf = obj_get(ngbuf);
	if (streams) {
		frag->streams = *streams;
		t_queue_init(streams);
	}
	frag->flags = *flags;
	ZERO(*flags);

	fragment_q *frags = fragments_ht_lookup_insert(call->sdp_fragments, call_str_dup(key));
	t_queue_push_tail(frags, frag);
}
bool trickle_ice_update(ng_buffer *ngbuf, call_t *call, sdp_ng_flags *flags,
		sdp_streams_q *streams)
{
	if (!flags->fragment)
		return false;

	struct call_monologue *ml = call_get_monologue(call, &flags->from_tag);
	if (!ml) {
		queue_sdp_fragment(ngbuf, call, &flags->from_tag, streams, flags);
		return true;
	}

	ice_update_media_streams(ml, streams, flags);

	return true;
}
#define MAX_FRAG_AGE 3000000
void dequeue_sdp_fragments(struct call_monologue *monologue) {
	call_t *call = monologue->call;

	fragment_q *frags = NULL;

	t_hash_table_steal_extended(call->sdp_fragments, &monologue->tag, NULL, &frags);
	if (!frags)
		return;

	// we own the queue now

	struct sdp_fragment *frag;
	while ((frag = t_queue_pop_head(frags))) {
		if (rtpe_now - frag->received > MAX_FRAG_AGE)
			goto next;

		ilog(LOG_DEBUG, "Dequeuing SDP fragment for " STR_FORMAT_M "/" STR_FORMAT_M,
				STR_FMT_M(&call->callid), STR_FMT_M(&monologue->tag));

		ice_update_media_streams(monologue, &frag->streams, &frag->flags);

next:
		fragment_free(frag);
	}

	t_queue_free(frags);
}
static gboolean fragment_check_cleanup(str *key, fragment_q *frags, void *p) {
	bool all = GPOINTER_TO_INT(p);
	if (!key || !frags)
		return TRUE;
	while (frags->length) {
		struct sdp_fragment *frag = frags->head->data;
		if (!all && rtpe_now - frag->received <= MAX_FRAG_AGE)
			break;
		t_queue_pop_head(frags);
		fragment_free(frag);
	}
	if (!frags->length) {
		t_queue_free(frags);
		return TRUE;
	}
	return FALSE;
}
void ice_fragments_cleanup(fragments_ht ht, bool all) {
	t_hash_table_foreach_remove(ht, fragment_check_cleanup, GINT_TO_POINTER(all));
}



enum ice_candidate_type ice_candidate_type(const str *s) {
	int i;
	for (i = 1; i < G_N_ELEMENTS(ice_type_strings); i++) {
		if (!str_cmp(s, ice_type_strings[i]))
			return i;
	}
	return ICT_UNKNOWN;
}

bool ice_has_related(enum ice_candidate_type t) {
	if (t == ICT_HOST)
		return false;
	/* ignoring ICT_UNKNOWN */
	return true;
}



static uint64_t __ice_pair_priority(const struct local_intf *ifa, struct ice_candidate *cand,
		int controlling)
{
	uint64_t g, d;

	g = ice_priority(ICT_HOST, ifa->unique_id, cand->component_id);
	d = cand->priority;

	if (!controlling) {
		uint64_t t = g;
		g = d;
		d = t;
	}

	return (MIN(g,d) << 32) + (MAX(g,d) << 1) + (g > d ? 1 : 0);
}
static void __do_ice_pair_priority(struct ice_candidate_pair *pair) {
	pair->pair_priority = __ice_pair_priority(pair->local_intf, pair->remote_candidate,
			AGENT_ISSET(pair->agent, CONTROLLING));
}
static void __new_stun_transaction(struct ice_candidate_pair *pair) {
	struct ice_agent *ag = pair->agent;

	t_hash_table_remove(ag->transaction_hash, pair->stun_transaction);
	random_string((void *) pair->stun_transaction, sizeof(pair->stun_transaction));
	t_hash_table_insert(ag->transaction_hash, pair->stun_transaction, pair);
}

/* agent must be locked */
static void __all_pairs_list(struct ice_agent *ag) {
	t_queue_clear(&ag->all_pairs_list);
	rtpe_g_tree_get_values(&ag->all_pairs_list.q, ag->all_pairs);
}

static void __tree_coll_callback(void *oo, void *nn) {
	struct ice_candidate_pair *o = oo, *n = nn;
	ilogs(ice, LOG_WARN | LOG_FLAG_LIMIT, "Priority collision between candidate pairs " PAIR_FORMAT " and "
			PAIR_FORMAT " - ICE will likely fail",
			PAIR_FMT(o), PAIR_FMT(n));
}

/* agent must be locked */
static struct ice_candidate_pair *__pair_candidate(stream_fd *sfd, struct ice_agent *ag,
		struct ice_candidate *cand)
{
	struct ice_candidate_pair *pair;

	if (sfd->socket.family != cand->endpoint.address.family)
		return NULL;

	pair = g_new0(__typeof(*pair), 1);

	pair->agent = ag;
	pair->remote_candidate = cand;
	pair->local_intf = sfd->local_intf;
	pair->sfd = sfd;
	if (cand->component_id != 1)
		PAIR_SET(pair, FROZEN);
	__do_ice_pair_priority(pair);
	__new_stun_transaction(pair);

	t_queue_push_tail(&ag->candidate_pairs, pair);
	t_hash_table_insert(ag->pair_hash, pair, pair);
	rtpe_g_tree_insert_coll(ag->all_pairs, pair, pair, __tree_coll_callback);

	ilogs(ice, LOG_DEBUG, "Created candidate pair "PAIR_FORMAT" between %s and %s%s%s, type %s", PAIR_FMT(pair),
			sockaddr_print_buf(&sfd->socket.local.address),
			FMT_M(endpoint_print_buf(&cand->endpoint)),
			ice_candidate_type_str(cand->type));

	return pair;
}

static unsigned int __pair_hash(const struct ice_candidate_pair *pair) {
	return GPOINTER_TO_UINT(pair->local_intf) ^ GPOINTER_TO_UINT(pair->remote_candidate);
}
static int __pair_equal(const struct ice_candidate_pair *A, const struct ice_candidate_pair *B) {
	return A->local_intf == B->local_intf
		&& A->remote_candidate == B->remote_candidate;
}
static unsigned int __cand_hash(const struct ice_candidate *cand) {
	return endpoint_hash(&cand->endpoint) ^ cand->component_id;
}
static int __cand_equal(const struct ice_candidate *A, const struct ice_candidate *B) {
	return endpoint_eq(&A->endpoint, &B->endpoint)
		&& A->component_id == B->component_id;
}
static unsigned int __found_hash(const struct ice_candidate *cand) {
	return str_hash(&cand->foundation) ^ cand->component_id;
}
static int __found_equal(const struct ice_candidate *A, const struct ice_candidate *B) {
	return str_equal(&A->foundation, &B->foundation)
		&& A->component_id == B->component_id;
}
static unsigned int __trans_hash(const uint32_t *tp) {
	return tp[0] ^ tp[1] ^ tp[2];
}
static int __trans_equal(const uint32_t *A, const uint32_t *B) {
	return A[0] == B[0] && A[1] == B[1] && A[2] == B[2];
}
static int __pair_prio_cmp(const void *a, const void *b) {
	const struct ice_candidate_pair *A = a, *B = b;
	/* highest priority first */
	if (A->pair_priority < B->pair_priority)
		return 1;
	if (A->pair_priority > B->pair_priority)
		return -1;
	/* lowest component first */
	if (A->remote_candidate->component_id < B->remote_candidate->component_id)
		return -1;
	if (A->remote_candidate->component_id > B->remote_candidate->component_id)
		return 1;
	/* highest local preference first, which is lowest unique_id first */
	if (A->local_intf->unique_id < B->local_intf->unique_id)
		return -1;
	if (A->local_intf->unique_id > B->local_intf->unique_id)
		return 1;
	return 0;
}


TYPED_GHASHTABLE_IMPL(candidate_ht, __cand_hash, __cand_equal, NULL, NULL)
TYPED_GHASHTABLE_IMPL(candidate_pair_ht, __pair_hash, __pair_equal, NULL, NULL)
TYPED_GHASHTABLE_IMPL(foundation_ht, __found_hash, __found_equal, NULL, NULL)
TYPED_GHASHTABLE_IMPL(priority_ht, g_direct_hash, g_direct_equal, NULL, NULL)
TYPED_GHASHTABLE_IMPL(transaction_ht, __trans_hash, __trans_equal, NULL, NULL)

static void __ice_agent_initialize(struct ice_agent *ag) {
	struct call_media *media = ag->media;
	call_t *call = ag->call;

	ag->candidate_hash = candidate_ht_new();
	ag->cand_prio_hash = priority_ht_new();
	ag->pair_hash = candidate_pair_ht_new();
	ag->transaction_hash = transaction_ht_new();
	ag->foundation_hash = foundation_ht_new();
	atomic64_set_na(&ag->agent_flags, 0);
	bf_copy(&ag->agent_flags, ICE_AGENT_CONTROLLING, &media->media_flags, MEDIA_FLAG_ICE_CONTROLLING);
	bf_copy(&ag->agent_flags, ICE_AGENT_LITE_SELF, &media->media_flags, MEDIA_FLAG_ICE_LITE_SELF);
	ag->logical_intf = media->logical_intf;
	ag->desired_family = media->desired_family;
	ag->nominated_pairs = g_tree_new(__pair_prio_cmp);
	ag->valid_pairs = g_tree_new(__pair_prio_cmp);
	ag->succeeded_pairs = g_tree_new(__pair_prio_cmp);
	ag->all_pairs = g_tree_new(__pair_prio_cmp);

	create_random_ice_string(call, &ag->ufrag[1], 8);
	create_random_ice_string(call, &ag->pwd[1], 26);

	atomic64_set_na(&ag->last_activity, rtpe_now);
}

static struct ice_agent *__ice_agent_new(struct call_media *media) {
	struct ice_agent *ag;
	call_t *call = media->call;

	ag = obj_alloc0(struct ice_agent, __ice_agent_free);
	ag->tt_obj.tt = &ice_agents_timer_thread;
	ag->tt_obj.thread = &ice_agents_timer_thread.threads[0]; // there's only one thread
	ag->call = obj_get(call);
	ag->media = media;
	mutex_init(&ag->lock);

	__ice_agent_initialize(ag);

	return ag;
}

/* called with the call lock held in W */
void ice_agent_init(struct ice_agent **agp, struct call_media *media) {
	struct ice_agent *ag;

	if (*agp)
		ag = *agp;
	else
		*agp = ag = __ice_agent_new(media);
}

static int __copy_cand(call_t *call, struct ice_candidate *dst, const struct ice_candidate *src) {
	int eq = (dst->priority == src->priority);
	*dst = *src;
	dst->foundation = call_str_cpy(&src->foundation);
	return eq ? 0 : 1;
}

static void __ice_reset(struct ice_agent *ag) {
	__agent_deschedule(ag);
	AGENT_CLEAR3(ag, COMPLETED, NOMINATING, USABLE);
	__ice_agent_free_components(ag);
	ZERO(ag->active_components);
	ag->start_nominating = 0;
	ag->tt_obj.last_run = 0;
	__ice_agent_initialize(ag);
}

/* if the other side did a restart */
static void __ice_restart(struct ice_agent *ag) {
	ilogs(ice, LOG_DEBUG, "ICE restart detected, resetting ICE agent");

	ag->ufrag[0] = STR_NULL;
	ag->pwd[0] = STR_NULL;
	ag->ufrag[1] = STR_NULL;
	ag->pwd[1] = STR_NULL;
	__ice_reset(ag);
}

/* if we're doing a restart */
void ice_restart(struct ice_agent *ag) {
	ilogs(ice, LOG_DEBUG, "Restarting ICE and resetting ICE agent");

	ag->ufrag[1] = STR_NULL;
	ag->pwd[1] = STR_NULL;
	__ice_reset(ag);
}

/* called with the call lock held in W, hence agent doesn't need to be locked */
void ice_update(struct ice_agent *ag, struct stream_params *sp, bool allow_reset) {
	struct ice_candidate *cand, *dup;
	struct call_media *media;
	call_t *call;
	int recalc = 0;
	unsigned int comps;
	struct packet_stream *components[MAX_COMPONENTS], *ps;
	candidate_q *candidates;
	stream_fd *sfd;

	if (!ag)
		return;

	log_info_ice_agent(ag);

	atomic64_set_na(&ag->last_activity, rtpe_now);
	media = ag->media;
	call = media->call;

	__role_change(ag, MEDIA_ISSET(media, ICE_CONTROLLING));

	if (sp) {
		if (ice_is_restart(ag, sp)) {
			if (!allow_reset)
				ilog(LOG_WARN, "ICE restart detected, but reset not allowed at this point");
			else
				__ice_restart(ag);
		}

		/* update remote info */
		if (sp->ice_ufrag.s)
			ag->ufrag[0] = call_str_cpy(&sp->ice_ufrag);
		if (sp->ice_pwd.s)
			ag->pwd[0] = call_str_cpy(&sp->ice_pwd);

		candidates = &sp->ice_candidates;
	}
	else /* this is a dummy update in case rtcp-mux has changed */
		candidates = &ag->remote_candidates;

	/* get our component streams */
	ZERO(components);
	comps = 0;
	for (__auto_type l = media->streams.head; l; l = l->next)
		components[comps++] = l->data;
	if (comps == 2 && (MEDIA_ISSET(media, RTCP_MUX) || !proto_is_rtp(media->protocol)))
		components[1] = NULL;

	comps = 0;
	for (__auto_type l = candidates->head; l; l = l->next) {
		if (ag->remote_candidates.length >= MAX_ICE_CANDIDATES) {
			ilogs(ice, LOG_WARNING, "Maximum number of ICE candidates exceeded");
			break;
		}

		cand = l->data;

		/* skip invalid */
		if (!cand->component_id || cand->component_id > G_N_ELEMENTS(components))
			continue;
		ps = components[cand->component_id - 1];

		if (ps) /* only count active components */
			comps = MAX(comps, cand->component_id);

		dup = t_hash_table_lookup(ag->candidate_hash, cand);
		if (!sp && dup) /* this isn't a real update, so only check pairings */
			goto pair;

		/* check for duplicates */
		if (dup) {
			/* if this is peer reflexive, we've learned it through STUN.
			 * otherwise it's simply one we've seen before. */
			if (dup->type == ICT_PRFLX) {
				ilogs(ice, LOG_DEBUG, "Replacing previously learned prflx ICE candidate with "
						STR_FORMAT_M ":%lu", STR_FMT_M(&cand->foundation),
						cand->component_id);
			}
			else {
				/* if the new one has higher priority then the old one, then we
				 * update it, otherwise we just drop it */
				if (cand->priority <= dup->priority) {
					ilogs(ice, LOG_DEBUG, "Dropping new ICE candidate " STR_FORMAT_M
							" in favour of "
							STR_FORMAT_M ":%lu",
							STR_FMT_M(&cand->foundation),
							STR_FMT_M(&dup->foundation), cand->component_id);
					continue;
				}

				ilogs(ice, LOG_DEBUG, "Replacing known ICE candidate " STR_FORMAT_M " with higher "
						"priority "
						STR_FORMAT_M ":%lu",
						STR_FMT_M(&dup->foundation),
						STR_FMT_M(&cand->foundation), cand->component_id);
			}

			/* priority and foundation may change */
			t_hash_table_remove(ag->foundation_hash, dup);
			recalc += __copy_cand(call, dup, cand);
		}
		else {
			ilogs(ice, LOG_DEBUG, "Learning new ICE candidate " STR_FORMAT_M ":%lu",
					STR_FMT_M(&cand->foundation), cand->component_id);
			dup = g_new(__typeof(*dup), 1);
			__copy_cand(call, dup, cand);
			t_hash_table_insert(ag->candidate_hash, dup, dup);
			t_hash_table_insert(ag->cand_prio_hash, GUINT_TO_POINTER(dup->priority), dup);
			t_queue_push_tail(&ag->remote_candidates, dup);
		}

		t_hash_table_insert(ag->foundation_hash, dup, dup);

pair:
		if (!ps)
			continue;

		for (__auto_type k = ps->sfds.head; k; k = k->next) {
			sfd = k->data;
			/* skip duplicates here also */
			if (__pair_lookup(ag, dup, sfd->local_intf))
				continue;
			__pair_candidate(sfd, ag, dup);
		}
	}

	if (comps)
		ag->active_components = comps;
	if (!ag->active_components) {
		/* determine components for tricke-ice case */
		comps = 2;
		if (!components[1])
			comps = 1;
		ag->active_components = comps;
	}

	/* if we're here, we can start our ICE checks */
	if (recalc)
		__recalc_pair_prios(ag);
	else
		__all_pairs_list(ag);

	if (comps)
		__do_ice_checks(ag);
	else
		__agent_shutdown(ag);

	log_info_pop();
}


static void ice_candidate_free(struct ice_candidate *p) {
	g_free(p);
}
void ice_candidates_free(candidate_q *q) {
	t_queue_clear_full(q, ice_candidate_free);
}
static void ice_candidate_pair_free(struct ice_candidate_pair *p) {
	g_free(p);
}
static void ice_candidate_pairs_free(candidate_pair_q *q) {
	t_queue_clear_full(q, ice_candidate_pair_free);
}


/* call must be locked */
void ice_shutdown(struct ice_agent **agp) {
	struct ice_agent *ag;

	if (!agp) {
		ilogs(ice, LOG_ERR, "ice agp is NULL");
		return ;
	}

	ag = *agp;
	if (!ag)
		return;

	__agent_deschedule(ag);

	*agp = NULL;
	obj_put(&ag->tt_obj);
}
static void __ice_agent_free_components(struct ice_agent *ag) {
	if (!ag) {
		ilogs(ice, LOG_ERR, "ice ag is NULL");
		return;
	}

	t_queue_clear(&ag->triggered);
	t_hash_table_destroy(ag->candidate_hash);
	t_hash_table_destroy(ag->cand_prio_hash);
	t_hash_table_destroy(ag->pair_hash);
	t_hash_table_destroy(ag->transaction_hash);
	t_hash_table_destroy(ag->foundation_hash);
	g_tree_destroy(ag->all_pairs);
	t_queue_clear(&ag->all_pairs_list);
	g_tree_destroy(ag->nominated_pairs);
	g_tree_destroy(ag->succeeded_pairs);
	g_tree_destroy(ag->valid_pairs);
	ice_candidates_free(&ag->remote_candidates);
	ice_candidate_pairs_free(&ag->candidate_pairs);
}
static void __ice_agent_free(struct ice_agent *ag) {
	if (!ag) {
		ilogs(ice, LOG_ERR, "ice ag is NULL");
		return;
	}

	__DBG("freeing ice_agent");

	__ice_agent_free_components(ag);
	mutex_destroy(&ag->lock);

	obj_put(ag->call);
}


static void __agent_schedule(struct ice_agent *ag, int64_t usec) {
	int64_t nxt;

	nxt = rtpe_now;
	nxt += usec;
	__agent_schedule_abs(ag, nxt);
}
static void __agent_schedule_abs(struct ice_agent *ag, int64_t tv) {
	int64_t nxt;
	int64_t diff;

	if (!ag)
		return;

	nxt = tv;

	struct timerthread_thread *tt = ag->tt_obj.thread;

	mutex_lock(&tt->lock);
	if (ag->tt_obj.last_run) {
		/* make sure we don't run more often than we should */
		diff = nxt - ag->tt_obj.last_run;
		if (diff < TIMER_RUN_INTERVAL * 1000)
			nxt += TIMER_RUN_INTERVAL * 1000 - diff;
	}
	timerthread_obj_schedule_abs_nl(&ag->tt_obj, nxt);
	mutex_unlock(&tt->lock);
}
static void __agent_deschedule(struct ice_agent *ag) {
	if (ag)
		timerthread_obj_deschedule(&ag->tt_obj);
}

void ice_init(void) {
	random_string((void *) &tie_breaker, sizeof(tie_breaker));
	timerthread_init(&ice_agents_timer_thread, 1, ice_agents_timer_run);
}

void ice_free(void) {
	timerthread_free(&ice_agents_timer_thread);
}

static void __fail_pair(struct ice_candidate_pair *pair) {
	ilogs(ice, LOG_DEBUG, "Setting ICE candidate pair "PAIR_FORMAT" as failed", PAIR_FMT(pair));
	PAIR_SET(pair, FAILED);
	PAIR_CLEAR(pair, IN_PROGRESS);
}

/* agent must NOT be locked, but call must be locked in R */
static void __do_ice_check(struct ice_candidate_pair *pair) {
	stream_fd *sfd = pair->sfd;
	struct ice_agent *ag = pair->agent;
	uint32_t prio, transact[3];

	if (AGENT_ISSET(ag, LITE_SELF))
		PAIR_SET(pair, SUCCEEDED);

	if (PAIR_ISSET(pair, SUCCEEDED) && !PAIR_ISSET(pair, TO_USE))
		return;

	if (!ag->pwd[0].s)
		return;

	prio = ice_priority(ICT_PRFLX, pair->local_intf->unique_id,
			pair->remote_candidate->component_id);

	mutex_lock(&ag->lock);

	pair->retransmit = rtpe_now;
	if (!PAIR_SET(pair, IN_PROGRESS)) {
		PAIR_CLEAR2(pair, FROZEN, FAILED);
		pair->retransmit_ms = STUN_RETRANSMIT_INTERVAL;
		pair->retransmits = 0;
	}
	else if (pair->retransmits > STUN_MAX_RETRANSMITS) {
		__fail_pair(pair);
		mutex_unlock(&ag->lock);
		return;
	}
	else {
		pair->retransmit_ms *= 2;
		pair->retransmits++;
	}
	pair->retransmit += pair->retransmit_ms * 1000; // XXX convert to micro
	__agent_schedule_abs(pair->agent, pair->retransmit);
	memcpy(transact, pair->stun_transaction, sizeof(transact));

	pair->was_controlling = AGENT_ISSET(ag, CONTROLLING);
	pair->was_nominated = PAIR_ISSET(pair, TO_USE);

	mutex_unlock(&ag->lock);

	ilogs(ice, LOG_DEBUG, "Sending %sICE/STUN request for candidate pair "PAIR_FORMAT" from %s to %s%s%s",
			PAIR_ISSET(pair, TO_USE) ? "nominating " : "",
			PAIR_FMT(pair), sockaddr_print_buf(&pair->local_intf->spec->local_address.addr),
			FMT_M(endpoint_print_buf(&pair->remote_candidate->endpoint)));

	stun_binding_request(&pair->remote_candidate->endpoint, transact, &ag->pwd[0], ag->ufrag,
			AGENT_ISSET(ag, CONTROLLING), tie_breaker,
			prio, &sfd->socket,
			PAIR_ISSET(pair, TO_USE));

}

static int __component_find(const void *a, const void *b) {
	const struct ice_candidate_pair *A = a;
	unsigned int comp = GPOINTER_TO_UINT(b);
	if (A->remote_candidate->component_id == comp)
		return TRUE;
	return FALSE;
}
static struct ice_candidate_pair *__get_pair_by_component(GTree *t, unsigned int component) {
	return rtpe_g_tree_find_first(t, __component_find, GUINT_TO_POINTER(component));
}
static void __get_pairs_by_component(candidate_pair_q *out, GTree *t, unsigned int component) {
	rtpe_g_tree_find_all(&out->q, t, __component_find, GUINT_TO_POINTER(component));
}

static void __get_complete_succeeded_pairs(candidate_pair_q *out, struct ice_agent *ag) {
	__get_complete_components(out, ag, ag->succeeded_pairs, ICE_PAIR_SUCCEEDED);
}
static void __get_complete_valid_pairs(candidate_pair_q *out, struct ice_agent *ag) {
	__get_complete_components(out, ag, ag->valid_pairs, ICE_PAIR_VALID);
}

static void __nominate_pairs(struct ice_agent *ag) {
	candidate_pair_q complete;
	struct ice_candidate_pair *pair;

	ilogs(ice, LOG_DEBUG, "Start nominating ICE pairs");

	AGENT_SET(ag, NOMINATING);
	ag->start_nominating = 0;

	__get_complete_succeeded_pairs(&complete, ag);

	for (__auto_type l = complete.head; l; l = l->next) {
		pair = l->data;
		ilogs(ice, LOG_DEBUG, "Nominating ICE pair "PAIR_FORMAT, PAIR_FMT(pair));
		PAIR_CLEAR(pair, IN_PROGRESS);
		PAIR_SET2(pair, NOMINATED, TO_USE);
		pair->retransmits = 0;
		__new_stun_transaction(pair);
		t_queue_push_tail(&ag->triggered, pair);
	}

	t_queue_clear(&complete);
}

/* call must be locked R or W, agent must not be locked */
static void __do_ice_checks(struct ice_agent *ag) {
	struct ice_candidate_pair *pair, *highest = NULL, *frozen = NULL, *valid;
	stream_fd *sfd;
	GQueue retransmits = G_QUEUE_INIT;
	int64_t next_run = 0;
	int have_more = 0;

	if (!ag) {
		ilogs(ice, LOG_ERR, "ice ag is NULL");
		return;
	}

	if (!ag->pwd[0].s)
		return;

	atomic64_set_na(&ag->last_activity, rtpe_now);

	__DBG("running checks, call "STR_FORMAT" tag "STR_FORMAT"", STR_FMT(&ag->call->callid),
			STR_FMT(&ag->media->monologue->tag));

	mutex_lock(&ag->lock);

	/* check if we're done and should start nominating pairs */
	if (AGENT_ISSET(ag, CONTROLLING) && !AGENT_ISSET(ag, NOMINATING) && ag->start_nominating) {
		if (rtpe_now >= ag->start_nominating)
			__nominate_pairs(ag);
		next_run = timeval_lowest(next_run, ag->start_nominating);
	}

	/* triggered checks are preferred */
	pair = t_queue_pop_head(&ag->triggered);
	if (pair) {
		__DBG("running triggered check on " PAIR_FORMAT, PAIR_FMT(pair));
		PAIR_CLEAR(pair, TRIGGERED);
		next_run = rtpe_now;
		goto check;
	}

	/* find the highest-priority non-frozen non-in-progress pair */
	for (__auto_type l = ag->all_pairs_list.head; l; l = l->next) {
		pair = l->data;

		__DBG("considering checking " PAIR_FORMAT, PAIR_FMT(pair));

		/* skip dead streams */
		sfd = pair->sfd;
		if (!sfd || !sfd->stream || !sfd->stream->selected_sfd)
			continue;
		if (PAIR_ISSET(pair, FAILED))
			continue;
		if (PAIR_ISSET(pair, SUCCEEDED) && !PAIR_ISSET(pair, TO_USE))
			continue;

		valid = __get_pair_by_component(ag->valid_pairs, pair->remote_candidate->component_id);

		if (PAIR_ISSET(pair, IN_PROGRESS)) {
			/* handle retransmits */
			/* but only if our priority is lower than any valid pair */
			if (valid && valid->pair_priority > pair->pair_priority)
				continue;

			if (pair->retransmit <= rtpe_now)
				g_queue_push_tail(&retransmits, pair); /* can't run check directly
									  due to locks */
			else
				next_run = timeval_lowest(next_run, pair->retransmit);
			continue;
		}

		/* don't do anything else if we already have a valid pair */
		if (valid)
			continue;
		/* or if we're in or past the final phase */
		if (AGENT_ISSET2(ag, NOMINATING, COMPLETED))
			continue;

		have_more = 1;

		/* remember the first frozen pair in case we find nothing else */
		if (PAIR_ISSET(pair, FROZEN)) {
			__DBG("pair " PAIR_FORMAT " is frozen", PAIR_FMT(pair));
			if (!frozen)
				frozen = pair;
			continue;
		}

		if (!highest)
			highest = pair;
	}

	if (highest) {
		pair = highest;
		__DBG("checking highest priority pair " PAIR_FORMAT, PAIR_FMT(pair));
	}
	else if (frozen) {
		pair = frozen;
		__DBG("checking highest priority frozen pair " PAIR_FORMAT, PAIR_FMT(pair));
	}
	else
		pair = NULL;

check:
	mutex_unlock(&ag->lock);

	if (pair)
		__do_ice_check(pair);

	while ((pair = g_queue_pop_head(&retransmits)))
		__do_ice_check(pair);


	/* determine when to run next */
	if (have_more)
		__agent_schedule(ag, 0);
	else if (next_run)
		__agent_schedule_abs(ag, next_run); /* for retransmits */
}

static void __agent_shutdown(struct ice_agent *ag) {
	ilogs(ice, LOG_DEBUG, "Shutting down ICE agent (nothing to do)");
	__agent_deschedule(ag);
}

/* agent must be locked for these */
static struct ice_candidate *__cand_lookup(struct ice_agent *ag, const endpoint_t *sin,
		unsigned int component)
{
	struct ice_candidate d;

	d.endpoint = *sin;
	d.component_id = component;
	return t_hash_table_lookup(ag->candidate_hash, &d);
}
static struct ice_candidate *__foundation_lookup(struct ice_agent *ag, const str *foundation,
		unsigned int component)
{
	struct ice_candidate d;

	d.foundation = *foundation;
	d.component_id = component;
	return t_hash_table_lookup(ag->foundation_hash, &d);
}
static struct ice_candidate_pair *__pair_lookup(struct ice_agent *ag, struct ice_candidate *cand,
		const struct local_intf *ifa)
{
	struct ice_candidate_pair p;

	p.local_intf = ifa;
	p.remote_candidate = cand;
	return t_hash_table_lookup(ag->pair_hash, &p);
}

static void __cand_ice_foundation(call_t *call, struct ice_candidate *cand) {
	char buf[64];
	int len;

	len = sprintf(buf, "%x%x%x", endpoint_hash(&cand->endpoint),
			cand->type, GPOINTER_TO_UINT(cand->transport));
	cand->foundation = call_str_cpy_len(buf, len);
}

/* agent must be locked */
static struct ice_candidate_pair *__learned_candidate(struct ice_agent *ag, stream_fd *sfd,
		const endpoint_t *src, unsigned long priority)
{
	struct ice_candidate *cand, *old_cand;
	struct ice_candidate_pair *pair;
	call_t *call = ag->call;
	struct packet_stream *ps = sfd->stream;

	cand = g_new0(__typeof(*cand), 1);
	cand->component_id = ps->component;
	cand->transport = sfd->local_intf->spec->local_address.type; // XXX add socket type into socket_t?
	cand->priority = priority;
	cand->endpoint = *src;
	cand->type = ICT_PRFLX;

	// check if we've already learned another candidate that belongs to this one. use the priority number
	// together with the component to guess a matching other candidate.
	unsigned long prio_base = priority + ps->component;
	struct ice_candidate *known_cand = NULL;
	for (unsigned int comp = 1; comp <= ag->active_components; comp++) {
		if (comp == ps->component)
			continue;
		unsigned long prio = prio_base - comp;
		known_cand = t_hash_table_lookup(ag->cand_prio_hash, GUINT_TO_POINTER(prio));
		if (known_cand)
			break;
	}
	if (known_cand) {
		// got one. use the previously learned generated ICE foundation string also for this one:
		cand->foundation = known_cand->foundation;
	}
	else {
		// make new:
		__cand_ice_foundation(call, cand);
	}

	old_cand = __foundation_lookup(ag, &cand->foundation, ps->component);
	if (old_cand && old_cand->priority > priority) {
		/* this is possible if two distinct requests are received from the same NAT IP
		 * address, but from different ports. we cannot distinguish such candidates and
		 * will drop the one with the lower priority */
		g_free(cand);
		pair = __pair_lookup(ag, old_cand, sfd->local_intf);
		if (pair)
			goto out; /* nothing to do */
		cand = old_cand;
		goto pair;
	}

	t_queue_push_tail(&ag->remote_candidates, cand);
	t_hash_table_insert(ag->candidate_hash, cand, cand);
	t_hash_table_insert(ag->cand_prio_hash, GUINT_TO_POINTER(cand->priority), cand);
	t_hash_table_insert(ag->foundation_hash, cand, cand);

pair:
	pair = __pair_candidate(sfd, ag, cand);
	PAIR_SET(pair, LEARNED);
	__all_pairs_list(ag);

out:
	return pair;
}

/* agent must NOT be locked */
static void __trigger_check(struct ice_candidate_pair *pair) {
	struct ice_agent *ag = pair->agent;

	ilogs(ice, LOG_DEBUG, "Triggering check for "PAIR_FORMAT, PAIR_FMT(pair));

	mutex_lock(&ag->lock);
	pair->retransmits = 0;
	if (PAIR_CLEAR(pair, FAILED))
		PAIR_CLEAR(pair, IN_PROGRESS);
	if (ag->triggered.length < 4 * MAX_ICE_CANDIDATES && !PAIR_SET(pair, TRIGGERED))
		t_queue_push_tail(&ag->triggered, pair);
	mutex_unlock(&ag->lock);

	__agent_schedule(ag, 0);
}

/* agent must be locked */
/* also regenerates all_pairs_list */
static void __recalc_pair_prios(struct ice_agent *ag) {
	struct ice_candidate_pair *pair;
	GQueue nominated, valid, succ, all;

	ilogs(ice, LOG_DEBUG, "Recalculating all ICE pair priorities");

	rtpe_g_tree_find_remove_all(&nominated, ag->nominated_pairs);
	rtpe_g_tree_find_remove_all(&succ, ag->succeeded_pairs);
	rtpe_g_tree_find_remove_all(&valid, ag->valid_pairs);
	rtpe_g_tree_find_remove_all(&all, ag->all_pairs);

	for (__auto_type l = ag->candidate_pairs.head; l; l = l->next) {
		pair = l->data;
		__do_ice_pair_priority(pair);
		/* this changes the packets, so we must keep these from being seen as retransmits */
		__new_stun_transaction(pair);
	}

	rtpe_g_tree_add_all(ag->nominated_pairs, &nominated, __tree_coll_callback);
	rtpe_g_tree_add_all(ag->succeeded_pairs, &succ, __tree_coll_callback);
	rtpe_g_tree_add_all(ag->valid_pairs, &valid, __tree_coll_callback);
	rtpe_g_tree_add_all(ag->all_pairs, &all, __tree_coll_callback);
	__all_pairs_list(ag);
}

/* agent must NOT be locked */
static void __role_change(struct ice_agent *ag, int new_controlling) {
	if (new_controlling && !AGENT_SET(ag, CONTROLLING))
		;
	else if (!new_controlling && AGENT_CLEAR(ag, CONTROLLING))
		;
	else
		return;

	ilogs(ice, LOG_DEBUG, "ICE role change, now %s", new_controlling ? "controlling" : "controlled");

	/* recalc priorities and resort list */

	mutex_lock(&ag->lock);
	__recalc_pair_prios(ag);
	mutex_unlock(&ag->lock);
}

/* initializes "out" */
static void __get_complete_components(candidate_pair_q *out, struct ice_agent *ag, GTree *t, unsigned int flag) {
	candidate_pair_q compo1 = TYPED_GQUEUE_INIT;
	struct ice_candidate_pair *pair1, *pairX;
	struct ice_candidate *cand;
	unsigned int i;

	__get_pairs_by_component(&compo1, t, 1);

	t_queue_init(out);

	for (__auto_type l = compo1.head; l; l = l->next) {
		pair1 = l->data;

		t_queue_clear(out);
		t_queue_push_tail(out, pair1);

		for (i = 2; i <= ag->active_components; i++) {
			cand = __foundation_lookup(ag, &pair1->remote_candidate->foundation, i);
			if (!cand)
				goto next_foundation;
			pairX = __pair_lookup(ag, cand, pair1->local_intf);
			if (!pairX)
				goto next_foundation;
			if (!bf_isset(&pairX->pair_flags, flag))
				goto next_foundation;
			t_queue_push_tail(out, pairX);
		}
		goto found;

next_foundation:
		;
	}

	/* nothing found */
	t_queue_clear(out);

found:
	t_queue_clear(&compo1);
}

/* call(W) or call(R)+agent must be locked - no ps->lock must be held */
static int __check_valid(struct ice_agent *ag) {
	struct call_media *media;
	struct packet_stream *ps;
	packet_stream_list *l;
	candidate_pair_list *k;
	candidate_pair_q all_compos;
	struct ice_candidate_pair *pair;
//	const struct local_intf *ifa;
	stream_fd *sfd;
	int is_complete = 1;

	if (!ag) {
		ilogs(ice, LOG_ERR, "ice ag is NULL");
		return 0;
	}

	media = ag->media;

	__get_complete_valid_pairs(&all_compos, ag);

	if (!all_compos.length) {
		is_complete = 0;
		__get_complete_succeeded_pairs(&all_compos, ag);
		if (!all_compos.length) {
			ilogs(ice, LOG_DEBUG, "ICE not completed yet and no usable candidates");
			return 0;
		}
	}

	pair = all_compos.head->data;
	if (is_complete) {
		ilogs(ice, LOG_DEBUG, "ICE completed, using pair " PAIR_FORMAT, PAIR_FMT(pair));
		AGENT_SET(ag, COMPLETED);
	}
	else {
		ilogs(ice, LOG_DEBUG, "ICE not completed yet, but can use pair " PAIR_FORMAT, PAIR_FMT(pair));
		AGENT_SET(ag, USABLE);
	}

	for (l = media->streams.head, k = all_compos.head; l && k; l = l->next, k = k->next) {
		ps = l->data;
		pair = k->data;

		LOCK(&ps->lock);
		if (memcmp(&ps->endpoint, &pair->remote_candidate->endpoint, sizeof(ps->endpoint))) {
			ilogs(ice, LOG_INFO, "ICE negotiated: new peer for component %u is %s%s%s", ps->component,
					FMT_M(endpoint_print_buf(&pair->remote_candidate->endpoint)));
			ps->endpoint = pair->remote_candidate->endpoint;
			PS_SET(ps, FILLED);
		}
		else
			ilogs(ice, LOG_INFO, "ICE negotiated: peer for component %u is %s%s%s", ps->component,
					FMT_M(endpoint_print_buf(&pair->remote_candidate->endpoint)));

		for (__auto_type m = ps->sfds.head; m; m = m->next) {
			sfd = m->data;
			if (sfd->local_intf != pair->local_intf)
				continue;
			ps->selected_sfd = sfd;
			if (ps->component == 1)
				ilogs(ice, LOG_INFO, "ICE negotiated: local interface %s",
						sockaddr_print_buf(&pair->local_intf->spec->local_address.addr));
			break;
		}
	}

	call_media_unkernelize(media, "ICE negotiation event");

	t_queue_clear(&all_compos);
	return 1;
}


/* call is locked in R */
/* return values:
 * 1 = ICE completed, interfaces selected
 * 0 = packet processed
 * -1 = generic error, process packet as normal
 * -2 = role conflict
 */
int ice_request(stream_fd *sfd, const endpoint_t *src,
		struct stun_attrs *attrs)
{
	struct packet_stream *ps = sfd->stream;
	struct call_media *media = ps->media;
	struct ice_agent *ag;
	const char *err;
	struct ice_candidate *cand;
	struct ice_candidate_pair *pair;
	int ret;

	ilogs(ice, LOG_DEBUG, "Received ICE/STUN request from %s on %s",
			endpoint_print_buf(src),
			endpoint_print_buf(&sfd->socket.local));

	ag = media->ice_agent;
	if (!ag)
		return -1;

	atomic64_set_na(&ag->last_activity, rtpe_now);

	/* determine candidate pair */
	{
		LOCK(&ag->lock);

		cand = __cand_lookup(ag, src, ps->component);

		if (!cand)
			pair = __learned_candidate(ag, sfd, src, attrs->priority);
		else
			pair = __pair_lookup(ag, cand, sfd->local_intf);

		err = "Failed to determine ICE candidate from STUN request";
		if (!pair)
			goto err;
	}

	PAIR_SET(pair, AUTHENTICATED);

	if (!AGENT_ISSET(ag, LITE_SELF)) {
		/* determine role conflict */
		if (attrs->controlling && AGENT_ISSET(ag, CONTROLLING)) {
			if (tie_breaker >= attrs->tiebreaker)
				return -2;
			else
				__role_change(ag, 0);
		}
		else if (attrs->controlled && !AGENT_ISSET(ag, CONTROLLING)) {
			if (tie_breaker >= attrs->tiebreaker)
				__role_change(ag, 1);
			else
				return -2;
		}
	}
	else
		PAIR_SET(pair, SUCCEEDED);


	if (PAIR_ISSET(pair, SUCCEEDED))
		;
	else
		__trigger_check(pair);

	ret = 0;

	if (attrs->use && !PAIR_SET(pair, NOMINATED)) {
		ilogs(ice, LOG_DEBUG, "ICE pair "PAIR_FORMAT" has been nominated by peer", PAIR_FMT(pair));

		LOCK(&ag->lock);

		// coverity[use : FALSE]
		rtpe_g_tree_insert_coll(ag->nominated_pairs, pair, pair, __tree_coll_callback);

		if (PAIR_ISSET(pair, SUCCEEDED)) {
			PAIR_SET(pair, VALID);
			rtpe_g_tree_insert_coll(ag->valid_pairs, pair, pair, __tree_coll_callback);
		}

		if (!AGENT_ISSET(ag, CONTROLLING))
			ret = __check_valid(ag);
	}

	return ret;

err:
	ilogs(ice, LOG_NOTICE | LOG_FLAG_LIMIT, "%s (from %s%s%s on interface %s)", err, FMT_M(endpoint_print_buf(src)),
			endpoint_print_buf(&sfd->socket.local));
	return 0;
}


static int __check_succeeded_complete(struct ice_agent *ag) {
	candidate_pair_q complete;
	int ret;

	__get_complete_succeeded_pairs(&complete, ag);
	if (complete.length) {
		struct ice_candidate_pair *pair = complete.head->data;
		ilogs(ice, LOG_DEBUG, "Best succeeded ICE pair with all components is "PAIR_FORMAT, PAIR_FMT(pair));
		ret = 1;
	}
	else {
		ilogs(ice, LOG_DEBUG, "No succeeded ICE pairs with all components yet");
		ret = 0;
	}
	t_queue_clear(&complete);
	return ret;
}

/* call is locked in R */
int ice_response(stream_fd *sfd, const endpoint_t *src,
		struct stun_attrs *attrs, void *transaction)
{
	struct ice_candidate_pair *pair, *opair;
	struct ice_agent *ag;
	struct packet_stream *ps = sfd->stream;
	struct call_media *media = ps->media;
	const char *err;
	unsigned int component;
	struct ice_candidate *cand;
	const struct local_intf *ifa;
	int ret, was_ctl;

	__DBG("received ICE response from %s on %s", endpoint_print_buf(src),
			endpoint_print_buf(&sfd->socket.local));

	ag = media->ice_agent;
	if (!ag)
		return -1;

	atomic64_set_na(&ag->last_activity, rtpe_now);

	{
		LOCK(&ag->lock);

		pair = t_hash_table_lookup(ag->transaction_hash, transaction);
		err = "ICE/STUN response with unknown transaction received";
		if (!pair)
			goto err;
		was_ctl = pair->was_controlling;
	}

	ifa = pair->local_intf;

	ilogs(ice, LOG_DEBUG, "Received ICE/STUN response code %u for candidate pair "PAIR_FORMAT" from %s%s%s to %s",
			attrs->error_code, PAIR_FMT(pair),
			FMT_M(endpoint_print_buf(&pair->remote_candidate->endpoint)),
			sockaddr_print_buf(&ifa->spec->local_address.addr));

	/* verify endpoints */
	err = "ICE/STUN response received, but source address didn't match remote candidate address";
	if (!endpoint_eq(src, &pair->remote_candidate->endpoint))
		goto err;

	err = "ICE/STUN response received, but destination address didn't match local interface address";
	if (pair->sfd != sfd)
		goto err;

	PAIR_CLEAR(pair, IN_PROGRESS);
	ret = 0;

	/* handle all errors */
	if (attrs->error_code) {
		err = "ICE/STUN error received";
		if (attrs->error_code != 487)
			goto err;
		__role_change(ag, !was_ctl);
		__trigger_check(pair);
		goto out;
	}

	PAIR_SET(pair, AUTHENTICATED);

	/* we don't discover peer reflexive here (RFC 5245 7.1.3.2.1) as we don't expect to be behind NAT */
	/* we also skip parts of 7.1.3.2.2 as we don't do server reflexive */

	{
		LOCK(&ag->lock);

		/* check if we're in the final (controlling) phase */
		if (pair->was_nominated && PAIR_CLEAR(pair, TO_USE)) {
			ilogs(ice, LOG_DEBUG, "Setting nominated ICE candidate pair "PAIR_FORMAT" as valid", PAIR_FMT(pair));
			PAIR_SET(pair, VALID);
			rtpe_g_tree_insert_coll(ag->valid_pairs, pair, pair, __tree_coll_callback);
			ret = __check_valid(ag);
			goto out;
		}

		if (PAIR_SET(pair, SUCCEEDED))
			goto out;

		ilogs(ice, LOG_DEBUG, "Setting ICE candidate pair "PAIR_FORMAT" as succeeded", PAIR_FMT(pair));
		rtpe_g_tree_insert_coll(ag->succeeded_pairs, pair, pair, __tree_coll_callback);

		if (!ag->start_nominating) {
			if (__check_succeeded_complete(ag)) {
				ag->start_nominating = rtpe_now;
				ag->start_nominating += 100000;
				__agent_schedule_abs(ag, ag->start_nominating);
			}
		}

		/* now unfreeze all other pairs from the same foundation */
		for (component = 1; component <= MAX_COMPONENTS; component++) {
			if (component == ps->component)
				continue;
			cand = __foundation_lookup(ag, &pair->remote_candidate->foundation, component);
			if (!cand)
				continue;
			opair = __pair_lookup(ag, cand, ifa);
			if (!opair)
				continue;

			if (PAIR_ISSET(opair, FAILED))
				continue;
			if (!PAIR_CLEAR(opair, FROZEN))
				continue;

			ilogs(ice, LOG_DEBUG, "Unfreezing related ICE pair "PAIR_FORMAT, PAIR_FMT(opair));
		}

		/* if this was previously nominated by the peer, it's now valid */
		if (PAIR_ISSET(pair, NOMINATED)) {
			PAIR_SET(pair, VALID);
			rtpe_g_tree_insert_coll(ag->valid_pairs, pair, pair, __tree_coll_callback);
		}

		ret = __check_valid(ag);
	}

out:
	return ret;

err:
	if (err)
		ilogs(ice, LOG_NOTICE | LOG_FLAG_LIMIT, "%s (from %s%s%s on interface %s)",
				err, FMT_M(endpoint_print_buf(src)), endpoint_print_buf(&sfd->socket.local));

	if (pair && attrs->error_code)
		__fail_pair(pair);

	return 0;
}



void ice_thread_launch(void) {
	timerthread_launch(&ice_agents_timer_thread, NULL, 0, "ICE");
}
static void ice_agents_timer_run(void *ptr) {
	struct ice_agent *ag = ptr;
	call_t *call;

	call = ag->call;
	log_info_ice_agent(ag);
	rwlock_lock_r(&call->master_lock);

	/* and run our checks */
	__do_ice_checks(ag);

	/* finally, release our reference and start over */
	log_info_pop();
	rwlock_unlock_r(&call->master_lock);
}

static void random_ice_string(char *buf, int len) {
	while (len--)
		*buf++ = ice_chars[ssl_random() % strlen(ice_chars)];
}

static void create_random_ice_string(call_t *call, str *s, int len) {
	char buf[30];

	assert(len < sizeof(buf));
	if (s->s)
		return;

	random_ice_string(buf, len);
	*s = call_str_cpy_len(buf, len);
}

void ice_foundation(str *s) {
	*s = STR_LEN(malloc(ICE_FOUNDATION_LENGTH), ICE_FOUNDATION_LENGTH);
	random_ice_string(s->s, ICE_FOUNDATION_LENGTH);
}

void ice_remote_candidates(candidate_q *out, struct ice_agent *ag) {
	candidate_pair_q all_compos;
	struct ice_candidate_pair *pair;

	t_queue_init(out);

	mutex_lock(&ag->lock);
	__get_complete_valid_pairs(&all_compos, ag);
	mutex_unlock(&ag->lock);

	for (__auto_type l = all_compos.head; l; l = l->next) {
		pair = l->data;
		t_queue_push_tail(out, pair->remote_candidate);
	}

	t_queue_clear(&all_compos);
}

bool ice_peer_address_known(struct ice_agent *ag, const endpoint_t *sin, struct packet_stream *ps,
		const struct local_intf *ifa)
{
	LOCK(&ag->lock);

	struct ice_candidate *cand = __cand_lookup(ag, sin, ps->component);
	if (!cand)
		return false;
	struct ice_candidate_pair *pair = __pair_lookup(ag, cand, ifa);
	if (!pair)
		return false;
	if (!PAIR_ISSET(pair, AUTHENTICATED))
		return false;

	return true;
}
