/*
 *  neard - Near Field Communication manager
 *
 *  Copyright (C) 2013  Mobica Limited. 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 <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/socket.h>

#include <near/types.h>

#include <src/near.h>
#include <src/ndef-private.h>
#include "test-utils.h"

#define TEST_SNEP_LOG(fmt, ...) do { \
	if (g_test_verbose()) {\
		g_printf("[SNEP unit] " fmt, ##__VA_ARGS__); \
	} \
	} while (0)

#define TEST_SNEP_PTR_UNUSED(x) do { if ((void *)x) {} } while (0)

static const char *short_text = "neard";

static const char *long_text = "The Linux NFC project aims to provide a " \
	"full NFC support for Linux. It is based on the neard NFC user space "\
	"stack running on top of the Linux kernel NFC subsystem. NFC stands " \
	"Near Field Communication. It is a short-range (a few centimeters)"\
	"radio technology that enables communication between devices that " \
	"either touch or are momentarily held close together. NFC is an open "\
	"technology standardized by the NFC Forum. It is based on RFID. ";

/* 'neard' - UTF-8 - en-US Text NDEF */
static uint8_t text[] = { 0xd1, 0x1, 0xb, 0x54, 0x5, 0x65, 0x6e,
			0x2d, 0x55, 0x53, 0x6e, 0x65, 0x61, 0x72, 0x64 };

/* sockets */
static int sockfd[2];
static int client;
static int server;

struct test_snep_context {
	uint8_t snep_version;
	uint32_t req_info_len;
	uint32_t payload_len;
	uint8_t *req_info;

	uint32_t acc_len; /* req GET specific */

	struct near_ndef_message *test_recd_msg;
};

/* variables used in dummy_req_{get|put} */
static struct test_snep_context *gcontext;
static struct near_ndef_record *stored_recd;
static GSList *test_fragments;

/* GET/PUT server functions */

/*
 * @brief Utility: Dummy PUT request handler
 */
static bool test_snep_dummy_req_put(int fd, void *data)
{
	struct p2p_snep_data *snep_data = data;
	GList *records;
	uint8_t *nfc_data;
	uint32_t nfc_data_length;
	uint32_t offset = 0;

	TEST_SNEP_LOG(">> dummy_req_put entry %p\n", data);

	if (!snep_data)
		goto error;

	if (stored_recd)
		TEST_SNEP_LOG("\tdummy_req_put already stored record\n");

	if (snep_data->nfc_data_length > snep_data->nfc_data_current_length)
		return true;

	test_fragments = g_slist_append(test_fragments, snep_data);

	nfc_data_length = 0;
	nfc_data = g_try_malloc0(snep_data->nfc_data_length);
	g_assert(nfc_data);

	while (g_slist_length(test_fragments) > 0) {
		static int frag_cnt;
		struct p2p_snep_data *fragment = test_fragments->data;

		TEST_SNEP_LOG("\tdummy_req_put frag=%d, len=%u, current=%u\n",
				frag_cnt, fragment->nfc_data_length,
				fragment->nfc_data_current_length);
		test_fragments = g_slist_remove(test_fragments, fragment);

		memcpy(nfc_data + offset, fragment->nfc_data,
			fragment->nfc_data_current_length - nfc_data_length);

		offset += fragment->nfc_data_current_length - nfc_data_length;
		nfc_data_length = offset;

		frag_cnt++;
	}

	records = near_ndef_parse_msg(nfc_data, nfc_data_length, NULL);
	if (!records) {
		TEST_SNEP_LOG("\tdummy_req_put parsing ndef failed\n");
		goto error_free_nfc_data;
	}

	if (g_list_length(records) != 1) {
		TEST_SNEP_LOG("\tdummy_req_put records number mismatch");
		goto error_free_records;
	}

	g_free(nfc_data);

	stored_recd = records->data;
	g_list_free(records);

	TEST_SNEP_LOG("\t\tdummy_req_put STORED REC data=%p length=%zu\n",
			stored_recd->data, stored_recd->data_len);

	near_snep_core_response_noinfo(fd, NEAR_SNEP_RESP_SUCCESS);
	return true;

error_free_records:
	near_ndef_records_free(records);
error_free_nfc_data:
	g_free(nfc_data);
error:
	TEST_SNEP_LOG("\tdummy_req_put error!!!\n");
	return false;
}

/*
 * @brief Utility: Dummy GET request handler
 */
static bool test_snep_dummy_req_get(int fd, void *data)
{
	struct p2p_snep_data *snep_data = data;

	TEST_SNEP_LOG(">> dummy_req_get entry %p\n", data);

	if (!snep_data)
		goto error;

	TEST_SNEP_LOG("\t\tdummy_req_get STORED REC data=%p length=%zu\n",
			stored_recd->data, stored_recd->data_len);

	near_snep_core_response_with_info(fd, NEAR_SNEP_RESP_SUCCESS,
					near_ndef_data_ptr(stored_recd),
					near_ndef_data_length(stored_recd));
	return true;

error:
	TEST_SNEP_LOG("\tdummy_req_get error!!!\n");
	return false;
}

static void test_snep_init(gpointer context, gconstpointer data)
{
	struct test_snep_context *ctx = context;
	struct timeval tv;
	int ret;
	const char *test_data = data;

	g_assert(socketpair(PF_LOCAL, SOCK_STREAM, 0, sockfd) == 0);

	client = 0;
	server = 1;

	tv.tv_sec = 1;
	tv.tv_usec = 0;
	ret = setsockopt(sockfd[client], SOL_SOCKET, SO_RCVTIMEO,
			(const void *) &tv, sizeof(tv));
	if (ret != 0)
		TEST_SNEP_LOG("set sock SO_RCVTIMEO failed");

	g_assert_cmpint(__near_snep_core_init(), ==, 0);

	stored_recd = NULL;

	ctx->test_recd_msg = test_ndef_create_test_record(test_data);

	ctx->snep_version = NEAR_SNEP_VERSION;
	ctx->req_info = ctx->test_recd_msg->data;
	ctx->req_info_len = ctx->test_recd_msg->length;
	ctx->payload_len = ctx->test_recd_msg->length;
	ctx->acc_len = 64;

	gcontext = ctx;
}

static void test_snep_cleanup(gpointer context, gconstpointer data)
{
	struct test_snep_context *ctx = context;

	__near_snep_core_cleanup();

	if (stored_recd)
		__near_ndef_record_free(stored_recd);

	if (ctx->test_recd_msg)
		near_ndef_msg_free(ctx->test_recd_msg);

	g_slist_free(test_fragments);

	close(sockfd[client]);
	close(sockfd[server]);

	gcontext = NULL;
}

/*
 * @brief Utility: Allocate and build SNEP request frame.
 *
 * @param[in] frame_len   Size of the entire frame
 * @param[in] ver         SNEP protocol version field
 * @param[in] resp_type   SNEP response code field
 * @param[in] info_len    SNEP info length field
 * @param[in] data        SNEP info field
 * @param[in] payload_len Size of the payload to be inserted
 * @return p2p_snep_resp_frame
 */
static struct p2p_snep_req_frame *test_snep_build_req_frame(
		size_t frame_len, uint8_t ver, uint8_t req_type,
		uint32_t info_len, uint8_t *data, uint32_t payload_len)
{
	struct p2p_snep_req_frame *req;

	req = g_try_malloc0(frame_len);
	g_assert(req);

	req->version = ver;
	req->request = req_type;
	req->length = GUINT_TO_BE(info_len);
	if (data)
		memcpy(req->ndef, data, payload_len);

	return req;
}

/*
 * @brief Utility: Allocate and build SNEP GET request frame.
 *
 * @param[in] frame_len   Size of the entire frame
 * @param[in] ver         SNEP protocol version field
 * @param[in] resp_type   SNEP response code field
 * @param[in] info_len    SNEP info length field
 * @param[in] data        SNEP info field
 * @param[in] acc_len     SNEP acceptable length field
 * @param[in] payload_len Size of the payload to be inserted
 * @return p2p_snep_resp_frame
 */
static struct p2p_snep_req_frame *test_snep_build_req_get_frame(
			size_t frame_len, uint8_t ver, uint8_t req_type,
			uint32_t info_len, uint32_t acc_len, uint8_t *data,
			uint32_t payload_len)
{
	struct p2p_snep_req_frame *req;
	uint32_t acc_len_be = GUINT_TO_BE(acc_len);

	req = g_try_malloc0(frame_len);
	g_assert(req);

	req->version = ver;
	req->request = req_type;
	req->length = GUINT_TO_BE(info_len);
	memcpy(req->ndef, &acc_len_be, sizeof(acc_len_be));
	memcpy(req->ndef + sizeof(acc_len_be), data, payload_len);

	return req;
}

/*
 * @brief Utility: Allocate and build SNEP response frame.
 *
 * @param[in] frame_len   Size of the entire frame
 * @param[in] ver         SNEP protocol version field
 * @param[in] resp_type   SNEP response code field
 * @param[in] info_len    SNEP info length field
 * @param[in] data        SNEP info field
 * @return p2p_snep_resp_frame
 */
static struct p2p_snep_resp_frame *test_snep_build_resp_frame(
			size_t frame_len, uint8_t ver, uint8_t resp_type,
			uint32_t info_len, uint8_t *data)
{
	struct p2p_snep_resp_frame *resp;

	resp = g_try_malloc0(frame_len);
	g_assert(resp);

	resp->version = ver;
	resp->response = resp_type;
	resp->length = GUINT_TO_BE(info_len);
	if (data)
		memcpy(resp->info, data, info_len);

	return resp;
}

/*
 * @brief Utility: Send the \p req frame to the socket and call
 * near_snep_core_read
 *
 * @param[in] req        Request frame to send
 * @param[in] frame_len  Size of the frame
 * @param[in] req_get    GET server function
 * @param[in] req_put    PUT server function
 * @return near_bool_t returned by near_snep_core_read
 */
static bool test_snep_read_req_common(
			struct p2p_snep_req_frame *req, size_t frame_len,
			near_server_io req_get, near_server_io req_put)
{
	bool ret;
	ssize_t nbytes;

	nbytes = send(sockfd[client], req, frame_len, 0);
	g_assert_cmpint(nbytes, >, 0);
	g_assert_cmpuint(nbytes, ==, frame_len);

	TEST_SNEP_LOG("sent 0x%02X request\n", req->request);

	ret = near_snep_core_read(sockfd[server], 0, 0, NULL, req_get, req_put,
									NULL);

	return ret;
}

/*
 * @brief Utility: Send \p frame_len bytes of the fragment \p data
 * to the socket and call near_snep_core_read
 *
 * @param[in] frag_len
 * @param[in] data
 * @return near_bool_t returned by near_snep_core_read
 *
 * @note does not call near_snep_core_read for now, since it can't handle
 * frame without SNEP header
 */
static bool test_snep_read_send_fragment(size_t frag_len,
						uint8_t *data)
{
	ssize_t nbytes;

	nbytes = send(sockfd[client], data, frag_len, 0);
	g_assert_cmpint(nbytes, >, 0);
	g_assert_cmpuint(nbytes, ==, frag_len);

	near_snep_core_read(sockfd[server], 0, 0, NULL,
			test_snep_dummy_req_get, test_snep_dummy_req_put,
									NULL);

	return true;
}

/*
 * @brief Utility: Receive remaining fragments and store in \p data_recvd
 *
 * @param[in]  frag_len
 * @param[in]  remaining_bytes
 * @param[out] data             Must be preallocated
 */
static void test_snep_read_recv_fragments(uint32_t frag_len,
				uint32_t remaining_bytes, uint8_t *data_recvd)
{
	struct p2p_snep_resp_frame *resp;
	uint32_t offset = 0;
	int nbytes;

	g_assert(data_recvd);

	resp = g_try_malloc0(frag_len);
	g_assert(resp);

	do {
		memset(resp, 0, frag_len);

		/* receive remaining fragments */
		nbytes = recv(sockfd[client], resp, frag_len, 0);
		g_assert_cmpint(nbytes, >, 0); /* TODO use explicit value? */

		/* store received data (no header this time) */
		memcpy(data_recvd + offset, resp, nbytes);
		offset += nbytes;
	} while (offset < remaining_bytes);

	g_free(resp);
}

/*
 * @brief Utility: Confirm that server didn't send any response
 */
static void test_snep_read_no_response(void)
{
	struct p2p_snep_resp_frame *resp;
	int nbytes;

	resp = g_try_malloc0(sizeof(*resp));
	g_assert(resp);

	nbytes = recv(sockfd[client], resp, sizeof(*resp), MSG_DONTWAIT);
	g_assert_cmpint(nbytes, <, 0);
	g_assert_cmpint(errno, ==, EAGAIN);

	g_free(resp);
}

/*
 * @brief Utility: Verify response sent by the server
 *
 * @param[in] exp_resp_code      Expected response code
 * @param[in] exp_resp_info_len  Expected response info length
 * @param[in] exp_resp_info      Expected response info
 */
static void test_snep_read_verify_resp(uint8_t exp_resp_code,
		uint32_t exp_resp_info_len, uint8_t *exp_resp_info)
{
	struct p2p_snep_resp_frame *resp;
	ssize_t nbytes, frame_len;

	frame_len = NEAR_SNEP_RESP_HEADER_LENGTH + exp_resp_info_len;
	resp = test_snep_build_resp_frame(frame_len, 0, 0, 0, NULL);
	g_assert(resp);

	nbytes = recv(sockfd[client], resp, frame_len, 0);
	g_assert_cmpint(nbytes, ==, frame_len);

	TEST_SNEP_LOG("received response = 0x%02X, exp = 0x%02X\n",
			resp->response, exp_resp_code);

	g_assert_cmpuint(resp->version, ==, NEAR_SNEP_VERSION);
	g_assert_cmpuint(resp->response, ==, exp_resp_code);
	g_assert_cmpuint(resp->length, ==, GUINT_TO_BE(exp_resp_info_len));
	g_assert_cmpmem(resp->info, GUINT_FROM_BE(resp->length),
			exp_resp_info, exp_resp_info_len);

	g_free(resp);
}

/*
 * @brief Utility: Verify code of the response sent by the server
 *
 * @param[in] exp_resp_code  Expected response code
 */
static void test_snep_read_verify_resp_code(uint8_t exp_resp_code)
{
	test_snep_read_verify_resp(exp_resp_code, 0, NULL);
}

/*
 * @brief Test: Confirm that server is able to handle PUT request
 *
 * Steps:
 * - Send well-formed PUT request
 * - Verify server responded with SUCCESS
 */
static void test_snep_read_put_req_ok(gpointer context, gconstpointer gp)
{
	struct test_snep_context *ctx = context;
	struct p2p_snep_req_frame *req;
	uint32_t frame_len, payload_len;
	bool ret;

	payload_len = ctx->req_info_len;
	frame_len = NEAR_SNEP_REQ_PUT_HEADER_LENGTH + payload_len;

	req = test_snep_build_req_frame(frame_len, NEAR_SNEP_VERSION,
					NEAR_SNEP_REQ_PUT, ctx->req_info_len,
					ctx->req_info, payload_len);
	g_assert(req);

	ret = test_snep_read_req_common(req, frame_len, test_snep_dummy_req_get,
					test_snep_dummy_req_put);
	g_assert(ret);

	test_snep_read_verify_resp_code(NEAR_SNEP_RESP_SUCCESS);

	g_free(req);
}

/*
 * @brief Test: Confirm that server checks the version field of the request.
 *
 * Steps:
 * - Send PUT request with incorrect version field
 * - Verify server responded with UNSUPPORTED VERSION
 */
static void test_snep_read_put_req_unsupp_ver(gpointer context,
						gconstpointer gp)
{
	struct test_snep_context *ctx = context;
	struct p2p_snep_req_frame *req;
	uint32_t frame_len, payload_len;
	bool ret;

	payload_len = ctx->req_info_len;
	frame_len = NEAR_SNEP_REQ_PUT_HEADER_LENGTH + payload_len;

	req = test_snep_build_req_frame(frame_len, 0xF8, NEAR_SNEP_REQ_PUT,
				ctx->req_info_len, ctx->req_info, payload_len);
	g_assert(req);

	ret = test_snep_read_req_common(req, frame_len, test_snep_dummy_req_get,
					test_snep_dummy_req_put);
	g_assert(ret);

	test_snep_read_verify_resp_code(NEAR_SNEP_RESP_VERSION);

	g_free(req);
}

/*
 * @brief Test: Confirm that server responds about no support for the
 * functionality in request message
 *
 * Steps:
 * - Send PUT request
 * - Pass NULL PUT request handler to the near_snep_core_read
 * - Verify server responded with NOT IMPLEMENTED
 */
static void test_snep_read_put_req_not_impl(gpointer context,
						gconstpointer gp)
{
	struct test_snep_context *ctx = context;
	struct p2p_snep_req_frame *req;
	uint32_t frame_len, payload_len;
	bool ret;

	payload_len = ctx->req_info_len;
	frame_len = NEAR_SNEP_REQ_PUT_HEADER_LENGTH + payload_len;

	req = test_snep_build_req_frame(frame_len, NEAR_SNEP_VERSION,
					NEAR_SNEP_REQ_PUT, ctx->req_info_len,
					ctx->req_info, payload_len);
	g_assert(req);

	ret = test_snep_read_req_common(req, frame_len, test_snep_dummy_req_get,
					NULL);
	g_assert(ret);

	test_snep_read_verify_resp_code(NEAR_SNEP_RESP_NOT_IMPL);

	g_free(req);
}

/*
 * @brief Test: Confirm that server is able to receive fragmented request msg
 *
 * Steps:
 * - Send PUT request with incomplete data
 * - Verify server responded with CONTINUE
 * - Send second fragment of the message
 * - Verify server didn't respond
 * - Send last fragment of the message
 * - Verify server responded with SUCCESS
 */
static void test_snep_read_put_req_fragmented(gpointer context,
						gconstpointer gp)
{
	struct test_snep_context *ctx = context;
	struct p2p_snep_req_frame *req;
	uint32_t frame_len, payload_len;
	bool ret;

	payload_len = ctx->req_info_len / 3;
	frame_len = NEAR_SNEP_REQ_PUT_HEADER_LENGTH + payload_len;

	req = test_snep_build_req_frame(frame_len, NEAR_SNEP_VERSION,
					NEAR_SNEP_REQ_PUT, ctx->req_info_len,
					ctx->req_info, payload_len);
	g_assert(req);

	/* send 1st fragment within PUT request */
	ret = test_snep_read_req_common(req, frame_len, test_snep_dummy_req_get,
					test_snep_dummy_req_put);
	g_assert(ret);

	test_snep_read_verify_resp_code(NEAR_SNEP_RESP_CONTINUE);

	/* send 2nd fragment */
	ret = test_snep_read_send_fragment(payload_len,
					ctx->req_info + payload_len);
	g_assert(ret);

	/* do not expect a response */
	test_snep_read_no_response();

	/* send last fragment */
	ret = test_snep_read_send_fragment(ctx->req_info_len - 2 * payload_len,
					ctx->req_info + 2 * payload_len);
	g_assert(ret);

	/*
	 * TODO expected SUCCESS response:
	 *     test_snep_read_verify_resp_code(NEAR_SNEP_RESP_SUCCESS);
	 */

	TEST_SNEP_LOG("EXPECTED FAIL: fragments are not received by SNEP\n");

	g_free(req);
}

/*
 * @brief Test: Confirm that server is able to handle GET request
 *
 * Steps:
 * - Send PUT request with some data
 * - Send GET request
 * - Verify server responded with SUCCESS and correct data
 */
static void test_snep_read_get_req_ok(gpointer context, gconstpointer gp)
{
	struct test_snep_context *ctx = context;
	struct p2p_snep_req_frame *req;
	uint32_t frame_len, payload_len, info_len;
	bool ret;

	/* send some data to the server */
	test_snep_read_put_req_ok(context, gp);

	info_len = ctx->req_info_len + NEAR_SNEP_ACC_LENGTH_SIZE;
	payload_len = ctx->req_info_len;

	frame_len = NEAR_SNEP_REQ_GET_HEADER_LENGTH + payload_len;

	req = test_snep_build_req_get_frame(frame_len, NEAR_SNEP_VERSION,
				NEAR_SNEP_REQ_GET, info_len,
				ctx->acc_len, ctx->req_info, payload_len);
	g_assert(req);

	ret = test_snep_read_req_common(req, frame_len, test_snep_dummy_req_get,
					test_snep_dummy_req_put);
	g_assert(ret);

	test_snep_read_verify_resp(NEAR_SNEP_RESP_SUCCESS, ctx->req_info_len,
				ctx->req_info);

	g_free(req);
}

/*
 * @brief Test: Confirm that server responds about no support for the
 * functionality in request message
 *
 * Steps:
 * - Send PUT request with some data
 * - Send GET request
 * - Pass NULL GET request handler to the near_snep_core_read
 * - Verify server responded with NOT IMPLEMENTED
 */
static void test_snep_read_get_req_not_impl(gpointer context,
						gconstpointer gp)
{
	struct test_snep_context *ctx = context;
	struct p2p_snep_req_frame *req;
	uint32_t frame_len, payload_len;
	bool ret;

	/* send some data to the server */
	test_snep_read_put_req_ok(context, gp);

	payload_len = ctx->req_info_len;
	frame_len = NEAR_SNEP_REQ_GET_HEADER_LENGTH + payload_len;

	/* build REQ GET frame */
	req = test_snep_build_req_get_frame(frame_len, NEAR_SNEP_VERSION,
			NEAR_SNEP_REQ_GET, ctx->req_info_len, ctx->acc_len,
			ctx->req_info, payload_len);
	g_assert(req);

	/* call snep_core_read with NULL req_get handler */
	ret = test_snep_read_req_common(req, frame_len, NULL,
					test_snep_dummy_req_put);
	g_assert(ret);

	test_snep_read_verify_resp_code(NEAR_SNEP_RESP_NOT_IMPL);

	g_free(req);
}

/*
 * @brief Test: Confirm that server is able to respond with fragmented message
 *
 * Steps:
 * - Send PUT request with some data
 * - Send GET request with Acceptable Length less than actual data length
 * - Verify that server returned with incomplete data
 * - Send CONTINUE or REJECT request (depending on \p client_resp param)
 * - If REJECT requested, verify that server didn't respond
 * - If CONTINUE requested, receive remaining fragments and verify data
 */
static void test_snep_read_get_req_frags_client_resp(gpointer context,
					gconstpointer gp, uint8_t client_resp)
{
	struct test_snep_context *ctx = context;
	struct p2p_snep_req_frame *req;
	struct p2p_snep_resp_frame *resp;
	uint32_t frame_len, payload_len;
	bool ret;
	ssize_t nbytes;
	uint8_t *data_recvd;
	uint32_t offset;
	uint32_t frag_len, info_len;

	/* send some data to the server */
	test_snep_read_put_req_ok(context, gp);

	payload_len = ctx->req_info_len;
	frame_len = NEAR_SNEP_REQ_GET_HEADER_LENGTH + payload_len;

	/* force fragmentation */
	ctx->acc_len = 60;
	g_assert(ctx->acc_len < ctx->req_info_len);
	g_assert(NEAR_SNEP_REQ_MAX_FRAGMENT_LENGTH < ctx->req_info_len);

	/* TODO frag_len should be calculated based on SNEP acc_len */
	TEST_SNEP_LOG("WORKAROUND: SNEP core ignores the acceptable length\n");
	frag_len = NEAR_SNEP_REQ_MAX_FRAGMENT_LENGTH;

	info_len = ctx->req_info_len + NEAR_SNEP_ACC_LENGTH_SIZE;

	req = test_snep_build_req_get_frame(frame_len, NEAR_SNEP_VERSION,
				NEAR_SNEP_REQ_GET, info_len,
				ctx->acc_len, ctx->req_info, payload_len);
	g_assert(req);

	/* send GET request */
	ret = test_snep_read_req_common(req, frame_len, test_snep_dummy_req_get,
					test_snep_dummy_req_put);
	g_assert(ret);
	g_free(req);

	frame_len = NEAR_SNEP_RESP_HEADER_LENGTH + payload_len;
	resp = test_snep_build_resp_frame(frame_len, 0, 0, 0, NULL);
	g_assert(resp);

	/* start receiving fragments */
	nbytes = recv(sockfd[client], resp, frame_len, 0);
	g_assert_cmpint(nbytes, >=, 0);
	g_assert_cmpuint(nbytes, ==, frag_len);
	g_assert_cmpuint(resp->length, ==, GUINT_TO_BE(ctx->req_info_len));
	g_assert(resp->info);

	data_recvd = g_try_malloc0(ctx->req_info_len);
	g_assert(data_recvd);

	/* store received info field */
	memcpy(data_recvd, resp->info, nbytes - NEAR_SNEP_RESP_HEADER_LENGTH);
	g_free(resp);

	offset = nbytes - NEAR_SNEP_RESP_HEADER_LENGTH;

	/* 1st fragment has been received, so request resp=CONTINUE/REJECT */
	frame_len = NEAR_SNEP_REQ_PUT_HEADER_LENGTH;
	req = test_snep_build_req_frame(frame_len, NEAR_SNEP_VERSION,
					client_resp, 0, NULL, 0);
	g_assert(req);

	ret = test_snep_read_req_common(req, frame_len, NULL, NULL);
	g_free(req);

	if (client_resp == NEAR_SNEP_REQ_REJECT) {
		g_assert(ret);
		test_snep_read_no_response();
	} else if (client_resp == NEAR_SNEP_REQ_CONTINUE) {
		g_assert(ret);

		/* receive remaining fragments */
		test_snep_read_recv_fragments(frag_len,
					ctx->req_info_len - offset,
					data_recvd + offset);

		/* verify data */
		g_assert_cmpmem(data_recvd, ctx->req_info_len, ctx->req_info,
				ctx->req_info_len);
	}

	g_free(data_recvd);
}

/* Refer to the test_snep_read_get_req_frags_client_resp for description */
static void test_snep_read_get_frags_continue(gpointer context,
						gconstpointer gp)
{
	test_snep_read_get_req_frags_client_resp(context, gp,
						NEAR_SNEP_REQ_CONTINUE);
}

/* Refer to the test_snep_read_get_req_frags_client_resp for description */
static void test_snep_read_get_frags_reject(gpointer context,
						gconstpointer gp)
{
	test_snep_read_get_req_frags_client_resp(context, gp,
						NEAR_SNEP_REQ_REJECT);
}

/*
 * @brief Test: Confirm that server is able to send simple response
 */
static void test_snep_response_noinfo(gpointer context, gconstpointer gp)
{
	int bytes_recv;
	struct p2p_snep_resp_frame resp;

	near_snep_core_response_noinfo(sockfd[client], NEAR_SNEP_RESP_SUCCESS);

	bytes_recv = recv(sockfd[server], &resp, sizeof(resp), 0);
	g_assert_cmpint(bytes_recv, ==, NEAR_SNEP_RESP_HEADER_LENGTH);
	g_assert_cmpint(resp.version, ==, NEAR_SNEP_VERSION);
	g_assert_cmpint(resp.response, ==, NEAR_SNEP_RESP_SUCCESS);
	g_assert_cmpint(resp.length, ==, 0);
}

/*
 * @brief Test: Confirm that server is able to communicate with the client
 */
static void test_snep_response_put_get_ndef(gpointer context,
						gconstpointer gp)
{
	ssize_t nbytes;

	struct p2p_snep_req_frame *req;
	struct p2p_snep_resp_frame *resp;
	struct near_ndef_message *ndef;

	bool ret;
	uint frame_len;

	ndef = near_ndef_prepare_text_record("UTF-8", "en-US", "neard");
	g_assert(ndef);
	g_assert(ndef->data);
	g_assert_cmpuint(ndef->length, >, 0);

	frame_len = NEAR_SNEP_RESP_HEADER_LENGTH + ndef->length;

	req = g_try_malloc0(frame_len);
	g_assert(req);

	req->version = 0x10;
	req->request = NEAR_SNEP_REQ_PUT;
	req->length = GUINT_TO_BE(ndef->length);
	memcpy(req->ndef, ndef->data, ndef->length);

	/* Send PUT request with text record */
	nbytes = send(sockfd[server], req, frame_len, 0);
	g_assert_cmpint(nbytes, >=, 0);
	g_assert_cmpuint(nbytes, ==, frame_len);

	/* UUT */
	ret = near_snep_core_read(sockfd[client], 0, 0, NULL,
			test_snep_dummy_req_get, test_snep_dummy_req_put, NULL);
	g_assert(ret);

	resp = g_try_malloc0(frame_len);
	g_assert(resp);

	/* Get response from server */
	nbytes = recv(sockfd[server], resp, frame_len, 0);
	g_assert_cmpint(nbytes, >, 0);
	g_assert_cmpuint(resp->response, ==, NEAR_SNEP_RESP_SUCCESS);

	/* Send GET request to retrieve a record */
	req->request = NEAR_SNEP_REQ_GET;
	req->length = 0;
	nbytes = send(sockfd[server], req, NEAR_SNEP_RESP_HEADER_LENGTH, 0);
	g_assert_cmpint(nbytes, >, 0);

	/* UUT */
	ret = near_snep_core_read(sockfd[client], 0, 0, NULL,
			test_snep_dummy_req_get, test_snep_dummy_req_put, NULL);
	g_assert(ret);

	/* Get response and verify */
	nbytes = recv(sockfd[server], resp, frame_len, 0);
	g_assert_cmpint(nbytes, >, 0);
	g_assert_cmpuint(resp->response, ==, NEAR_SNEP_RESP_SUCCESS);
	g_assert_cmpint(resp->length, ==, GUINT_TO_BE(ndef->length));
	g_assert_cmpuint(resp->length, ==, GUINT_TO_BE(ndef->length));
	g_assert_cmpmem(resp->info, GUINT_FROM_BE(resp->length), text, ndef->length);

	g_free(req);
	g_free(resp);
	near_ndef_msg_free(ndef);
}

int main(int argc, char **argv)
{
	GTestSuite *ts;
	GTestFixtureFunc init = test_snep_init;
	GTestFixtureFunc exit = test_snep_cleanup;
	size_t fs = sizeof(struct test_snep_context);

	g_test_init(&argc, &argv, NULL);

	ts = g_test_create_suite("testSNEP-response");
	g_test_suite_add(ts,
		g_test_create_case("noinfo", fs, short_text,
			init, test_snep_response_noinfo, exit));

	g_test_suite_add_suite(g_test_get_root(), ts);

	ts = g_test_create_suite("testSNEP-readGET");
	g_test_suite_add(ts,
		g_test_create_case("Request ok", fs, short_text,
			init, test_snep_read_get_req_ok, exit));
	g_test_suite_add(ts,
		g_test_create_case("Request not implemented", fs, short_text,
			init, test_snep_read_get_req_not_impl, exit));
	g_test_suite_add(ts,
		g_test_create_case("Request fragmented CONTINUE",
			fs, long_text, init,
			test_snep_read_get_frags_continue, exit));
	g_test_suite_add(ts,
		g_test_create_case("Request fragmented REJECT",
			fs, long_text, init,
			test_snep_read_get_frags_reject, exit));

	g_test_suite_add_suite(g_test_get_root(), ts);

	ts = g_test_create_suite("testSNEP-readPUT");
	g_test_suite_add(ts,
		g_test_create_case("Request ok", fs, short_text,
			init, test_snep_read_put_req_ok, exit));
	g_test_suite_add(ts,
		g_test_create_case("Request unsupported ver", fs, short_text,
			init, test_snep_read_put_req_unsupp_ver, exit));
	g_test_suite_add(ts,
		g_test_create_case("Request not implemented", fs, short_text,
			init, test_snep_read_put_req_not_impl, exit));
	g_test_suite_add(ts,
		g_test_create_case("Request fragmented", fs, long_text,
			init, test_snep_read_put_req_fragmented, exit));

	g_test_suite_add_suite(g_test_get_root(), ts);

	ts = g_test_create_suite("testSNEP-misc");
	g_test_suite_add(ts,
		g_test_create_case("PUT and GET request NDEF",
			fs, short_text, init,
			test_snep_response_put_get_ndef, exit));

	g_test_suite_add_suite(g_test_get_root(), ts);

	return g_test_run();
}
