/*
   This file is part of GNUnet.
   Copyright (C) 2009, 2013, 2016 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 revocation/test_revocation.c
 * @brief base testcase for revocation exchange
 * @author Matthias Wachs
 * @author Christian Grothoff
 */
#include "platform.h"
#include "gnunet_core_service.h"
#include "gnunet_identity_service.h"
#include "gnunet_revocation_service.h"
#include "gnunet_testbed_service.h"

#define NUM_TEST_PEERS 2

struct TestPeer
{
  struct GNUNET_TESTBED_Peer *p;
  struct GNUNET_TESTBED_Operation *identity_op;
  struct GNUNET_TESTBED_Operation *core_op;
  struct GNUNET_IDENTITY_Handle *idh;
  const struct GNUNET_CONFIGURATION_Handle *cfg;
  const struct GNUNET_CRYPTO_PrivateKey *privkey;
  struct GNUNET_CRYPTO_PublicKey pubkey;
  struct GNUNET_CRYPTO_EcdsaSignature sig;
  struct GNUNET_IDENTITY_Operation *create_id_op;
  struct GNUNET_IDENTITY_EgoLookup *ego_lookup;
  struct GNUNET_REVOCATION_Handle *revok_handle;
  struct GNUNET_CORE_Handle *ch;
  struct GNUNET_REVOCATION_PowCalculationHandle *pow;
};

static struct TestPeer testpeers[2];

/**
 * Return value from main, set to 0 on success.
 */
static int ok;


static void
do_shutdown (void *cls)
{
  for (unsigned int c = 0; c < NUM_TEST_PEERS; c++)
  {
    if (NULL != testpeers[c].create_id_op)
    {
      GNUNET_IDENTITY_cancel (testpeers[c].create_id_op);
      testpeers[c].create_id_op = NULL;
    }
    if (NULL != testpeers[c].ego_lookup)
    {
      GNUNET_IDENTITY_ego_lookup_cancel (testpeers[c].ego_lookup);
      testpeers[c].ego_lookup = NULL;
    }
    if (NULL != testpeers[c].revok_handle)
    {
      GNUNET_REVOCATION_revoke_cancel (testpeers[c].revok_handle);
      testpeers[c].revok_handle = NULL;
    }
    if (NULL != testpeers[c].identity_op)
    {
      GNUNET_TESTBED_operation_done (testpeers[c].identity_op);
      testpeers[c].identity_op = NULL;
    }
    if (NULL != testpeers[c].core_op)
    {
      GNUNET_TESTBED_operation_done (testpeers[c].core_op);
      testpeers[c].core_op = NULL;
    }
  }
}


static void
check_revocation (void *cls);


static void
revocation_remote_cb (void *cls, int is_valid)
{
  static int repeat = 0;

  if (GNUNET_NO == is_valid)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Local revocation successful\n");
    ok = 0;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (repeat < 10)
  {
    repeat++;
    GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
                                  &check_revocation,
                                  NULL);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Flooding of revocation failed\n");
  ok = 2;
  GNUNET_SCHEDULER_shutdown ();
}


static void
check_revocation (void *cls)
{
  GNUNET_REVOCATION_query (testpeers[0].cfg,
                           &testpeers[1].pubkey,
                           &revocation_remote_cb,
                           NULL);
}


static void
revocation_cb (void *cls, enum GNUNET_GenericReturnValue is_valid)
{
  testpeers[1].revok_handle = NULL;
  if (GNUNET_NO == is_valid)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Revocation successful\n");
    check_revocation (NULL);
  }
}


static struct GNUNET_REVOCATION_PowP *proof_of_work;

static void
ego_cb (void *cls, struct GNUNET_IDENTITY_Ego *ego)
{
  static int completed = 0;
  const struct GNUNET_CRYPTO_PrivateKey *privkey;

  if ((NULL != ego) && (cls == &testpeers[0]))
  {
    testpeers[0].ego_lookup = NULL;
    testpeers[0].privkey = GNUNET_IDENTITY_ego_get_private_key (ego);
    GNUNET_IDENTITY_ego_get_public_key (ego, &testpeers[0].pubkey);
    completed++;
  }
  if ((NULL != ego) && (cls == &testpeers[1]))
  {
    testpeers[1].ego_lookup = NULL;
    testpeers[1].privkey = GNUNET_IDENTITY_ego_get_private_key (ego);
    GNUNET_IDENTITY_ego_get_public_key (ego, &testpeers[1].pubkey);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Calculating proof of work...\n");
    privkey = GNUNET_IDENTITY_ego_get_private_key (ego);
    proof_of_work = GNUNET_malloc (GNUNET_REVOCATION_MAX_PROOF_SIZE);
    GNUNET_REVOCATION_pow_init (privkey,
                                proof_of_work);
    testpeers[1].pow = GNUNET_REVOCATION_pow_start (proof_of_work,
                                                    1,
                                                    5);
    int res =
      GNUNET_REVOCATION_pow_round (testpeers[1].pow);
    while (GNUNET_OK != res)
    {
      res =
        GNUNET_REVOCATION_pow_round (testpeers[1].pow);
    }
    fprintf (stderr, "Done calculating proof of work\n");
    completed++;
  }
  if (2 == completed)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Egos retrieved\n");
    testpeers[1].revok_handle = GNUNET_REVOCATION_revoke (testpeers[1].cfg,
                                                          proof_of_work,
                                                          &revocation_cb,
                                                          NULL);
    GNUNET_REVOCATION_pow_stop (testpeers[1].pow);
  }
}


static void
identity_create_cb (void *cls,
                    const struct GNUNET_CRYPTO_PrivateKey *pk,
                    enum GNUNET_ErrorCode ec)
{
  static int completed = 0;

  if ((GNUNET_EC_NONE == ec) && (cls == &testpeers[0]))
  {
    testpeers[0].create_id_op = NULL;
    completed++;
  }
  if ((GNUNET_EC_NONE == ec) && (cls == &testpeers[1]))
  {
    testpeers[1].create_id_op = NULL;
    completed++;
  }
  if (2 != completed)
    return;
  fprintf (stderr, "Identities created\n");
  testpeers[0].ego_lookup = GNUNET_IDENTITY_ego_lookup (testpeers[0].cfg,
                                                        "client",
                                                        &ego_cb,
                                                        &testpeers[0]);
  testpeers[1].ego_lookup = GNUNET_IDENTITY_ego_lookup (testpeers[1].cfg,
                                                        "toberevoked",
                                                        &ego_cb,
                                                        &testpeers[1]);
}


static void
identity_completion_cb (void *cls,
                        struct GNUNET_TESTBED_Operation *op,
                        void *ca_result,
                        const char *emsg)
{
  static int completed = 0;

  completed++;
  if (NUM_TEST_PEERS != completed)
    return;
  fprintf (stderr, "All peers connected @ IDENTITY ...\n");
  testpeers[0].create_id_op = GNUNET_IDENTITY_create (testpeers[0].idh,
                                                      "client",
                                                      NULL,
                                                      GNUNET_PUBLIC_KEY_TYPE_ECDSA,
                                                      &identity_create_cb,
                                                      &testpeers[0]);
  testpeers[1].create_id_op = GNUNET_IDENTITY_create (testpeers[1].idh,
                                                      "toberevoked",
                                                      NULL,
                                                      GNUNET_PUBLIC_KEY_TYPE_ECDSA,
                                                      &identity_create_cb,
                                                      &testpeers[1]);
}


static void *
identity_connect_adapter (void *cls,
                          const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  struct TestPeer *me = cls;

  me->cfg = cfg;
  me->idh = GNUNET_IDENTITY_connect (cfg, NULL, NULL);
  if (NULL == me->idh)
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to create IDENTITY handle \n");
  return me->idh;
}


static void
identity_disconnect_adapter (void *cls, void *op_result)
{
  struct TestPeer *me = cls;

  GNUNET_IDENTITY_disconnect (me->idh);
  me->idh = NULL;
}


static void *
connect_cb (void *cls,
            const struct GNUNET_PeerIdentity *peer,
            struct GNUNET_MQ_Handle *mq)
{
  static int connects = 0;

  connects++;
  if (NUM_TEST_PEERS * NUM_TEST_PEERS == connects)
  {
    fprintf (stderr, "All peers connected @ CORE ...\n");

    /* Connect to identity service */
    testpeers[0].identity_op =
      GNUNET_TESTBED_service_connect (NULL,
                                      testpeers[0].p,
                                      "identity",
                                      &identity_completion_cb,
                                      NULL,
                                      &identity_connect_adapter,
                                      &identity_disconnect_adapter,
                                      &testpeers[0]);
    testpeers[1].identity_op =
      GNUNET_TESTBED_service_connect (NULL,
                                      testpeers[1].p,
                                      "identity",
                                      *identity_completion_cb,
                                      NULL,
                                      &identity_connect_adapter,
                                      &identity_disconnect_adapter,
                                      &testpeers[1]);
  }
  return NULL;
}


static void
core_completion_cb (void *cls,
                    struct GNUNET_TESTBED_Operation *op,
                    void *ca_result,
                    const char *emsg)
{
  static int completed = 0;

  completed++;
  if (NUM_TEST_PEERS == completed)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connected to CORE\n");
  }
}


static void *
core_connect_adapter (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  struct TestPeer *me = cls;

  me->cfg = cfg;
  me->ch = GNUNET_CORE_connect (cfg, me, NULL, &connect_cb, NULL, NULL);
  if (NULL == me->ch)
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to create CORE handle \n");
  return me->ch;
}


static void
core_disconnect_adapter (void *cls, void *op_result)
{
  struct TestPeer *me = cls;

  GNUNET_CORE_disconnect (me->ch);
  me->ch = NULL;
}


static void
test_connection (void *cls,
                 struct GNUNET_TESTBED_RunHandle *h,
                 unsigned int num_peers,
                 struct GNUNET_TESTBED_Peer **peers,
                 unsigned int links_succeeded,
                 unsigned int links_failed)
{
  unsigned int c;

  GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL);
  if (NUM_TEST_PEERS != num_peers)
  {
    ok = 4;
    fprintf (stderr,
             "Only %u out of %u peers were started ...\n",
             num_peers,
             NUM_TEST_PEERS);
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  /* We are generating a CLIQUE */
  if (NUM_TEST_PEERS * (NUM_TEST_PEERS - 1) == links_succeeded)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Testbed connected peers, initializing test\n");
    for (c = 0; c < num_peers; c++)
    {
      testpeers[c].p = peers[c];
      testpeers[c].core_op =
        GNUNET_TESTBED_service_connect (NULL,
                                        testpeers[c].p,
                                        "core",
                                        &core_completion_cb,
                                        NULL,
                                        &core_connect_adapter,
                                        &core_disconnect_adapter,
                                        &testpeers[c]);
    }
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Testbed failed to connect peers\n");
    ok = 5;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
}


int
main (int argc, char *argv[])
{
  ok = 1;
  /* Connecting initial topology */
  (void) GNUNET_TESTBED_test_run ("test-revocation",
                                  "test_revocation.conf",
                                  NUM_TEST_PEERS,
                                  0,
                                  NULL,
                                  NULL,
                                  &test_connection,
                                  NULL);
  return ok;
}


/* end of test_revocation.c */
