/*
   CTDB event daemon - protocol test

   Copyright (C) Amitay Isaacs  2018

   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 3 of the License, or
   (at your option) any later version.

   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, see <http://www.gnu.org/licenses/>.
*/

#include "replace.h"

#include <talloc.h>
#include <assert.h>

#define EVENT_PROTOCOL_TEST
#include "event/event_protocol.c"

#include "tests/src/protocol_common_basic.h"

/*
 * Functions to fill and verify event protocol structures
 */

static void fill_ctdb_event_script(TALLOC_CTX *mem_ctx,
				   struct ctdb_event_script *p)
{
	fill_ctdb_stringn(mem_ctx, &p->name);
	fill_ctdb_timeval(&p->begin);
	fill_ctdb_timeval(&p->end);
	p->result = rand32i();
	fill_ctdb_stringn(mem_ctx, &p->output);
}

static void verify_ctdb_event_script(struct ctdb_event_script *p1,
				     struct ctdb_event_script *p2)
{
	verify_ctdb_stringn(&p1->name, &p2->name);
	verify_ctdb_timeval(&p1->begin, &p2->begin);
	verify_ctdb_timeval(&p1->end, &p2->end);
	assert(p1->result == p2->result);
	verify_ctdb_stringn(&p1->output, &p2->output);
}

static void fill_ctdb_event_script_list(TALLOC_CTX *mem_ctx,
					struct ctdb_event_script_list *p)
{
	int i;

	p->num_scripts = rand_int(32);
	if (p->num_scripts > 0) {
		p->script = talloc_array(mem_ctx,
					 struct ctdb_event_script,
					 p->num_scripts);
		assert(p->script != NULL);

		for (i=0; i<p->num_scripts; i++) {
			fill_ctdb_event_script(mem_ctx, &p->script[i]);
		}
	} else {
		p->script = NULL;
	}
}

static void verify_ctdb_event_script_list(struct ctdb_event_script_list *p1,
					  struct ctdb_event_script_list *p2)
{
	int i;

	assert(p1->num_scripts == p2->num_scripts);
	for (i=0; i<p1->num_scripts; i++) {
		verify_ctdb_event_script(&p1->script[i], &p2->script[i]);
	}
}

static void fill_ctdb_event_request_run(TALLOC_CTX *mem_ctx,
					struct ctdb_event_request_run *p)
{
	fill_ctdb_stringn(mem_ctx, &p->component);
	fill_ctdb_stringn(mem_ctx, &p->event);
	fill_ctdb_stringn(mem_ctx, &p->args);
	p->timeout = rand32();
	p->flags = rand32();
}

static void verify_ctdb_event_request_run(struct ctdb_event_request_run *p1,
					  struct ctdb_event_request_run *p2)
{
	verify_ctdb_stringn(&p1->component, &p2->component);
	verify_ctdb_stringn(&p1->event, &p2->event);
	verify_ctdb_stringn(&p1->args, &p2->args);
	assert(p1->timeout == p2->timeout);
	assert(p1->flags == p2->flags);
}

static void fill_ctdb_event_request_status(TALLOC_CTX *mem_ctx,
					   struct ctdb_event_request_status *p)
{
	fill_ctdb_stringn(mem_ctx, &p->component);
	fill_ctdb_stringn(mem_ctx, &p->event);
}

static void verify_ctdb_event_request_status(
					struct ctdb_event_request_status *p1,
					struct ctdb_event_request_status *p2)
{
	verify_ctdb_stringn(&p1->component, &p2->component);
	verify_ctdb_stringn(&p1->event, &p2->event);
}

static void fill_ctdb_event_request_script(TALLOC_CTX *mem_ctx,
					   struct ctdb_event_request_script *p)
{
	fill_ctdb_stringn(mem_ctx, &p->component);
	fill_ctdb_stringn(mem_ctx, &p->script);
	if (rand_int(1) == 0) {
		p->action = CTDB_EVENT_SCRIPT_DISABLE;
	} else {
		p->action = CTDB_EVENT_SCRIPT_ENABLE;
	}
}

static void fill_ctdb_event_reply_status(TALLOC_CTX *mem_ctx,
					 struct ctdb_event_reply_status *p)
{
	p->summary = rand32i();
	p->script_list = talloc(mem_ctx, struct ctdb_event_script_list);
	assert(p->script_list != NULL);

	fill_ctdb_event_script_list(mem_ctx, p->script_list);
}

static void verify_ctdb_event_reply_status(struct ctdb_event_reply_status *p1,
					   struct ctdb_event_reply_status *p2)
{
	assert(p1->summary == p2->summary);
	verify_ctdb_event_script_list(p1->script_list, p2->script_list);
}

static void verify_ctdb_event_request_script(
					struct ctdb_event_request_script *p1,
					struct ctdb_event_request_script *p2)
{
	verify_ctdb_stringn(&p1->component, &p2->component);
	verify_ctdb_stringn(&p1->script, &p2->script);
	assert(p1->action == p2->action);
}

static void fill_ctdb_event_request_data(TALLOC_CTX *mem_ctx,
					 struct ctdb_event_request *p,
					 uint32_t cmd)
{
	p->cmd = cmd;

	switch (cmd) {
	case CTDB_EVENT_CMD_RUN:
		p->data.run = talloc(mem_ctx, struct ctdb_event_request_run);
		assert(p->data.run != NULL);

		fill_ctdb_event_request_run(mem_ctx, p->data.run);
		break;

	case CTDB_EVENT_CMD_STATUS:
		p->data.status = talloc(mem_ctx,
					struct ctdb_event_request_status);
		assert(p->data.status != NULL);

		fill_ctdb_event_request_status(mem_ctx, p->data.status);
		break;

	case CTDB_EVENT_CMD_SCRIPT:
		p->data.script = talloc(mem_ctx,
					struct ctdb_event_request_script);
		assert(p->data.script != NULL);

		fill_ctdb_event_request_script(mem_ctx, p->data.script);
		break;

	default:
		assert(cmd > 0 && cmd < CTDB_EVENT_CMD_MAX);
	}
}

static void verify_ctdb_event_request_data(struct ctdb_event_request *p1,
					   struct ctdb_event_request *p2)
{
	assert(p1->cmd == p2->cmd);

	switch (p1->cmd) {
	case CTDB_EVENT_CMD_RUN:
		verify_ctdb_event_request_run(p1->data.run, p2->data.run);
		break;

	case CTDB_EVENT_CMD_STATUS:
		verify_ctdb_event_request_status(p1->data.status,
						 p2->data.status);
		break;

	case CTDB_EVENT_CMD_SCRIPT:
		verify_ctdb_event_request_script(p1->data.script,
						 p2->data.script);
		break;

	default:
		assert(p1->cmd > 0 && p1->cmd < CTDB_EVENT_CMD_MAX);
	}
}

static void fill_ctdb_event_reply_data(TALLOC_CTX *mem_ctx,
				       struct ctdb_event_reply *p,
				       uint32_t cmd)
{
	p->cmd = cmd;
	p->result = rand32i();

	if (p->result != 0) {
		return;
	}

	switch (cmd) {
	case CTDB_EVENT_CMD_STATUS:
		p->data.status = talloc(mem_ctx,
					struct ctdb_event_reply_status);
		assert(p->data.status != NULL);

		fill_ctdb_event_reply_status(mem_ctx, p->data.status);
		break;

	default:
		assert(cmd > 0 && cmd < CTDB_EVENT_CMD_MAX);
	}
}

static void verify_ctdb_event_reply_data(struct ctdb_event_reply *p1,
					 struct ctdb_event_reply *p2)
{
	assert(p1->cmd == p2->cmd);
	assert(p1->result == p2->result);

	if (p1->result != 0) {
		return;
	}

	switch (p1->cmd) {
	case CTDB_EVENT_CMD_STATUS:
		verify_ctdb_event_reply_status(p1->data.status,
					       p2->data.status);
		break;

	default:
		assert(p1->cmd > 0 && p1->cmd < CTDB_EVENT_CMD_MAX);
	}
}

static void fill_ctdb_event_header(struct ctdb_event_header *p)
{
	p->length = 0; /* updated by push functions */
	p->version = 0; /* updated by push functions */
	p->reqid = rand32();
}

static void verify_ctdb_event_header(struct ctdb_event_header *p1,
				     struct ctdb_event_header *p2)
{
	assert(p1->length == p2->length);
	assert(p1->version == p2->version);
	assert(p1->reqid == p2->reqid);
}

static void fill_ctdb_event_request(TALLOC_CTX *mem_ctx,
				    struct ctdb_event_request *p,
				    uint32_t cmd)
{
	fill_ctdb_event_request_data(mem_ctx, p, cmd);
}

static void verify_ctdb_event_request(struct ctdb_event_request *p1,
				      struct ctdb_event_request *p2)
{
	verify_ctdb_event_request_data(p1, p2);
}

static void fill_ctdb_event_reply(TALLOC_CTX *mem_ctx,
				  struct ctdb_event_reply *p,
				  uint32_t cmd)
{
	fill_ctdb_event_reply_data(mem_ctx, p, cmd);
}

static void verify_ctdb_event_reply(struct ctdb_event_reply *p1,
				    struct ctdb_event_reply *p2)
{
	verify_ctdb_event_reply_data(p1, p2);
}

#define EVENT_PROTOCOL1_TEST(TYPE, NAME) \
static void TEST_FUNC(NAME)(uint32_t cmd) \
{ \
	TALLOC_CTX *mem_ctx; \
	TYPE c1, *c2; \
	uint8_t *buf; \
	size_t buflen, np; \
	int ret; \
\
	protocol_test_iterate_tag("%s %u\n", #NAME, cmd); \
	mem_ctx = talloc_new(NULL); \
	assert(mem_ctx != NULL); \
	FILL_FUNC(NAME)(mem_ctx, &c1, cmd); \
	buflen = LEN_FUNC(NAME)(&c1); \
	buf = talloc_size(mem_ctx, buflen); \
	assert(buf != NULL); \
	np = 0; \
	PUSH_FUNC(NAME)(&c1, buf, &np); \
	assert(np == buflen); \
	np = 0; \
	ret = PULL_FUNC(NAME)(buf, buflen, mem_ctx, &c2, &np); \
	assert(ret == 0); \
	assert(np == buflen); \
	VERIFY_FUNC(NAME)(&c1, c2); \
	talloc_free(mem_ctx); \
}

#define EVENT_PROTOCOL2_TEST(TYPE, NAME) \
static void TEST_FUNC(NAME)(uint32_t cmd) \
{ \
	TALLOC_CTX *mem_ctx; \
	struct ctdb_event_header h1, h2; \
	TYPE c1, *c2; \
	uint8_t *buf; \
	size_t buflen, len; \
	int ret; \
\
	protocol_test_iterate_tag("%s %u\n", #NAME, cmd); \
	mem_ctx = talloc_new(NULL); \
	assert(mem_ctx != NULL); \
	fill_ctdb_event_header(&h1); \
	FILL_FUNC(NAME)(mem_ctx, &c1, cmd); \
	buflen = LEN_FUNC(NAME)(&h1, &c1); \
	buf = talloc_size(mem_ctx, buflen); \
	assert(buf != NULL); \
	len = 0; \
	ret = PUSH_FUNC(NAME)(&h1, &c1, buf, &len); \
	assert(ret == EMSGSIZE); \
	assert(len == buflen); \
	ret = PUSH_FUNC(NAME)(&h1, &c1, buf, &buflen); \
	assert(ret == 0); \
	ret = PULL_FUNC(NAME)(buf, buflen, &h2, mem_ctx, &c2); \
	assert(ret == 0); \
	verify_ctdb_event_header(&h1, &h2); \
	VERIFY_FUNC(NAME)(&c1, c2); \
	talloc_free(mem_ctx); \
}

PROTOCOL_TYPE3_TEST(struct ctdb_event_script, ctdb_event_script);
PROTOCOL_TYPE3_TEST(struct ctdb_event_script_list, ctdb_event_script_list);

PROTOCOL_TYPE3_TEST(struct ctdb_event_request_run, ctdb_event_request_run);
PROTOCOL_TYPE3_TEST(struct ctdb_event_request_status,
		    ctdb_event_request_status);
PROTOCOL_TYPE3_TEST(struct ctdb_event_request_script,
		    ctdb_event_request_script);

PROTOCOL_TYPE3_TEST(struct ctdb_event_reply_status, ctdb_event_reply_status);

EVENT_PROTOCOL1_TEST(struct ctdb_event_request, ctdb_event_request_data);
EVENT_PROTOCOL1_TEST(struct ctdb_event_reply, ctdb_event_reply_data);

EVENT_PROTOCOL2_TEST(struct ctdb_event_request, ctdb_event_request);
EVENT_PROTOCOL2_TEST(struct ctdb_event_reply, ctdb_event_reply);

static void event_protocol_test(void)
{
	uint32_t cmd;

	TEST_FUNC(ctdb_event_script)();
	TEST_FUNC(ctdb_event_script_list)();

	TEST_FUNC(ctdb_event_request_run)();
	TEST_FUNC(ctdb_event_request_status)();
	TEST_FUNC(ctdb_event_request_script)();

	TEST_FUNC(ctdb_event_reply_status)();

	for (cmd=1; cmd<CTDB_EVENT_CMD_MAX; cmd++) {
		TEST_FUNC(ctdb_event_request_data)(cmd);
	}
	for (cmd=1; cmd<CTDB_EVENT_CMD_MAX; cmd++) {
		TEST_FUNC(ctdb_event_reply_data)(cmd);
	}

	for (cmd=1; cmd<CTDB_EVENT_CMD_MAX; cmd++) {
		TEST_FUNC(ctdb_event_request)(cmd);
	}
	for (cmd=1; cmd<CTDB_EVENT_CMD_MAX; cmd++) {
		TEST_FUNC(ctdb_event_reply)(cmd);
	}
}

int main(int argc, const char **argv)
{
	protocol_test_iterate(argc, argv, event_protocol_test);
	return 0;
}
