/*
     This file is part of GNUnet.
     Copyright (C) 2006, 2009, 2015, 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 transport-testing.h
 * @brief testing lib for transport service
 * @author Matthias Wachs
 * @author Christian Grothoff
 */
#ifndef TRANSPORT_TESTING_H
#define TRANSPORT_TESTING_H
#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet_hello_lib.h"
#include "gnunet_peerstore_service.h"
#include "gnunet_transport_core_service.h"
#include "gnunet_transport_application_service.h"
#include "gnunet_transport_manipulation_service.h"
#include "gnunet_testing_lib.h"


/* ************* Basic functions for starting/stopping/connecting *********** */

/**
 * Context for a single peer
 */
struct GNUNET_TRANSPORT_TESTING_PeerContext;

/**
 * Definition for a transport testing handle
 */
struct GNUNET_TRANSPORT_TESTING_Handle;


/**
 * Context for a single peer
 */
struct GNUNET_TRANSPORT_TESTING_PeerContext
{
  /**
   * Next element in the DLL
   */
  struct GNUNET_TRANSPORT_TESTING_PeerContext *next;

  /**
   * Previous element in the DLL
   */
  struct GNUNET_TRANSPORT_TESTING_PeerContext *prev;

  /**
   * Transport testing handle this peer belongs to
   */
  struct GNUNET_TRANSPORT_TESTING_Handle *tth;

  /**
   * Application handle
   */
  struct GNUNET_TRANSPORT_ApplicationHandle *ah;

  /**
   * Peer's configuration
   */
  struct GNUNET_CONFIGURATION_Handle *cfg;

  /**
   * Peer's transport service handle
   */
  struct GNUNET_TRANSPORT_CoreHandle *th;

  /**
   * Peer's ATS handle.
   */
  struct GNUNET_ATS_ConnectivityHandle *ats;

  /**
   * Peer's PEERSTORE Handle
   */
  struct GNUNET_PEERSTORE_Handle *ph;

  /**
   * Peer's transport get hello handle to retrieve peer's HELLO message
   */
  struct GNUNET_PEERSTORE_IterateContext *pic;

  /**
   * Hello
   */
  char *hello;

  /**
   * Hello size
   */
  size_t hello_size;

  /**
   * Peer's testing handle
   */
  struct GNUNET_TESTING_Peer *peer;

  /**
   * Peer identity
   */
  struct GNUNET_PeerIdentity id;

  /**
   * Handle for the peer's ARM process
   */
  struct GNUNET_OS_Process *arm_proc;

  /**
   * Receive callback
   */
  struct GNUNET_MQ_MessageHandler *handlers;

  /**
   * Notify connect callback
   */
  GNUNET_TRANSPORT_NotifyConnect nc;

  /**
   * Notify disconnect callback
   */
  GNUNET_TRANSPORT_NotifyDisconnect nd;

  /**
   * Startup completed callback
   */
  GNUNET_SCHEDULER_TaskCallback start_cb;

  /**
   * Hello get task
   */
  struct GNUNET_SCHEDULER_Task *rh_task;

  /**
   * Closure for the @a nc and @a nd callbacks
   */
  void *cb_cls;

  /**
   * Closure for @e start_cb.
   */
  void *start_cb_cls;

  /**
   * An unique number to identify the peer
   */
  unsigned int no;
};


/**
 * Handle for a request to connect two peers.
 */
struct GNUNET_TRANSPORT_TESTING_ConnectRequest
{
  /**
   * Kept in a DLL.
   */
  struct GNUNET_TRANSPORT_TESTING_ConnectRequest *next;

  /**
   * Kept in a DLL.
   */
  struct GNUNET_TRANSPORT_TESTING_ConnectRequest *prev;

  /**
   * Peer we want to connect.
   */
  struct GNUNET_TRANSPORT_TESTING_PeerContext *p1;

  /**
   * Peer we want to connect.
   */
  struct GNUNET_TRANSPORT_TESTING_PeerContext *p2;

  /**
   * Task by which we accomplish the connection.
   */
  struct GNUNET_SCHEDULER_Task *tct;

  /**
   * Handle by which we ask ATS to facilitate the connection.
   */
  struct GNUNET_ATS_ConnectivitySuggestHandle *ats_sh;

  /**
   * Function to call upon completion.
   */
  GNUNET_SCHEDULER_TaskCallback cb;

  /**
   * Closure for @e cb.
   */
  void *cb_cls;

  /**
   * Message queue for sending from @a p1 to @a p2.
   */
  struct GNUNET_MQ_Handle *mq;

  /**
   * Set if peer1 says the connection is up to peer2.
   */
  int p1_c;

  /**
   * Set if peer2 says the connection is up to peer1.
   */
  int p2_c;

  /**
   * #GNUNET_YES if both @e p1_c and @e p2_c are #GNUNET_YES.
   */
  int connected;
};


/**
 * Handle for a test run.
 */
struct GNUNET_TRANSPORT_TESTING_Handle
{
  /**
   * Testing library system handle
   */
  struct GNUNET_TESTING_System *tl_system;

  /**
   * head DLL of connect contexts
   */
  struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc_head;

  /**
   * head DLL of connect contexts
   */
  struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc_tail;

  /**
   * head DLL of peers
   */
  struct GNUNET_TRANSPORT_TESTING_PeerContext *p_head;

  /**
   * tail DLL of peers
   */
  struct GNUNET_TRANSPORT_TESTING_PeerContext *p_tail;
};


/**
 * Initialize the transport testing
 *
 * @return transport testing handle
 */
struct GNUNET_TRANSPORT_TESTING_Handle *
GNUNET_TRANSPORT_TESTING_init (void);


/**
 * Clean up the transport testing
 *
 * @param tth transport testing handle
 */
void
GNUNET_TRANSPORT_TESTING_done (struct GNUNET_TRANSPORT_TESTING_Handle *tth);


/**
 * Start a peer with the given configuration
 *
 * @param tth the testing handle
 * @param cfgname configuration file
 * @param peer_id an identification number for the peer
 * @param handlers functions for receiving messages
 * @param nc connect callback
 * @param nd disconnect callback
 * @param cb_cls closure for @a nc and @a nd callback
 * @param start_cb start callback
 * @param start_cb_cls closure for @a start_cb
 * @return the peer context
 */
struct GNUNET_TRANSPORT_TESTING_PeerContext *
GNUNET_TRANSPORT_TESTING_start_peer (
  struct GNUNET_TRANSPORT_TESTING_Handle *tth,
  const char *cfgname,
  int peer_id,
  const struct GNUNET_MQ_MessageHandler *handlers,
  GNUNET_TRANSPORT_NotifyConnect nc,
  GNUNET_TRANSPORT_NotifyDisconnect nd,
  void *cb_cls,
  GNUNET_SCHEDULER_TaskCallback start_cb,
  void *start_cb_cls);


/**
 * Shutdown the given peer
 *
 * @param p the peer
 */
void
GNUNET_TRANSPORT_TESTING_stop_peer (
  struct GNUNET_TRANSPORT_TESTING_PeerContext *pc);


/**
 * Stops and restarts the given peer, sleeping (!) for 5s in between.
 *
 * @param p the peer
 * @param restart_cb restart callback
 * @param restart_cb_cls callback closure
 * @return #GNUNET_OK in success otherwise #GNUNET_SYSERR
 */
int
GNUNET_TRANSPORT_TESTING_restart_peer (
  struct GNUNET_TRANSPORT_TESTING_PeerContext *p,
  GNUNET_SCHEDULER_TaskCallback restart_cb,
  void *restart_cb_cls);


/**
 * Connect the given peers and call the callback when both peers
 * report the inbound connection. Remarks: start_peer's notify_connect
 * callback can be called before.
 *
 * @param p1 peer 1
 * @param p2 peer 2
 * @param cb the callback to call when both peers notified that they are
 * connected
 * @param cls callback cls
 * @return a connect request handle
 */
struct GNUNET_TRANSPORT_TESTING_ConnectRequest *
GNUNET_TRANSPORT_TESTING_connect_peers (
  struct GNUNET_TRANSPORT_TESTING_PeerContext *p1,
  struct GNUNET_TRANSPORT_TESTING_PeerContext *p2,
  GNUNET_SCHEDULER_TaskCallback cb,
  void *cls);


/**
 * Cancel the request to connect two peers.  You MUST cancel the
 * request if you stop the peers before the peers connected
 * successfully.
 *
 * @param cc a connect request handle
 */
void
GNUNET_TRANSPORT_TESTING_connect_peers_cancel (
  struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc);


/**
 * Function called on matching connect requests.
 *
 * @param cls closure
 * @param cc request matching the query
 */
typedef void (*GNUNET_TRANSPORT_TESTING_ConnectContextCallback) (
  void *cls,
  struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cc);


/**
 * Find any connecting context matching the given pair of peers.
 *
 * @param p1 first peer
 * @param p2 second peer
 * @param cb function to call
 * @param cb_cls closure for @a cb
 */
void
GNUNET_TRANSPORT_TESTING_find_connecting_context (
  struct GNUNET_TRANSPORT_TESTING_PeerContext *p1,
  struct GNUNET_TRANSPORT_TESTING_PeerContext *p2,
  GNUNET_TRANSPORT_TESTING_ConnectContextCallback cb,
  void *cb_cls);


/* ********************** high-level process functions *************** */


/**
 * Function called once the peers have been launched and
 * connected by #GNUNET_TRANSPORT_TESTING_connect_check().
 *
 * @param cls closure
 * @param num_peers size of the @a p array
 * @param p the peers that were launched
 */
typedef void (*GNUNET_TRANSPORT_TESTING_ConnectContinuation) (
  void *cls,
  unsigned int num_peers,
  struct GNUNET_TRANSPORT_TESTING_PeerContext *p[]);


/**
 * Internal data structure.
 */
struct GNUNET_TRANSPORT_TESTING_ConnectRequestList;

/**
 * Internal data structure.
 */
struct GNUNET_TRANSPORT_TESTING_InternalPeerContext;


GNUNET_NETWORK_STRUCT_BEGIN
struct GNUNET_TRANSPORT_TESTING_TestMessage
{
  /**
   * Type is (usually) #GNUNET_TRANSPORT_TESTING_SIMPLE_MTYPE.
   */
  struct GNUNET_MessageHeader header;

  /**
   * Monotonically increasing counter throughout the test.
   */
  uint32_t num GNUNET_PACKED;
};
GNUNET_NETWORK_STRUCT_END


/**
 * Function called by the transport for each received message.
 *
 * @param cls closure
 * @param receiver receiver of the message
 * @param sender sender of the message
 * @param message the message
 */
typedef void (*GNUNET_TRANSPORT_TESTING_ReceiveCallback) (
  void *cls,
  struct GNUNET_TRANSPORT_TESTING_PeerContext *receiver,
  const struct GNUNET_PeerIdentity *sender,
  const struct GNUNET_TRANSPORT_TESTING_TestMessage *message);


/**
 * Function called to notify transport users that another
 * peer connected to us.
 *
 * @param cls closure
 * @param me peer experiencing the event
 * @param other peer that connected to @a me
 */
typedef void (*GNUNET_TRANSPORT_TESTING_NotifyConnect) (
  void *cls,
  struct GNUNET_TRANSPORT_TESTING_PeerContext *me,
  const struct GNUNET_PeerIdentity *other);


/**
 * Function called to notify transport users that another
 * peer disconnected from us.
 *
 * @param cls closure
 * @param me peer experiencing the event
 * @param other peer that disconnected from @a me
 */
typedef void (*GNUNET_TRANSPORT_TESTING_NotifyDisconnect) (
  void *cls,
  struct GNUNET_TRANSPORT_TESTING_PeerContext *me,
  const struct GNUNET_PeerIdentity *other);


/**
 * Closure that must be passed to
 * #GNUNET_TRANSPORT_TESTING_connect_check.
 */
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext
{
  /**
   * How should we continue after the connect?
   */
  GNUNET_SCHEDULER_TaskCallback connect_continuation;

  /**
   * Closure for @e connect_continuation.
   */
  void *connect_continuation_cls;

  /**
   * Which configuration file should we pass to the
   * #GNUNET_PROGRAM_run() of the testcase?
   */
  const char *config_file;

  /**
   * Receiver argument to give for peers we start.
   */
  GNUNET_TRANSPORT_TESTING_ReceiveCallback rec;

  /**
   * Notify connect argument to give for peers we start.
   */
  GNUNET_TRANSPORT_TESTING_NotifyConnect nc;

  /**
   * Notify disconnect argument to give for peers we start.
   */
  GNUNET_TRANSPORT_TESTING_NotifyDisconnect nd;

  /**
   * Closure for @e rec, @e nc and @e nd.
   */
  void *cls;

  /**
   * Custom task to run on shutdown.
   */
  GNUNET_SCHEDULER_TaskCallback shutdown_task;

  /**
   * Closure for @e shutdown_task.
   */
  void *shutdown_task_cls;

  /**
   * Custom task to run after peers were started but before we try to
   * connect them.  If this function is set, we wait ONE second after
   * running this function until we continue with connecting the
   * peers.
   */
  GNUNET_SCHEDULER_TaskCallback pre_connect_task;

  /**
   * Closure for @e shutdown_task.
   */
  void *pre_connect_task_cls;

  /**
   * When should the testcase time out?
   */
  struct GNUNET_TIME_Relative timeout;

  /**
   * Should we try to create connections in both directions?
   */
  int bi_directional;

  /* ******* fields set by #GNUNET_TRANSPORT_TESTING_connect_check **** */

  /**
   * Number of peers involved in the test.
   */
  unsigned int num_peers;

  /**
   * Configuration files we have, array with @e num_peers entries.
   */
  char **cfg_files;

  /**
   * Array with @e num_peers entries.
   */
  struct GNUNET_TRANSPORT_TESTING_PeerContext **p;

  /**
   * Name of the plugin.
   */
  const char *test_plugin;

  /**
   * Name of the testcase.
   */
  const char *test_name;

  /**
   * Configuration object for the testcase.
   */
  const struct GNUNET_CONFIGURATION_Handle *cfg;

  /**
   * Main testing handle.
   */
  struct GNUNET_TRANSPORT_TESTING_Handle *tth;

  /**
   * Result from the main function, set to #GNUNET_OK on success.
   * Clients should set to #GNUNET_SYSERR to indicate test failure.
   */
  int global_ret;

  /**
   * Generator for the `num` field in test messages.  Incremented each
   * time #GNUNET_TRANSPORT_TESTING_simple_send or
   * #GNUNET_TRANSPORT_TESTING_large_send are used to transmit a
   * message.
   */
  uint32_t send_num_gen;

  /* ******* internal state, clients should not mess with this **** */

  /**
   * Task run on timeout.
   */
  struct GNUNET_SCHEDULER_Task *timeout_task;

  /**
   * Task run to connect peers.
   */
  struct GNUNET_SCHEDULER_Task *connect_task;

  /**
   * Number of peers that have been started.
   */
  unsigned int started;

  /**
   * DLL of active connect requests.
   */
  struct GNUNET_TRANSPORT_TESTING_ConnectRequestList *crl_head;

  /**
   * DLL of active connect requests.
   */
  struct GNUNET_TRANSPORT_TESTING_ConnectRequestList *crl_tail;

  /**
   * Array with @e num_peers entries.
   */
  struct GNUNET_TRANSPORT_TESTING_InternalPeerContext *ip;
};


/**
 * Find peer by peer ID.
 *
 * @param ccc context to search
 * @param peer peer to look for
 * @return NULL if @a peer was not found
 */
struct GNUNET_TRANSPORT_TESTING_PeerContext *
GNUNET_TRANSPORT_TESTING_find_peer (
  struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc,
  const struct GNUNET_PeerIdentity *peer);


/**
 * Common implementation of the #GNUNET_TRANSPORT_TESTING_CheckCallback.
 * Starts and connects the two peers, then invokes the
 * `connect_continuation` from @a cls.  Sets up a timeout to
 * abort the test, and a shutdown handler to clean up properly
 * on exit.
 *
 * @param cls closure of type `struct
 * GNUNET_TRANSPORT_TESTING_ConnectCheckContext`
 * @param tth_ initialized testing handle
 * @param test_plugin_ name of the plugin
 * @param test_name_ name of the test
 * @param num_peers number of entries in the @a cfg_file array
 * @param cfg_files array of names of configuration files for the peers
 * @return #GNUNET_SYSERR on error
 */
int
GNUNET_TRANSPORT_TESTING_connect_check (
  void *cls,
  struct GNUNET_TRANSPORT_TESTING_Handle *tth_,
  const char *test_plugin_,
  const char *test_name_,
  unsigned int num_peers,
  char *cfg_files[]);


/**
 * Main function of a testcase.  Called with the initial setup data
 * for the test as derived from the source name and the binary name.
 *
 * @param cls closure
 * @param tth_ initialized testing handle
 * @param test_plugin_ name of the plugin
 * @param test_name_ name of the test
 * @param num_peers number of entries in the @a cfg_file array
 * @param cfg_files array of names of configuration files for the peers
 * @return #GNUNET_SYSERR on error
 */
typedef int (*GNUNET_TRANSPORT_TESTING_CheckCallback) (
  void *cls,
  struct GNUNET_TRANSPORT_TESTING_Handle *tth_,
  const char *test_plugin_,
  const char *test_name_,
  unsigned int num_peers,
  char *cfg_files[]);


/**
 * Setup testcase.  Calls @a check with the data the test needs.
 *
 * @param argv0 binary name (argv[0])
 * @param filename source file name (__FILE__)
 * @param num_peers number of peers to start
 * @param check main function to run
 * @param check_cls closure for @a check
 * @return #GNUNET_OK on success
 */
int
GNUNET_TRANSPORT_TESTING_main_ (const char *argv0,
                                const char *filename,
                                unsigned int num_peers,
                                GNUNET_TRANSPORT_TESTING_CheckCallback check,
                                void *check_cls);


/**
 * Setup testcase.  Calls @a check with the data the test needs.
 *
 * @param num_peers number of peers to start
 * @param check main function to run
 * @param check_cls closure for @a check
 * @return #GNUNET_OK on success
 */
#define GNUNET_TRANSPORT_TESTING_main(num_peers, check, check_cls) \
  GNUNET_TRANSPORT_TESTING_main_ (argv[0],                         \
                                  __FILE__,                        \
                                  num_peers,                       \
                                  check,                           \
                                  check_cls)

/* ***************** Convenience functions for sending ********* */

/**
 * Send a test message of type @a mtype and size @a msize from
 * peer @a sender to peer @a receiver.  The peers should be
 * connected when this function is called.
 *
 * @param sender the sending peer
 * @param receiver the receiving peer
 * @param mtype message type to use
 * @param msize size of the message, at least `sizeof (struct
 * GNUNET_TRANSPORT_TESTING_TestMessage)`
 * @param num unique message number
 * @param cont continuation to call after transmission
 * @param cont_cls closure for @a cont
 * @return #GNUNET_OK if message was queued,
 *         #GNUNET_NO if peers are not connected
 *         #GNUNET_SYSERR if @a msize is illegal
 */
int
GNUNET_TRANSPORT_TESTING_send (
  struct GNUNET_TRANSPORT_TESTING_PeerContext *sender,
  struct GNUNET_TRANSPORT_TESTING_PeerContext *receiver,
  uint16_t mtype,
  uint16_t msize,
  uint32_t num,
  GNUNET_SCHEDULER_TaskCallback cont,
  void *cont_cls);


/**
 * Message type used by #GNUNET_TRANSPORT_TESTING_simple_send().
 */
#define GNUNET_TRANSPORT_TESTING_SIMPLE_MTYPE 12345

/**
 * Alternative message type for tests.
 */
#define GNUNET_TRANSPORT_TESTING_SIMPLE_MTYPE2 12346


/**
 * Type of the closure argument to pass to
 * #GNUNET_TRANSPORT_TESTING_simple_send() and
 * #GNUNET_TRANSPORT_TESTING_large_send().
 */
struct GNUNET_TRANSPORT_TESTING_SendClosure
{
  /**
   * Context for the transmission.
   */
  struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc;

  /**
   * Function that returns the desired message size. Overrides
   * the message size, can be NULL in which case the message
   * size is the default.
   */
  size_t (*get_size_cb) (unsigned int n);

  /**
   * Number of messages to be transmitted in a loop.
   * Use zero for "forever" (until external shutdown).
   */
  unsigned int num_messages;

  /**
   * Function to call after all transmissions, can be NULL.
   */
  GNUNET_SCHEDULER_TaskCallback cont;

  /**
   * Closure for @e cont.
   */
  void *cont_cls;
};


/**
 * Task that sends a minimalistic test message from the
 * first peer to the second peer.
 *
 * @param cls the `struct GNUNET_TRANSPORT_TESTING_SendClosure`
 *        which should contain at least two peers, the first two
 *        of which should be currently connected
 */
void
GNUNET_TRANSPORT_TESTING_simple_send (void *cls);

/**
 * Size of a message sent with
 * #GNUNET_TRANSPORT_TESTING_large_send().  Big enough
 * to usually force defragmentation.
 */
#define GNUNET_TRANSPORT_TESTING_LARGE_MESSAGE_SIZE 2600

/**
 * Task that sends a large test message from the
 * first peer to the second peer.
 *
 * @param cls the `struct GNUNET_TRANSPORT_TESTING_SendClosure`
 *        which should contain at least two peers, the first two
 *        of which should be currently connected
 */
void
GNUNET_TRANSPORT_TESTING_large_send (void *cls);


/* ********************** log-only convenience functions ************* */


/**
 * Log a connect event.
 *
 * @param cls NULL
 * @param me peer that had the event
 * @param other peer that connected.
 */
void
GNUNET_TRANSPORT_TESTING_log_connect (
  void *cls,
  struct GNUNET_TRANSPORT_TESTING_PeerContext *me,
  const struct GNUNET_PeerIdentity *other);


/**
 * Log a disconnect event.
 *
 * @param cls NULL
 * @param me peer that had the event
 * @param other peer that disconnected.
 */
void
GNUNET_TRANSPORT_TESTING_log_disconnect (
  void *cls,
  struct GNUNET_TRANSPORT_TESTING_PeerContext *me,
  const struct GNUNET_PeerIdentity *other);


/* ********************** low-level filename functions *************** */


/**
 * Extracts the test filename from an absolute file name and removes
 * the extension.
 *
 * @param file absolute file name
 * @return resulting test name
 */
char *
GNUNET_TRANSPORT_TESTING_get_test_name (const char *file);


/**
 * This function takes the filename (e.g. argv[0), removes a "lt-"-prefix and
 * if existing ".exe"-prefix and adds the peer-number
 *
 * @param file filename of the test, e.g. argv[0]
 * @param count peer number
 * @return configuration name to use
 */
char *
GNUNET_TRANSPORT_TESTING_get_config_name (const char *file, int count);


/**
 * Extracts the plugin anme from an absolute file name and the test name
 * @param file absolute file name
 * @param test test name
 * @return the plugin name
 */
char *
GNUNET_TRANSPORT_TESTING_get_test_plugin_name (const char *executable,
                                               const char *testname);


/**
 * Extracts the filename from an absolute file name and removes the
 * extension
 *
 * @param file absolute file name
 * @return the source name
 */
char *
GNUNET_TRANSPORT_TESTING_get_test_source_name (const char *file);

#endif
/* end of transport_testing.h */
