#include <string.h>

#include "key-map.h"
#include "ascii.h"

#include "test-utils.h"

static void
test_valid_single (void)
{
  static const struct {
    const char *in;
    const char *exp_hex;
  } valid[] = {
    { "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym",
      "314cb82ac8b5fe90cf18bf190afa4759b80779709f991f736f044d5e13bcbca6" },
    { "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym\n",
      "314cb82ac8b5fe90cf18bf190afa4759b80779709f991f736f044d5e13bcbca6" },
    { " ssh-ed25519\tAAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym\n",
      "314cb82ac8b5fe90cf18bf190afa4759b80779709f991f736f044d5e13bcbca6" },
    { " ssh-ed25519\tAAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym comment\n",
      "314cb82ac8b5fe90cf18bf190afa4759b80779709f991f736f044d5e13bcbca6" },
  };

  for (size_t i = 0; i < sizeof (valid) / sizeof (valid[0]); i++)
    {
      struct sigsum_key_map map;
      struct sigsum_pubkey exp;
      struct sigsum_error err;

      if (!sigsum_key_map_parse_ssh (&map, strlen (valid[i].in), valid[i].in, &err))
	die ("sigsum_ssh_keys_parse: failed on test case %zu: %s", i, err.msg);

      if (!sigsum_decode_hex_key (&exp, valid[i].exp_hex))
	die ("sigsum_decode_hex_key: unexpected error on case %zu", i);

      if (map.num_keys != 1)
	die ("sigsum_ssh_keys_parse: unexpected number of keys %zu", map.num_keys);

      const struct sigsum_pubkey *key = &map.keys[0].key;

      if (memcmp (key->k, exp.k, sizeof (key->k)))
	{
	  dump_hex (stderr, sizeof (key->k), key->k);
	  die ("sigsum_ssh_keys_parse: unexpected output");
	}
      free ((void *) map.keys);
    }
}

static void
test_invalid_single (void)
{
  static const struct {
    const char *in;
    const char *exp_err;
  } invalid[] = {
    { "\fssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym",
      "expected ssh-ed25519" },
    { "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym\f",
      "unexpected base64 blob size" },
    { "ssh-ed25519-sk AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym",
      "expected ssh-ed25519" },
    { "ssh-ed25519 A.AAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym",
      "invalid base64" },
    { "ssh-ed25519 AAAAC3NzbC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym",
      "not an ed25519 blob" },
  };
  for (size_t i = 0; i < sizeof (invalid) / sizeof (invalid[0]); i++)
    {
      struct sigsum_key_map map;
      struct sigsum_error err;
      if (sigsum_key_map_parse_ssh (&map, strlen (invalid[i].in), invalid[i].in, &err))
	die ("sigsum_ssh_keys_parse: test case %zu: accepted invalid key", i);
      if (!strstr (err.msg, invalid[i].exp_err))
	die ("sigsum_ssh_keys_parse: test case %zu: unexpected error: %s", i, err.msg);
    }
}

static void
test_file (void)
{
  static const struct {
    const char *desc;
    unsigned exp_count; /* A non-zero count indicates expected success. */
    const char *in;
  } tests[] = {
    {"empty", 0, ""},
    {"comment only", 0, "# no keys\n"},
    {
      "single-key", 1,
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym\n",
    },
    {
      "two-keys", 2,
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym\n"
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLyn\n"
    },
    {
      "two-keys-comments", 2,
      "# a key\n"
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym key 1\n"
      "\n"
      "# some empty lines\n"
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLyn key 2\n",
    },
    {
      "line-with-garbage", 0,
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym\n"
      "xxx\n"
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLyn\n"
    },
    {
      "line-with-bad-comment", 0,
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym\n"
      "  # xxx\n"
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLyn\n"
    },
    {
      "duplicate-key", 0,
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym key 1\n"
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLyn key 2\n"
      "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFMuCrItf6Qzxi/GQr6R1m4B3lwn5kfc28ETV4TvLym same as key 1"
    },
  };
  for (size_t i = 0; i < sizeof (tests) / sizeof (tests[0]); i++)
    {
      struct sigsum_key_map map;
      struct sigsum_error err;
      if (sigsum_key_map_parse_ssh (&map, strlen (tests[i].in), tests[i].in, &err))
	{
	  if (tests[i].exp_count == 0 || map.num_keys != tests[i].exp_count)
	    die ("sigsum_ssh_keys_parse: test case %zu (%s): unexpected key count: %zu",
		 i, tests[i].desc, map.num_keys);
	  free ((void *) map.keys);
	}
      else
	{
	  if (tests[i].exp_count > 0)
	    die ("sigsum_ssh_keys_parse: test case %zu (%s): failed on line %zu: %s",
		 i, tests[i].desc, err.line, err.msg);
	}
    }
}
int
main (void)
{
  test_valid_single ();
  test_invalid_single ();
  test_file ();

  return 0;
}
