/*
 * Copyright (C) 2011-2017 Tobias Brunner
 * Copyright (C) 2009 Martin Willi
 *
 * Copyright (C) secunet Security Networks AG
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#include "trap_manager.h"

#include <daemon.h>
#include <threading/mutex.h>
#include <threading/rwlock.h>
#include <threading/rwlock_condvar.h>
#include <collections/linked_list.h>

#define INSTALL_DISABLED ((u_int)~0)

typedef struct private_trap_manager_t private_trap_manager_t;
typedef struct trap_listener_t trap_listener_t;

/**
 * listener to track acquires
 */
struct trap_listener_t {

	/**
	 * Implements listener interface
	 */
	listener_t listener;

	/**
	 * points to trap_manager
	 */
	private_trap_manager_t *traps;
};

/**
 * Private data of an trap_manager_t object.
 */
struct private_trap_manager_t {

	/**
	 * Public trap_manager_t interface.
	 */
	trap_manager_t public;

	/**
	 * Installed traps, as entry_t
	 */
	linked_list_t *traps;

	/**
	 * read write lock for traps list
	 */
	rwlock_t *lock;

	/**
	 * listener to track acquiring IKE_SAs
	 */
	trap_listener_t listener;

	/**
	 * list of acquires we currently handle
	 */
	linked_list_t *acquires;

	/**
	 * mutex for list of acquires
	 */
	mutex_t *mutex;

	/**
	 * number of threads currently installing trap policies, or INSTALL_DISABLED
	 */
	u_int installing;

	/**
	 * condvar to signal trap policy installation
	 */
	rwlock_condvar_t *condvar;

	/**
	 * Whether to ignore traffic selectors from acquires
	 */
	bool ignore_acquire_ts;

	/**
	 * Current acquire sequence number if not generated by the kernel
	 */
	refcount_t acquire_seq;
};

/**
 * A installed trap entry
 */
typedef struct {
	/** name of the trapped CHILD_SA */
	char *name;
	/** ref to peer_cfg to initiate */
	peer_cfg_t *peer_cfg;
	/** ref to instantiated CHILD_SA (i.e the trap policy) */
	child_sa_t *child_sa;
	/** TRUE in case of wildcard Transport Mode SA */
	bool wildcard;
	/** TRUE for CHILD_SAs that are externally managed */
	bool external;
} entry_t;

/**
 * A handled acquire
 */
typedef struct {
	/** pending IKE_SA connecting upon acquire */
	ike_sa_t *ike_sa;
	/** reqid of pending trap policy */
	uint32_t reqid;
	/** destination address (wildcard case) */
	host_t *dst;
	/** data from the kernel */
	kernel_acquire_data_t *data;
	/** optional sequence number from the kernel if an acquire is retriggered */
	uint32_t new_seq;
} acquire_t;

/**
 * actually uninstall and destroy an installed entry
 */
static void destroy_entry(entry_t *this)
{
	if (!this->external)
	{
		this->child_sa->destroy(this->child_sa);
	}
	this->peer_cfg->destroy(this->peer_cfg);
	free(this->name);
	free(this);
}

/**
 * destroy a cached acquire entry
 */
static void destroy_acquire(acquire_t *this)
{
	DESTROY_IF(this->dst);
	kernel_acquire_data_destroy(this->data);
	free(this);
}

CALLBACK(acquire_by_reqid, bool,
	acquire_t *this, va_list args)
{
	uint32_t reqid, cpu;
	sec_label_t *label;

	VA_ARGS_VGET(args, reqid, cpu, label);
	return this->reqid == reqid && this->data->cpu == cpu &&
		   sec_labels_equal(this->data->label, label);
}

CALLBACK(acquire_by_dst, bool,
	acquire_t *this, va_list args)
{
	host_t *dst;

	VA_ARGS_VGET(args, dst);
	return this->dst && this->dst->ip_equals(this->dst, dst);
}

/**
 * Check if any remote TS are dynamic
 */
static bool dynamic_remote_ts(child_cfg_t *child)
{
	enumerator_t *enumerator;
	linked_list_t *other_ts;
	traffic_selector_t *ts;
	bool found = FALSE;

	other_ts = child->get_traffic_selectors(child, FALSE, NULL);
	enumerator = other_ts->create_enumerator(other_ts);
	while (enumerator->enumerate(enumerator, &ts))
	{
		if (ts->is_dynamic(ts))
		{
			found = TRUE;
			break;
		}
	}
	enumerator->destroy(enumerator);
	other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy));
	return found;
}

/**
 * Install the given trap
 */
static status_t install_trap(child_sa_t *child_sa, linked_list_t *local,
							 linked_list_t *remote)
{
	linked_list_t *my_ts, *other_ts, *proposals;
	proposal_t *proposal;
	child_cfg_t *child;
	protocol_id_t proto = PROTO_ESP;

	child = child_sa->get_config(child_sa);

	my_ts = child->get_traffic_selectors(child, TRUE, local);
	other_ts = child->get_traffic_selectors(child, FALSE, remote);

	/* we don't know the finally negotiated protocol (ESP|AH), we install
	 * the SA with the protocol of the first proposal */
	proposals = child->get_proposals(child, TRUE, FALSE);
	if (proposals->get_first(proposals, (void**)&proposal) == SUCCESS)
	{
		proto = proposal->get_protocol(proposal);
	}
	proposals->destroy_offset(proposals, offsetof(proposal_t, destroy));

	child_sa->set_protocol(child_sa, proto);
	child_sa->set_mode(child_sa, child->get_mode(child));

	child_sa->set_policies(child_sa, my_ts, other_ts);
	my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy));
	other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy));

	return child_sa->install_policies(child_sa);
}

METHOD(trap_manager_t, install, bool,
	private_trap_manager_t *this, peer_cfg_t *peer, child_cfg_t *child)
{
	entry_t *entry, *found = NULL;
	ike_cfg_t *ike_cfg;
	child_sa_t *child_sa;
	host_t *me, *other;
	linked_list_t *local, *remote;
	enumerator_t *enumerator;
	status_t status;
	bool result = FALSE, wildcard = FALSE;

	/* try to resolve addresses */
	ike_cfg = peer->get_ike_cfg(peer);
	other = ike_cfg->resolve_other(ike_cfg, AF_UNSPEC);
	if (other && other->is_anyaddr(other) &&
		child->get_mode(child) == MODE_TRANSPORT)
	{
		/* allow wildcard for Transport Mode SAs */
		me = host_create_any(other->get_family(other));
		wildcard = TRUE;
	}
	else if (other && other->is_anyaddr(other))
	{
		other->destroy(other);
		DBG1(DBG_CFG, "installing trap failed, remote address unknown");
		return FALSE;
	}
	else
	{	/* depending on the traffic selectors we don't really need a remote
		 * host yet, but we might fail later if no IP can be resolved */
		if (!other && dynamic_remote_ts(child))
		{	/* with dynamic TS we do need a host, otherwise 0.0.0.0/0 is used,
			 * which is probably not what users expect*/
			DBG1(DBG_CFG, "installing trap failed, remote address unknown with "
				 "dynamic traffic selector");
			return FALSE;
		}
		me = ike_cfg->resolve_me(ike_cfg, other ? other->get_family(other)
												: AF_UNSPEC);
		if (!other)
		{
			other = host_create_any(me ? me->get_family(me) : AF_INET);
		}
		other->set_port(other, ike_cfg->get_other_port(ike_cfg));
		if ((!me || me->is_anyaddr(me)) && !other->is_anyaddr(other))
		{
			DESTROY_IF(me);
			me = charon->kernel->get_source_addr(charon->kernel, other, NULL);
		}
		if (!me)
		{
			me = host_create_any(other->get_family(other));
		}
		me->set_port(me, ike_cfg->get_my_port(ike_cfg));
	}

	this->lock->write_lock(this->lock);
	if (this->installing == INSTALL_DISABLED)
	{	/* flush() has been called */
		this->lock->unlock(this->lock);
		other->destroy(other);
		me->destroy(me);
		return FALSE;
	}
	enumerator = this->traps->create_enumerator(this->traps);
	while (enumerator->enumerate(enumerator, &entry))
	{
		if (!entry->external &&
			streq(entry->name, child->get_name(child)) &&
			streq(entry->peer_cfg->get_name(entry->peer_cfg),
				  peer->get_name(peer)))
		{
			found = entry;
			if (entry->child_sa)
			{	/* replace it with an updated version if already installed */
				this->traps->remove_at(this->traps, enumerator);
			}
			break;
		}
	}
	enumerator->destroy(enumerator);

	if (found)
	{
		if (!found->child_sa)
		{
			DBG1(DBG_CFG, "CHILD_SA '%s' is already being routed", found->name);
			this->lock->unlock(this->lock);
			other->destroy(other);
			me->destroy(me);
			return FALSE;
		}
		/* config might have changed so update everything */
		DBG1(DBG_CFG, "updating already routed CHILD_SA '%s'", found->name);
	}

	INIT(entry,
		.name = strdup(child->get_name(child)),
		.peer_cfg = peer->get_ref(peer),
		.wildcard = wildcard,
	);
	this->traps->insert_first(this->traps, entry);
	this->installing++;
	/* don't hold lock while creating CHILD_SA and installing policies */
	this->lock->unlock(this->lock);

	/* create and route CHILD_SA */
	child_sa_create_t child_data = {
		/* TODO: no reason to allocate unique interface IDs, there is currently
		 * no event to use them upon trap installation and we'd also have to
		 * pass them in a later initiate() call */
		.if_id_in_def = peer->get_if_id(peer, TRUE),
		.if_id_out_def = peer->get_if_id(peer, FALSE),
		.cpu = CPU_ID_MAX,
	};
	child_sa = child_sa_create(me, other, child, &child_data);

	local = linked_list_create_with_items(me, NULL);
	remote = linked_list_create_with_items(other, NULL);

	status = install_trap(child_sa, local, remote);

	local->destroy_offset(local, offsetof(host_t, destroy));
	remote->destroy_offset(remote, offsetof(host_t, destroy));
	if (status != SUCCESS)
	{
		DBG1(DBG_CFG, "installing trap failed");
		this->lock->write_lock(this->lock);
		this->traps->remove(this->traps, entry, NULL);
		this->lock->unlock(this->lock);
		entry->child_sa = child_sa;
		destroy_entry(entry);
	}
	else
	{
		this->lock->write_lock(this->lock);
		entry->child_sa = child_sa;
		this->lock->unlock(this->lock);
		result = TRUE;
	}
	if (found)
	{
		destroy_entry(found);
	}
	this->lock->write_lock(this->lock);
	/* do this at the end, so entries created temporarily are also destroyed */
	this->installing--;
	this->condvar->signal(this->condvar);
	this->lock->unlock(this->lock);
	return result;
}

METHOD(trap_manager_t, uninstall, bool,
	private_trap_manager_t *this, char *peer, char *child)
{
	enumerator_t *enumerator;
	entry_t *entry, *found = NULL;

	this->lock->write_lock(this->lock);
	while (this->installing)
	{
		this->condvar->wait(this->condvar, this->lock);
	}
	enumerator = this->traps->create_enumerator(this->traps);
	while (enumerator->enumerate(enumerator, &entry))
	{
		if (!entry->external &&
			streq(entry->name, child) &&
		    (!peer || streq(peer, entry->peer_cfg->get_name(entry->peer_cfg))))
		{
			this->traps->remove_at(this->traps, enumerator);
			found = entry;
			break;
		}
	}
	enumerator->destroy(enumerator);
	this->lock->unlock(this->lock);

	if (!found)
	{
		return FALSE;
	}
	destroy_entry(found);
	return TRUE;
}

METHOD(trap_manager_t, install_external, bool,
	private_trap_manager_t *this, peer_cfg_t *peer, child_sa_t *child,
	linked_list_t *local, linked_list_t *remote)
{
	entry_t *entry;

	this->lock->write_lock(this->lock);
	if (this->installing == INSTALL_DISABLED)
	{	/* flush() has been called */
		this->lock->unlock(this->lock);
		return FALSE;
	}

	INIT(entry,
		.name = strdup(child->get_name(child)),
		.peer_cfg = peer->get_ref(peer),
		.child_sa = child,
		.external = TRUE,
	);
	this->traps->insert_first(this->traps, entry);
	this->lock->unlock(this->lock);

	if (install_trap(child, local, remote) != SUCCESS)
	{
		DBG1(DBG_CFG, "installing trap failed");
		this->lock->write_lock(this->lock);
		this->traps->remove(this->traps, entry, NULL);
		this->lock->unlock(this->lock);
		destroy_entry(entry);
		return FALSE;
	}
	return TRUE;
}

METHOD(trap_manager_t, remove_external, bool,
	private_trap_manager_t *this, child_sa_t *child)
{
	enumerator_t *enumerator;
	entry_t *entry, *found = NULL;

	this->lock->write_lock(this->lock);
	enumerator = this->traps->create_enumerator(this->traps);
	while (enumerator->enumerate(enumerator, &entry))
	{
		if (entry->external && entry->child_sa == child)
		{
			this->traps->remove_at(this->traps, enumerator);
			found = entry;
			break;
		}
	}
	enumerator->destroy(enumerator);
	this->lock->unlock(this->lock);

	if (!found)
	{
		return FALSE;
	}
	destroy_entry(found);
	return TRUE;
}

CALLBACK(trap_filter, bool,
	rwlock_t *lock, enumerator_t *orig, va_list args)
{
	entry_t *entry;
	peer_cfg_t **peer_cfg;
	child_sa_t **child_sa;

	VA_ARGS_VGET(args, peer_cfg, child_sa);

	while (orig->enumerate(orig, &entry))
	{
		if (!entry->child_sa || entry->external)
		{	/* skip entries that are currently being installed or are managed
			 * externally */
			continue;
		}
		if (peer_cfg)
		{
			*peer_cfg = entry->peer_cfg;
		}
		if (child_sa)
		{
			*child_sa = entry->child_sa;
		}
		return TRUE;
	}
	return FALSE;
}

METHOD(trap_manager_t, create_enumerator, enumerator_t*,
	private_trap_manager_t *this)
{
	this->lock->read_lock(this->lock);
	return enumerator_create_filter(this->traps->create_enumerator(this->traps),
									trap_filter, this->lock,
									(void*)this->lock->unlock);
}

METHOD(trap_manager_t, acquire, void,
	private_trap_manager_t *this, uint32_t reqid, kernel_acquire_data_t *data)
{
	enumerator_t *enumerator;
	entry_t *entry, *found = NULL;
	acquire_t *acquire = NULL;
	peer_cfg_t *peer;
	child_cfg_t *child;
	ike_sa_t *ike_sa;
	host_t *host = NULL;
	uint32_t allocated_reqid, seq = 0;
	bool wildcard;

	this->lock->read_lock(this->lock);
	enumerator = this->traps->create_enumerator(this->traps);
	while (enumerator->enumerate(enumerator, &entry))
	{
		if (entry->child_sa &&
			entry->child_sa->get_reqid(entry->child_sa) == reqid)
		{
			found = entry;
			break;
		}
	}
	enumerator->destroy(enumerator);

	if (!found)
	{
		DBG1(DBG_CFG, "trap not found, unable to acquire reqid %d", reqid);
		this->lock->unlock(this->lock);
		return;
	}
	wildcard = found->wildcard;

	if (wildcard && (!data->dst || !data->src))
	{
		DBG1(DBG_CFG, "unable to process acquire without selectors for "
			 "trap without destination address (reqid %u)", reqid);
		this->lock->unlock(this->lock);
		return;
	}

	this->mutex->lock(this->mutex);
	if (wildcard)
	{
		/* for wildcard acquires we check that we don't have a pending acquire
		 * with the same peer */
		uint8_t mask;

		data->dst->to_subnet(data->dst, &host, &mask);
		if (this->acquires->find_first(this->acquires, acquire_by_dst,
									   (void**)&acquire, host))
		{
			host->destroy(host);
		}
	}
	else
	{
		this->acquires->find_first(this->acquires, acquire_by_reqid,
								   (void**)&acquire, reqid, data->cpu,
									data->label);
	}
	if (!acquire)
	{
		seq = data->seq = data->seq ?: ref_get_nonzero(&this->acquire_seq);
		INIT(acquire,
			.dst = host,
			.reqid = reqid,
			.data = kernel_acquire_data_clone(data),
		);
		this->acquires->insert_last(this->acquires, acquire);
	}
	else if (data->seq && data->seq != acquire->data->seq)
	{
		/* acquire got retriggered, update to latest sequence number */
		acquire->new_seq = data->seq;
	}
	this->mutex->unlock(this->mutex);
	if (!seq)
	{
		DBG1(DBG_CFG, "ignoring acquire for reqid %u, connection attempt "
			 "pending", reqid);
		this->lock->unlock(this->lock);
		return;
	}
	peer = found->peer_cfg->get_ref(found->peer_cfg);
	child = found->child_sa->get_config(found->child_sa);
	child = child->get_ref(child);
	/* only pass allocated reqids explicitly, take a reference */
	allocated_reqid = found->child_sa->get_reqid_ref(found->child_sa);
	/* don't hold the lock while checking out the IKE_SA */
	this->lock->unlock(this->lock);

	if (wildcard)
	{	/* the peer config would match IKE_SAs with other peers */
		ike_sa = charon->ike_sa_manager->create_new(charon->ike_sa_manager,
											peer->get_ike_version(peer), TRUE);
		if (ike_sa)
		{
			ike_cfg_t *ike_cfg;
			uint16_t port;
			uint8_t mask;

			ike_sa->set_peer_cfg(ike_sa, peer);
			ike_cfg = ike_sa->get_ike_cfg(ike_sa);

			port = ike_cfg->get_other_port(ike_cfg);
			data->dst->to_subnet(data->dst, &host, &mask);
			host->set_port(host, port);
			ike_sa->set_other_host(ike_sa, host);

			port = ike_cfg->get_my_port(ike_cfg);
			data->src->to_subnet(data->src, &host, &mask);
			host->set_port(host, port);
			ike_sa->set_my_host(ike_sa, host);

			charon->bus->set_sa(charon->bus, ike_sa);
		}
	}
	else
	{
		ike_sa = charon->ike_sa_manager->checkout_by_config(
											charon->ike_sa_manager, peer);
	}
	peer->destroy(peer);

	if (ike_sa)
	{
		child_init_args_t args = {
			.reqid = allocated_reqid,
			.cpu = data->cpu,
			.src = data->src,
			.dst = data->dst,
			.label = data->label,
			.seq = seq,
		};

		if (this->ignore_acquire_ts || ike_sa->get_version(ike_sa) == IKEV1)
		{	/* in IKEv1, don't prepend the acquiring packet TS, as we only
			 * have a single TS that we can establish in a Quick Mode. */
			args.src = args.dst = NULL;
		}

		this->mutex->lock(this->mutex);
		acquire->ike_sa = ike_sa;
		this->mutex->unlock(this->mutex);

		if (ike_sa->initiate(ike_sa, child, &args) != DESTROY_ME)
		{
			charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
		}
		else
		{
			charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
														ike_sa);
		}
	}
	else
	{
		this->mutex->lock(this->mutex);
		this->acquires->remove(this->acquires, acquire, NULL);
		this->mutex->unlock(this->mutex);
		destroy_acquire(acquire);
		child->destroy(child);
	}
	if (allocated_reqid)
	{
		charon->kernel->release_reqid(charon->kernel, allocated_reqid);
	}
}

/**
 * Update the sequence number of the CHILD_SA before installing it in case
 * the acquire got retriggered.
 */
static void update_acquire_seq(private_trap_manager_t *this, ike_sa_t *ike_sa,
							   child_sa_t *child_sa)
{
	enumerator_t *enumerator;
	acquire_t *acquire;
	uint32_t seq;

	/* ignore trap policies */
	if (!ike_sa)
	{
		return;
	}
	/* if a CHILD_SA was not triggered by an acquire (e.g. manually or via
	 * start action) and the kernel supports sequence numbers, we check if the
	 * negotiated TS match the triggering packet and set the sequence number
	 * to remove the temporary state in the kernel */
	seq = child_sa->get_acquire_seq(child_sa);
	if (!seq &&
		!(charon->kernel->get_features(charon->kernel) & KERNEL_ACQUIRE_SEQ))
	{
		return;
	}

	this->mutex->lock(this->mutex);
	enumerator = this->acquires->create_enumerator(this->acquires);
	while (enumerator->enumerate(enumerator, &acquire))
	{
		if (!seq)
		{
			/* if not triggered by an acquire, compare the TS from the packet
			 * that triggered the current acquire, set the seq if they match */
			if (!child_sa_ts_match(child_sa, acquire->data->src,
								   acquire->data->dst))
			{
				continue;
			}
			child_sa->set_acquire_seq(child_sa, acquire->data->seq);
		}
		else if (!acquire->ike_sa || seq != acquire->data->seq)
		{
			continue;
		}
		if (acquire->new_seq)
		{
			child_sa->set_acquire_seq(child_sa, acquire->new_seq);
			acquire->data->seq = acquire->new_seq;
			acquire->new_seq = 0;
		}
		break;
	}
	enumerator->destroy(enumerator);
	this->mutex->unlock(this->mutex);
}

/**
 * Complete the acquire, if successful or failed.
 */
static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa,
					 child_sa_t *child_sa)
{
	enumerator_t *enumerator;
	acquire_t *acquire;
	uint32_t seq = 0;

	/* ignore trap policies and CHILD_SAs not triggered by an acquire */
	if (!ike_sa || (child_sa && !(seq = child_sa->get_acquire_seq(child_sa))))
	{
		return;
	}

	this->mutex->lock(this->mutex);
	enumerator = this->acquires->create_enumerator(this->acquires);
	while (enumerator->enumerate(enumerator, &acquire))
	{
		/* just look at the sequence number when handling a CHILD_SA event,
		 * otherwise, compare the IKE_SA */
		if (!acquire->ike_sa || (seq && seq != acquire->data->seq) ||
			(!seq && acquire->ike_sa != ike_sa))
		{
			continue;
		}
		this->acquires->remove_at(this->acquires, enumerator);
		destroy_acquire(acquire);
	}
	enumerator->destroy(enumerator);
	this->mutex->unlock(this->mutex);
}

METHOD(listener_t, ike_state_change, bool,
	trap_listener_t *listener, ike_sa_t *ike_sa, ike_sa_state_t state)
{
	switch (state)
	{
		case IKE_DESTROYING:
			complete(listener->traps, ike_sa, NULL);
			return TRUE;
		default:
			return TRUE;
	}
}

METHOD(listener_t, child_state_change, bool,
	trap_listener_t *listener, ike_sa_t *ike_sa, child_sa_t *child_sa,
	child_sa_state_t state)
{
	switch (state)
	{
		case CHILD_INSTALLING:
			update_acquire_seq(listener->traps, ike_sa, child_sa);
			return TRUE;
		case CHILD_INSTALLED:
		case CHILD_DESTROYING:
			complete(listener->traps, ike_sa, child_sa);
			return TRUE;
		default:
			return TRUE;
	}
}

METHOD(listener_t, ike_reestablish_pre, bool,
	trap_listener_t *listener, ike_sa_t *old, ike_sa_t *new)
{
	private_trap_manager_t *this = listener->traps;
	enumerator_t *enumerator;
	acquire_t *acquire;

	if (old->has_condition(old, COND_REDIRECTED))
	{
		/* if we get redirected during IKE_AUTH, we just migrate to the new SA.
		 * we'd have to disable listening for child state changes otherwise (due
		 * to task migration).  and if the initiation failed, the initial SA
		 * couldn't be used anyway, so we can also just track the destruction of
		 * of the new one in that case */
		this->mutex->lock(this->mutex);
		enumerator = this->acquires->create_enumerator(this->acquires);
		while (enumerator->enumerate(enumerator, &acquire))
		{
			if (acquire->ike_sa == old)
			{
				acquire->ike_sa = new;
			}
		}
		enumerator->destroy(enumerator);
		this->mutex->unlock(this->mutex);
	}
	return TRUE;
}

METHOD(trap_manager_t, flush, void,
	private_trap_manager_t *this)
{
	this->lock->write_lock(this->lock);
	while (this->installing)
	{
		this->condvar->wait(this->condvar, this->lock);
	}
	this->traps->destroy_function(this->traps, (void*)destroy_entry);
	this->traps = linked_list_create();
	this->installing = INSTALL_DISABLED;
	this->lock->unlock(this->lock);
}

METHOD(trap_manager_t, destroy, void,
	private_trap_manager_t *this)
{
	charon->bus->remove_listener(charon->bus, &this->listener.listener);
	this->traps->destroy_function(this->traps, (void*)destroy_entry);
	this->acquires->destroy_function(this->acquires, (void*)destroy_acquire);
	this->condvar->destroy(this->condvar);
	this->mutex->destroy(this->mutex);
	this->lock->destroy(this->lock);
	free(this);
}

/**
 * See header
 */
trap_manager_t *trap_manager_create(void)
{
	private_trap_manager_t *this;

	INIT(this,
		.public = {
			.install = _install,
			.uninstall = _uninstall,
			.install_external = _install_external,
			.remove_external = _remove_external,
			.create_enumerator = _create_enumerator,
			.acquire = _acquire,
			.flush = _flush,
			.destroy = _destroy,
		},
		.listener = {
			.traps = this,
			.listener = {
				.ike_state_change = _ike_state_change,
				.ike_reestablish_pre = _ike_reestablish_pre,
				.child_state_change = _child_state_change,
			},
		},
		.traps = linked_list_create(),
		.acquires = linked_list_create(),
		.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
		.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
		.condvar = rwlock_condvar_create(),
		.ignore_acquire_ts = lib->settings->get_bool(lib->settings,
										"%s.ignore_acquire_ts", FALSE, lib->ns),
	);
	charon->bus->add_listener(charon->bus, &this->listener.listener);

	return &this->public;
}
