/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2025 Brett A C Sheffield <bacs@librecast.net> */

#include "test.h"
#include <key.h>
#include <sys/param.h>

#define NKEYS 42

/* return -1 if found, 0 if not */
static int find_key(lc_keyring_t *keyring, key_combo_t *key)
{
	for (size_t i = 0; i < keyring->nkeys; i++) {
#if 0
		hash_hex_debug(stderr, keyring->key[i], sizeof key->s.pk);
		hash_hex_debug(stderr, key->s.pk, sizeof key->s.pk);
#endif
		if (memcmp(keyring->key[i], key->s.pk, sizeof key->s.pk) == 0) return -1;
	}
	return 0;
}

static int verify_keys(lc_keyring_t *keyring, key_combo_t keys[NKEYS], char *keymap)
{
	for (int i = 0; i < NKEYS; i++) {
		if (!isset(keymap, i)) continue;
		test_assert(find_key(keyring, &keys[i]), "key %i on keyring", i);
	}
	return 0;
}

static void freakeys(lc_keyring_t *keyring)
{
	keyring_freekeys(keyring);
	lc_keyring_free(keyring);
	memset(keyring, 0, sizeof *keyring);
}

static unsigned int hamm(char *map)
{
	int len = howmany(NKEYS, CHAR_BIT);
	unsigned int c = 0;
	while (len--) for (char v = map[len]; v; c++) v &= v - 1;
	return c;
}

static int load_keys(state_t *state, lc_keyring_t *keyring, char *keymap)
{
	int rc = keyring_load(state, keyring);
	unsigned int nkeys = hamm(keymap);
	if (!test_assert(rc == 0, "keyring_load() returned %i", rc)) return -1;
	if (!test_assert(keyring->nkeys == nkeys, "%zu keys loaded", keyring->nkeys)) return -1;
	return 0;
}

/* tests of basic keyring_* functions */
static int keyring_tests(void)
{
	char fakehome[] = "0000-0019-XXXXXX";
	char keymap[howmany(NKEYS, CHAR_BIT)];
	key_combo_t keys[NKEYS];
	lc_keyring_t keyring = {0};
	state_t state = {0};
	int rc;

	/* generate some keys */
	for (int i = 0; i < NKEYS; i++) {
		rc = key_gen_combopair_hex(&keys[i]);
		test_assert(rc == 0, "generate key %i", i);
	}

	/* create fake home directory */
	if (!test_assert(mkdtemp(fakehome) != NULL, "mkdtemp()")) {
		perror("mkdtemp");
		return test_status;
	}
	rc = state_dirs(&state, fakehome);
	if (!test_assert(rc == 0, "state_dirs() returned %i", rc)) return test_status;

	/* add keys to keyring file on disk */
	memset(keymap, 0, sizeof keymap);
	for (int i = 0; i < NKEYS; i++) {
		rc = keyring_add(&state, keys[i].phex);
		if (!test_assert(rc == 0, "keyring_add() key %i", i)) goto err_free_state;
		setbit(keymap, i);
	}

	/* load keys from disk */
	if (load_keys(&state, &keyring, keymap)) goto err_free_keyring;

	/* verify we have all keys */
	verify_keys(&keyring, keys, keymap);

	/* delete some keys randomly */
	for (int i = 0; i < NKEYS; i++) {
		if (arc4random_uniform(100) < 34) continue;
		rc = keyring_del(&state, keys[i].phex);
		clrbit(keymap, i);
	}

	/* (re)load keys from disk */
	freakeys(&keyring);
	if (load_keys(&state, &keyring, keymap)) goto err_free_state;

	/* verify we have correct keys */
	verify_keys(&keyring, keys, keymap);

err_free_keyring:
	freakeys(&keyring);
err_free_state:
	free_state(&state);

	return test_status;
}

int main(void)
{
	char name[] = "lcagent key (keyring mgmt functions)";

	test_name(name);

	if (keyring_tests()) return test_status;

	return test_status;
}
