/*
 *
 *  neard - Near Field Communication manager
 *
 *  Copyright (C) 2012  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>

#include <linux/socket.h>

#include <near/nfc_copy.h>
#include <near/plugin.h>
#include <near/types.h>
#include <near/adapter.h>
#include <near/device.h>
#include <near/ndef.h>
#include <near/tlv.h>
#include <near/snep.h>

#include "near.h"

struct snep_fragment {
	uint32_t len;
	uint8_t *data;
};

struct p2p_snep_put_req_data {
	uint8_t fd;
	uint32_t adapter_idx;
	uint32_t target_idx;
	near_device_io_cb cb;
	guint watch;

	GSList *fragments;
};

struct p2p_snep_req_frame {
	uint8_t version;
	uint8_t request;
	uint32_t length;
	uint8_t ndef[];
} __attribute__((packed));

struct p2p_snep_resp_frame {
	uint8_t version;
	uint8_t response;
	uint32_t length;
	uint8_t info[];
} __attribute__((packed));

static GHashTable *snep_client_hash;

/* Callback: free snep data */
static void free_snep_core_client(gpointer data)
{
	struct p2p_snep_data *snep_data = data;

	DBG("");

	g_free(snep_data->nfc_data);
	g_free(snep_data);
}

/* Send a short response code */
void near_snep_core_response_noinfo(int client_fd, uint8_t response)
{
	struct p2p_snep_resp_frame resp;

	DBG("Response 0x%x", response);

	resp.version = NEAR_SNEP_VERSION;
	resp.response = response;
	resp.length = 0;

	send(client_fd, &resp, sizeof(resp), 0);
}

/*
 * near_snep_core_parse_handover_record
 *
 * The hr frame should be here BUT:
 *	The first 4 bytes are the Max Allowed Length
 *
 * - Because of an Android's BUGs:
 *	- the Hr frame is not correct; a Hr record
 *	is embedded in a ... Hr record !!! The author
 *	used 'Hr' instead of 'cr'
 *	- The OOB block is badly written:
 *	- the payload ID should be the same in the 'ac' record
 *		and the OOB record.
 *	- The OOB data length bytes must be swapped (Big endian to Little E.)
 *
 * The hack fixes the first issue (bluetooth.c fixes the second) !
 * */
void near_snep_core_parse_handover_record(int client_fd, uint8_t *ndef,
		uint32_t nfc_data_length)
{
	GList *records;
	struct near_ndef_message *msg = NULL;

	if (!ndef)
		return;

	/*
	 * Bugfix Android: Fix 'cr' instead of 'Hr'
	 * Bug is in Google:HandoverManager.java:645
	 */
	if (nfc_data_length > 9 && strncmp((char *)(ndef + 9), "Hr", 2) == 0) {
		DBG("Android 4.1.1 found !!!");
		*(ndef + 9) = 'c';
	}

	/* Parse the incoming frame */
	records = near_ndef_parse_msg(ndef, nfc_data_length, &msg);
	if (!records)
		return;

	near_ndef_records_free(records);

	if (!msg)
		return;

	near_info("Send SNEP / Hs frame");

	near_snep_core_response_with_info(client_fd, NEAR_SNEP_RESP_SUCCESS,
								msg->data, msg->length);

	near_ndef_msg_free(msg);
}

/*
 * This code will read the ndef message.
 * return	 <0 on error
 *		==0 if not more bytes
 *		>0 if there's still some data to read
 */
static int snep_core_read_ndef(int client_fd,
					struct p2p_snep_data *snep_data)
{
	int bytes_recv, remaining_bytes;

	DBG("");

	remaining_bytes = snep_data->nfc_data_length -
					snep_data->nfc_data_current_length;

	bytes_recv = recv(client_fd, snep_data->nfc_data_ptr, remaining_bytes,
								MSG_DONTWAIT);
	if (bytes_recv < 0) {
		near_error("%d %s", bytes_recv, strerror(errno));

		/* Some more data should show up */
		if (errno == EAGAIN)
			return EAGAIN;	/* Positive !!*/

		goto out;
	}

	snep_data->nfc_data_current_length += bytes_recv;
	snep_data->nfc_data_ptr += bytes_recv;

	/* Is the read complete ? */
	if (snep_data->nfc_data_length == snep_data->nfc_data_current_length)
		return 0;

	if (!snep_data->respond_continue) {
		snep_data->respond_continue = TRUE;
		near_snep_core_response_noinfo(client_fd, NEAR_SNEP_RESP_CONTINUE);
	}

	return 1;

out:
	g_hash_table_remove(snep_client_hash, GINT_TO_POINTER(client_fd));

	return -errno;		/* Negative on error */
}

static void free_snep_core_fragment(gpointer data)
{
	struct snep_fragment *fragment = data;

	if (fragment)
		g_free(fragment->data);

	g_free(fragment);
}

static void free_snep_core_push_data(gpointer userdata, int status)
{
	struct p2p_snep_put_req_data *data;

	DBG("");

	if (!userdata)
		return;

	data = (struct p2p_snep_put_req_data *) userdata;

	close(data->fd);

	if (data->cb)
		data->cb(data->adapter_idx, data->target_idx, status);

	if (data->watch > 0)
		g_source_remove(data->watch);

	g_slist_free_full(data->fragments, free_snep_core_fragment);
	g_free(data);
}

static int snep_core_send_fragment(struct p2p_snep_put_req_data *req)
{
	struct snep_fragment *fragment;
	int err;

	DBG("");

	if (!req || !req->fragments ||
		g_slist_length(req->fragments) == 0)
		return -EINVAL;

	fragment = req->fragments->data;

	err = send(req->fd, fragment->data, fragment->len, 0);

	req->fragments = g_slist_remove(req->fragments, fragment);
	g_free(fragment->data);
	g_free(fragment);

	return err;
}

static int snep_core_push_response(struct p2p_snep_put_req_data *req)
{
	struct p2p_snep_resp_frame frame;
	uint8_t *ndef;
	uint32_t ndef_len;
	int bytes_recv, err;

	DBG("");

	bytes_recv = recv(req->fd, &frame, sizeof(frame), 0);
	if (bytes_recv < 0) {
		near_error("Read SNEP frame error %d %s", bytes_recv,
							strerror(errno));
		return bytes_recv;
	}

	/* Check frame length */
	frame.length = g_ntohl(frame.length);

	DBG("Response 0x%x %p", frame.response, &frame);
	switch (frame.response) {
	case NEAR_SNEP_RESP_CONTINUE:
		while (g_slist_length(req->fragments) != 0) {
			err = snep_core_send_fragment(req);
			if (err < 0)
				return err;
		}

		return frame.response;

	case NEAR_SNEP_RESP_SUCCESS:
		if (frame.length == 0)
			return 0;

		/* Get the incoming data */
		ndef_len = frame.length;
		ndef = g_try_malloc0(ndef_len);
		if (!ndef)
			return -ENOMEM;

		bytes_recv = recv(req->fd, ndef, ndef_len, 0);
		if (bytes_recv < 0) {
			near_error("Read SNEP frame error: %d %s", bytes_recv,
							strerror(errno));
			return bytes_recv;
		}

		/* Not enough bytes */
		if (bytes_recv < 6)
			return -EINVAL;

		if (strncmp((char *)(ndef + 3), "Hs", 2) == 0)
			near_snep_core_parse_handover_record(req->fd, ndef,
								ndef_len);

		g_free(ndef);

		return 0;
	}

	return -1;
}

static gboolean snep_core_push_event(GIOChannel *channel,
				GIOCondition condition,	gpointer data)
{
	int err;

	DBG("push_event condition 0x%x", condition);

	if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {

		near_error("Error with SNEP channel");

		free_snep_core_push_data(data, -1);

		return FALSE;
	}

	err = snep_core_push_response(data);
	if (err <= 0) {
		free_snep_core_push_data(data, err);

		return FALSE;
	}

	return TRUE;
}

static int snep_core_push_prepare_fragments(struct p2p_snep_put_req_data *req,
						struct near_ndef_message *ndef)
{
	struct snep_fragment *fragment;
	uint32_t max_fragment_len;

	DBG("");

	max_fragment_len = NEAR_SNEP_REQ_MAX_FRAGMENT_LENGTH;

	while (ndef->offset < ndef->length) {

		fragment = g_try_malloc0(sizeof(struct snep_fragment));
		if (!fragment)
			return -ENOMEM;

		if (max_fragment_len <= (ndef->length - ndef->offset))
			fragment->len = max_fragment_len;
		else
			fragment->len = ndef->length - ndef->offset;

		fragment->data = g_try_malloc0(fragment->len);
		if (!fragment->data) {
			g_free(fragment);
			return -ENOMEM;
		}

		memcpy(fragment->data, ndef->data + ndef->offset,
					fragment->len);
		ndef->offset += fragment->len;
		req->fragments = g_slist_append(req->fragments, fragment);
	}

	return 0;
}

static bool snep_core_process_request(int client_fd,
					struct p2p_snep_data *snep_data,
					near_server_io req_get,
					near_server_io req_put)
{
	bool ret;
	int err;

	DBG("request %d", snep_data->request);

	/* Now, we process the request code */
	switch (snep_data->request) {
	case NEAR_SNEP_REQ_PUT:
		DBG("NEAR_SNEP_REQ_PUT");
		if (req_put)
			ret = (*req_put)(client_fd, snep_data);
		else {
			near_snep_core_response_noinfo(client_fd,
						NEAR_SNEP_RESP_NOT_IMPL);
			ret = true;
		}

		/* free and leave */
		g_hash_table_remove(snep_client_hash,
						GINT_TO_POINTER(client_fd));
		break;

	case NEAR_SNEP_REQ_GET:
		DBG("NEAR_SNEP_REQ_GET");
		if (req_get)
			ret =  (*req_get)(client_fd, snep_data);
		else {
			near_snep_core_response_noinfo(client_fd,
						NEAR_SNEP_RESP_NOT_IMPL);
			ret = true;
		}

		/* If there's some fragments, don't delete before the CONT */
		if (!snep_data->req) {
			/* free and leave */
			DBG("Clean Table");
			g_hash_table_remove(snep_client_hash,
						GINT_TO_POINTER(client_fd));
		}
		break;

	case NEAR_SNEP_REQ_REJECT:
		DBG("NEAR_SNEP_REQ_REJECT");
		if (!snep_data->req->fragments) {
			near_error("error: NEAR_SNEP_REQ_REJECT but no fragment");
			ret = false;
		}
		else {
			ret = true;
		}

		g_slist_free_full(snep_data->req->fragments,
						free_snep_core_fragment);
		g_free(snep_data->req);
		snep_data->req = NULL;

		g_hash_table_remove(snep_client_hash,
						GINT_TO_POINTER(client_fd));

		break;

	case NEAR_SNEP_REQ_CONTINUE:
		/*
		 * NEAR_SNEP_REQ_CONTINUE indicates that we have to send the
		 * remaining fragments...
		 */

		if (!snep_data->req) {
			ret = true;
			break;
		}

		DBG("NEAR_SNEP_REQ_CONTINUE");
		if (!snep_data->req->fragments) {
			near_error("error: NEAR_SNEP_REQ_CONTINUE but no fragment");
			ret = false;
			goto leave_cont;
		}

		/* Send fragments, one after the other (no ack expected) */
		while (g_slist_length(snep_data->req->fragments) != 0) {
			err = snep_core_send_fragment(snep_data->req);
			if (err < 0) {
				ret = false;
				goto leave_cont;
			}
		}

		ret = true;

leave_cont:
		/* No more fragment to send, clean memory */
		g_slist_free_full(snep_data->req->fragments,
						free_snep_core_fragment);
		g_free(snep_data->req);
		snep_data->req = NULL;

		g_hash_table_remove(snep_client_hash,
						GINT_TO_POINTER(client_fd));

		break;

	default:
		near_error("Unsupported SNEP request code");
		ret = false;
		break;
	}

	return ret;
}

/*
 * SNEP Core: read function
 *	This function handles SNEP REQUEST codes:
 *	GET, PUT and CONTINUE (REJECT is not handled).
 *
 *	We read the first 6 bytes (the header) and check
 *	- the read size ( should be 6 )
 *	- the version (on MAJOR)
 *
 *	Then, we check snep_data. If it exists, it means that we are in
 *	a fragment/continue situation (a 1st fragment was sent, and we
 *	expect a CONTINUE for the remaining bytes).
 *	If there's no existing snep_data, we create a new one and read the
 *	missing bytes (llcp removes fragmentation issues)
 *
 */
bool near_snep_core_read(int client_fd,
				uint32_t adapter_idx, uint32_t target_idx,
				near_tag_io_cb cb,
				near_server_io req_get,
				near_server_io req_put,
				gpointer data)
{
	struct p2p_snep_data *snep_data;
	struct p2p_snep_req_frame frame;
	int bytes_recv, ret;
	uint32_t ndef_length;

	DBG("");

	/* Check previous/pending snep_data */
	snep_data = g_hash_table_lookup(snep_client_hash,
					GINT_TO_POINTER(client_fd));

	/*
	 * If snep data is already there, and there are more bytes to read
	 * we just go ahead and read more fragments from the client.
	 */
	if (snep_data &&
			snep_data->nfc_data_length !=
					snep_data->nfc_data_current_length) {
		ret = snep_core_read_ndef(client_fd, snep_data);
		if (ret)
			return ret;

		goto process_request;
	}

	/*
	 * We already got something from this client, we should try
	 * to continue reading.
	 */
	/* TODO Try with PEEK */
	bytes_recv = recv(client_fd, &frame, sizeof(frame), 0);
	if (bytes_recv < 0) {
		near_error("Read error SNEP %d %s", bytes_recv,
							strerror(errno));
		return false;
	}

	/* Check frame size */
	if (bytes_recv != sizeof(frame)) {
		near_error("Bad frame size: %d", bytes_recv);
		return false;
	}

	/* If major is different, send UNSUPPORTED VERSION */
	if (NEAR_SNEP_MAJOR(frame.version) != NEAR_SNEP_MAJOR(NEAR_SNEP_VERSION)) {
		near_error("Unsupported version (%d)", frame.version);
		near_snep_core_response_noinfo(client_fd, NEAR_SNEP_RESP_VERSION);
		return true;
	}

	/*
	 * This is a fragmentation SNEP operation since we have pending
	 * frames. But the ndef length and the current data length are
	 * identical. So this is a CONTINUE for a fragmented GET, and
	 * we should just process a CONTINUE frame and send the fragments
	 * back to the client. This will be done from snep_core_process_request().
	 */
	if (snep_data) {
		snep_data->request = frame.request;
		goto process_request;
	}

	/* This is a new request from the client */
	snep_data = g_try_malloc0(sizeof(struct p2p_snep_data));
	if (!snep_data)
		return false;

	/* the whole frame length */
	ndef_length = GINT_FROM_BE(frame.length);

	snep_data->nfc_data = g_try_malloc0(ndef_length + TLV_SIZE);
	if (!snep_data->nfc_data) {
		g_free(snep_data);
		return false;
	}

	/* fill the struct */
	snep_data->nfc_data_length = ndef_length;
	snep_data->nfc_data_ptr = snep_data->nfc_data;
	snep_data->adapter_idx = adapter_idx;
	snep_data->target_idx = target_idx;
	snep_data->request = frame.request;
	snep_data->respond_continue = FALSE;
	snep_data->cb = cb;

	/* Add to the client hash table */
	g_hash_table_insert(snep_client_hash,
					GINT_TO_POINTER(client_fd), snep_data);

	if (ndef_length > 0) {
		if ((frame.request == NEAR_SNEP_REQ_GET) ||
				(frame.request == NEAR_SNEP_REQ_PUT)) {
			/* We should read the missing bytes */
			ret = snep_core_read_ndef(client_fd, snep_data);
			if (ret)
				return ret;
		}
	}

process_request:
	return snep_core_process_request(client_fd, snep_data,
							req_get, req_put);

}

/*
 * send a response frame with some datas. If the frame is too long, we
 * have to fragment the frame, using snep fragmentation protocol.
 * Return:
 * < 0 if error
 * 0 if no fragment;
 * > 0 if there's still some fragments
 *
 */
static int near_snep_core_response(int fd, struct p2p_snep_put_req_data *req,
		uint8_t resp_code, struct near_ndef_message *ndef)
{
	struct p2p_snep_req_frame header;
	struct snep_fragment *fragment;
	uint32_t max_fragment_len;
	bool fragmenting;
	int err;
	int snep_req_header_length, snep_additional_length;

	DBG("resp: 0x%02X", resp_code);

	max_fragment_len = NEAR_SNEP_REQ_MAX_FRAGMENT_LENGTH;
	header.version = NEAR_SNEP_VERSION;

	if (resp_code == NEAR_SNEP_REQ_GET) {	/* Get for android */
		snep_req_header_length = NEAR_SNEP_REQ_GET_HEADER_LENGTH;
		snep_additional_length = 4;	/* 4 Acceptable Length */
	} else {
		snep_req_header_length = NEAR_SNEP_REQ_PUT_HEADER_LENGTH;
		snep_additional_length = 0;
	}

	header.length = GUINT32_TO_BE(ndef->length + snep_additional_length);
	header.request = resp_code;

	fragment = g_try_malloc0(sizeof(struct snep_fragment));

	if (!fragment) {
		err = -ENOMEM;
		goto error;
	}

	if (max_fragment_len >= (ndef->length + snep_req_header_length)) {
		fragment->len = ndef->length + snep_req_header_length;
		fragmenting = false;
	} else {
		fragment->len = max_fragment_len;
		fragmenting = true;
	}

	fragment->data = g_try_malloc0(fragment->len);
	if (!fragment->data) {
		g_free(fragment);
		err = ENOMEM;
		goto error;
	}

	/* Header to data - common header */
	memcpy(fragment->data, (uint8_t *)&header, NEAR_SNEP_REQ_PUT_HEADER_LENGTH);

	/* if GET, we add the Acceptable length */
	if (header.request == NEAR_SNEP_REQ_GET)
		near_put_be32(snep_req_header_length,
				fragment->data + NEAR_SNEP_REQ_PUT_HEADER_LENGTH);

	if (fragmenting) {
		memcpy(fragment->data + snep_req_header_length, ndef->data,
				max_fragment_len - snep_req_header_length);
		ndef->offset = max_fragment_len - snep_req_header_length;

		err = snep_core_push_prepare_fragments(req, ndef);
		if (err < 0) {
			g_free(fragment->data);
			g_free(fragment);
			goto error;
		}
	} else {
		memcpy(fragment->data + snep_req_header_length,
						ndef->data, ndef->length);
	}

	err = send(fd, fragment->data, fragment->len, 0);
	if (err < 0) {
		near_error("Sending failed %d", err);
		g_free(fragment->data);
		g_free(fragment);
		goto error;
	}

	g_free(fragment->data);
	g_free(fragment);

	return 0;

error:
	if (req)
		free_snep_core_push_data(req, err);

	return err;
}

void near_snep_core_response_with_info(int client_fd, uint8_t response,
				uint8_t *data, int length)
{
	struct p2p_snep_data *snep_data;
	struct p2p_snep_put_req_data *req;
	struct near_ndef_message *ndef;

	DBG("Response with info 0x%x (len:%d)", response, length);

	req = NULL;
	ndef = NULL;

	/* get the snep data */
	snep_data = g_hash_table_lookup(snep_client_hash,
						GINT_TO_POINTER(client_fd));
	if (!snep_data) {
		DBG("snep_data not found");
		goto done;
	}

	/* Prepare the ndef struct */
	ndef = g_try_malloc0(sizeof(struct near_ndef_message));
	if (!ndef)
		goto done;

	ndef->data = g_try_malloc0(length);
	if (!ndef->data) {
		g_free(ndef);
		ndef = NULL;
		goto done;
	}

	/* Fill the ndef */
	ndef->length = length;
	ndef->offset = 0;
	memcpy(ndef->data, data, length);

	ndef->offset = 0;

	/* Now prepare req struct */
	req = g_try_malloc0(sizeof(struct p2p_snep_put_req_data));
	if (!req)
		goto done;

	/* Prepare the callback */
	snep_data->req = req;

	req->fd = client_fd;
	req->adapter_idx = snep_data->adapter_idx;
	req->target_idx = snep_data->target_idx;
	req->cb = snep_data->cb;

	/* send it !*/
	near_snep_core_response(client_fd, req, response, ndef);

done:
	/* If no fragment, free mem */
	if (req) {
		if (req->fragments == 0) {
			g_free(req);
			snep_data->req = NULL;
		}
	}

	near_ndef_msg_free(ndef);
}

/* SNEP Core: on P2P push */
int near_snep_core_push(int fd, uint32_t adapter_idx, uint32_t target_idx,
			struct near_ndef_message *ndef,
			near_device_io_cb cb,
			gpointer data)
{
	struct p2p_snep_put_req_data *req;
	GIOChannel *channel;
	uint8_t resp_code;
	int err;

	DBG("");

	req = g_try_malloc0(sizeof(struct p2p_snep_put_req_data));
	if (!req) {
		err = -ENOMEM;
		goto error;
	}

	channel = g_io_channel_unix_new(fd);
	g_io_channel_set_close_on_unref(channel, TRUE);

	req->fd = fd;
	req->adapter_idx = adapter_idx;
	req->target_idx = target_idx;
	req->cb = cb;
	ndef->offset = 0;
	req->watch = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_NVAL |
					G_IO_ERR, snep_core_push_event,
					(gpointer) req);

	/* Check if Hr or Hs for Handover over SNEP */
	if (*(char *)(ndef->data + 3) == 'H')
		resp_code = NEAR_SNEP_REQ_GET;		/* Get for android */
	else
		resp_code = NEAR_SNEP_REQ_PUT;

	return near_snep_core_response(fd, req, resp_code, ndef);

error:
	free_snep_core_push_data(req, err);

	return err;

}

/* SNEP core functions: close */
void near_snep_core_close(int client_fd, int err, gpointer data)
{
	struct p2p_snep_data *snep_data;

	DBG("");

	snep_data = g_hash_table_lookup(snep_client_hash,
					GINT_TO_POINTER(client_fd));
	if (!snep_data)
		return;

	snep_data->cb(snep_data->adapter_idx, snep_data->target_idx, err);

	g_hash_table_remove(snep_client_hash, GINT_TO_POINTER(client_fd));
}

int __near_snep_core_init(void)
{
	snep_client_hash = g_hash_table_new_full(g_direct_hash,
							g_direct_equal, NULL,
							free_snep_core_client);

	return 0;
}

void __near_snep_core_cleanup(void)
{
	g_hash_table_destroy(snep_client_hash);
	snep_client_hash = NULL;
}
