/*
   This file is part of GNUnet.
   Copyright (C) 2008--2013 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 testbed/gnunet-service-testbed.h
 * @brief data structures shared amongst components of TESTBED service
 * @author Sree Harsha Totakura
 */

#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet_testbed_service.h"
#include "gnunet_transport_service.h"
#include "gnunet_core_service.h"

#include "testbed.h"
#include "testbed_api.h"
#include "testbed_api_operations.h"
#include "testbed_api_hosts.h"
#include "gnunet_testing_lib.h"
#include "gnunet-service-testbed_links.h"


/**
 * Generic logging
 */
#define LOG(kind, ...)                           \
  GNUNET_log (kind, __VA_ARGS__)

/**
 * Debug logging
 */
#define LOG_DEBUG(...)                          \
  LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)

/**
 * By how much should the arrays lists grow
 */
#define LIST_GROW_STEP 10


/**
 * A routing entry
 */
struct Route
{
  /**
   * destination host
   */
  uint32_t dest;

  /**
   * The destination host is reachable thru
   */
  uint32_t thru;
};


/**
 * Context information for operations forwarded to subcontrollers
 */
struct ForwardedOperationContext
{
  /**
   * The next pointer for DLL
   */
  struct ForwardedOperationContext *next;

  /**
   * The prev pointer for DLL
   */
  struct ForwardedOperationContext *prev;

  /**
   * The generated operation context
   */
  struct OperationContext *opc;

  /**
   * The client to which we have to reply
   */
  struct GNUNET_SERVICE_Client *client;

  /**
   * Closure pointer
   */
  void *cls;

  /**
   * Task ID for the timeout task
   */
  struct GNUNET_SCHEDULER_Task *timeout_task;

  /**
   * The id of the operation that has been forwarded
   */
  uint64_t operation_id;

  /**
   * The type of the operation which is forwarded
   */
  enum OperationType type;
};


/**
 * A DLL of host registrations to be made
 */
struct HostRegistration
{
  /**
   * next registration in the DLL
   */
  struct HostRegistration *next;

  /**
   * previous registration in the DLL
   */
  struct HostRegistration *prev;

  /**
   * The callback to call after this registration's status is available
   */
  GNUNET_TESTBED_HostRegistrationCompletion cb;

  /**
   * The closure for the above callback
   */
  void *cb_cls;

  /**
   * The host that has to be registered
   */
  struct GNUNET_TESTBED_Host *host;
};


/**
 * Context information used while linking controllers
 */
struct LinkControllersContext
{
  /**
   * The client which initiated the link controller operation
   */
  struct GNUNET_SERVICE_Client *client;

  /**
   * The ID of the operation
   */
  uint64_t operation_id;
};


/**
 * A peer
 */
struct Peer
{
  union
  {
    struct
    {
      /**
       * The peer handle from testing API
       */
      struct GNUNET_TESTING_Peer *peer;

      /**
       * The modified (by GNUNET_TESTING_peer_configure) configuration this
       * peer is configured with
       */
      struct GNUNET_CONFIGURATION_Handle *cfg;

      /**
       * Is the peer running
       */
      int is_running;
    } local;

    struct
    {
      /**
       * The slave this peer is started through
       */
      struct Slave *slave;

      /**
       * The id of the remote host this peer is running on
       */
      uint32_t remote_host_id;
    } remote;
  } details;

  /**
   * Is this peer locally created?
   */
  int is_remote;

  /**
   * Our local reference id for this peer
   */
  uint32_t id;

  /**
   * References to peers are using in forwarded overlay contexts and remote
   * overlay connect contexts. A peer can only be destroyed after all such
   * contexts are destroyed. For this, we maintain a reference counter. When we
   * use a peer in any such context, we increment this counter. We decrement it
   * when we are destroying these contexts
   */
  uint32_t reference_cnt;

  /**
   * While destroying a peer, due to the fact that there could be references to
   * this peer, we delay the peer destroy to a further time. We do this by using
   * this flag to destroy the peer while destroying a context in which this peer
   * has been used. When the flag is set to 1 and reference_cnt = 0 we destroy
   * the peer
   */
  uint32_t destroy_flag;
};


/**
 * The main context information associated with the client which started us
 */
struct Context
{
  /**
   * The client handle associated with this context
   */
  struct GNUNET_SERVICE_Client *client;

  /**
   * The network address of the master controller
   */
  char *master_ip;

  /**
   * The TESTING system handle for starting peers locally
   */
  struct GNUNET_TESTING_System *system;

  /**
   * Our host id according to this context
   */
  uint32_t host_id;
};


/**
 * The structure for identifying a shared service
 */
struct SharedService
{
  /**
   * The name of the shared service
   */
  char *name;

  /**
   * Number of shared peers per instance of the shared service
   */
  uint32_t num_shared;

  /**
   * Number of peers currently sharing the service
   */
  uint32_t num_sharing;
};


struct RegisteredHostContext;


/**
 * Context information to used during operations which forward the overlay
 * connect message
 */
struct ForwardedOverlayConnectContext
{
  /**
   * next ForwardedOverlayConnectContext in the DLL
   */
  struct ForwardedOverlayConnectContext *next;

  /**
   * previous ForwardedOverlayConnectContext in the DLL
   */
  struct ForwardedOverlayConnectContext *prev;

  /**
   * Which host does this FOCC belong to?
   */
  struct RegisteredHostContext *rhc;

  /**
   * A copy of the original overlay connect message
   */
  struct GNUNET_MessageHeader *orig_msg;

  /**
   * The client handle
   */
  struct GNUNET_SERVICE_Client *client;

  /**
   * The id of the operation which created this context information
   */
  uint64_t operation_id;

  /**
   * the id of peer 1
   */
  uint32_t peer1;

  /**
   * The id of peer 2
   */
  uint32_t peer2;

  /**
   * Id of the host where peer2 is running
   */
  uint32_t peer2_host_id;
};


/**
 * This context information will be created for each host that is registered at
 * slave controllers during overlay connects.
 */
struct RegisteredHostContext
{
  /**
   * The host which is being registered
   */
  struct GNUNET_TESTBED_Host *reg_host;

  /**
   * The host of the controller which has to connect to the above rhost
   */
  struct GNUNET_TESTBED_Host *host;

  /**
   * Head of the ForwardedOverlayConnectContext DLL
   */
  struct ForwardedOverlayConnectContext *focc_dll_head;

  /**
   * Tail of the ForwardedOverlayConnectContext DLL
   */
  struct ForwardedOverlayConnectContext *focc_dll_tail;

  /**
   * Enumeration of states for this context
   */
  enum RHCState
  {
    /**
     * The initial state
     */
    RHC_INIT = 0,

    /**
     * State where we attempt to do the overlay connection again
     */
    RHC_DONE
  } state;
};


/**
 * Context data for #GNUNET_MESSAGE_TYPE_TESTBED_SHUTDOWN_PEERS handler
 */
struct HandlerContext_ShutdownPeers
{
  /**
   * The number of slave we expect to hear from since we forwarded the
   * #GNUNET_MESSAGE_TYPE_TESTBED_SHUTDOWN_PEERS message to them
   */
  unsigned int nslaves;

  /**
   * Did we observe a timeout with respect to this operation at any of the
   * slaves
   */
  int timeout;
};


/**
 * Our configuration
 */
extern struct GNUNET_CONFIGURATION_Handle *GST_config;

/**
 * The master context; generated with the first INIT message
 */
extern struct Context *GST_context;

/**
 * DLL head for forwarded operation contexts
 */
extern struct ForwardedOperationContext *fopcq_head;

/**
 * DLL tail for forwarded operation contexts
 */
extern struct ForwardedOperationContext *fopcq_tail;

/**
 * A list of peers we know about
 */
extern struct Peer **GST_peer_list;

/**
 * Array of hosts
 */
extern struct GNUNET_TESTBED_Host **GST_host_list;

/**
 * Operation queue for open file descriptors
 */
extern struct OperationQueue *GST_opq_openfds;

/**
 * Timeout for operations which may take some time
 */
extern struct GNUNET_TIME_Relative GST_timeout;

/**
 * The size of the peer list
 */
extern unsigned int GST_peer_list_size;

/**
 * The current number of peers running locally under this controller
 */
extern unsigned int GST_num_local_peers;

/**
 * The size of the host list
 */
extern unsigned int GST_host_list_size;

/**
 * The directory where to store load statistics data
 */
extern char *GST_stats_dir;

/**
 * Condition to check if host id is valid
 */
#define VALID_HOST_ID(id) \
  (((id) < GST_host_list_size) && (NULL != GST_host_list[id]))

/**
 * Condition to check if peer id is valid
 */
#define VALID_PEER_ID(id) \
  (((id) < GST_peer_list_size) && (NULL != GST_peer_list[id]))


/**
 * Similar to GNUNET_array_grow(); however instead of calling GNUNET_array_grow()
 * several times we call it only once. The array is also made to grow in steps
 * of LIST_GROW_STEP.
 *
 * @param ptr the array pointer to grow
 * @param size the size of array
 * @param accommodate_size the size which the array has to accommdate; after
 *          this call the array will be big enough to accommdate sizes up to
 *          accommodate_size
 */
#define GST_array_grow_large_enough(ptr, size, accommodate_size) \
  do                                                                    \
  {                                                                     \
    unsigned int growth_size;                                           \
    GNUNET_assert (size <= accommodate_size);                            \
    growth_size = size;                                                 \
    while (growth_size <= accommodate_size)                             \
    growth_size += LIST_GROW_STEP;                                    \
    GNUNET_array_grow (ptr, size, growth_size);                         \
    GNUNET_assert (size > accommodate_size);                            \
  } while (0)


/**
 * Function to destroy a peer
 *
 * @param peer the peer structure to destroy
 */
void
GST_destroy_peer (struct Peer *peer);


/**
 * Stops and destroys all peers
 */
void
GST_destroy_peers (void);


/**
 * Finds the route with directly connected host as destination through which
 * the destination host can be reached
 *
 * @param host_id the id of the destination host
 * @return the route with directly connected destination host; NULL if no route
 *           is found
 */
struct Route *
GST_find_dest_route (uint32_t host_id);


/**
 * Handler for #GNUNET_MESSAGE_TYPE_TESTBED_OVERLAY_CONNECT messages
 *
 * @param cls identification of the client
 * @param msg the actual message
 */
void
handle_overlay_connect (void *cls,
                        const struct GNUNET_TESTBED_OverlayConnectMessage *msg);


/**
 * Adds a host registration's request to a slave's registration queue
 *
 * @param slave the slave controller at which the given host has to be
 *          registered
 * @param cb the host registration completion callback
 * @param cb_cls the closure for the host registration completion callback
 * @param host the host which has to be registered
 */
void
GST_queue_host_registration (struct Slave *slave,
                             GNUNET_TESTBED_HostRegistrationCompletion cb,
                             void *cb_cls, struct GNUNET_TESTBED_Host *host);


/**
 * Callback to relay the reply msg of a forwarded operation back to the client
 *
 * @param cls ForwardedOperationContext
 * @param msg the message to relay
 */
void
GST_forwarded_operation_reply_relay (void *cls,
                                     const struct GNUNET_MessageHeader *msg);


/**
 * Task to free resources when forwarded operation has been timed out.
 *
 * @param cls the ForwardedOperationContext
 */
void
GST_forwarded_operation_timeout (void *cls);


/**
 * Clears the forwarded operations queue
 */
void
GST_clear_fopcq (void);


/**
 * Send operation failure message to client
 *
 * @param client the client to which the failure message has to be sent to
 * @param operation_id the id of the failed operation
 * @param emsg the error message; can be NULL
 */
void
GST_send_operation_fail_msg (struct GNUNET_SERVICE_Client *client,
                             uint64_t operation_id,
                             const char *emsg);


/**
 * Notify OC subsystem that @a client disconnected.
 *
 * @param client the client that disconnected
 */
void
GST_notify_client_disconnect_oc (struct GNUNET_SERVICE_Client *client);


/**
 * Notify peers subsystem that @a client disconnected.
 *
 * @param client the client that disconnected
 */
void
GST_notify_client_disconnect_peers (struct GNUNET_SERVICE_Client *client);


/**
 * Function to send generic operation success message to given client
 *
 * @param client the client to send the message to
 * @param operation_id the id of the operation which was successful
 */
void
GST_send_operation_success_msg (struct GNUNET_SERVICE_Client *client,
                                uint64_t operation_id);


/**
 * Check #GNUNET_MESSAGE_TYPE_TESTBED_REMOTE_OVERLAY_CONNECT messages
 *
 * @param cls identification of the client
 * @param msg the actual message
 * @return #GNUNET_OK if @a msg is well-formed
 */
int
check_remote_overlay_connect (void *cls,
                              const struct
                              GNUNET_TESTBED_RemoteOverlayConnectMessage *msg);


/**
 * Handler for #GNUNET_MESSAGE_TYPE_TESTBED_REMOTE_OVERLAY_CONNECT messages
 *
 * @param cls identification of the client
 * @param msg the actual message
 */
void
handle_remote_overlay_connect (void *cls,
                               const struct
                               GNUNET_TESTBED_RemoteOverlayConnectMessage *msg);


/**
 * Check #GNUNET_MESSAGE_TYPE_TESTBED_CREATEPEER messages
 *
 * @param cls identification of the client
 * @param msg the actual message
 * @return #GNUNET_OK if @a msg is well-formed
 */
int
check_peer_create (void *cls,
                   const struct GNUNET_TESTBED_PeerCreateMessage *msg);


/**
 * Handler for #GNUNET_MESSAGE_TYPE_TESTBED_CREATEPEER messages
 *
 * @param cls identification of the client
 * @param msg the actual message
 */
void
handle_peer_create (void *cls,
                    const struct GNUNET_TESTBED_PeerCreateMessage *msg);


/**
 * Message handler for #GNUNET_MESSAGE_TYPE_TESTBED_DESTROYPEER messages
 *
 * @param cls identification of the client
 * @param msg the actual message
 */
void
handle_peer_destroy (void *cls,
                     const struct GNUNET_TESTBED_PeerDestroyMessage *msg);


/**
 * Message handler for #GNUNET_MESSAGE_TYPE_TESTBED_START_PEER messages
 *
 * @param cls identification of the client
 * @param msg the actual message
 */
void
handle_peer_start (void *cls,
                   const struct GNUNET_TESTBED_PeerStartMessage *msg);


/**
 * Message handler for #GNUNET_MESSAGE_TYPE_TESTBED_DESTROYPEER messages
 *
 * @param cls identification of the client
 * @param msg the actual message
 */
void
handle_peer_stop (void *cls,
                  const struct GNUNET_TESTBED_PeerStopMessage *msg);


/**
 * Handler for #GNUNET_MESSAGE_TYPE_TESTBED_GETPEERCONFIG messages
 *
 * @param cls identification of the client
 * @param msg the actual message
 */
void
handle_peer_get_config (void *cls,
                        const struct
                        GNUNET_TESTBED_PeerGetConfigurationMessage *msg);


/**
 * Handler for #GNUNET_MESSAGE_TYPE_TESTBED_SHUTDOWN_PEERS messages
 *
 * @param cls identification of the client
 * @param msg the actual message
 */
void
handle_shutdown_peers (void *cls,
                       const struct GNUNET_TESTBED_ShutdownPeersMessage *msg);


/**
 * Check #GNUNET_MESSAGE_TYPE_TESTBED_MANAGE_PEER_SERVICE message
 *
 * @param cls identification of client
 * @param msg the actual message
 * @return #GNUNET_OK if @a msg is well-formed
 */
int
check_manage_peer_service (void *cls,
                           const struct
                           GNUNET_TESTBED_ManagePeerServiceMessage *msg);


/**
 * Handler for #GNUNET_MESSAGE_TYPE_TESTBED_MANAGE_PEER_SERVICE messages
 *
 * @param cls identification of client
 * @param msg the actual message
 */
void
handle_manage_peer_service (void *cls,
                            const struct
                            GNUNET_TESTBED_ManagePeerServiceMessage *msg);


/**
 * Check #GNUNET_MESSAGE_TYPDE_TESTBED_RECONFIGURE_PEER type messages.
 *
 * @param cls identification of the client
 * @param msg the actual message
 * @return #GNUNET_OK if @a msg is well-formed
 */
int
check_peer_reconfigure (void *cls,
                        const struct
                        GNUNET_TESTBED_PeerReconfigureMessage *msg);


/**
 * Handler for #GNUNET_MESSAGE_TYPDE_TESTBED_RECONFIGURE_PEER type messages.
 * Should stop the peer asynchronously, destroy it and create it again with the
 * new configuration.
 *
 * @param cls identification of the client
 * @param msg the actual message
 */
void
handle_peer_reconfigure (void *cls,
                         const struct
                         GNUNET_TESTBED_PeerReconfigureMessage *msg);


/**
 * Frees the ManageServiceContext queue
 */
void
GST_free_mctxq (void);


/**
 * Cleans up the queue used for forwarding link controllers requests
 */
void
GST_free_lcf (void);


/**
 * Cleans up the route list
 */
void
GST_route_list_clear (void);


/**
 * Processes a forwarded overlay connect context in the queue of the given RegisteredHostContext
 *
 * @param rhc the RegisteredHostContext
 */
void
GST_process_next_focc (struct RegisteredHostContext *rhc);


/**
 * Cleans up ForwardedOverlayConnectContext
 *
 * @param focc the ForwardedOverlayConnectContext to cleanup
 */
void
GST_cleanup_focc (struct ForwardedOverlayConnectContext *focc);


/**
 * Clears all pending overlay connect contexts in queue
 */
void
GST_free_occq (void);


/**
 * Clears all pending remote overlay connect contexts in queue
 */
void
GST_free_roccq (void);


/**
 * Cleans up the Peer reconfigure context list
 */
void
GST_free_prcq (void);


/**
 * Initializes the cache
 *
 * @param size the size of the cache
 */
void
GST_cache_init (unsigned int size);


/**
 * Clear cache
 */
void
GST_cache_clear (void);


/**
 * Looks up in the hello cache and returns the HELLO of the given peer
 *
 * @param peer_id the index of the peer whose HELLO has to be looked up
 * @return the HELLO message; NULL if not found
 */
const struct GNUNET_MessageHeader *
GST_cache_lookup_hello (const unsigned int peer_id);


/**
 * Caches the HELLO of the given peer. Updates the HELLO if it was already
 * cached before
 *
 * @param peer_id the peer identity of the peer whose HELLO has to be cached
 * @param hello the HELLO message
 */
void
GST_cache_add_hello (const unsigned int peer_id,
                     const struct GNUNET_MessageHeader *hello);


/**
 * Initialize logging CPU and IO statisticfs.  Checks the configuration for
 * "STATS_DIR" and logs to a file in that directory.  The file is name is
 * generated from the hostname and the process's PID.
 */
void
GST_stats_init (const struct GNUNET_CONFIGURATION_Handle *cfg);


/**
 * Shutdown the status calls module.
 */
void
GST_stats_destroy (void);

/* End of gnunet-service-testbed.h */
