#include <string.h>

#include "proof.h"

#include "ascii.h"
#include "xalloc.h"

DEFINE_LIST(cosignature_list, cosignature_alloc, struct sigsum_cosignature);
DEFINE_LIST(hash_list, hash_alloc, struct sigsum_hash);

/* Checks that buffer starts with given prefix. On success, advances
   past prefix and returns true, otherwise returns false. */
static bool
skip_prefix (struct sigsum_buffer *buf, size_t size, const char *prefix)
{
  if (buf->size < size || memcmp (buf->data, prefix, size) != 0)
    return false;

  buf->data += size;
  buf->size -= size;
  return true;
}

/* PARSE_KEYWORD and MATCH are macros with string literal arguments,
   depending on sizeof producing the length of the string + 1 for the
   terminating (and ignored) NUL character. */

/* The keyword must be a string literal. */
#define PARSE_KEYWORD(line, keyword) \
  skip_prefix(line, sizeof (keyword), keyword "=")

/* The value must be a string literal. */
#define MATCH(buf, value)					\
  ((buf)->size == sizeof (value) - 1				\
   && memcmp ((buf)->data, (value), sizeof (value) - 1) == 0)

static bool
parse_hash (struct sigsum_hash *hash, const struct sigsum_buffer *hex)
{
  return hex->size == 64
    && _sigsum_decode_hex_hash (hash, hex->data);
}

static bool
parse_leaf (struct sigsum_hash *kh, struct sigsum_signature *sig,
	    const struct sigsum_buffer *line)
{
  /* Hash, SPC, signature */
  return line->size == 64 + 1 + 128 && line->data[64] == ' '
    && _sigsum_decode_hex_hash (kh, line->data)
    && _sigsum_decode_hex_signature (sig, line->data + 65);
}

static bool
parse_decimal (sigsum_size_t *x, const struct sigsum_buffer *line)
{
  return _sigsum_decode_decimal (x, line->size, line->data);
}

static bool
parse_signature (struct sigsum_signature *signature, const struct sigsum_buffer *line)
{
  return line->size == 128
    && _sigsum_decode_hex_signature (signature, line->data);
}

static bool
parse_cosignature (struct sigsum_cosignature *cosignature, const struct sigsum_buffer *line)
{
  /* Hash, SPC, number (at least one digit), SPC, signature */
  if (line->size < 64 + 3 + 128)
    return false;
  size_t timestamp_size = line->size - (64 + 2 + 128);

  return line->data[64] == ' ' && line->data[65 + timestamp_size] == ' '
    && _sigsum_decode_hex_hash (&cosignature->key_hash, line->data)
    && _sigsum_decode_decimal (&cosignature->timestamp, timestamp_size, line->data + 65)
    && _sigsum_decode_hex_signature (&cosignature->signature, line->data + 66 + timestamp_size);
}

#define FAIL(err_var, error_msg) do {		\
    if (err_var) *(err_var) = (error_msg);	\
    return false;				\
  } while (0)


static bool
parse_cosignature_list (size_t *n, const struct sigsum_cosignature **p,
			struct sigsum_buffer *buffer,
			const char **err)
{
  struct cosignature_list cosignatures = { 0, 0, NULL };
  while (buffer->size > 0)
    {
      struct sigsum_buffer line;
      if (!_sigsum_buffer_line_strict (buffer, &line))
	{
	  free (cosignatures.list);
	  FAIL (err, "missing newline when expecting cosignature line");
	}
      if (line.size == 0)
	break;
      if (! (PARSE_KEYWORD (&line, "cosignature")
	     && parse_cosignature (cosignature_alloc (&cosignatures), &line)))
	{
	  free (cosignatures.list);
	  FAIL (err, "bad cosignature line");
	}
    }
  *n = cosignatures.size;
  *p = cosignatures.list;
  return true;
}

static bool
parse_inclusion_proof (sigsum_size_t *leaf_index,
		       size_t *n, const struct sigsum_hash **p,
		       struct sigsum_buffer *buffer,
		       const char **err)
{
  struct sigsum_buffer line;
  if (! (_sigsum_buffer_line_strict (buffer, &line) && PARSE_KEYWORD (&line, "leaf_index")
	 && parse_decimal (leaf_index, &line)))
    FAIL (err, "bad leaf_index line");

  struct hash_list path = { 0, 0, NULL };
  do
    {
      if (path.size >= SIGSUM_PROOF_MAX_PATH_LENGTH)
	{
	  free (path.list);
	  FAIL (err, "too long inclusion path");
	}
      if (! (_sigsum_buffer_line_strict (buffer, &line) && PARSE_KEYWORD (&line, "node_hash")
	     && parse_hash (hash_alloc (&path), &line)))
	{
	  free (path.list);
	  FAIL (err, "bad node_hash line");
	}
    } while (buffer->size > 0);

  *n = path.size;
  *p = path.list;
  return true;
}

bool
sigsum_proof_parse (struct sigsum_proof *proof, size_t size, const char *data,
		    const char **err)
{
  struct sigsum_buffer buffer = { size, data };
  struct sigsum_buffer line;

  if (! (_sigsum_buffer_line_strict (&buffer, &line) && MATCH (&line, "version=2")))
    FAIL (err, "bad version line");

  if (! (_sigsum_buffer_line_strict (&buffer, &line) && PARSE_KEYWORD (&line, "log")
	&& parse_hash (&proof->log_key_hash, &line)))
    FAIL (err, "bad log line");

  if (! (_sigsum_buffer_line_strict (&buffer, &line) && PARSE_KEYWORD (&line, "leaf")
	&& parse_leaf (&proof->submitter_key_hash, &proof->leaf_signature, &line)))
    FAIL (err, "bad leaf line");

  /* Required expect empty line. */
  if (! (_sigsum_buffer_line_strict (&buffer, &line) && line.size == 0))
    FAIL (err, "no empty line after leaf");

  if (! (_sigsum_buffer_line_strict (&buffer, &line) && PARSE_KEYWORD (&line, "size")
	&& parse_decimal (&proof->tree_head.size, &line)))
    FAIL (err, "bad size line");

  if (! (_sigsum_buffer_line_strict (&buffer, &line) && PARSE_KEYWORD (&line, "root_hash")
	&& parse_hash (&proof->tree_head.root_hash, &line)))
    FAIL (err, "bad root_hash line");

  if (! (_sigsum_buffer_line_strict (&buffer, &line) && PARSE_KEYWORD (&line, "signature")
	&& parse_signature (&proof->log_signature, &line)))
    FAIL (err, "bad signature line");

  if (!parse_cosignature_list (&proof->num_cosignatures, &proof->cosignatures,
			       &buffer, err))
    return false;

  if (proof->tree_head.size <= 1)
    {
      /* Leaf index implicitly zero, and empty path. */
      if (buffer.size > 0)
	{
	  XFREE (proof->cosignatures);
	  FAIL (err, "trivial tree, unexpected data after cosignatures");
	}
      proof->leaf_index = 0;
      proof->path_length = 0;
      proof->path = NULL;
      return true;
    }

  if (!parse_inclusion_proof (&proof->leaf_index, &proof->path_length, &proof->path,
			      &buffer, err))
    {
      XFREE (proof->cosignatures);
      return false;
    }
  return true;
}

void
sigsum_proof_clear (struct sigsum_proof *proof)
{
  XFREE (proof->cosignatures);
  XFREE (proof->path);
}
