/*
 * Handle SCN registration/deregistration/events
 *
 * Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
 */

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "config.h"
#include <libisns/isns.h>
#include <libisns/attrs.h>
#include "objects.h"
#include <libisns/message.h>
#include "security.h"
#include <libisns/util.h>
#include "db.h"

typedef struct isns_scn isns_scn_t;
typedef struct isns_scn_funnel isns_scn_funnel_t;

struct isns_scn {
	isns_scn_t *		scn_next;
	char *			scn_name;
	isns_object_t *		scn_entity;
	isns_object_t *		scn_owner;
	isns_attr_t *		scn_attr;

	isns_simple_t *		scn_message;
	isns_simple_t *		scn_pending;
	unsigned int		scn_retries;
	time_t			scn_timeout;
	uint16_t		scn_xid;

	time_t			scn_last_update;
	isns_scn_funnel_t *	scn_current_funnel;
	isns_scn_funnel_t *	scn_funnels;
};

struct isns_scn_funnel {
	isns_scn_funnel_t *	scn_next;
	isns_portal_info_t	scn_portal;
	isns_socket_t *		scn_socket;
	unsigned int		scn_bad;
};

static isns_server_t *		isns_scn_server = NULL;
static isns_scn_t *		isns_scn_list;

static isns_scn_t *		isns_scn_create_scn(isns_object_t *, uint32_t, isns_db_t *);
static void			isns_scn_delete_scn(isns_object_t *);
static isns_scn_t *		isns_scn_setup(isns_scn_t *, isns_object_t *);
static void			isns_scn_callback(const isns_db_event_t *, void *);
static void			isns_scn_free(isns_scn_t *);

/*
 * Initialize SCN machinery
 */
void
isns_scn_init(isns_server_t *srv)
{
	isns_db_t	*db = srv->is_db;
	isns_object_list_t nodes = ISNS_OBJECT_LIST_INIT;
	isns_scn_t	**tail;
	unsigned int	i;

	isns_scn_server = srv;
	isns_register_callback(isns_scn_callback, db);

	/* Recover SCN state. */
	isns_db_gang_lookup(db, &isns_iscsi_node_template, NULL, &nodes);
#ifdef notyet
	isns_db_gang_lookup(db, &isns_fc_node_template, NULL, &nodes);
#endif

	tail = &isns_scn_list;
	for (i = 0; i < nodes.iol_count; ++i) {
		isns_object_t	*node = nodes.iol_data[i];
		isns_scn_t	*scn;

		if (!node->ie_scn_mask)
			continue;

		isns_debug_state("Recovering SCN state for %s %u\n",
				node->ie_template->iot_name,
				node->ie_index);
		scn = isns_scn_setup(NULL, node);
		if (scn) {
			*tail = scn;
			tail = &scn->scn_next;
		}
	}
}

/*
 * Support for SCNRegister calls
 */
isns_simple_t *
isns_create_scn_registration2(isns_client_t *clnt, unsigned int bitmap, isns_source_t *source)
{
	isns_simple_t	*call;

	if (!source)
		source = clnt->ic_source;
	call = isns_simple_create(ISNS_SCN_REGISTER, source, NULL);
	if (call) {
		isns_attr_list_append_attr(&call->is_message_attrs,
				isns_source_attr(source));
		isns_attr_list_append_uint32(&call->is_operating_attrs,
				ISNS_TAG_ISCSI_SCN_BITMAP,
				bitmap);
	}
	return call;
}

isns_simple_t *
isns_create_scn_registration(isns_client_t *clnt, unsigned int bitmap)
{
	return isns_create_scn_registration2(clnt, bitmap, clnt->ic_source);
}

/*
 * Create an SCN
 */
isns_simple_t *
isns_create_scn(isns_source_t *source, isns_attr_t *nodeattr, isns_attr_t *tsattr)
{
	isns_simple_t	*call;

	call = isns_simple_create(ISNS_STATE_CHANGE_NOTIFICATION, source, NULL);
	if (call && nodeattr)
		isns_attr_list_append_attr(&call->is_message_attrs, nodeattr);
	if (call && tsattr)
		isns_attr_list_append_attr(&call->is_message_attrs, tsattr);
	return call;
}

static void
isns_scn_add_event(isns_simple_t *call, uint32_t scn_bits,
			const isns_object_t *obj,
			const isns_object_t *dd)
{
	isns_attr_list_t	*attrs = &call->is_message_attrs;

	isns_attr_list_append_uint32(attrs,
			ISNS_TAG_ISCSI_SCN_BITMAP,
			scn_bits);
	isns_object_extract_keys(obj, attrs);
	if (dd)
		isns_object_extract_keys(dd, attrs);
}

/*
 * Process a SCN registration
 */
int
isns_process_scn_register(isns_server_t *srv, isns_simple_t *call, isns_simple_t **result)
{
	isns_attr_list_t *keys = &call->is_message_attrs;
	isns_attr_list_t *attrs = &call->is_operating_attrs;
	isns_db_t	*db = srv->is_db;
	isns_attr_t	*attr;
	isns_object_t	*node = NULL;
	uint32_t	scn_bitmap;
	isns_scn_t	*scn;
	int		status = ISNS_SUCCESS;

	/*
	 * 5.6.5.5
	 * The SCNReg request PDU Payload contains a Source Attribute, a Message
	 * Key Attribute, and an Operating Attribute.  Valid Message Key
	 * Attributes for a SCNReg are shown below:
	 *
	 * Valid Message Key Attributes for SCNReg
	 * ---------------------------------------
	 *  iSCSI Name
	 *  FC Port Name WWPN
	 */
	if (keys->ial_count != 1 || attrs->ial_count != 1)
		return ISNS_SCN_REGISTRATION_REJECTED;

	attr = keys->ial_data[0];
	if (attr->ia_tag_id != ISNS_TAG_ISCSI_NAME &&
	    attr->ia_tag_id != ISNS_TAG_FC_PORT_NAME_WWPN)
		return ISNS_SCN_REGISTRATION_REJECTED;

	/* Look up the storage node for this source. If it does
	 * not exist, reject the message. */
	node = isns_db_lookup(db, NULL, keys);
	if (node == NULL)
		return ISNS_SOURCE_UNKNOWN;

	/*
	 * Policy: verify that the client is permitted
	 * to access this entity.
	 *
	 * This includes
	 *  -	the client node must be the object owner,
	 *	or a control node.
	 *  -	the policy must allow monitoring of
	 *	this object type.
	 */
	if (!isns_policy_validate_object_access(call->is_policy,
				call->is_source,
				node, call->is_function))
		goto unauthorized;

	/*
	 * 5.6.5.5
	 * The SCN Bitmap is the only operating attribute of this message
	 * [...]
	 * Control Nodes MAY conduct registrations for management SCNs;
	 * iSNS clients that are not supporting Control Nodes MUST NOT
	 * conduct registrations for management SCNs.
	 *
	 * Implementer's note: for iFCP sources, we should check for
	 * ISNS_TAG_IFCP_SCN_BITMAP.
	 */
	attr = attrs->ial_data[0];
	if (attr->ia_tag_id != ISNS_TAG_ISCSI_SCN_BITMAP
	 || !ISNS_ATTR_IS_UINT32(attr))
		goto rejected;

	scn_bitmap = attr->ia_value.iv_uint32;
	if (!isns_policy_validate_scn_bitmap(call->is_policy, scn_bitmap))
		goto unauthorized;

	/*
	 * 5.6.5.5
	 * If no SCN Port fields of any Portals of the Storage Node are
	 * registered to receive SCN messages, then the SCNReg message SHALL
	 * be rejected with Status Code 17 (SCN Registration Rejected).
	 */
	if (!(scn = isns_scn_create_scn(node, scn_bitmap, db)))
		goto rejected;

	*result = isns_simple_create(ISNS_SCN_REGISTER, srv->is_source, NULL);
	status = ISNS_SUCCESS;

out:
	if (node)
		isns_object_release(node);

	return status;

rejected:
	status = ISNS_SCN_REGISTRATION_REJECTED;
	goto out;

unauthorized:
	status = ISNS_SOURCE_UNAUTHORIZED;
	goto out;
}

/*
 * Process a SCNDereg message
 */
int
isns_process_scn_deregistration(isns_server_t *srv, isns_simple_t *call, isns_simple_t **result)
{
	isns_attr_list_t *keys = &call->is_message_attrs;
	isns_db_t	*db = srv->is_db;
	isns_attr_t	*attr;
	isns_object_t	*node = NULL;
	int		status = ISNS_SUCCESS;

	/*
	 * 5.6.5.6
	 * The SCNDereg request message PDU Payload contains a Source Attribute
	 * and Message Key Attribute(s).  Valid Message Key Attributes for a
	 * SCNDereg are shown below:
	 *
	 *	  Valid Message Key Attributes for SCNDereg
	 *	  -----------------------------------------
	 *	   iSCSI Name
	 *	   FC Port Name WWPN
	 *
	 * There are no Operating Attributes in the SCNDereg message.
	 */
	if (keys->ial_count != 1)
		return ISNS_SCN_REGISTRATION_REJECTED;

	attr = keys->ial_data[0];
	if (attr->ia_tag_id != ISNS_TAG_ISCSI_NAME &&
	    attr->ia_tag_id != ISNS_TAG_FC_PORT_NAME_WWPN)
		return ISNS_SCN_REGISTRATION_REJECTED;

	/* Look up the storage node for this source. If it does
	 * not exist, reject the message. */
	/*
	 * Look up the storage node for this source. If it does
	 * not exist call this success.
	 *
	 * Implementor's comment: the specification is unclear
	 * on how to handle the case where a SCN deregistration
	 * is received when there are no registrations. It
	 * seems like SUCCESS is better than INVALID_DEREGISTRATION
	 */
	node = isns_db_lookup(db, NULL, keys);
	if (node == NULL) {
		*result = isns_simple_create(ISNS_SCN_DEREGISTER,
					     srv->is_source, NULL);
		return ISNS_SUCCESS;
	}

	/*
	 * Policy: verify that the client is permitted
	 * to access this entity.
	 *
	 * This includes
	 *  -	the client node must be the object owner,
	 *	or a control node.
	 *  -	the policy must allow monitoring of
	 *	this object type.
	 */
	if (!isns_policy_validate_object_access(call->is_policy,
				call->is_source,
				node, call->is_function))
		goto unauthorized;

	isns_object_set_scn_mask(node, 0);
	isns_scn_delete_scn(node);

	*result = isns_simple_create(ISNS_SCN_DEREGISTER, srv->is_source, NULL);
	status = ISNS_SUCCESS;

out:
	if (node)
		isns_object_release(node);

	return status;

unauthorized:
	status = ISNS_SOURCE_UNAUTHORIZED;
	goto out;
}

/*
 * Set up the SCN object.
 */
static isns_scn_t *
isns_scn_setup(isns_scn_t *scn, isns_object_t *node)
{
	isns_object_list_t portals = ISNS_OBJECT_LIST_INIT;
	isns_object_t	*entity;
	unsigned int	i;

	entity = isns_object_get_entity(node);
	if (entity == NULL
	 || !isns_object_find_descendants(entity,
			 &isns_portal_template, NULL, &portals))
		return NULL;

	for (i = 0; i < portals.iol_count; ++i) {
		isns_object_t	*portal = portals.iol_data[i];
		isns_portal_info_t info;
		isns_scn_funnel_t *funnel;

		/* Extract address and SCN port from portal */
		if (!isns_portal_from_object(&info,
				ISNS_TAG_PORTAL_IP_ADDRESS,
				ISNS_TAG_SCN_PORT,
				portal))
			continue;

		/* We know where to send our notifications! */
		if (scn == NULL) {
			isns_attr_t	*attr;

			if (!isns_object_get_attr(node, ISNS_TAG_ISCSI_NAME, &attr)
			 && !isns_object_get_attr(node, ISNS_TAG_FC_PORT_NAME_WWPN, &attr)) {
				isns_error("Attempt to set up SCN for strange node type\n");
				return NULL;
			}

			scn = isns_calloc(1, sizeof(*scn));
			scn->scn_entity = isns_object_get(entity);
			scn->scn_owner = isns_object_get(node);
			scn->scn_attr = isns_attr_get(attr);
			scn->scn_name = isns_strdup(attr->ia_value.iv_string);
		}

		funnel = isns_calloc(1, sizeof(*funnel));
		funnel->scn_portal = info;
		funnel->scn_next = scn->scn_funnels;
		scn->scn_funnels = funnel;
	}

	isns_object_list_destroy(&portals);
	return scn;
}

/*
 * See if an SCN object exists for the given target;
 * if it doesn't, then create one.
 */
static isns_scn_t *
isns_scn_create_scn(isns_object_t *node, uint32_t bitmap,
		__attribute__((unused))isns_db_t *db)
{
	isns_scn_t	*scn;

	for (scn = isns_scn_list; scn; scn = scn->scn_next) {
		if (scn->scn_owner == node)
			goto done;
	}

	/* Not found - create it */
	scn = isns_scn_setup(NULL, node);
	if (scn == NULL)
		return NULL;

	scn->scn_next = isns_scn_list;
	isns_scn_list = scn;

done:
	/* We're all set - update the bitmap */
	isns_object_set_scn_mask(node, bitmap);
	return scn;
}

static void
isns_scn_delete_scn(isns_object_t *node)
{
	isns_scn_t	*scn, **pos;

	pos = &isns_scn_list;
	while ((scn = *pos) != NULL) {
		if (scn->scn_owner == node) {
			isns_debug_scn("Deregistering SCN for node %u\n",
					node->ie_index);
			*pos = scn->scn_next;
			isns_scn_free(scn);
			return;
		}
		pos = &scn->scn_next;
	}
}

static void
isns_scn_release_funnels(isns_scn_t *scn)
{
	isns_scn_funnel_t *funnel;

	while ((funnel = scn->scn_funnels) != NULL) {
		scn->scn_funnels = funnel->scn_next;
		if (funnel->scn_socket)
			isns_socket_free(funnel->scn_socket);
		isns_free(funnel);
	}
}

static void
isns_scn_free(isns_scn_t *scn)
{
	isns_scn_release_funnels(scn);
	isns_object_release(scn->scn_owner);
	isns_object_release(scn->scn_entity);
	isns_attr_release(scn->scn_attr);
	isns_free(scn->scn_name);
	isns_free(scn);
}

/*
 * Check whether we should send an event to the target
 */
static inline int
isns_scn_match(isns_scn_t *scn, uint32_t event,
		const isns_object_t *node,
		uint32_t node_type)
{
	if (event == 0)
		return 0;

	if (node->ie_scn_mask & ISNS_SCN_MANAGEMENT_REGISTRATION_MASK)
		return event | ISNS_SCN_MANAGEMENT_REGISTRATION_MASK;

#if 0
	/* This is a normal (non-control) node. Check whether the object
	 * is in the scope of this client. */
	if (!isns_object_in_scope(scn->scn_owner, node))
		return 0;
#endif

	if (node->ie_scn_mask & ISNS_SCN_TARGET_AND_SELF_ONLY_MASK) {
		if (node != scn->scn_owner && !(node_type & ISNS_ISCSI_TARGET_MASK))
			return 0;
	}
	if (node->ie_scn_mask & ISNS_SCN_INITIATOR_AND_SELF_ONLY_MASK) {
		if (node != scn->scn_owner && !(node_type & ISNS_ISCSI_INITIATOR_MASK))
			return 0;
	}

	return event;
}

/*
 * Helper to create time stamp attr
 */
static isns_attr_t *
isns_create_timestamp_attr(void)
{
	isns_value_t	value = ISNS_VALUE_INIT(uint64, time(NULL));

	return isns_attr_alloc(ISNS_TAG_TIMESTAMP, NULL, &value);
}

/*
 * This function is invoked whenever someone changes the
 * database.
 *
 * SCNs are another area where the RFC is fabulously wishy washy.
 * It is not entirely clear when DD/DDS information should be
 * included in a management SCN - one *reasonable* interpretation
 * would be that this happens for DDReg/DDDereg/DDSReg/DDSDereg
 * events only. But some sections make it sound as if DD
 * information is included for all management SCNs.
 */
void
isns_scn_callback(const isns_db_event_t *ev, __attribute__((unused))void *ptr)
{
	isns_object_t	*obj = ev->ie_object;
	isns_scn_t	*scn, **pos;
	isns_attr_t	*timestamp;
	uint32_t	node_type;

	/* Never send out notifications for policy objects and the like. */
	if (obj->ie_flags & ISNS_OBJECT_PRIVATE)
		return;

	/* When an entity is nuked, remove all SCNs to nodes
	 * that registered from there */
	if (ISNS_IS_ENTITY(obj) && (ev->ie_bits & ISNS_SCN_OBJECT_REMOVED_MASK)) {
		pos = &isns_scn_list;
		while ((scn = *pos) != NULL) {
			if (scn->scn_entity != obj) {
				pos = &scn->scn_next;
				continue;
			}
			isns_debug_scn("Deleting SCN registration for %s\n",
					scn->scn_name);
			*pos = scn->scn_next;
			isns_scn_free(scn);
		}
		return;
	}

	/* For now we handle iSCSI nodes only. Maybe later we'll
	 * do iFC nodes as well. */
	if (!ISNS_IS_ISCSI_NODE(obj))
		return;
	if (!isns_object_get_uint32(obj, ISNS_TAG_ISCSI_NODE_TYPE, &node_type))
		return;

	if (ev->ie_recipient) {
		isns_object_t *dst = ev->ie_recipient;

		isns_debug_scn("SCN unicast <%s %u, %s> -> %s %u\n",
				obj->ie_template->iot_name, obj->ie_index,
				isns_event_string(ev->ie_bits),
				dst->ie_template->iot_name, dst->ie_index);
	} else {
		isns_debug_scn("SCN multicast <%s %u, %s>\n",
				obj->ie_template->iot_name, obj->ie_index,
				isns_event_string(ev->ie_bits));
	}
	timestamp = isns_create_timestamp_attr();

	pos = &isns_scn_list;
	while ((scn = *pos) != NULL) {
		unsigned int	scn_bits, management;
		isns_object_t	*recipient, *dd = NULL;
		isns_simple_t	*call;

		recipient = scn->scn_owner;

		/* Check if the node has gone away completely. */
		if (recipient->ie_scn_mask == 0) {
			*pos = scn->scn_next;
			isns_scn_free(scn);
			continue;
		}

		if (recipient->ie_container == NULL) {
			isns_warning("Internal bug - SCN recipient without container\n");
			/* Clear the bitmask and loop over - this will remove it */
			recipient->ie_scn_mask = 0;
			continue;
		}

		/* See if portals were added/removed.
		 * This does not catch updates that modified *just*
		 * the SCN port */
		if (recipient->ie_container->ie_mtime != scn->scn_last_update) {
			/* Rebuild the list of SCN portals */
			isns_scn_release_funnels(scn);
			scn->scn_last_update = 0;
		}
		pos = &scn->scn_next;

		/* Check for unicast events (triggered for DD addition/removal).
		 * For unicast events, we do not mask the SCN bits, so that
		 * clients who have registered for non-management events
		 * will see the membership events for their DDs nevertheless. */
		if (ev->ie_recipient == NULL) {
			scn_bits = ev->ie_bits & recipient->ie_scn_mask;
			if (scn_bits == 0)
				continue;
			/* Management SCNs should not be delivered to nodes
			 * that have not registered for them. */
			if ((ev->ie_bits & ISNS_SCN_MANAGEMENT_REGISTRATION_MASK)
			 && !(recipient->ie_scn_mask & ISNS_SCN_MANAGEMENT_REGISTRATION_MASK))
				continue;
		} else if (recipient == ev->ie_recipient) {
			scn_bits = ev->ie_bits;
		} else {
			/* No match, skip this recipient */
			continue;
		}

		if (scn->scn_last_update == 0) {
			scn->scn_last_update = recipient->ie_container->ie_mtime;
			isns_scn_setup(scn, recipient);
		}

		/* We check for SCN capable portals when processing
		 * the SCN registration. But the portals may go away
		 * in the meantime. */
		if (scn->scn_funnels == NULL)
			continue;

		/* Check SCN bitmask. This will modify the event bits. */
		scn_bits = isns_scn_match(scn, scn_bits, obj, node_type);
		if (scn_bits == 0)
			continue;
		management = !!(scn_bits & ISNS_SCN_MANAGEMENT_REGISTRATION_MASK);

		/*
		 * 2.2.3
		 * A regular SCN registration indicates that the
		 * Discovery Domain Service SHALL be used to control the
		 * distribution of SCN messages.  Receipt of regular
		 * SCNs is limited to the discovery domains in which
		 * the SCN-triggering event takes place.  Regular SCNs
		 * do not contain information about discovery domains.
		 *
		 * Implementer's note: We override check for unicast events.
		 * The reason is that DDDereg will sever the
		 * relationship, and we would never send an SCN for that
		 * event.
		 */
		if (!management && !ev->ie_recipient) {
			if (!isns_object_test_visibility(obj, recipient))
				continue;
		}

		isns_debug_scn("preparing to send SCN to %s\n",
				scn->scn_name);

		if ((call = scn->scn_message) == NULL) {
			call = isns_create_scn(isns_scn_server->is_source,
					scn->scn_attr,
					timestamp);
			if (call == NULL)
				continue;
			scn->scn_message = call;
		}

		/*
		 * If the SCN is a Management SCN, then the SCN message
		 * SHALL also list the DD_ID and/or DDS_ID of the
		 * Discovery Domains and Discovery Domain Sets (if any)
		 * that caused the change in state for that Storage Node.
		 * These additional attributes (i.e., DD_ID and/or DDS_ID)
		 * shall immediately follow the iSCSI Name or FC Port
		 * Name and precede the next SCN bitmap for the next
		 * notification message (if any).
		 */
		if (management && ev->ie_trigger)
			dd = ev->ie_trigger;

		isns_scn_add_event(call, scn_bits, obj, dd);

	}

	isns_attr_release(timestamp);
}

/*
 * Obtain a socket to talk to this guy.
 * Not entirely trivial - this can be both an established
 * (incoming) connection, or one that we should establish.
 *
 * Note, we do not support transmission on the incoming
 * connection yet.
 */
static isns_socket_t *
isns_scn_get_socket(isns_scn_t *scn)
{
	isns_scn_funnel_t *f, *best = NULL;
	isns_socket_t	*sock;
	unsigned int	worst = 0, loops = 0, nfunnels;

	/* Keep it simple for now */
	if ((f = scn->scn_current_funnel) != NULL && f->scn_socket) {
		if (!f->scn_bad)
			return f->scn_socket;
		/* Oops, we've seen timeouts on this socket. */
		isns_socket_free(f->scn_socket);
		f->scn_socket = NULL;
	}

again:
	nfunnels = 0;
	for (f = scn->scn_funnels; f; f = f->scn_next) {
		unsigned int	badness = f->scn_bad;

		if (!best || badness < best->scn_bad)
			best = f;
		if (badness > worst)
			worst = badness;
		nfunnels++;
	}

	if (!best)
		return NULL;

	sock = isns_connect_to_portal(&best->scn_portal);
	if (sock == NULL) {
		/* Make sure we try each funnel exactly once */
		best->scn_bad = worst + 1;
		if (++loops < nfunnels)
			goto again;
		return NULL;
	}

	/* Set the security context */
	isns_socket_set_security_ctx(sock,
			isns_default_security_context(1));

	isns_debug_scn("SCN: %s using portal %s\n",
			scn->scn_name,
			isns_portal_string(&best->scn_portal));
	scn->scn_current_funnel = best;
	best->scn_socket = sock;
	return sock;
}

/*
 * This is the callback function invoked when the SCN message reply
 * comes in, or when the message timed out.
 */
static void
isns_process_scn_response(uint32_t xid,
		__attribute__((unused))int status,
		isns_simple_t *msg)
{
	isns_scn_t	*scn;

	if (msg == NULL) {
		isns_debug_scn("SCN timed out\n");
		return;
	}

	isns_debug_scn("Received an SCN response\n");
	for (scn = isns_scn_list; scn; scn = scn->scn_next) {
		if (scn->scn_pending && scn->scn_xid == xid) {
			isns_debug_scn("SCN: %s acknowledged notification\n",
					scn->scn_name);
			isns_simple_free(scn->scn_pending);
			scn->scn_pending = NULL;

			if (scn->scn_current_funnel)
				scn->scn_current_funnel->scn_bad = 0;
		}
	}
}
/*
 * Transmit all pending SCN messages
 *
 * 2.9.2
 * If a Network Entity has multiple Portals with registered SCN UDP Ports,
 * then SCN messages SHALL be delivered to each Portal registered to
 * receive such messages.
 *
 * FIXME: we should make this timer based just as the ESI code.
 */
time_t
isns_scn_transmit_all(void)
{
	time_t		now = time(NULL), next_timeout;
	isns_scn_t	*scn;

	for (scn = isns_scn_list; scn; scn = scn->scn_next) {
		isns_simple_t	*call;
		isns_socket_t	*sock;

		/* We do not allow more than one outstanding
		 * notification for now. */
		if ((call = scn->scn_pending) != NULL) {
			if (scn->scn_timeout > now)
				continue;
			scn->scn_current_funnel->scn_bad++;
			if (--(scn->scn_retries))
				goto retry;
			isns_warning("SCN for %s timed out\n",
					scn->scn_name);
			isns_simple_free(call);
			scn->scn_pending = NULL;
		}

		if ((call = scn->scn_message) == NULL)
			continue;

		isns_debug_scn("SCN: transmit pending message for %s\n",
				scn->scn_name);
		scn->scn_retries = isns_config.ic_scn_retries;
		scn->scn_pending = call;
		scn->scn_message = NULL;

retry:
		if ((sock = isns_scn_get_socket(scn)) == NULL) {
			/* Sorry, no can do. */
			isns_warning("SCN for %s dropped - no portal\n",
					scn->scn_name);
			scn->scn_pending = NULL;
			isns_simple_free(call);
			continue;
		}

		isns_simple_transmit(sock, call, NULL,
				isns_config.ic_scn_timeout,
				isns_process_scn_response);
		scn->scn_xid = call->is_xid;
		scn->scn_timeout = now + isns_config.ic_scn_timeout;
	}

	next_timeout = now + 3600;
	for (scn = isns_scn_list; scn; scn = scn->scn_next) {
		if (scn->scn_pending && scn->scn_timeout < next_timeout)
			next_timeout = scn->scn_timeout;
	}

	return next_timeout;
}

/*
 * Process an incoming State Change Notification
 */
int
isns_process_scn(isns_server_t *srv, isns_simple_t *call, isns_simple_t **reply)
{
	isns_attr_list_t *list = &call->is_message_attrs;
	isns_attr_t	*dstattr, *tsattr;
	const char	*dst_name;
	unsigned int	i;

	/* The first attribute is the destination, and should match
	 * our source name. Don't bother checking. The second is the
	 * time stamp. 
	 */
	if (list->ial_count < 2)
		goto rejected;

	dstattr = list->ial_data[0];
	if (dstattr->ia_tag_id != ISNS_TAG_ISCSI_NAME
	 && dstattr->ia_tag_id != ISNS_TAG_FC_PORT_NAME_WWPN)
		goto rejected;
	if (!ISNS_ATTR_IS_STRING(dstattr))
		goto rejected;
	dst_name = dstattr->ia_value.iv_string;

	tsattr = list->ial_data[1];
	if (tsattr->ia_tag_id != ISNS_TAG_TIMESTAMP)
		return ISNS_SCN_EVENT_REJECTED;

	for (i = 2; i < list->ial_count; ) {
		isns_object_template_t *tmpl;
		isns_attr_t	*bmattr, *srcattr;
		const char	*node_name;
		uint32_t	bitmap;

		if (i + 1 >= list->ial_count)
			goto rejected;

		bmattr = list->ial_data[i++];
		srcattr = list->ial_data[i++];

		/* Validate that bitmap and node type match */
		switch (bmattr->ia_tag_id) {
		case ISNS_TAG_ISCSI_SCN_BITMAP:
			if (srcattr->ia_tag_id != ISNS_TAG_ISCSI_NAME)
				goto rejected;
			tmpl = &isns_iscsi_node_template;
			break;

		case ISNS_TAG_IFCP_SCN_BITMAP:
			if (srcattr->ia_tag_id != ISNS_TAG_FC_PORT_NAME_WWPN)
				goto rejected;
			tmpl = &isns_fc_port_template;
			break;

		default:
			goto rejected;
		}

		/* Skip over and DD_ID or DDS_ID attrs */
		while (i < list->ial_count) {
			isns_attr_t *ddattr = list->ial_data[i];

			if (ddattr->ia_tag_id == ISNS_TAG_ISCSI_SCN_BITMAP
			 || ddattr->ia_tag_id == ISNS_TAG_IFCP_SCN_BITMAP)
				break;
			++i;
		}

		if (!ISNS_ATTR_IS_UINT32(bmattr))
			goto rejected;
		bitmap = bmattr->ia_value.iv_uint32;

		if (!ISNS_ATTR_IS_STRING(srcattr))
			goto rejected;
		node_name = srcattr->ia_value.iv_string;

		if (srv->is_scn_callback)
			srv->is_scn_callback(srv->is_db, bitmap, tmpl, node_name, dst_name);
	}

	/*
	 * 5.7.5.8.  SCN Response (SCNRsp)
	 * The SCNRsp response contains the SCN Destination Attribute
	 * representing the Node identifier that received the SCN.
	 */
	*reply = isns_create_scn(srv->is_source,
			list->ial_data[0],
			NULL);
	return ISNS_SUCCESS;

rejected:
	return ISNS_SCN_EVENT_REJECTED;
}
