#include <stdbool.h>

#include <osmocom/core/application.h>
#include <osmocom/core/logging.h>

#include <openbsc/debug.h>
#include <openbsc/gsm_data.h>
#include <openbsc/gsm_subscriber.h>
#include <openbsc/auth.h>

#define min(A,B) ((A)>(B)? (B) : (A))

static char *auth_tuple_str(struct gsm_auth_tuple *atuple)
{
	static char buf[256];
	char *pos = buf;
	int len = sizeof(buf);
	int l;

#define print2buf(FMT, args...) do {\
		l = snprintf(pos, len, FMT, ## args); \
		pos += l;\
		len -= l;\
	} while (0)

	print2buf("gsm_auth_tuple {\n");
	print2buf("  .use_count = %d\n", atuple->use_count);
	print2buf("  .key_seq = %d\n", atuple->key_seq);
	print2buf("  .rand = %s\n", osmo_hexdump(atuple->vec.rand, sizeof(atuple->vec.rand)));
	print2buf("  .sres = %s\n", osmo_hexdump(atuple->vec.sres, sizeof(atuple->vec.sres)));
	print2buf("  .kc = %s\n", osmo_hexdump(atuple->vec.kc, sizeof(atuple->vec.kc)));
	print2buf("}\n");
#undef print2buf

	return buf;
}

static bool auth_tuple_is(struct gsm_auth_tuple *atuple,
			  const char *expect_str)
{
	int l, l1, l2;
	int i;
	char *tuple_str = auth_tuple_str(atuple);
	bool same = (strcmp(expect_str, tuple_str) == 0);
	if (!same) {
		l1 = strlen(expect_str);
		l2 = strlen(tuple_str);
		printf("Expected %d:\n%s\nGot %d:\n%s\n",
		       l1, expect_str, l2, tuple_str);
		l = min(l1, l2);
		for (i = 0; i < l; i++) {
			if (expect_str[i] != tuple_str[i]) {
				printf("Difference at pos %d"
				       " (%c 0x%0x != %c 0x%0x)\n",
				       i, expect_str[i], expect_str[i],
				       tuple_str[i], tuple_str[i]);
				break;
			}
		}
	}
	return same;
}

/* override, requires '-Wl,--wrap=db_get_authinfo_for_subscr' */
int __real_db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo,
				      struct gsm_subscriber *subscr);

int test_get_authinfo_rc = 0;
struct gsm_auth_info test_auth_info = {0};
struct gsm_auth_info default_auth_info = {
	.auth_algo = AUTH_ALGO_COMP128v1,
	.a3a8_ki_len = 16,
	.a3a8_ki = { 0 }
};

int __wrap_db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo,
				      struct gsm_subscriber *subscr)
{
	*ainfo = test_auth_info;
	printf("wrapped: db_get_authinfo_for_subscr(): rc = %d\n", test_get_authinfo_rc);
	return test_get_authinfo_rc;
}

/* override, requires '-Wl,--wrap=db_get_lastauthtuple_for_subscr' */
int __real_db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
					   struct gsm_subscriber *subscr);

int test_get_lastauthtuple_rc = 0;
struct gsm_auth_tuple test_last_auth_tuple = { 0 };
struct gsm_auth_tuple default_auth_tuple = { 0 };

int __wrap_db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
					   struct gsm_subscriber *subscr)
{
	*atuple = test_last_auth_tuple;
	printf("wrapped: db_get_lastauthtuple_for_subscr(): rc = %d\n", test_get_lastauthtuple_rc);
	return test_get_lastauthtuple_rc;
}

/* override, requires '-Wl,--wrap=db_sync_lastauthtuple_for_subscr' */
int __real_db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
					    struct gsm_subscriber *subscr);
int test_sync_lastauthtuple_rc = 0;
int __wrap_db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
					    struct gsm_subscriber *subscr)
{
	test_last_auth_tuple = *atuple;
	printf("wrapped: db_sync_lastauthtuple_for_subscr(): rc = %d\n", test_sync_lastauthtuple_rc);
	return test_sync_lastauthtuple_rc;
}

int auth_get_tuple_for_subscr_verbose(struct gsm_auth_tuple *atuple,
				      struct gsm_subscriber *subscr,
				      int key_seq)
{
	int auth_action;
	auth_action = auth_get_tuple_for_subscr(atuple, subscr, key_seq);
	printf("auth_get_tuple_for_subscr(key_seq=%d) --> auth_action == %s\n",
	       key_seq, auth_action_str(auth_action));
	return auth_action;
}

/* override libssl RAND_bytes() to get testable crypto results */
int osmo_get_rand_id(uint8_t *rand, size_t len)
{
	memset(rand, 23, len);
	return 1;
}

static void test_error()
{
	int auth_action;

	struct gsm_auth_tuple atuple = {0};
	struct gsm_subscriber subscr = {0};
	int key_seq = 0;

	printf("\n* test_error()\n");

	/* any error (except -ENOENT) */
	test_get_authinfo_rc = -EIO;
	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
							key_seq);
	OSMO_ASSERT(auth_action == AUTH_ERROR);
}

static void test_auth_not_avail()
{
	int auth_action;

	struct gsm_auth_tuple atuple = {0};
	struct gsm_subscriber subscr = {0};
	int key_seq = 0;

	printf("\n* test_auth_not_avail()\n");

	/* no entry */
	test_get_authinfo_rc = -ENOENT;
	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
							key_seq);
	OSMO_ASSERT(auth_action == AUTH_NOT_AVAIL);
}

static void test_auth_then_ciph1()
{
	int auth_action;

	struct gsm_auth_tuple atuple = {0};
	struct gsm_subscriber subscr = {0};
	int key_seq;

	printf("\n* test_auth_then_ciph1()\n");

	/* Ki entry, but no auth tuple negotiated yet */
	test_auth_info = default_auth_info;
	test_last_auth_tuple = default_auth_tuple;
	test_get_authinfo_rc = 0;
	test_get_lastauthtuple_rc = -ENOENT;
	key_seq = 0;
	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
							key_seq);
	OSMO_ASSERT(auth_action == AUTH_DO_AUTH_THEN_CIPH);
	OSMO_ASSERT(auth_tuple_is(&atuple,
		"gsm_auth_tuple {\n"
		"  .use_count = 1\n"
		"  .key_seq = 0\n"
		"  .rand = 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 \n"
		"  .sres = a1 ab c6 90 \n"
		"  .kc = 0f 27 ed f3 ac 97 ac 00 \n"
		"}\n"
		));

	/* With a different last saved key_seq stored in the out-arg of
	 * db_get_lastauthtuple_for_subscr() by coincidence, expect absolutely
	 * the same as above. */
	test_auth_info = default_auth_info;
	test_last_auth_tuple = default_auth_tuple;
	test_last_auth_tuple.key_seq = 3;
	test_get_authinfo_rc = 0;
	test_get_lastauthtuple_rc = -ENOENT;
	key_seq = 0;
	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
							key_seq);
	OSMO_ASSERT(auth_action == AUTH_DO_AUTH_THEN_CIPH);
	OSMO_ASSERT(auth_tuple_is(&atuple,
		"gsm_auth_tuple {\n"
		"  .use_count = 1\n"
		"  .key_seq = 0\n"
		"  .rand = 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 \n"
		"  .sres = a1 ab c6 90 \n"
		"  .kc = 0f 27 ed f3 ac 97 ac 00 \n"
		"}\n"
		));
}

static void test_auth_then_ciph2()
{
	int auth_action;

	struct gsm_auth_tuple atuple = {0};
	struct gsm_subscriber subscr = {0};
	int key_seq;

	printf("\n* test_auth_then_ciph2()\n");

	/* Ki entry, auth tuple negotiated, but invalid incoming key_seq */
	test_auth_info = default_auth_info;
	test_last_auth_tuple = default_auth_tuple;
	test_last_auth_tuple.key_seq = 2;
	test_get_authinfo_rc = 0;
	test_get_lastauthtuple_rc = 0;
	key_seq = GSM_KEY_SEQ_INVAL;
	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
							key_seq);
	OSMO_ASSERT(auth_action == AUTH_DO_AUTH_THEN_CIPH);
	OSMO_ASSERT(auth_tuple_is(&atuple,
		"gsm_auth_tuple {\n"
		"  .use_count = 1\n"
		"  .key_seq = 3\n"
		"  .rand = 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 \n"
		"  .sres = a1 ab c6 90 \n"
		"  .kc = 0f 27 ed f3 ac 97 ac 00 \n"
		"}\n"
		));

	/* Change the last saved key_seq, expect last_auth_tuple.key_seq + 1 */
	test_auth_info = default_auth_info;
	test_last_auth_tuple = default_auth_tuple;
	test_last_auth_tuple.key_seq = 3;
	test_get_authinfo_rc = 0;
	test_get_lastauthtuple_rc = 0;
	key_seq = GSM_KEY_SEQ_INVAL;
	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
							key_seq);
	OSMO_ASSERT(auth_action == AUTH_DO_AUTH_THEN_CIPH);
	OSMO_ASSERT(auth_tuple_is(&atuple,
		"gsm_auth_tuple {\n"
		"  .use_count = 1\n"
		"  .key_seq = 4\n"
		"  .rand = 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 \n"
		"  .sres = a1 ab c6 90 \n"
		"  .kc = 0f 27 ed f3 ac 97 ac 00 \n"
		"}\n"
		));
}

static void test_auth_reuse()
{
	int auth_action;
	struct gsm_auth_tuple atuple = {0};
	struct gsm_subscriber subscr = {0};
	int key_seq;

	printf("\n* test_auth_reuse()\n");

	/* Ki entry, auth tuple negotiated, valid+matching incoming key_seq */
	test_auth_info = default_auth_info;
	test_last_auth_tuple = default_auth_tuple;
	test_last_auth_tuple.key_seq = key_seq = 3;
	test_last_auth_tuple.use_count = 1;
	test_get_authinfo_rc = 0;
	test_get_lastauthtuple_rc = 0;
	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
							key_seq);
	OSMO_ASSERT(auth_action == AUTH_DO_CIPH);
	OSMO_ASSERT(auth_tuple_is(&atuple,
		"gsm_auth_tuple {\n"
		"  .use_count = 2\n"
		"  .key_seq = 3\n"
		"  .rand = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \n"
		"  .sres = 00 00 00 00 \n"
		"  .kc = 00 00 00 00 00 00 00 00 \n"
		"}\n"
		));
}

static void test_auth_reuse_key_seq_mismatch()
{
	int auth_action;
	struct gsm_auth_tuple atuple = {0};
	struct gsm_subscriber subscr = {0};
	int key_seq;

	printf("\n* test_auth_reuse_key_seq_mismatch()\n");

	/* Ki entry, auth tuple negotiated, valid+matching incoming key_seq */
	test_auth_info = default_auth_info;
	test_last_auth_tuple = default_auth_tuple;
	test_last_auth_tuple.key_seq = 3;
	key_seq = 4;
	test_last_auth_tuple.use_count = 1;
	test_get_authinfo_rc = 0;
	test_get_lastauthtuple_rc = 0;
	auth_action = auth_get_tuple_for_subscr_verbose(&atuple, &subscr,
							key_seq);
	OSMO_ASSERT(auth_action == AUTH_DO_AUTH_THEN_CIPH);
	OSMO_ASSERT(auth_tuple_is(&atuple,
		"gsm_auth_tuple {\n"
		"  .use_count = 1\n"
		"  .key_seq = 4\n"
		"  .rand = 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 \n"
		"  .sres = a1 ab c6 90 \n"
		"  .kc = 0f 27 ed f3 ac 97 ac 00 \n"
		"}\n"
		));
}

int main(void)
{
	osmo_init_logging(&log_info);
	log_set_log_level(osmo_stderr_target, LOGL_INFO);

	test_error();
	test_auth_not_avail();
	test_auth_then_ciph1();
	test_auth_then_ciph2();
	test_auth_reuse();
	test_auth_reuse_key_seq_mismatch();
	return 0;
}
