/*
 * Common handling for iSNS message parsing
 *
 * Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
 *
 */

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

typedef void isns_simple_callback_fn_t(uint32_t, int status, isns_simple_t *);

static int	isns_attr_list_scanner_get_pg(struct isns_attr_list_scanner *st);

/*
 * Allocate an empty simple message
 */
static isns_simple_t *
__isns_alloc_simple(void)
{
	isns_simple_t	*simp;

	simp = isns_calloc(1, sizeof(*simp));

	isns_attr_list_init(&simp->is_message_attrs);
	isns_attr_list_init(&simp->is_operating_attrs);

	return simp;
}

/*
 * Create a simple message, and set the source name
 */
isns_simple_t *
isns_simple_create(uint32_t function, isns_source_t *source,
		const isns_attr_list_t *key)
{
	isns_simple_t	*simp;

	simp = __isns_alloc_simple();
	simp->is_function = function;
	simp->is_source = source;
	if (source != NULL)
		source->is_users++;

	if (key)
		isns_attr_list_copy(&simp->is_message_attrs, key);

	return simp;
}

/*
 * Perform a call to the server, waiting for the response.
 */
int
isns_simple_call(isns_socket_t *sock, isns_simple_t **inout)
{
	isns_simple_t	*simp = *inout;
	isns_message_t	*msg, *resp;
	int		status;

	isns_simple_print(simp, isns_debug_message);

	status = isns_simple_encode(simp, &msg);
	if (status != ISNS_SUCCESS) {
		isns_error("Unable to encode %s: %s\n",
				isns_function_name(simp->is_function),
				isns_strerror(status));
		return status;
	}

	isns_debug_message("Sending request, len=%d\n",
			buf_avail(msg->im_payload));

	resp = isns_socket_call(sock, msg,
			isns_config.ic_network.call_timeout);
	isns_assert(msg->im_users == 1);
	isns_message_release(msg);

	if (resp == NULL) {
		isns_error("Timed out while waiting for reply\n");
		return ISNS_INTERNAL_ERROR;
	}

	isns_debug_message("Received reply, len=%d\n",
			buf_avail(resp->im_payload));
	isns_assert(resp->im_users == 1);

	status = isns_message_status(resp);
	if (status != ISNS_SUCCESS) {
		isns_message_release(resp);
		return status;
	}

	status = isns_simple_decode(resp, &simp);
	isns_message_release(resp);

	if (status) {
		isns_error("Unable to decode server response: %s (status 0x%04x)\n",
				isns_strerror(status), status);
		return status;
	}

	isns_simple_print(simp, isns_debug_message);

	isns_simple_free(*inout);
	*inout = simp;
	return ISNS_SUCCESS;
}

/*
 * This callback is invoked from the network layer when
 * we received a response to an async message
 */
static void
isns_simple_recv_response(isns_message_t *cmsg, isns_message_t *rmsg)
{
	isns_simple_callback_fn_t *user_callback;
	isns_simple_t	*resp = NULL;
	int		status = ISNS_INTERNAL_ERROR;

	/* rmsg being NULL means the call timed out. */
	if (rmsg == NULL)
		goto callback;

	status = isns_message_status(rmsg);
	if (status != ISNS_SUCCESS) {
		isns_error("Server flags error: %s (status 0x%04x)\n",
			    isns_strerror(status), status);
		goto callback;
	}

	status = isns_simple_decode(rmsg, &resp);
	if (status) {
		isns_error("Unable to decode server response: %s (status 0x%04x)\n",
				isns_strerror(status), status);
		resp = NULL;
		goto callback;
	}

	isns_simple_print(resp, isns_debug_message);

callback:
	user_callback = cmsg->im_calldata;
	if (user_callback)
		user_callback(cmsg->im_xid, status, resp);
	if (resp)
		isns_simple_free(resp);
}

/*
 * Transmit a call, without waiting for the response.
 */
int
isns_simple_transmit(isns_socket_t *sock, isns_simple_t *call,
			__attribute__((unused))const isns_portal_info_t *dest,
			unsigned int timeout,
			isns_simple_callback_fn_t *user_callback)
{
	isns_message_t	*msg;
	int		status;

	isns_simple_print(call, isns_debug_message);

	status = isns_simple_encode(call, &msg);
	if (status != ISNS_SUCCESS) {
		isns_error("Unable to encode %s: %s\n",
				isns_function_name(call->is_function),
				isns_strerror(status));
		return status;
	}

	isns_debug_message("Sending message, len=%d\n",
			buf_avail(msg->im_payload));

	if (user_callback) {
		msg->im_callback = isns_simple_recv_response;
		msg->im_calldata = user_callback;
	}

	if (!isns_socket_submit(sock, msg, timeout))
		status = ISNS_INTERNAL_ERROR;
	isns_message_release(msg);
	return status;
}

/*
 * Delete the simple message object
 */
void
isns_simple_free(isns_simple_t *simp)
{
	if (simp == NULL)
		return;

	isns_attr_list_destroy(&simp->is_message_attrs);
	isns_attr_list_destroy(&simp->is_operating_attrs);
	isns_source_release(simp->is_source);
	isns_policy_release(simp->is_policy);
	isns_free(simp);
}

/*
 * Get the source associated with this simple message
 */
isns_source_t *
isns_simple_get_source(isns_simple_t *simp)
{
	return simp->is_source;
}

const isns_attr_list_t *
isns_simple_get_attrs(isns_simple_t *simp)
{
	return &simp->is_operating_attrs;
}

/*
 * Determine whether message includes a source attr.
 */
static inline int
isns_simple_include_source(uint16_t function)
{
	if (function & 0x8000)
		return 0;
	switch (function) {
	case ISNS_STATE_CHANGE_NOTIFICATION:
	case ISNS_ENTITY_STATUS_INQUIRY:
		return 0;
	}
	return 1;
}

/*
 * Decode a simple message
 */
int
isns_simple_decode(isns_message_t *msg, isns_simple_t **result)
{
	isns_simple_t	*simp = __isns_alloc_simple();
	buf_t		*bp = msg->im_payload;
	int		status = ISNS_SUCCESS;

	simp->is_function = msg->im_header.i_function;
	simp->is_xid = msg->im_xid;

	if (isns_simple_include_source(simp->is_function)) {
		status = isns_source_decode(bp, &simp->is_source);
		if (status != ISNS_SUCCESS)
			goto out;
	}

	switch (simp->is_function & 0x7FFF) {
	case ISNS_ENTITY_STATUS_INQUIRY:
	case ISNS_STATE_CHANGE_NOTIFICATION:
		/* Server messages do not include a source */
		status = isns_attr_list_decode(bp,
				&simp->is_message_attrs);
		break;

	default:
		status = isns_attr_list_decode_delimited(bp,
				&simp->is_message_attrs);
		if (status == ISNS_SUCCESS)
			status = isns_attr_list_decode(bp,
				&simp->is_operating_attrs);
	}

	if (msg->im_header.i_flags & ISNS_F_REPLACE)
		simp->is_replace = 1;

out:
	if (status == ISNS_SUCCESS) {
		*result = simp;
	} else {
		isns_simple_free(simp);
		*result = NULL;
	}
	return status;
}

/*
 * Encode a simple message reply or response
 */
static int
__isns_simple_encode(isns_simple_t *simp, buf_t *bp)
{
	int	status = ISNS_SUCCESS;

	if (isns_simple_include_source(simp->is_function)) {
		if (simp->is_source == NULL) {
			isns_error("Cannot encode %s message - caller forgot to set source\n",
				  isns_function_name(simp->is_function));
			return ISNS_SOURCE_UNKNOWN;
		}
		status = isns_source_encode(bp, simp->is_source);
	}

	if (status == ISNS_SUCCESS)
		status = isns_attr_list_encode(bp, &simp->is_message_attrs);

	/* Some functions have just one set of attrs. */
	switch (simp->is_function & 0x7fff) {
	/* It's not entirely clear which calls actually have the delimiter.
	 * The spec is sometimes a little vague on this. */
	case ISNS_SCN_DEREGISTER:
	case ISNS_ENTITY_STATUS_INQUIRY:
	case ISNS_STATE_CHANGE_NOTIFICATION:
		break;

	default:
		if (status == ISNS_SUCCESS)
			status = isns_encode_delimiter(bp);
		if (status == ISNS_SUCCESS)
			status = isns_attr_list_encode(bp, &simp->is_operating_attrs);
		break;
	}

	return status;
}

int
isns_simple_encode(isns_simple_t *simp, isns_message_t **result)
{
	isns_message_t *msg;
	int status, flags;

	flags = ISNS_F_CLIENT;
	if (simp->is_replace)
		flags |= ISNS_F_REPLACE;
	msg = isns_create_message(simp->is_function, flags);

	/* FIXME: for UDP sockets, isns_simple_t may contain a
	   destination address. */

	status = __isns_simple_encode(simp, msg->im_payload);
	if (status != ISNS_SUCCESS) {
		isns_message_release(msg);
		msg = NULL;
	}

	/* Report the XID to the caller */
	simp->is_xid = msg->im_xid;

	*result = msg;
	return status;
}

int
isns_simple_encode_response(isns_simple_t *reg,
		const isns_message_t *request, isns_message_t **result)
{
	isns_message_t *msg;
	int status;

	msg = isns_create_reply(request);

	status = __isns_simple_encode(reg, msg->im_payload);
	if (status != ISNS_SUCCESS) {
		isns_message_release(msg);
		msg = NULL;
	}

	*result = msg;
	return status;
}

int
isns_simple_decode_response(isns_message_t *resp, isns_simple_t **result)
{
	return isns_simple_decode(resp, result);
}

/*
 * Extract the list of objects from a DevAttrReg/DevAttrQry
 * response or similar.
 */
int
isns_simple_response_get_objects(isns_simple_t *resp,
		isns_object_list_t *result)
{
	struct isns_attr_list_scanner state;
	int	status = ISNS_SUCCESS;

	isns_attr_list_scanner_init(&state, NULL, &resp->is_operating_attrs);
	while (1) {
		isns_object_t	*obj;

		status = isns_attr_list_scanner_next(&state);
		if (status == ISNS_NO_SUCH_ENTRY) {
			status = ISNS_SUCCESS;
			break;
		}
		if (status)
			break;

		obj = isns_create_object(state.tmpl, &state.keys, NULL);

		isns_object_set_attrlist(obj, &state.attrs);
		if (obj != state.key_obj)
			isns_object_list_append(result, obj);
		isns_object_release(obj);
	}

	isns_attr_list_scanner_destroy(&state);
	return status;
}

/*
 * Print a simple message object
 */
void
isns_simple_print(isns_simple_t *simp, isns_print_fn_t *fn)
{
	char	buffer[256];

	if (fn == isns_debug_message
	 && !isns_debug_enabled(DBG_MESSAGE))
		return;

	fn("---%s%s---\n",
			isns_function_name(simp->is_function),
			simp->is_replace? "[REPLACE]" : "");
	if (simp->is_source) {
		fn("Source:\n", buffer);
		isns_attr_print(simp->is_source->is_attr, fn);
	} else {
		fn("Source: <empty>\n");
	}

	if (simp->is_message_attrs.ial_count == 0) {
		fn("Message attributes: <empty list>\n");
	} else {
		fn("Message attributes:\n");
		isns_attr_list_print(&simp->is_message_attrs, fn);
	}
	if (simp->is_operating_attrs.ial_count == 0) {
		fn("Operating attributes: <empty list>\n");
	} else {
		fn("Operating attributes:\n");
		isns_attr_list_print(&simp->is_operating_attrs, fn);
	}
}

/*
 * This set of functions analyzes the operating attrs of a registration,
 * or a query response, and chops it up into separate chunks, one
 * per objects.
 *
 * It always returns the keys and attrs for one object,
 * following the ordering constraints laid out in the RFC.
 */
void
isns_attr_list_scanner_init(struct isns_attr_list_scanner *st,
			isns_object_t *key_obj,
			const isns_attr_list_t *attrs)
{
	memset(st, 0, sizeof(*st));
	st->orig_attrs = *attrs;
	st->key_obj = key_obj;
}

void
isns_attr_list_scanner_destroy(struct isns_attr_list_scanner *st)
{
	isns_attr_list_destroy(&st->keys);
	isns_attr_list_destroy(&st->attrs);
	memset(st, 0, sizeof(*st));
}

int
isns_attr_list_scanner_next(struct isns_attr_list_scanner *st)
{
	isns_attr_t	*attr;
	unsigned int	i, pos = st->pos;

	isns_attr_list_destroy(&st->keys);
	isns_attr_list_destroy(&st->attrs);

	if (st->orig_attrs.ial_count <= pos)
		return ISNS_NO_SUCH_ENTRY;

	attr = st->orig_attrs.ial_data[pos];

	/* handle those funky inlined PGT definitions */
	if (st->pgt_next_attr && attr->ia_tag_id == st->pgt_next_attr)
		return isns_attr_list_scanner_get_pg(st);

	/* This isn't really structured programming anymore */
	if (st->index_acceptable
	 && (st->tmpl = isns_object_template_for_index_tag(attr->ia_tag_id)))
		goto copy_attrs;

	/*
	 * Find the object template for the given key attr(s).
	 * This function also enforces restrictions on the
	 * order of key attributes.
	 */
	st->tmpl = isns_object_template_find(attr->ia_tag_id);
	if (st->tmpl == NULL) {
		isns_debug_protocol("%s: attr %u is not a key attr\n",
				__FUNCTION__, attr->ia_tag_id);
		return ISNS_INVALID_REGISTRATION;
	}

	/* Copy the key attrs */
	for (i = 0; i < st->tmpl->iot_num_keys; ++i, ++pos) {
		if (pos >= st->orig_attrs.ial_count) {
			isns_debug_protocol("%s: incomplete %s object "
					"(key attr %u missing)\n",
					__FUNCTION__, st->tmpl->iot_name, pos);
			return ISNS_INVALID_REGISTRATION;
		}
		attr = st->orig_attrs.ial_data[pos];

		/* Make sure key attrs are complete and in order */
		if (attr->ia_tag_id != st->tmpl->iot_keys[i]) {
			isns_debug_protocol("%s: incomplete %s object "
					"(key attr %u missing)\n",
					__FUNCTION__, st->tmpl->iot_name, pos);
			return ISNS_INVALID_REGISTRATION;
		}

		isns_attr_list_append_attr(&st->keys, attr);
	}

	/*
	 * Consume all non-key attributes corresponding to the
	 * object class. We stop whenever we hit another
	 * key attribute, or an attribute that does not belong to
	 * the object type (eg when a storage node is followed by
	 * a PGT attribute, as described in section 5.6.5.1).
	 */
copy_attrs:
	while (pos < st->orig_attrs.ial_count) {
		uint32_t	tag;

		attr = st->orig_attrs.ial_data[pos];
		tag = attr->ia_tag_id;

		if (!isns_object_attr_valid(st->tmpl, tag)
		 || isns_object_template_find(tag) != NULL)
			break;

		pos++;
		isns_attr_list_append_attr(&st->attrs, attr);
	}
	st->pos = pos;

	return ISNS_SUCCESS;
}

int
isns_attr_list_scanner_get_pg(struct isns_attr_list_scanner *st)
{
	isns_attr_t	*attr, *next = NULL;
	unsigned int	pos = st->pos;


	attr = st->orig_attrs.ial_data[st->pos++];
	if (st->pgt_next_attr == ISNS_TAG_PG_TAG) {
		isns_object_t	*base = st->pgt_base_object;

		if (ISNS_ATTR_IS_NIL(attr))
			st->pgt_value = 0;
		else if (ISNS_ATTR_IS_UINT32(attr))
			st->pgt_value = attr->ia_value.iv_uint32;
		else
			return ISNS_INVALID_REGISTRATION;

		if (ISNS_IS_PORTAL(base)
		 && isns_portal_from_object(&st->pgt_portal_info,
					ISNS_TAG_PORTAL_IP_ADDRESS,
					ISNS_TAG_PORTAL_TCP_UDP_PORT,
					base)) {
			st->pgt_next_attr = ISNS_TAG_PG_ISCSI_NAME;
		} else
		if (ISNS_IS_ISCSI_NODE(base)
		 && isns_object_get_string(base,
					ISNS_TAG_ISCSI_NAME,
					&st->pgt_iscsi_name)) {
			st->pgt_next_attr = ISNS_TAG_PG_PORTAL_IP_ADDR;
		} else {
			return ISNS_INTERNAL_ERROR;
		}

		/* Trailing PGT at end of list. Shrug. */
		if (st->pos >= st->orig_attrs.ial_count)
			return ISNS_NO_SUCH_ENTRY;

		attr = st->orig_attrs.ial_data[st->pos++];
		if (attr->ia_tag_id != st->pgt_next_attr) {
			/* Some clients may do this; catch them so
			 * we can fix it. */
			isns_error("Oops, client sends PGT followed by <%s>\n",
					attr->ia_tag->it_name);
			return ISNS_INVALID_REGISTRATION;
		}
	}

	st->tmpl = &isns_iscsi_pg_template;
	if (st->pgt_next_attr == ISNS_TAG_PG_ISCSI_NAME) {
		isns_attr_list_append_attr(&st->keys, attr);
		isns_portal_to_attr_list(&st->pgt_portal_info,
					ISNS_TAG_PG_PORTAL_IP_ADDR,
					ISNS_TAG_PG_PORTAL_TCP_UDP_PORT,
					&st->keys);
	} else
	if (st->pgt_next_attr == ISNS_TAG_PG_PORTAL_IP_ADDR) {
		if (st->pos >= st->orig_attrs.ial_count)
			return ISNS_INVALID_REGISTRATION;

		next = st->orig_attrs.ial_data[st->pos++];
		if (next->ia_tag_id != ISNS_TAG_PG_PORTAL_TCP_UDP_PORT)
			return ISNS_INVALID_REGISTRATION;

		isns_attr_list_append_string(&st->keys,
					ISNS_TAG_PG_ISCSI_NAME,
					st->pgt_iscsi_name);
		isns_attr_list_append_attr(&st->keys, attr);
		isns_attr_list_append_attr(&st->keys, next);
	} else {
		return ISNS_INTERNAL_ERROR;
	}

	isns_attr_list_append_uint32(&st->attrs,
				ISNS_TAG_PG_TAG,
				st->pgt_value);

	/* Copy other PG attributes if present */
	for (pos = st->pos; pos < st->orig_attrs.ial_count; ++pos) {
		uint32_t	tag;

		attr = st->orig_attrs.ial_data[pos];
		tag = attr->ia_tag_id;

		/*
		 * Additional sets of PGTs and PG iSCSI Names to be
		 * associated to the registered Portal MAY follow.
		 */
		if (tag == ISNS_TAG_PG_TAG) {
			st->pgt_next_attr = tag;
			break;
		}

		if (tag == ISNS_TAG_PG_ISCSI_NAME
		 || tag == ISNS_TAG_PG_PORTAL_IP_ADDR
		 || tag == ISNS_TAG_PG_PORTAL_TCP_UDP_PORT
		 || !isns_object_attr_valid(st->tmpl, tag))
			break;

		isns_attr_list_append_attr(&st->attrs, attr);
	}
	st->pos = pos;

	return ISNS_SUCCESS;
}

/*
 * Get the name of a function
 */
#define __ISNS_MAX_FUNCTION	16
static const char *	isns_req_function_names[__ISNS_MAX_FUNCTION] = {
[ISNS_DEVICE_ATTRIBUTE_REGISTER]= "DevAttrReg",
[ISNS_DEVICE_ATTRIBUTE_QUERY]	= "DevAttrQry",
[ISNS_DEVICE_GET_NEXT]		= "DevGetNext",
[ISNS_DEVICE_DEREGISTER]	= "DevDereg",
[ISNS_SCN_REGISTER]		= "SCNReg",
[ISNS_SCN_DEREGISTER]		= "SCNDereg",
[ISNS_SCN_EVENT]		= "SCNEvent",
[ISNS_STATE_CHANGE_NOTIFICATION]= "SCN",
[ISNS_DD_REGISTER]		= "DDReg",
[ISNS_DD_DEREGISTER]		= "DDDereg",
[ISNS_DDS_REGISTER]		= "DDSReg",
[ISNS_DDS_DEREGISTER]		= "DDSDereg",
[ISNS_ENTITY_STATUS_INQUIRY]	= "ESI",
[ISNS_HEARTBEAT]		= "Heartbeat",
};
static const char *	isns_resp_function_names[__ISNS_MAX_FUNCTION] = {
[ISNS_DEVICE_ATTRIBUTE_REGISTER]= "DevAttrRegResp",
[ISNS_DEVICE_ATTRIBUTE_QUERY]	= "DevAttrQryResp",
[ISNS_DEVICE_GET_NEXT]		= "DevGetNextResp",
[ISNS_DEVICE_DEREGISTER]	= "DevDeregResp",
[ISNS_SCN_REGISTER]		= "SCNRegResp",
[ISNS_SCN_DEREGISTER]		= "SCNDeregResp",
[ISNS_SCN_EVENT]		= "SCNEventResp",
[ISNS_STATE_CHANGE_NOTIFICATION]= "SCNResp",
[ISNS_DD_REGISTER]		= "DDRegResp",
[ISNS_DD_DEREGISTER]		= "DDDeregResp",
[ISNS_DDS_REGISTER]		= "DDSRegResp",
[ISNS_DDS_DEREGISTER]		= "DDSDeregResp",
[ISNS_ENTITY_STATUS_INQUIRY]	= "ESIRsp",
/* No response code for heartbeat */
};

const char *
isns_function_name(uint32_t function)
{
	static char	namebuf[32];
	const char	**names, *name;
	unsigned int	num = function;

	names = isns_req_function_names;
	if (num & 0x8000) {
		names = isns_resp_function_names;
		num &= 0x7fff;
	}
	name = NULL;
	if (num < __ISNS_MAX_FUNCTION)
		name = names[num];
	if (name == NULL) {
		snprintf(namebuf, sizeof(namebuf),
				"<function %08x>",
				function);
		name = namebuf;
	}

	return name;
}

