/* Test routines for the BSSGP implementation in libosmogb
 *
 * (C) 2014 by sysmocom - s.f.m.c. GmbH
 * Author: Jacob Erlbeck <jerlbeck@sysmocom.de>
 *
 * Skeleton based on bssgp_fc_test.c
 * (C) 2012 by Harald Welte <laforge@gnumonks.org>
 *
 * SPDX-License-Identifier: GPL-2.0+
 */

#undef _GNU_SOURCE
#define _GNU_SOURCE

#include <osmocom/core/application.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/prim.h>
#include <osmocom/gprs/gprs_bssgp.h>
#include <osmocom/gprs/gprs_ns.h>
#include <osmocom/gprs/gprs_bssgp_bss.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <dlfcn.h>

#define BSS_NSEI 0x0b55

static struct osmo_prim_hdr last_oph = {0};

/* override */
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
		const struct sockaddr *dest_addr, socklen_t addrlen)
{
	typedef ssize_t (*sendto_t)(int, const void *, size_t, int,
			const struct sockaddr *, socklen_t);
	static sendto_t real_sendto = NULL;
	uint32_t dest_host = htonl(((struct sockaddr_in *)dest_addr)->sin_addr.s_addr);

	if (!real_sendto)
		real_sendto = dlsym(RTLD_NEXT, "sendto");

	fprintf(stderr, "MESSAGE to 0x%08x, msg length %zu\n%s\n",
		dest_host, len, osmo_hexdump(buf, len));

	return len;
}

/* override */
int gprs_ns_callback(enum gprs_ns_evt event, struct gprs_nsvc *nsvc,
			 struct msgb *msg, uint16_t bvci)
{
	fprintf(stderr, "CALLBACK, event %d, msg length %td, bvci 0x%04x\n%s\n\n",
			event, msgb_bssgp_len(msg), bvci,
			osmo_hexdump(msgb_bssgph(msg), msgb_bssgp_len(msg)));
	return 0;
}

struct msgb *last_ns_tx_msg = NULL;

/* override */
int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg)
{
	msgb_free(last_ns_tx_msg);
	last_ns_tx_msg = msg;

	return msgb_length(msg);
}

int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
{
	printf("BSSGP primitive, SAP %d, prim = %d, op = %d, msg = %s\n",
	       oph->sap, oph->primitive, oph->operation, msgb_hexdump(oph->msg));

	last_oph.sap = oph->sap;
	last_oph.primitive = oph->primitive;
	last_oph.operation = oph->operation;
	last_oph.msg = NULL;
	return -1;
}

static void msgb_bssgp_send_and_free(struct msgb *msg)
{
	msgb_nsei(msg) = BSS_NSEI;

	bssgp_rcvmsg(msg);

	msgb_free(msg);
}

static void send_bssgp_supend(enum bssgp_pdu_type pdu_type, uint32_t tlli)
{
	struct msgb *msg = bssgp_msgb_alloc();
	uint8_t rai[] = {0x0f, 0xf1, 0x80, 0x20, 0x37, 0x00};

	msgb_v_put(msg, pdu_type);

	bssgp_msgb_tlli_put(msg, tlli);
	msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, sizeof(rai), &rai[0]);

	msgb_bssgp_send_and_free(msg);
}

static void send_bssgp_resume(enum bssgp_pdu_type pdu_type, uint32_t tlli)
{
	struct msgb *msg = bssgp_msgb_alloc();
	uint8_t rai[] = {0x0f, 0xf1, 0x80, 0x20, 0x37, 0x00};
	uint8_t suspend_ref = 1;

	msgb_v_put(msg, pdu_type);

	bssgp_msgb_tlli_put(msg, tlli);
	msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, sizeof(rai), &rai[0]);
	msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref);

	msgb_bssgp_send_and_free(msg);
}

static void test_bssgp_suspend_resume(void)
{
	const uint32_t tlli = 0xf0123456;

	printf("----- %s START\n", __func__);
	memset(&last_oph, 0, sizeof(last_oph));

	send_bssgp_supend(BSSGP_PDUT_SUSPEND, tlli);
	OSMO_ASSERT(last_oph.primitive == PRIM_BSSGP_GMM_SUSPEND);

	send_bssgp_resume(BSSGP_PDUT_RESUME, tlli);
	OSMO_ASSERT(last_oph.primitive == PRIM_BSSGP_GMM_RESUME);

	printf("----- %s END\n", __func__);
}

static void send_bssgp_status(enum gprs_bssgp_cause cause, uint16_t *bvci)
{
	struct msgb *msg = bssgp_msgb_alloc();
	uint8_t cause_ = cause;

	msgb_v_put(msg, BSSGP_PDUT_STATUS);
	msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause_);
	if (bvci) {
		uint16_t bvci_ = htons(*bvci);
		msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &bvci_);
	}

	msgb_bssgp_send_and_free(msg);
}

static void test_bssgp_status(void)
{
	uint16_t bvci;

	printf("----- %s START\n", __func__);

	send_bssgp_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL);
	OSMO_ASSERT(last_oph.primitive == PRIM_NM_STATUS);

	/* Enforce prim != PRIM_NM_STATUS */
	last_oph.primitive = PRIM_NM_LLC_DISCARDED;

	bvci = 1234;
	send_bssgp_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci);
	OSMO_ASSERT(last_oph.primitive == PRIM_NM_STATUS);

	printf("----- %s END\n", __func__);
}

static void test_bssgp_bad_reset(void)
{
	struct msgb *msg;
	uint16_t bvci_be = htons(2);
	uint8_t cause = BSSGP_CAUSE_OML_INTERV;

	printf("----- %s START\n", __func__);
	msg = bssgp_msgb_alloc();

	msgb_v_put(msg, BSSGP_PDUT_BVC_RESET);
	msgb_tvlv_put(msg, BSSGP_IE_BVCI, sizeof(bvci_be), (uint8_t *)&bvci_be);
	msgb_tvlv_put(msg, BSSGP_IE_CAUSE, sizeof(cause), &cause);

	msgb_bvci(msg) = 0xbad;

	msgb_bssgp_send_and_free(msg);

	printf("----- %s END\n", __func__);
}

static void test_bssgp_flow_control_bvc(void)
{
	struct bssgp_bvc_ctx bctx = {
		.nsei = 0x1234,
		.bvci = 0x5678,
	};
	const uint8_t  tag = 42;
	const uint32_t bmax = 0x1022 * 100;
	const uint32_t rate = 0xc040 / 8 * 100;
	const uint32_t bmax_ms = bmax / 2;
	const uint32_t rate_ms = rate / 2;
	uint8_t  ratio = 0x78;
	uint32_t qdelay = 0x1144 * 10;
	int rc;

	static uint8_t expected_simple_msg[] = {
		0x26,
		0x1e, 0x81, 0x2a,		/* tag */
		0x05, 0x82, 0x10, 0x22,		/* Bmax */
		0x03, 0x82, 0xc0, 0x40,		/* R */
		0x01, 0x82, 0x08, 0x11,		/* Bmax_MS */
		0x1c, 0x82, 0x60, 0x20,		/* R_MS */
	};

	static uint8_t expected_ext_msg[] = {
		0x26,
		0x1e, 0x81, 0x2a,		/* tag */
		0x05, 0x82, 0x10, 0x22,		/* Bmax */
		0x03, 0x82, 0xc0, 0x40,		/* R */
		0x01, 0x82, 0x08, 0x11,		/* Bmax_MS */
		0x1c, 0x82, 0x60, 0x20,		/* R_MS */
		0x3c, 0x81, 0x78,		/* ratio */
		0x06, 0x82, 0x11, 0x44,		/* Qdelay */
	};

	printf("----- %s START\n", __func__);

	rc = bssgp_tx_fc_bvc(&bctx, tag, bmax, rate, bmax_ms, rate_ms,
		NULL, NULL);

	OSMO_ASSERT(rc >= 0);
	OSMO_ASSERT(last_ns_tx_msg != NULL);
	printf("Got message: %s\n", msgb_hexdump(last_ns_tx_msg));
	OSMO_ASSERT(msgb_length(last_ns_tx_msg) == sizeof(expected_simple_msg));
	OSMO_ASSERT(0 == memcmp(msgb_data(last_ns_tx_msg),
			expected_simple_msg, sizeof(expected_simple_msg)));

	rc = bssgp_tx_fc_bvc(&bctx, tag, bmax, rate, bmax_ms, rate_ms,
		&ratio, &qdelay);

	OSMO_ASSERT(rc >= 0);
	OSMO_ASSERT(last_ns_tx_msg != NULL);
	printf("Got message: %s\n", msgb_hexdump(last_ns_tx_msg));
	OSMO_ASSERT(msgb_length(last_ns_tx_msg) == sizeof(expected_ext_msg));
	OSMO_ASSERT(0 == memcmp(msgb_data(last_ns_tx_msg),
			expected_ext_msg, sizeof(expected_ext_msg)));

	msgb_free(last_ns_tx_msg);
	last_ns_tx_msg = NULL;

	printf("----- %s END\n", __func__);
}

static void test_bssgp_msgb_copy(void)
{
	struct msgb *msg, *msg2;
	uint16_t bvci_be = htons(2);
	uint8_t cause = BSSGP_CAUSE_OML_INTERV;

	printf("----- %s START\n", __func__);
	msg = bssgp_msgb_alloc();

	msg->l3h = msgb_data(msg);
	msgb_v_put(msg, BSSGP_PDUT_BVC_RESET);
	msgb_tvlv_put(msg, BSSGP_IE_BVCI, sizeof(bvci_be), (uint8_t *)&bvci_be);
	msgb_tvlv_put(msg, BSSGP_IE_CAUSE, sizeof(cause), &cause);

	msgb_bvci(msg) = 0xbad;
	msgb_nsei(msg) = 0xbee;

	printf("Old msgb: %s\n", msgb_hexdump(msg));
	msg2 = bssgp_msgb_copy(msg, "test");
	printf("New msgb: %s\n", msgb_hexdump(msg2));

	OSMO_ASSERT(msgb_bvci(msg2) == 0xbad);
	OSMO_ASSERT(msgb_nsei(msg2) == 0xbee);
	OSMO_ASSERT(msgb_l3(msg2) == msgb_data(msg2));
	OSMO_ASSERT(msgb_bssgph(msg2) == msgb_data(msg2));
	OSMO_ASSERT(msgb_bssgp_len(msg2) == msgb_length(msg2));

	msgb_free(msg);
	msgb_free(msg2);

	printf("----- %s END\n", __func__);
}

static struct log_info info = {};

int main(int argc, char **argv)
{
	struct sockaddr_in bss_peer= {0};
	void *ctx = talloc_named_const(NULL, 0, "gprs_bssgp_test");

	osmo_init_logging2(ctx, &info);
	log_set_use_color(osmo_stderr_target, 0);
	log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);

	msgb_talloc_ctx_init(ctx, 0);

	bssgp_nsi = gprs_ns_instantiate(gprs_ns_callback, NULL);

	bss_peer.sin_family = AF_INET;
	bss_peer.sin_port = htons(32000);
	bss_peer.sin_addr.s_addr = htonl(0x7f0000ff);

	gprs_ns_nsip_connect(bssgp_nsi, &bss_peer, BSS_NSEI, BSS_NSEI+1);


	printf("===== BSSGP test START\n");
	test_bssgp_suspend_resume();
	test_bssgp_status();
	test_bssgp_bad_reset();
	test_bssgp_flow_control_bvc();
	test_bssgp_msgb_copy();
	printf("===== BSSGP test END\n\n");

	exit(EXIT_SUCCESS);
}
