/*
     This file is part of GNUnet.
     Copyright (C) 2018 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     or (at your option) any later version.

     GNUnet is distributed in the hope that it will be useful, but
     WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */

/**
 * @file hello/hello-ng.c
 * @brief helper library for handling HELLOs
 * @author Christian Grothoff
 */
#include "platform.h"
#include "gnunet_signatures.h"
#include "gnunet_hello_lib.h"
#include "gnunet_protocols.h"
#include "gnunet_util_lib.h"
#include "gnunet_ats_service.h"

/**
 * Binary block we sign when we sign an address.
 */
struct SignedAddress
{
  /**
   * Purpose must be #GNUNET_SIGNATURE_PURPOSE_TRANSPORT_ADDRESS
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * When was the address generated.
   */
  struct GNUNET_TIME_AbsoluteNBO mono_time;

  /**
   * Hash of the address.
   */
  struct GNUNET_HashCode h_addr;
};


/**
 * Build address record by signing raw information with private key.
 *
 * @param address text address at @a communicator to sign
 * @param nt network type of @a address
 * @param mono_time monotonic time at which @a address was valid
 * @param private_key signing key to use
 * @param result[out] where to write address record (allocated)
 * @param result_size[out] set to size of @a result
 */
void
GNUNET_HELLO_sign_address (
  const char *address,
  enum GNUNET_NetworkType nt,
  struct GNUNET_TIME_Absolute mono_time,
  const struct GNUNET_CRYPTO_EddsaPrivateKey *private_key,
  void **result,
  size_t *result_size)
{
  struct SignedAddress sa;
  struct GNUNET_CRYPTO_EddsaSignature sig;
  char *sig_str;

  sa.purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_TRANSPORT_ADDRESS);
  sa.purpose.size = htonl (sizeof(sa));
  sa.mono_time = GNUNET_TIME_absolute_hton (mono_time);
  GNUNET_CRYPTO_hash (address, strlen (address), &sa.h_addr);
  GNUNET_CRYPTO_eddsa_sign (private_key, &sa, &sig);
  sig_str = NULL;
  (void) GNUNET_STRINGS_base64_encode (&sig, sizeof(sig), &sig_str);
  *result_size =
    1 + GNUNET_asprintf ((char **) result,
                         "%s;%llu;%u;%s",
                         sig_str,
                         (unsigned long long) mono_time.abs_value_us,
                         (unsigned int) nt,
                         address);
  GNUNET_free (sig_str);
}


/**
 * Check signature and extract address record.
 *
 * @param raw raw signed address
 * @param raw_size size of @a raw
 * @param pid public key to use for signature verification
 * @param nt[out] set to network type
 * @param mono_time[out] when was the address generated
 * @return NULL on error, otherwise the address
 */
char *
GNUNET_HELLO_extract_address (const void *raw,
                              size_t raw_size,
                              const struct GNUNET_PeerIdentity *pid,
                              enum GNUNET_NetworkType *nt,
                              struct GNUNET_TIME_Absolute *mono_time)
{
  const struct GNUNET_CRYPTO_EddsaPublicKey *public_key = &pid->public_key;
  const char *raws = raw;
  unsigned long long raw_us;
  unsigned int raw_nt;
  const char *sc;
  const char *sc2;
  const char *sc3;
  const char *raw_addr;
  struct GNUNET_TIME_Absolute raw_mono_time;
  struct SignedAddress sa;
  struct GNUNET_CRYPTO_EddsaSignature *sig;

  if ('\0' != raws[raw_size])
  {
    GNUNET_break_op (0);
    return NULL;
  }
  if (NULL == (sc = strchr (raws, ';')))
  {
    GNUNET_break_op (0);
    return NULL;
  }
  if (NULL == (sc2 = strchr (sc + 1, ';')))
  {
    GNUNET_break_op (0);
    return NULL;
  }
  if (NULL == (sc3 = strchr (sc2 + 1, ';')))
  {
    GNUNET_break_op (0);
    return NULL;
  }
  if (1 != sscanf (sc + 1, "%llu;%u;", &raw_us, &raw_nt))
  {
    GNUNET_break_op (0);
    return NULL;
  }
  raw_mono_time.abs_value_us = raw_us;
  sig = NULL;
  if (sizeof(struct GNUNET_CRYPTO_EddsaSignature) !=
      GNUNET_STRINGS_base64_decode (raws, sc - raws, (void **) &sig))
  {
    GNUNET_break_op (0);
    GNUNET_free (sig);
    return NULL;
  }
  raw_addr = sc3 + 1;

  sa.purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_TRANSPORT_ADDRESS);
  sa.purpose.size = htonl (sizeof(sa));
  sa.mono_time = GNUNET_TIME_absolute_hton (raw_mono_time);
  GNUNET_CRYPTO_hash (raw_addr, strlen (raw_addr), &sa.h_addr);
  if (GNUNET_YES !=
      GNUNET_CRYPTO_eddsa_verify (GNUNET_SIGNATURE_PURPOSE_TRANSPORT_ADDRESS,
                                  &sa,
                                  sig,
                                  public_key))
  {
    GNUNET_break_op (0);
    GNUNET_free (sig);
    return NULL;
  }
  GNUNET_free (sig);
  *mono_time = raw_mono_time;
  *nt = (enum GNUNET_NetworkType) raw_nt;
  return GNUNET_strdup (raw_addr);
}


/**
 * Given an address as a string, extract the prefix that identifies
 * the communicator offering transmissions to that address.
 *
 * @param address a peer's address
 * @return NULL if the address is mal-formed, otherwise the prefix
 */
char *
GNUNET_HELLO_address_to_prefix (const char *address)
{
  const char *dash;

  dash = strchr (address, '-');
  if (NULL == dash)
    return NULL;
  return GNUNET_strndup (address, dash - address);
}
