/*
   This file is part of GNUnet.
   Copyright (C) 2008--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 testbed/gnunet-service-testbed_oc.c
 * @brief code for handling overlay connect operations
 * @author Sree Harsha Totakura
 */

#include "platform.h"
#include "gnunet-service-testbed.h"
#include "gnunet-service-testbed_connectionpool.h"
#include "gnunet_transport_hello_service.h"

/**
 * Redefine LOG with a changed log component string
 */
#ifdef LOG
#undef LOG
#endif
#define LOG(kind, ...)                                   \
  GNUNET_log_from (kind, "testbed-OC", __VA_ARGS__)


/**
 * Context information for requesting ATS to connect to a peer
 */
struct ConnectivitySuggestContext
{
  /**
   * The transport handle obtained from cache. Do NOT close/disconnect.
   */
  struct GNUNET_TRANSPORT_CoreHandle *th_;

  /**
   * Configuration of the peer from cache. Do not free!
   */
  const struct GNUNET_CONFIGURATION_Handle *cfg;

  /**
   * The GetCacheHandle for the peer2's transport handle
   * (used to offer the HELLO to the peer).
   */
  struct GST_ConnectionPool_GetHandle *cgh_p2_th;

  /**
   * The GetCacheHandle for the peer2's ATS handle.
   */
  struct GST_ConnectionPool_GetHandle *cgh_p2_ats;

  /**
   * The ATS handle for the connectivity suggestion.
   */
  struct GNUNET_ATS_ConnectivitySuggestHandle *csh;
};


/**
 * Types for context information we create for overlay connect requests
 */
enum OverlayConnectContextType
{
  /**
   * This type is used if the overlay connection is local i.e. the connection
   * has to be made between local peers
   */
  OCC_TYPE_LOCAL,

  /**
   * Type to be used when the first peer is local and the other peer is on a slave
   * controller started by us
   */
  OCC_TYPE_REMOTE_SLAVE,

  /**
   * Type to be used when the first peer is local and the other peer is on a
   * controller which is not started by us.
   */
  OCC_TYPE_REMOTE_LATERAL
};


/**
 * Context data for operations on second peer in local overlay connection
 * contexts
 */
struct LocalPeer2Context
{
  /**
   * The handle for offering the HELLO of the first peer to the second
   * peer.
   */
  struct GNUNET_TRANSPORT_OfferHelloHandle *ohh;

  /**
   * The transport ConnectivitySuggestContext
   */
  struct ConnectivitySuggestContext tcc;
};


/**
 * Context data for operations on second peer in remote overlay connection
 * contexts
 */
struct RemotePeer2Context
{
  /**
   * Controller of peer 2; If #OCC_TYPE_REMOTE_LATERAL is the type of overlay
   * connection then this can be NULL until the connection to the controller is
   * established
   */
  struct GNUNET_TESTBED_Controller *p2c;

  /**
   * Operation context for the suboperation we start to get the identity of the
   * second peer
   */
  struct OperationContext *opc;

  /**
   * Notification handle acquire to connect to a remote controller.  Only used
   * if the type of overlay connection is #OCC_TYPE_REMOTE_LATERAL.
   */
  struct NeighbourConnectNotification *ncn;

  /**
   * The neighbour handle.  Only used if the type of overlay connection is
   * #OCC_TYPE_REMOTE_LATERAL.
   */
  struct Neighbour *p2n;
};

/**
 * Context information for connecting 2 peers in overlay.
 */
struct OverlayConnectContext
{
  /**
   * The next pointer for maintaining a DLL of all OverlayConnectContexts
   */
  struct OverlayConnectContext *next;

  /**
   * The prev pointer for maintaining a DLL of all OverlayConnectContexts
   */
  struct OverlayConnectContext *prev;

  /**
   * The client which has requested for overlay connection. This is used to send
   * either a success of failure message
   */
  struct GNUNET_SERVICE_Client *client;

  /**
   * the first peer which is to expect an overlay connection from the second peer.
   */
  struct Peer *peer;

  /**
   * Transport handle of the first peer obtained from cache to get its HELLO. Do
   * NOT close/disconnect.
   */
  struct GNUNET_TRANSPORT_CoreHandle *p1th_;

  /**
   * The #GST_ConnectionPool_GetHandle for the peer1's transport handle
   */
  struct GST_ConnectionPool_GetHandle *cgh_p1th;

  /**
   * The #GST_ConnectionPool_GetHandle for registering callback to notify CORE
   * level peer connects and to get our identity.
   */
  struct GST_ConnectionPool_GetHandle *cgh_ch;

  /**
   * HELLO of the first peer.  This should be sent to the second peer.
   */
  struct GNUNET_MessageHeader *hello;

  /**
   * Get GetHelloHandle to acquire a HELLO of the first peer
   */
  struct GNUNET_TRANSPORT_HelloGetHandle *ghh;

  /**
   * The error message we send if this overlay connect operation has timed out
   */
  char *emsg;

  /**
   * Context information for operations on the second peer
   */
  union
  {
    /**
     * Context information to be used if the second peer is local
     */
    struct LocalPeer2Context local;

    /**
     * Context information to be used if the second peer is remote
     */
    struct RemotePeer2Context remote;
  } p2ctx;

  /**
   * The peer identity of the first peer
   */
  struct GNUNET_PeerIdentity peer_identity;

  /**
   * The peer identity of the other peer
   */
  struct GNUNET_PeerIdentity other_peer_identity;

  /**
   * The id of the operation responsible for creating this context
   */
  uint64_t op_id;

  /**
   * The id of the task for sending HELLO of peer 2 to peer 1 and ask peer 1 to
   * connect to peer 2
   */
  struct GNUNET_SCHEDULER_Task *send_hello_task;

  /**
   * The id of the overlay connect timeout task
   */
  struct GNUNET_SCHEDULER_Task *timeout_task;

  /**
   * The id of the cleanup task
   */
  struct GNUNET_SCHEDULER_Task *cleanup_task;

  /**
   * The type of this context information
   */
  enum OverlayConnectContextType type;

  /**
   * The id of the second peer which has to connect to the first peer
   */
  uint32_t other_peer_id;
};


/**
 * Context information for remote overlay connect operations.  Remote overlay
 * connections are used when peers A and B reside on different hosts.  In these
 * operations the host controller for peer B is asked by the host controller of
 * peer A to make peer B connect to peer A by sending the controller of peer B
 * the HELLO of peer A.
 */
struct RemoteOverlayConnectCtx
{
  /**
   * the next pointer for DLL
   */
  struct RemoteOverlayConnectCtx *next;

  /**
   * the prev pointer for DLL
   */
  struct RemoteOverlayConnectCtx *prev;

  /**
   * The peer handle of peer B
   */
  struct Peer *peer;

  /**
   * Peer A's HELLO
   */
  struct GNUNET_MessageHeader *hello;

  /**
   * The handle for offering HELLO
   */
  struct GNUNET_TRANSPORT_OfferHelloHandle *ohh;

  /**
   * The transport try connect context
   */
  struct ConnectivitySuggestContext tcc;

  /**
   * The peer identity of peer A
   */
  struct GNUNET_PeerIdentity a_id;

  /**
   * Task for offering HELLO of A to B and doing try_connect
   */
  struct GNUNET_SCHEDULER_Task *attempt_connect_task_id;

  /**
   * Task to timeout RequestOverlayConnect
   */
  struct GNUNET_SCHEDULER_Task *timeout_rocc_task_id;

  /**
   * The id of the operation responsible for creating this context
   */
  uint64_t op_id;
};


/**
 * DLL head for OverlayConnectContext DLL - to be used to clean up during shutdown
 */
static struct OverlayConnectContext *occq_head;

/**
 * DLL tail for OverlayConnectContext DLL
 */
static struct OverlayConnectContext *occq_tail;

/**
 * DLL head for RequectOverlayConnectContext DLL - to be used to clean up during
 * shutdown
 */
static struct RemoteOverlayConnectCtx *roccq_head;

/**
 * DLL tail for RequectOverlayConnectContext DLL
 */
static struct RemoteOverlayConnectCtx *roccq_tail;


/**
 * Cleans up ForwardedOverlayConnectContext
 *
 * @param focc the ForwardedOverlayConnectContext to cleanup
 */
void
GST_cleanup_focc (struct ForwardedOverlayConnectContext *focc)
{
  struct RegisteredHostContext *rhc = focc->rhc;

  GNUNET_CONTAINER_DLL_remove (rhc->focc_dll_head,
                               rhc->focc_dll_tail,
                               focc);
  GNUNET_free (focc->orig_msg);
  GNUNET_free (focc);
}


/**
 * Timeout task for cancelling a forwarded overlay connect connect
 *
 * @param cls the `struct ForwardedOperationContext`
 */
static void
forwarded_overlay_connect_timeout (void *cls)
{
  struct ForwardedOperationContext *fopc = cls;
  struct RegisteredHostContext *rhc;
  struct ForwardedOverlayConnectContext *focc;

  fopc->timeout_task = NULL;
  rhc = fopc->cls;
  focc = rhc->focc_dll_head;
  LOG_DEBUG ("Overlay linking between peers %u and %u failed\n",
             focc->peer1,
             focc->peer2);
  GST_cleanup_focc (focc);
  GST_forwarded_operation_timeout (fopc);
  if (NULL != rhc->focc_dll_head)
    GST_process_next_focc (rhc);
}


/**
 * Callback to be called when forwarded overlay connection operation has a reply
 * from the sub-controller successful. We have to relay the reply msg back to
 * the client
 *
 * @param cls ForwardedOperationContext
 * @param msg the peer create success message
 */
static void
forwarded_overlay_connect_listener (void *cls,
                                    const struct GNUNET_MessageHeader *msg)
{
  struct ForwardedOperationContext *fopc = cls;
  struct RegisteredHostContext *rhc;
  struct ForwardedOverlayConnectContext *focc;

  rhc = fopc->cls;
  GST_forwarded_operation_reply_relay (cls, msg);
  focc = rhc->focc_dll_head;
  GST_cleanup_focc (focc);
  if (NULL != rhc->focc_dll_head)
    GST_process_next_focc (rhc);
}


/**
 * 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)
{
  struct ForwardedOperationContext *fopc;
  struct ForwardedOverlayConnectContext *focc;
  struct Peer *peer;
  struct Slave *slave;

  focc = rhc->focc_dll_head;
  GNUNET_assert (NULL != focc);
  GNUNET_assert (RHC_DONE == rhc->state);
  GNUNET_assert (VALID_PEER_ID (focc->peer1));
  peer = GST_peer_list[focc->peer1];
  GNUNET_assert (GNUNET_YES == peer->is_remote);
  GNUNET_assert (NULL != (slave = peer->details.remote.slave));
  fopc = GNUNET_new (struct ForwardedOperationContext);
  fopc->client = focc->client;
  fopc->operation_id = focc->operation_id;
  fopc->cls = rhc;
  fopc->type = OP_OVERLAY_CONNECT;
  fopc->opc =
    GNUNET_TESTBED_forward_operation_msg_ (slave->controller,
                                           focc->operation_id,
                                           focc->orig_msg,
                                           &forwarded_overlay_connect_listener,
                                           fopc);
  GNUNET_free (focc->orig_msg);
  focc->orig_msg = NULL;
  fopc->timeout_task = GNUNET_SCHEDULER_add_delayed (GST_timeout,
                                                     &
                                                     forwarded_overlay_connect_timeout,
                                                     fopc);
  GNUNET_CONTAINER_DLL_insert_tail (fopcq_head,
                                    fopcq_tail,
                                    fopc);
}


/**
 * Cleans up any used handles in local peer2 context
 *
 * @param lp2c the local peer2 context information
 */
static void
cleanup_occ_lp2c (struct LocalPeer2Context *lp2c)
{
  if (NULL != lp2c->ohh)
  {
    GNUNET_TRANSPORT_offer_hello_cancel (lp2c->ohh);
    lp2c->ohh = NULL;
  }
  if (NULL != lp2c->tcc.cgh_p2_th)
  {
    GST_connection_pool_get_handle_done (lp2c->tcc.cgh_p2_th);
    lp2c->tcc.cgh_p2_th = NULL;
  }
  if (NULL != lp2c->tcc.cgh_p2_ats)
  {
    GST_connection_pool_get_handle_done (lp2c->tcc.cgh_p2_ats);
    lp2c->tcc.cgh_p2_ats = NULL;
  }
  if (NULL != lp2c->tcc.csh)
  {
    GNUNET_ATS_connectivity_suggest_cancel (lp2c->tcc.csh);
    lp2c->tcc.csh = NULL;
  }
}


/**
 * Cleans up any used handles in remote peer2 context.  Relinquishes the
 * remote controller connection if it has been established on-demand.
 *
 * @param rp2c the remote peer2 context information
 */
static void
cleanup_occ_rp2c (struct RemotePeer2Context *rp2c)
{
  if (NULL != rp2c->opc)
  {
    GNUNET_TESTBED_forward_operation_msg_cancel_ (rp2c->opc);
    rp2c->opc = NULL;
  }
  if (NULL != rp2c->ncn)
  {
    GST_neighbour_get_connection_cancel (rp2c->ncn);
    rp2c->ncn = NULL;
  }
  if ((NULL != rp2c->p2c) && (NULL != rp2c->p2n))
  {
    GST_neighbour_release_connection (rp2c->p2n);
    rp2c->p2n = NULL;
  }
}


/**
 * Condition for checking if given peer is ready to be destroyed
 *
 * @param peer the peer to check
 */
#define PEER_EXPIRED(peer)                      \
  ((GNUNET_YES == peer->destroy_flag) && (0 == peer->reference_cnt))

/**
 * Cleanup overlay connect context structure
 *
 * @param occ the overlay connect context
 */
static void
cleanup_occ (struct OverlayConnectContext *occ)
{
  struct Peer *peer2;

  LOG_DEBUG ("0x%llx: Cleaning up occ\n",
             (unsigned long long) occ->op_id);
  GNUNET_free (occ->emsg);
  GNUNET_free (occ->hello);
  if (NULL != occ->send_hello_task)
    GNUNET_SCHEDULER_cancel (occ->send_hello_task);
  if (NULL != occ->cleanup_task)
    GNUNET_SCHEDULER_cancel (occ->cleanup_task);
  if (NULL != occ->timeout_task)
    GNUNET_SCHEDULER_cancel (occ->timeout_task);
  if (NULL != occ->cgh_ch)
    GST_connection_pool_get_handle_done (occ->cgh_ch);
  if (NULL != occ->ghh)
    GNUNET_TRANSPORT_hello_get_cancel (occ->ghh);
  GST_connection_pool_get_handle_done (occ->cgh_p1th);
  GNUNET_assert (NULL != GST_peer_list);
  GNUNET_assert (occ->peer->reference_cnt > 0);
  occ->peer->reference_cnt--;
  if (PEER_EXPIRED (occ->peer))
    GST_destroy_peer (occ->peer);
  switch (occ->type)
  {
  case OCC_TYPE_LOCAL:
    peer2 = GST_peer_list[occ->other_peer_id];
    GNUNET_assert (peer2->reference_cnt > 0);
    peer2->reference_cnt--;
    if (PEER_EXPIRED (peer2))
      GST_destroy_peer (peer2);
    cleanup_occ_lp2c (&occ->p2ctx.local);
    break;

  case OCC_TYPE_REMOTE_SLAVE:
  case OCC_TYPE_REMOTE_LATERAL:
    cleanup_occ_rp2c (&occ->p2ctx.remote);
    break;
  }
  GNUNET_CONTAINER_DLL_remove (occq_head,
                               occq_tail,
                               occ);
  GNUNET_free (occ);
}


/**
 * Task for cleaning up overlay connect context structure
 *
 * @param cls the overlay connect context
 */
static void
do_cleanup_occ (void *cls)
{
  struct OverlayConnectContext *occ = cls;

  occ->cleanup_task = NULL;
  cleanup_occ (occ);
}


/**
 * Task which will be run when overlay connect request has been timed out
 *
 * @param cls the OverlayConnectContext
 */
static void
timeout_overlay_connect (void *cls)
{
  struct OverlayConnectContext *occ = cls;

  GNUNET_assert (NULL != occ->timeout_task);
  occ->timeout_task = NULL;
  /* LOG (GNUNET_ERROR_TYPE_WARNING, */
  /*      "0x%llx: Timeout while connecting peers %u and %u: %s\n", occ->op_id, */
  /*      occ->peer->id, occ->other_peer_id, occ->emsg); */
  GST_send_operation_fail_msg (occ->client,
                               occ->op_id,
                               occ->emsg);
  cleanup_occ (occ);
}


/**
 * Notify OC subsystem that @a client disconnected.
 *
 * @param client the client that disconnected
 */
void
GST_notify_client_disconnect_oc (struct GNUNET_SERVICE_Client *client)
{
  struct ForwardedOperationContext *fopc;
  struct ForwardedOperationContext *fopcn;
  struct OverlayConnectContext *occ;
  struct OverlayConnectContext *occn;

  for (fopc = fopcq_head; NULL != fopc; fopc = fopcn)
  {
    fopcn = fopc->next;
    if (fopc->client == client)
    {
      GNUNET_SCHEDULER_cancel (fopc->timeout_task);
      GST_forwarded_operation_timeout (fopc);
    }
  }
  for (occ = occq_head; NULL != occ; occ = occn)
  {
    occn = occ->next;
    if (occ->client == client)
      cleanup_occ (occ);
  }
  // FIXME: implement clean up for client_keep replacements!
}


/**
 * FIXME.
 */
static void
send_overlay_connect_success_msg (struct OverlayConnectContext *occ)
{
  struct GNUNET_MQ_Envelope *env;
  struct GNUNET_TESTBED_ConnectionEventMessage *msg;

  LOG_DEBUG ("0x%llx: Peers connected - Sending overlay connect success\n",
             (unsigned long long) occ->op_id);
  env = GNUNET_MQ_msg (msg,
                       GNUNET_MESSAGE_TYPE_TESTBED_PEER_CONNECT_EVENT);
  msg->event_type = htonl (GNUNET_TESTBED_ET_CONNECT);
  msg->peer1 = htonl (occ->peer->id);
  msg->peer2 = htonl (occ->other_peer_id);
  msg->operation_id = GNUNET_htonll (occ->op_id);
  GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (occ->client),
                  env);
}


/**
 * Function called to notify transport users that another
 * peer connected to us.
 *
 * @param cls closure
 * @param new_peer the peer that connected
 */
static void
overlay_connect_notify (void *cls,
                        const struct GNUNET_PeerIdentity *new_peer)
{
  struct OverlayConnectContext *occ = cls;
  char *new_peer_str;
  char *other_peer_str;

  LOG_DEBUG ("Overlay connect notify\n");
  if (0 ==
      GNUNET_memcmp (new_peer,
                     &occ->peer_identity))
    return;
  new_peer_str = GNUNET_strdup (GNUNET_i2s (new_peer));
  other_peer_str = GNUNET_strdup (GNUNET_i2s (&occ->other_peer_identity));
  if (0 !=
      GNUNET_memcmp (new_peer,
                     &occ->other_peer_identity))
  {
    LOG_DEBUG ("Unexpected peer %s connected when expecting peer %s\n",
               new_peer_str,
               other_peer_str);
    GNUNET_free (new_peer_str);
    GNUNET_free (other_peer_str);
    return;
  }
  GNUNET_free (new_peer_str);
  LOG_DEBUG ("0x%llx: Peer %s connected to peer %s\n",
             (unsigned long long) occ->op_id,
             other_peer_str,
             GNUNET_i2s (&occ->peer_identity));
  GNUNET_free (other_peer_str);
  if (NULL != occ->send_hello_task)
  {
    GNUNET_SCHEDULER_cancel (occ->send_hello_task);
    occ->send_hello_task = NULL;
  }
  GNUNET_assert (NULL != occ->timeout_task);
  GNUNET_SCHEDULER_cancel (occ->timeout_task);
  occ->timeout_task = NULL;
  switch (occ->type)
  {
  case OCC_TYPE_LOCAL:
    cleanup_occ_lp2c (&occ->p2ctx.local);
    break;

  case OCC_TYPE_REMOTE_SLAVE:
  case OCC_TYPE_REMOTE_LATERAL:
    cleanup_occ_rp2c (&occ->p2ctx.remote);
    break;
  }
  GNUNET_free (occ->emsg);
  occ->emsg = NULL;
  send_overlay_connect_success_msg (occ);
  occ->cleanup_task = GNUNET_SCHEDULER_add_now (&do_cleanup_occ,
                                                occ);
}


/**
 * Callback from cache with needed ATS handle set
 *
 * @param cls a `struct OverlayConnectCtx *`
 * @param ch the handle to CORE. Can be NULL if it is not requested
 * @param th the handle to TRANSPORT. Can be NULL if it is not requested
 * @param ac the handle to ATS. Can be NULL if it is not requested
 * @param my_identity the identity of our peer
 * @param cfg configuration of the peer
 */
static void
occ_cache_get_handle_ats_occ_cb (void *cls,
                                 struct GNUNET_CORE_Handle *ch,
                                 struct GNUNET_TRANSPORT_CoreHandle *th,
                                 struct GNUNET_ATS_ConnectivityHandle *ac,
                                 const struct GNUNET_PeerIdentity *my_identity,
                                 const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  struct OverlayConnectContext *occ = cls;
  struct LocalPeer2Context *lp2c;

  GNUNET_assert (OCC_TYPE_LOCAL == occ->type);
  GNUNET_assert (NULL != occ->timeout_task);
  GNUNET_free (occ->emsg);
  if (NULL == ac)
  {
    GNUNET_asprintf (&occ->emsg,
                     "0x%llx: Failed to connect to ATS of peer with id: %u",
                     (unsigned long long) occ->op_id,
                     occ->peer->id);
    GNUNET_SCHEDULER_cancel (occ->timeout_task);
    occ->timeout_task =
      GNUNET_SCHEDULER_add_now (&timeout_overlay_connect,
                                occ);
    return;
  }
  occ->emsg = NULL;

  GNUNET_asprintf (&occ->emsg,
                   "0x%llx: Timeout during GNUNET_ATS_connectivity_suggest() at peer %s",
                   (unsigned long long) occ->op_id,
                   GNUNET_i2s (&occ->other_peer_identity));

  lp2c = &occ->p2ctx.local;
  lp2c->tcc.csh =
    GNUNET_ATS_connectivity_suggest (ac,
                                     &occ->peer_identity,
                                     1);
}


/**
 * Callback from cache with needed ATS handle set
 *
 * @param cls a `struct RemoteOverlayConnectCtx *`
 * @param ch the handle to CORE. Can be NULL if it is not requested
 * @param th the handle to TRANSPORT. Can be NULL if it is not requested
 * @param ac the handle to ATS. Can be NULL if it is not requested
 * @param my_identity the identity of our peer
 */
static void
occ_cache_get_handle_ats_rocc_cb (void *cls,
                                  struct GNUNET_CORE_Handle *ch,
                                  struct GNUNET_TRANSPORT_CoreHandle *th,
                                  struct GNUNET_ATS_ConnectivityHandle *ac,
                                  const struct GNUNET_PeerIdentity *my_identity,
                                  const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  struct RemoteOverlayConnectCtx *rocc = cls;

  rocc->tcc.csh =
    GNUNET_ATS_connectivity_suggest (ac,
                                     &rocc->a_id,
                                     1);
}


/**
 * Task to offer HELLO of peer 1 to peer 2 and try to make peer 2 to connect to
 * peer 1.
 *
 * @param cls the OverlayConnectContext
 */
static void
send_hello (void *cls);


/**
 * Task that is run when hello has been sent If tc->reason =
 * #GNUNET_SCHEDULER_REASON_TIMEOUT then sending HELLO failed; if
 * #GNUNET_SCHEDULER_REASON_READ_READY is succeeded
 *
 * @param cls the overlay connect context
 */
static void
occ_hello_sent_cb (void *cls)
{
  struct OverlayConnectContext *occ = cls;
  struct LocalPeer2Context *lp2c;
  struct Peer *peer2;

  GNUNET_assert (OCC_TYPE_LOCAL == occ->type);
  GNUNET_assert (NULL != occ->timeout_task);
  lp2c = &occ->p2ctx.local;
  lp2c->ohh = NULL;

  GNUNET_assert (NULL == occ->send_hello_task);
  GNUNET_free (occ->emsg);

  GNUNET_asprintf (&occ->emsg,
                   "0x%llx: Timeout while acquiring ATS of %s from cache",
                   (unsigned long long) occ->op_id,
                   GNUNET_i2s (&occ->other_peer_identity));
  GNUNET_assert (NULL != (peer2 = GST_peer_list[occ->other_peer_id]));
  lp2c->tcc.cgh_p2_ats =
    GST_connection_pool_get_handle (occ->other_peer_id,
                                    peer2->details.local.cfg,
                                    GST_CONNECTIONPOOL_SERVICE_ATS_CONNECTIVITY,
                                    &occ_cache_get_handle_ats_occ_cb,
                                    occ, NULL, NULL, NULL);
}


/**
 * Sends the HELLO of peer1 to peer2's controller through remote overlay connect
 * request.
 *
 * @param occ the overlay connect context.  Its type must be either
 *          #OCC_TYPE_REMOTE_SLAVE or #OCC_TYPE_REMOTE_LATERAL
 */
static void
send_hello_thru_rocc (struct OverlayConnectContext *occ)
{
  struct GNUNET_TESTBED_RemoteOverlayConnectMessage *msg;
  char *other_peer_str;
  uint16_t msize;
  uint16_t hello_size;

  GNUNET_assert (OCC_TYPE_LOCAL != occ->type);
  GNUNET_assert (NULL != occ->hello);
  other_peer_str = GNUNET_strdup (GNUNET_i2s (&occ->other_peer_identity));
  LOG_DEBUG (
    "0x%llx: Offering HELLO of %s (size: %u) to %s via Remote Overlay Request\n",
    (unsigned long long) occ->op_id,
    GNUNET_i2s (&occ->peer_identity),
    ntohs (occ->hello->size),
    other_peer_str);
  GNUNET_free (other_peer_str);
  hello_size = ntohs (occ->hello->size);
  msize = sizeof(struct GNUNET_TESTBED_RemoteOverlayConnectMessage)
          + hello_size;
  msg = GNUNET_malloc (msize);
  msg->header.type =
    htons (GNUNET_MESSAGE_TYPE_TESTBED_REMOTE_OVERLAY_CONNECT);
  msg->header.size = htons (msize);
  msg->peer = htonl (occ->other_peer_id);
  msg->operation_id = GNUNET_htonll (occ->op_id);
  msg->peer_identity = occ->peer_identity;
  GNUNET_memcpy (msg->hello,
                 occ->hello,
                 hello_size);
  GNUNET_TESTBED_queue_message_ (occ->p2ctx.remote.p2c,
                                 &msg->header);
}


/**
 * Task to offer HELLO of peer 1 to peer 2.  If peer2 is local it is offered
 * using its TRANSPORT connection; if remote the HELLO is sent remotely by using
 * send_hello_thru_rocc()
 *
 * @param cls the OverlayConnectContext
 */
static void
send_hello (void *cls)
{
  struct OverlayConnectContext *occ = cls;
  struct LocalPeer2Context *lp2c;
  char *other_peer_str;

  occ->send_hello_task = NULL;
  GNUNET_assert (NULL != occ->timeout_task);
  GNUNET_assert (NULL != occ->hello);
  if (OCC_TYPE_LOCAL != occ->type)
  {
    send_hello_thru_rocc (occ);
    return;
  }
  lp2c = &occ->p2ctx.local;
  other_peer_str = GNUNET_strdup (GNUNET_i2s (&occ->other_peer_identity));
  LOG_DEBUG ("0x%llx: Offering HELLO of %s to %s\n",
             (unsigned long long) occ->op_id,
             GNUNET_i2s (&occ->peer_identity),
             other_peer_str);
  GNUNET_free (other_peer_str);
  lp2c->ohh =
    GNUNET_TRANSPORT_offer_hello (lp2c->tcc.cfg,
                                  occ->hello,
                                  &occ_hello_sent_cb,
                                  occ);
  if (NULL == lp2c->ohh)
  {
    GNUNET_break (0);
    occ->send_hello_task =
      GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
                                      (GNUNET_TIME_UNIT_MILLISECONDS,
                                      100
                                      + GNUNET_CRYPTO_random_u32
                                        (GNUNET_CRYPTO_QUALITY_WEAK, 500)),
                                    &send_hello, occ);
  }
}


/**
 * Callback from cache with needed handles set
 *
 * @param cls the closure passed to GST_cache_get_handle_transport()
 * @param ch the handle to CORE. Can be NULL if it is not requested
 * @param th the handle to TRANSPORT. Can be NULL if it is not requested
 * @param ac the handle to ATS. Can be NULL if it is not requested
 * @param ignore_ peer identity which is ignored in this callback
 * @param cfg configuration of the peer
 */
static void
p2_transport_connect_cache_callback (void *cls,
                                     struct GNUNET_CORE_Handle *ch,
                                     struct GNUNET_TRANSPORT_CoreHandle *th,
                                     struct GNUNET_ATS_ConnectivityHandle *ac,
                                     const struct GNUNET_PeerIdentity *ignore_,
                                     const struct
                                     GNUNET_CONFIGURATION_Handle *cfg)
{
  struct OverlayConnectContext *occ = cls;

  GNUNET_assert (OCC_TYPE_LOCAL == occ->type);
  if (NULL == th)
  {
    GNUNET_asprintf (&occ->emsg,
                     "0x%llx: Cannot connect to TRANSPORT of %s",
                     (unsigned long long) occ->op_id,
                     GNUNET_i2s (&occ->other_peer_identity));
    GNUNET_SCHEDULER_cancel (occ->timeout_task);
    occ->timeout_task =
      GNUNET_SCHEDULER_add_now (&timeout_overlay_connect, occ);
    return;
  }
  occ->p2ctx.local.tcc.th_ = th;
  occ->p2ctx.local.tcc.cfg = cfg;
  GNUNET_asprintf (&occ->emsg,
                   "0x%llx: Timeout while offering HELLO to %s",
                   (unsigned long long) occ->op_id,
                   GNUNET_i2s (&occ->other_peer_identity));
  occ->send_hello_task = GNUNET_SCHEDULER_add_now (&send_hello, occ);
}


/**
 * Connects to the transport of the other peer if it is a local peer and
 * schedules the send hello task
 *
 * @param occ the overlay connect context
 */
static void
p2_transport_connect (struct OverlayConnectContext *occ)
{
  struct Peer *peer2;

  /* HUH? Why to *obtain* HELLO? Seems we use this to *SEND* the
     HELLO! */
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Connecting to transport of peer %s to obtain HELLO\n",
              GNUNET_i2s (&occ->other_peer_identity));
  GNUNET_assert (NULL == occ->emsg);
  GNUNET_assert (NULL != occ->hello);
  GNUNET_assert (NULL == occ->ghh);
  GNUNET_assert (NULL == occ->p1th_);
  GNUNET_assert (NULL == occ->cgh_p1th);
  if (OCC_TYPE_LOCAL == occ->type)
  {
    GNUNET_assert (NULL != (peer2 = GST_peer_list[occ->other_peer_id]));
    occ->p2ctx.local.tcc.cgh_p2_th =
      GST_connection_pool_get_handle (occ->other_peer_id,
                                      peer2->details.local.cfg,
                                      GST_CONNECTIONPOOL_SERVICE_TRANSPORT,
                                      &p2_transport_connect_cache_callback,
                                      occ, NULL, NULL, NULL);
    return;
  }
  GNUNET_asprintf (&occ->emsg,
                   "0x%llx: Timeout while offering HELLO to %s",
                   (unsigned long long) occ->op_id,
                   GNUNET_i2s (&occ->other_peer_identity));
  occ->send_hello_task = GNUNET_SCHEDULER_add_now (&send_hello, occ);
}


/**
 * Test for checking whether HELLO message is empty
 *
 * @param cls empty flag to set
 * @param address the HELLO
 * @param expiration expiration of the HELLO
 * @return #GNUNET_OK
 */
static int
test_address (void *cls,
              const struct GNUNET_HELLO_Address *address,
              struct GNUNET_TIME_Absolute expiration)
{
  int *empty = cls;

  *empty = GNUNET_NO;
  return GNUNET_OK;
}


/**
 * Function called whenever there is an update to the HELLO of peers in the
 * OverlayConnectClosure. If we have a valid HELLO, we connect to the peer 2's
 * transport and offer peer 1's HELLO and ask peer 2 to connect to peer 1
 *
 * @param cls closure
 * @param hello our updated HELLO
 */
static void
hello_update_cb (void *cls,
                 const struct GNUNET_MessageHeader *hello)
{
  struct OverlayConnectContext *occ = cls;
  int empty;
  uint16_t msize;

  msize = ntohs (hello->size);
  empty = GNUNET_YES;
  (void) GNUNET_HELLO_iterate_addresses ((const struct GNUNET_HELLO_Message *)
                                         hello, GNUNET_NO,
                                         &test_address,
                                         &empty);
  if (GNUNET_YES == empty)
  {
    LOG_DEBUG ("0x%llx: HELLO of %s is empty\n",
               (unsigned long long) occ->op_id,
               GNUNET_i2s (&occ->peer_identity));
    return;
  }
  LOG_DEBUG ("0x%llx: Received HELLO of %s\n",
             (unsigned long long) occ->op_id,
             GNUNET_i2s (&occ->peer_identity));
  occ->hello = GNUNET_malloc (msize);
  GST_cache_add_hello (occ->peer->id, hello);
  GNUNET_memcpy (occ->hello, hello, msize);
  GNUNET_TRANSPORT_hello_get_cancel (occ->ghh);
  occ->ghh = NULL;
  GST_connection_pool_get_handle_done (occ->cgh_p1th);
  occ->cgh_p1th = NULL;
  occ->p1th_ = NULL;
  GNUNET_free (occ->emsg);
  occ->emsg = NULL;
  p2_transport_connect (occ);
}


/**
 * Callback from cache with needed handles set
 *
 * @param cls the closure passed to GST_cache_get_handle_transport()
 * @param ch the handle to CORE. Can be NULL if it is not requested
 * @param th the handle to TRANSPORT. Can be NULL if it is not requested
 * @param ac the handle to ATS. Can be NULL if it is not requested
 * @param ignore_ peer identity which is ignored in this callback
 */
static void
p1_transport_connect_cache_callback (void *cls,
                                     struct GNUNET_CORE_Handle *ch,
                                     struct GNUNET_TRANSPORT_CoreHandle *th,
                                     struct GNUNET_ATS_ConnectivityHandle *ac,
                                     const struct GNUNET_PeerIdentity *ignore_,
                                     const struct
                                     GNUNET_CONFIGURATION_Handle *cfg)
{
  struct OverlayConnectContext *occ = cls;

  GNUNET_free (occ->emsg);
  occ->emsg = NULL;
  if (NULL == th)
  {
    GNUNET_asprintf (&occ->emsg,
                     "0x%llx: Cannot connect to TRANSPORT of %s",
                     (unsigned long long) occ->op_id,
                     GNUNET_i2s (&occ->peer_identity));
    GNUNET_SCHEDULER_cancel (occ->timeout_task);
    occ->timeout_task =
      GNUNET_SCHEDULER_add_now (&timeout_overlay_connect, occ);
    return;
  }
  GNUNET_assert (NULL == occ->p1th_);
  GNUNET_assert (NULL != occ->cgh_p1th);
  occ->p1th_ = th;
  GNUNET_asprintf (&occ->emsg,
                   "0x%llx: Timeout while acquiring HELLO of peer %s",
                   (unsigned long long) occ->op_id,
                   GNUNET_i2s (&occ->peer_identity));
  occ->ghh = GNUNET_TRANSPORT_hello_get (cfg,
                                         GNUNET_TRANSPORT_AC_ANY,
                                         &hello_update_cb,
                                         occ);
}


/**
 * Callback from cache with needed CORE handle set
 *
 * @param cls the closure passed to GST_cache_get_handle_transport()
 * @param ch the handle to CORE. Can be NULL if it is not requested
 * @param th the handle to TRANSPORT. Can be NULL if it is not requested
 * @param ac the handle to ATS. Can be NULL if it is not requested
 * @param my_identity the identity of our peer
 */
static void
occ_cache_get_handle_core_cb (void *cls,
                              struct GNUNET_CORE_Handle *ch,
                              struct GNUNET_TRANSPORT_CoreHandle *th,
                              struct GNUNET_ATS_ConnectivityHandle *ac,
                              const struct GNUNET_PeerIdentity *my_identity,
                              const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  struct OverlayConnectContext *occ = cls;
  const struct GNUNET_MessageHeader *hello;

  GNUNET_assert (NULL != occ->timeout_task);
  GNUNET_free (occ->emsg);
  if ((NULL == ch) || (NULL == my_identity))
  {
    GNUNET_asprintf (&occ->emsg,
                     "0x%llx: Failed to connect to CORE of peer with "
                     "id: %u",
                     (unsigned long long) occ->op_id,
                     occ->peer->id);
    GNUNET_SCHEDULER_cancel (occ->timeout_task);
    occ->timeout_task =
      GNUNET_SCHEDULER_add_now (&timeout_overlay_connect, occ);
    return;
  }
  occ->emsg = NULL;
  occ->peer_identity = *my_identity;
  if (NULL !=
      GNUNET_CORE_get_mq (ch,
                          &occ->other_peer_identity))
  {
    LOG_DEBUG ("0x%llx: Target peer %s already connected\n",
               (unsigned long long) occ->op_id,
               GNUNET_i2s (&occ->other_peer_identity));
    LOG_DEBUG ("0x%llx: Target peer %s connected\n",
               (unsigned long long) occ->op_id,
               GNUNET_i2s (&occ->peer_identity));
    GNUNET_SCHEDULER_cancel (occ->timeout_task);
    occ->timeout_task = NULL;
    send_overlay_connect_success_msg (occ);
    occ->cleanup_task = GNUNET_SCHEDULER_add_now (&do_cleanup_occ, occ);
    return;
  }
  LOG_DEBUG ("0x%llx: Acquiring HELLO of peer %s\n",
             (unsigned long long) occ->op_id,
             GNUNET_i2s (&occ->peer_identity));
  /* Lookup for HELLO in hello cache */
  if (NULL != (hello = GST_cache_lookup_hello (occ->peer->id)))
  {
    LOG_DEBUG ("0x%llx: HELLO of peer %s found in cache\n",
               (unsigned long long) occ->op_id,
               GNUNET_i2s (&occ->peer_identity));
    occ->hello = GNUNET_copy_message (hello);
    p2_transport_connect (occ);
    return;
  }
  GNUNET_asprintf (&occ->emsg,
                   "0x%llx: Timeout while acquiring TRANSPORT of %s from cache",
                   (unsigned long long) occ->op_id,
                   GNUNET_i2s (&occ->peer_identity));
  occ->cgh_p1th =
    GST_connection_pool_get_handle (occ->peer->id,
                                    occ->peer->details.local.cfg,
                                    GST_CONNECTIONPOOL_SERVICE_TRANSPORT,
                                    p1_transport_connect_cache_callback,
                                    occ,
                                    NULL, NULL, NULL);
}


/**
 * Callback to be called when forwarded get peer config operation as part of
 * overlay connect is successful. Connection to Peer 1's core is made and is
 * checked for new connection from peer 2
 *
 * @param cls ForwardedOperationContext
 * @param msg the peer create success message
 */
static void
overlay_connect_get_config (void *cls,
                            const struct GNUNET_MessageHeader *msg)
{
  struct OverlayConnectContext *occ = cls;
  struct RemotePeer2Context *rp2c;
  const struct GNUNET_TESTBED_PeerConfigurationInformationMessage *cmsg;

  GNUNET_assert (OCC_TYPE_LOCAL != occ->type);
  rp2c = &occ->p2ctx.remote;
  rp2c->opc = NULL;
  GNUNET_assert (NULL != occ->timeout_task);
  if (GNUNET_MESSAGE_TYPE_TESTBED_PEER_INFORMATION != ntohs (msg->type))
  {
    GNUNET_SCHEDULER_cancel (occ->timeout_task);
    occ->timeout_task =
      GNUNET_SCHEDULER_add_now (&timeout_overlay_connect, occ);
  }
  cmsg =
    (const struct GNUNET_TESTBED_PeerConfigurationInformationMessage *) msg;
  occ->other_peer_identity = cmsg->peer_identity;
  GNUNET_free (occ->emsg);
  GNUNET_asprintf (&occ->emsg,
                   "0x%llx: Timeout while connecting to CORE of peer with "
                   "id: %u",
                   (unsigned long long) occ->op_id,
                   occ->peer->id);
  occ->cgh_ch =
    GST_connection_pool_get_handle (occ->peer->id,
                                    occ->peer->details.local.cfg,
                                    GST_CONNECTIONPOOL_SERVICE_CORE,
                                    &occ_cache_get_handle_core_cb,
                                    occ,
                                    &occ->other_peer_identity,
                                    &overlay_connect_notify,
                                    occ);
}


/**
 * Callback which will be called after a host registration succeeded or failed
 *
 * @param cls the RegisteredHostContext
 * @param emsg the error message; NULL if host registration is successful
 */
static void
host_registration_comp (void *cls, const char *emsg)
{
  struct RegisteredHostContext *rhc = cls;

  rhc->state = RHC_DONE;
  GST_process_next_focc (rhc);
}


/**
 * Iterator to match a registered host context
 *
 * @param cls pointer 2 pointer of RegisteredHostContext
 * @param key current key code
 * @param value value in the hash map
 * @return #GNUNET_YES if we should continue to
 *         iterate,
 *         #GNUNET_NO if not.
 */
static int
reghost_match_iterator (void *cls,
                        const struct GNUNET_HashCode *key,
                        void *value)
{
  struct RegisteredHostContext **rh = cls;
  struct RegisteredHostContext *rh_val = value;

  if ((rh_val->host == (*rh)->host) && (rh_val->reg_host == (*rh)->reg_host))
  {
    GNUNET_free (*rh);
    *rh = rh_val;
    return GNUNET_NO;
  }
  return GNUNET_YES;
}


/**
 * Function to generate the hashcode corresponding to a RegisteredHostContext
 *
 * @param reg_host the host which is being registered in RegisteredHostContext
 * @param host the host of the controller which has to connect to the above rhost
 * @return the hashcode
 */
static struct GNUNET_HashCode
hash_hosts (struct GNUNET_TESTBED_Host *reg_host,
            struct GNUNET_TESTBED_Host *host)
{
  struct GNUNET_HashCode hash;
  uint32_t host_ids[2];

  host_ids[0] = GNUNET_TESTBED_host_get_id_ (reg_host);
  host_ids[1] = GNUNET_TESTBED_host_get_id_ (host);
  GNUNET_CRYPTO_hash (host_ids, sizeof(host_ids), &hash);
  return hash;
}


/**
 * Checks if the given host is registered at the given slave.
 *
 * @param slave the slave where registration has to be checked.  The check is
 *          actually done through a locally maintained hashmap.  No
 *          communication with the slave is involved.
 * @param host the host to register
 * @return If the given host is not registered already or the registration is
 *           pending, it returns the registration context.  Any overlay connects
 *           to be forwarded should be queued in the context so that they can be
 *           executed when the registration is completed.  If the given host is
 *           already registered, NULL is returned.
 */
static struct RegisteredHostContext *
register_host (struct Slave *slave,
               struct GNUNET_TESTBED_Host *host)
{
  struct GNUNET_HashCode hash;
  struct RegisteredHostContext *rhc;

  rhc = GNUNET_new (struct RegisteredHostContext);
  rhc->reg_host = host;
  rhc->host = GST_host_list[slave->host_id];
  GNUNET_assert (NULL != rhc->reg_host);
  GNUNET_assert (NULL != rhc->host);
  rhc->state = RHC_INIT;
  hash = hash_hosts (rhc->reg_host, rhc->host);
  if ((GNUNET_NO ==
       GNUNET_CONTAINER_multihashmap_contains (slave->reghost_map,
                                               &hash)) ||
      (GNUNET_SYSERR !=
       GNUNET_CONTAINER_multihashmap_get_multiple (slave->reghost_map,
                                                   &hash,
                                                   reghost_match_iterator,
                                                   &rhc)))
  {
    /* create and add a new registered host context */
    /* add the focc to its queue */
    GNUNET_CONTAINER_multihashmap_put (slave->reghost_map,
                                       &hash,
                                       rhc,
                                       GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
    GST_queue_host_registration (slave,
                                 host_registration_comp,
                                 rhc,
                                 rhc->reg_host);
  }
  else
  {
    /* rhc is now set to the existing one from the hash map by
     * reghost_match_iterator() */
    /* if queue is empty then ignore creating focc and proceed with normal
     * forwarding */
    if (RHC_DONE == rhc->state)
      return NULL;
  }
  return rhc;
}


/**
 * Forwards the overlay connect request to a slave controller.  Before
 * forwarding, any hosts which are needed to be known by the slave controller to
 * execute the overlay connect request are registered at slave.
 *
 * @param msg the overlay connect request message to be forwarded
 * @param client the client to which the status of the forwarded request has to
 *          be notified
 */
static void
forward_overlay_connect (const struct GNUNET_TESTBED_OverlayConnectMessage *msg,
                         struct GNUNET_SERVICE_Client *client)
{
  struct ForwardedOperationContext *fopc;
  struct Route *route_to_peer2_host;
  struct Route *route_to_peer1_host;
  struct Peer *peer;
  struct RegisteredHostContext *rhc;
  struct ForwardedOverlayConnectContext *focc;
  uint64_t op_id;
  uint32_t peer2_host_id;
  uint32_t p1;
  uint32_t p2;

  p1 = ntohl (msg->peer1);
  p2 = ntohl (msg->peer2);
  op_id = GNUNET_ntohll (msg->operation_id);
  peer2_host_id = ntohl (msg->peer2_host_id);
  GNUNET_assert (VALID_PEER_ID (p1));
  GNUNET_assert (VALID_HOST_ID (peer2_host_id));
  peer = GST_peer_list[p1];
  GNUNET_assert (GNUNET_YES == peer->is_remote);
  LOG_DEBUG ("0x%llx: Forwarding overlay connect\n",
             (unsigned long long) op_id);
  route_to_peer2_host = GST_find_dest_route (peer2_host_id);
  route_to_peer1_host = GST_find_dest_route
                          (peer->details.remote.remote_host_id);
  GNUNET_assert (NULL != route_to_peer1_host);
  if ((NULL != route_to_peer2_host) &&
      (route_to_peer1_host->dest == route_to_peer2_host->dest))
    goto forward;
  /* Peer2 is either with us OR peer1 and peer2 can be reached through
     different subtrees OR peer2 is on a subtree unknown to us */
  if (NULL != (rhc = register_host (peer->details.remote.slave,
                                    GST_host_list[peer2_host_id])))
  {
    LOG_DEBUG ("Queueing forwarding FOCC for connecting peers %u and %u\n", p1,
               p2);
    focc = GNUNET_new (struct ForwardedOverlayConnectContext);
    focc->rhc = rhc;
    focc->peer1 = p1;
    focc->peer2 = p2;
    focc->peer2_host_id = peer2_host_id;
    focc->orig_msg = GNUNET_copy_message (&msg->header);
    focc->operation_id = op_id;
    focc->client = client;
    GNUNET_CONTAINER_DLL_insert_tail (rhc->focc_dll_head,
                                      rhc->focc_dll_tail,
                                      focc);
    return;
  }

forward:
  LOG_DEBUG ("Forwarding without FOCC for connecting peers %u and %u\n", p1,
             p2);
  fopc = GNUNET_new (struct ForwardedOperationContext);
  fopc->client = client;
  fopc->operation_id = op_id;
  fopc->type = OP_OVERLAY_CONNECT;
  fopc->opc =
    GNUNET_TESTBED_forward_operation_msg_ (peer->details.remote.
                                           slave->controller, op_id,
                                           &msg->header,
                                           &GST_forwarded_operation_reply_relay,
                                           fopc);
  fopc->timeout_task =
    GNUNET_SCHEDULER_add_delayed (GST_timeout,
                                  &GST_forwarded_operation_timeout,
                                  fopc);
  GNUNET_CONTAINER_DLL_insert_tail (fopcq_head,
                                    fopcq_tail,
                                    fopc);
}


/**
 * Callback called when a connection to the controller of peer2 has been
 * established
 *
 * @param cls the overlay connect contexts
 * @param c handle to the controller connection
 */
static void
p2_controller_connect_cb (void *cls,
                          struct GNUNET_TESTBED_Controller *c)
{
  struct OverlayConnectContext *occ = cls;
  struct RemotePeer2Context *rp2c;
  struct GNUNET_TESTBED_PeerGetConfigurationMessage cmsg;

  GNUNET_assert (OCC_TYPE_LOCAL != occ->type);
  rp2c = &occ->p2ctx.remote;
  rp2c->ncn = NULL;
  rp2c->p2c = c;
  cmsg.header.size =
    htons (sizeof(struct GNUNET_TESTBED_PeerGetConfigurationMessage));
  cmsg.header.type =
    htons (GNUNET_MESSAGE_TYPE_TESTBED_GET_PEER_INFORMATION);
  cmsg.peer_id = htonl (occ->other_peer_id);
  cmsg.operation_id = GNUNET_htonll (occ->op_id);
  rp2c->opc =
    GNUNET_TESTBED_forward_operation_msg_ (rp2c->p2c,
                                           occ->op_id,
                                           &cmsg.header,
                                           &overlay_connect_get_config,
                                           occ);
  GNUNET_free (occ->emsg);
  GNUNET_asprintf (&occ->emsg,
                   "0x%llx: Timeout while getting peer identity of peer "
                   "with id: %u",
                   (unsigned long long) occ->op_id,
                   occ->other_peer_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)
{
  struct GNUNET_SERVICE_Client *client = cls;
  struct Peer *peer;
  struct Peer *peer2;
  struct OverlayConnectContext *occ;
  struct Neighbour *p2n;
  uint64_t operation_id;
  uint32_t p1;
  uint32_t p2;
  uint32_t peer2_host_id;

  p1 = ntohl (msg->peer1);
  p2 = ntohl (msg->peer2);
  if (p1 == p2)
  {
    GNUNET_break (0);
    GNUNET_SERVICE_client_drop (client);
    return;
  }
  if (! VALID_PEER_ID (p1))
  {
    GNUNET_break (0);
    GNUNET_SERVICE_client_drop (client);
    return;
  }
  peer = GST_peer_list[p1];
  operation_id = GNUNET_ntohll (msg->operation_id);
  LOG_DEBUG
    ("Received overlay connect for peers %u and %u with op id: 0x%llx\n",
    p1,
    p2,
    (unsigned long long) operation_id);
  peer2_host_id = ntohl (msg->peer2_host_id);
  if (GNUNET_YES == peer->is_remote)
  {
    if (! VALID_HOST_ID (peer2_host_id))
    {
      GNUNET_break (0);
      GNUNET_SERVICE_client_drop (client);
      return;
    }
    forward_overlay_connect (msg, client);
    GNUNET_SERVICE_client_continue (client);
    return;
  }
  p2n = NULL;
  occ = GNUNET_new (struct OverlayConnectContext);
  occ->type = OCC_TYPE_LOCAL;
  if (! VALID_PEER_ID (p2))         /* May be peer2 is on a another controller */
  {
    if (NULL == (p2n = GST_get_neighbour (peer2_host_id)))
    {
      if (! VALID_HOST_ID (peer2_host_id))
      {
        GNUNET_break (0);
        LOG (GNUNET_ERROR_TYPE_WARNING,
             "0x%llx: Peer %u's host not in our neighbours list\n",
             (unsigned long long) operation_id, p2);
        GNUNET_SERVICE_client_drop (client);
        GNUNET_free (occ);
        return;
      }
      p2n = GST_create_neighbour (GST_host_list[peer2_host_id]);
    }
    occ->type = OCC_TYPE_REMOTE_LATERAL;
    occ->p2ctx.remote.p2n = p2n;
  }
  else if (GNUNET_YES == GST_peer_list[p2]->is_remote)
  {
    occ->type = OCC_TYPE_REMOTE_SLAVE;
    occ->p2ctx.remote.p2c = GST_peer_list[p2]->details.remote.slave->controller;
  }
  GNUNET_CONTAINER_DLL_insert_tail (occq_head,
                                    occq_tail,
                                    occ);
  occ->client = client;
  occ->other_peer_id = p2;
  GST_peer_list[p1]->reference_cnt++;
  occ->peer = GST_peer_list[p1];
  occ->op_id = operation_id;
  GNUNET_assert (NULL == occ->timeout_task);
  occ->timeout_task =
    GNUNET_SCHEDULER_add_delayed (GST_timeout,
                                  &timeout_overlay_connect,
                                  occ);
  switch (occ->type)
  {
  case OCC_TYPE_REMOTE_LATERAL:
    GNUNET_asprintf (&occ->emsg,
                     "0x%llx: Timeout while acquiring connection to peer %u's "
                     "host: %u\n",
                     (unsigned long long) occ->op_id,
                     occ->other_peer_id,
                     peer2_host_id);
    occ->p2ctx.remote.ncn
      = GST_neighbour_get_connection (p2n,
                                      &p2_controller_connect_cb,
                                      occ);
    break;
  case OCC_TYPE_REMOTE_SLAVE:
    p2_controller_connect_cb (occ,
                              occ->p2ctx.remote.p2c);
    break;
  case OCC_TYPE_LOCAL:
    peer2 = GST_peer_list[occ->other_peer_id];
    peer2->reference_cnt++;
    GNUNET_TESTING_peer_get_identity (peer2->details.local.peer,
                                      &occ->other_peer_identity);
    GNUNET_asprintf (&occ->emsg,
                     "0x%llx: Timeout while connecting to CORE of peer with "
                     "id: %u",
                     (unsigned long long) occ->op_id,
                     occ->peer->id);
    LOG_DEBUG ("Peer %u has PID %s\n",
               occ->other_peer_id,
               GNUNET_i2s (&occ->other_peer_identity));
    {
      struct GNUNET_PeerIdentity lpid;

      GNUNET_TESTING_peer_get_identity (peer->details.local.peer,
                                        &lpid);
      LOG_DEBUG ("Peer %u has PID %s\n",
                 p1,
                 GNUNET_i2s (&lpid));
    }
    occ->cgh_ch =
      GST_connection_pool_get_handle (occ->peer->id,
                                      occ->peer->details.local.cfg,
                                      GST_CONNECTIONPOOL_SERVICE_CORE,
                                      &occ_cache_get_handle_core_cb, occ,
                                      &occ->other_peer_identity,
                                      &overlay_connect_notify, occ);
    break;
  }
  GNUNET_SERVICE_client_continue (client);
}


/**
 * Function to cleanup RemoteOverlayConnectCtx and any associated tasks
 * with it
 *
 * @param rocc the RemoteOverlayConnectCtx
 */
static void
cleanup_rocc (struct RemoteOverlayConnectCtx *rocc)
{
  LOG_DEBUG ("0x%llx: Cleaning up rocc\n",
             (unsigned long long) rocc->op_id);
  if (NULL != rocc->attempt_connect_task_id)
    GNUNET_SCHEDULER_cancel (rocc->attempt_connect_task_id);
  if (NULL != rocc->timeout_rocc_task_id)
    GNUNET_SCHEDULER_cancel (rocc->timeout_rocc_task_id);
  if (NULL != rocc->ohh)
    GNUNET_TRANSPORT_offer_hello_cancel (rocc->ohh);
  if (NULL != rocc->tcc.csh)
    GNUNET_ATS_connectivity_suggest_cancel (rocc->tcc.csh);
  GST_connection_pool_get_handle_done (rocc->tcc.cgh_p2_th);
  GST_connection_pool_get_handle_done (rocc->tcc.cgh_p2_ats);
  GNUNET_assert (rocc->peer->reference_cnt > 0);
  rocc->peer->reference_cnt--;
  if ((GNUNET_YES == rocc->peer->destroy_flag) &&
      (0 == rocc->peer->reference_cnt))
    GST_destroy_peer (rocc->peer);
  GNUNET_free (rocc->hello);
  GNUNET_CONTAINER_DLL_remove (roccq_head,
                               roccq_tail,
                               rocc);
  GNUNET_free (rocc);
}


/**
 * Task to timeout rocc and cleanit up
 *
 * @param cls the RemoteOverlayConnectCtx
 */
static void
timeout_rocc_task (void *cls)
{
  struct RemoteOverlayConnectCtx *rocc = cls;

  GNUNET_assert (rocc->timeout_rocc_task_id != NULL);
  rocc->timeout_rocc_task_id = NULL;
  LOG_DEBUG ("0x%llx: rocc timed out\n",
             (unsigned long long) rocc->op_id);
  cleanup_rocc (rocc);
}


/**
 * Function called to notify transport users that another
 * peer connected to us.
 *
 * @param cls the RemoteOverlayConnectContext
 * @param new_peer the peer that connected
 */
static void
cache_transport_peer_connect_notify (void *cls,
                                     const struct GNUNET_PeerIdentity *new_peer)
{
  struct RemoteOverlayConnectCtx *rocc = cls;

  LOG_DEBUG ("0x%llx: Request Overlay connect notify\n",
             (unsigned long long) rocc->op_id);
  GNUNET_assert (0 ==
                 memcmp (new_peer, &rocc->a_id,
                         sizeof(struct GNUNET_PeerIdentity)));
  LOG_DEBUG ("0x%llx: Peer %s connected\n",
             (unsigned long long) rocc->op_id,
             GNUNET_i2s (&rocc->a_id));
  cleanup_rocc (rocc);
}


/**
 * Task to offer the HELLO message to the peer and ask it to connect to the peer
 * whose identity is in RemoteOverlayConnectCtx
 *
 * @param cls the RemoteOverlayConnectCtx
 */
static void
attempt_connect_task (void *cls);


/**
 * Task that is run when hello has been sent If tc->reason =
 * #GNUNET_SCHEDULER_REASON_TIMEOUT then sending HELLO failed; if
 * #GNUNET_SCHEDULER_REASON_READ_READY is succeeded
 *
 * @param cls the overlay connect context
 */
static void
rocc_hello_sent_cb (void *cls)
{
  struct RemoteOverlayConnectCtx *rocc = cls;

  rocc->ohh = NULL;
  GNUNET_assert (NULL == rocc->attempt_connect_task_id);
  LOG_DEBUG ("0x%llx: HELLO of peer %s delivered to local peer with id: %u\n",
             (unsigned long long) rocc->op_id,
             GNUNET_i2s (&rocc->a_id),
             rocc->peer->id);
  rocc->tcc.cgh_p2_ats =
    GST_connection_pool_get_handle (rocc->peer->id,
                                    rocc->peer->details.local.cfg,
                                    GST_CONNECTIONPOOL_SERVICE_ATS_CONNECTIVITY,
                                    &occ_cache_get_handle_ats_rocc_cb,
                                    rocc, NULL, NULL, NULL);
}


/**
 * Task to offer the HELLO message to the peer and ask it to connect to the peer
 * whose identity is in RemoteOverlayConnectCtx
 *
 * @param cls the RemoteOverlayConnectCtx
 */
static void
attempt_connect_task (void *cls)
{
  struct RemoteOverlayConnectCtx *rocc = cls;

  GNUNET_assert (NULL != rocc->attempt_connect_task_id);
  rocc->attempt_connect_task_id = NULL;
  LOG_DEBUG ("0x%llx: Offering HELLO of peer %s to remote peer with id: %u\n",
             (unsigned long long) rocc->op_id,
             GNUNET_i2s (&rocc->a_id),
             rocc->peer->id);
  rocc->ohh =
    GNUNET_TRANSPORT_offer_hello (rocc->tcc.cfg,
                                  rocc->hello,
                                  &rocc_hello_sent_cb,
                                  rocc);
  if (NULL == rocc->ohh)
    rocc->attempt_connect_task_id =
      GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
                                      (GNUNET_TIME_UNIT_MILLISECONDS,
                                      100
                                      + GNUNET_CRYPTO_random_u32
                                        (GNUNET_CRYPTO_QUALITY_WEAK, 500)),
                                    &attempt_connect_task, rocc);
}


/**
 * Callback from cache with needed handles set
 *
 * @param cls the closure passed to GST_cache_get_handle_transport()
 * @param ch the handle to CORE. Can be NULL if it is not requested
 * @param th the handle to TRANSPORT. Can be NULL if it is not requested
 * @param ac the handle to ATS. Can be NULL if it is not requested
 * @param ignore_ peer identity which is ignored in this callback
 */
static void
rocc_cache_get_handle_transport_cb (void *cls,
                                    struct GNUNET_CORE_Handle *ch,
                                    struct GNUNET_TRANSPORT_CoreHandle *th,
                                    struct GNUNET_ATS_ConnectivityHandle *ac,
                                    const struct GNUNET_PeerIdentity *ignore_,
                                    const struct
                                    GNUNET_CONFIGURATION_Handle *cfg)
{
  struct RemoteOverlayConnectCtx *rocc = cls;

  if (NULL == th)
  {
    rocc->timeout_rocc_task_id =
      GNUNET_SCHEDULER_add_now (&timeout_rocc_task, rocc);
    return;
  }
  rocc->tcc.th_ = th;
  rocc->tcc.cfg = cfg;
  if (NULL !=
      GNUNET_TRANSPORT_core_get_mq (rocc->tcc.th_,
                                    &rocc->a_id))
  {
    LOG_DEBUG ("0x%llx: Target peer %s already connected to local peer: %u\n",
               (unsigned long long) rocc->op_id,
               GNUNET_i2s (&rocc->a_id),
               rocc->peer->id);
    cleanup_rocc (rocc);
    return;
  }
  rocc->attempt_connect_task_id =
    GNUNET_SCHEDULER_add_now (&attempt_connect_task, rocc);
}


/**
 * 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)
{
  uint32_t peer_id;
  uint16_t msize;
  uint16_t hsize;

  msize = ntohs (msg->header.size);
  if (GNUNET_MESSAGE_TYPE_HELLO != ntohs (msg->hello->type))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  hsize = ntohs (msg->hello->size);
  if ((sizeof(struct GNUNET_TESTBED_RemoteOverlayConnectMessage) + hsize) !=
      msize)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  peer_id = ntohl (msg->peer);
  if ((peer_id >= GST_peer_list_size) ||
      (NULL == GST_peer_list[peer_id]))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * 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)
{
  struct GNUNET_SERVICE_Client *client = cls;
  struct RemoteOverlayConnectCtx *rocc;
  struct Peer *peer;
  struct GNUNET_PeerIdentity pid;
  static char pid_str[16];
  uint32_t peer_id;
  uint16_t hsize;

  hsize = ntohs (msg->hello->size);
  peer_id = ntohl (msg->peer);
  peer = GST_peer_list[peer_id];
  if (GNUNET_YES == peer->is_remote)
  {
    struct GNUNET_MessageHeader *msg2;

    msg2 = GNUNET_copy_message (&msg->header);
    GNUNET_TESTBED_queue_message_ (peer->details.remote.slave->controller,
                                   msg2);
    GNUNET_SERVICE_client_continue (client);
    return;
  }
  rocc = GNUNET_new (struct RemoteOverlayConnectCtx);
  rocc->op_id = GNUNET_ntohll (msg->operation_id);
  GNUNET_CONTAINER_DLL_insert_tail (roccq_head,
                                    roccq_tail,
                                    rocc);
  rocc->a_id = msg->peer_identity;
  GNUNET_TESTING_peer_get_identity (peer->details.local.peer,
                                    &pid);
  (void) GNUNET_strlcpy (pid_str,
                         GNUNET_i2s (&pid),
                         sizeof(pid_str));
  LOG_DEBUG (
    "0x%llx: Remote overlay connect %s to peer %s with hello size: %u\n",
    (unsigned long long) rocc->op_id,
    pid_str,
    GNUNET_i2s (&rocc->a_id),
    hsize);
  rocc->peer = peer;
  rocc->peer->reference_cnt++;
  rocc->hello = GNUNET_malloc (hsize);
  GNUNET_memcpy (rocc->hello,
                 msg->hello,
                 hsize);
  rocc->tcc.cgh_p2_th =
    GST_connection_pool_get_handle (peer_id,
                                    rocc->peer->details.local.cfg,
                                    GST_CONNECTIONPOOL_SERVICE_TRANSPORT,
                                    &rocc_cache_get_handle_transport_cb,
                                    rocc,
                                    &rocc->a_id,
                                    &cache_transport_peer_connect_notify,
                                    rocc);
  rocc->timeout_rocc_task_id =
    GNUNET_SCHEDULER_add_delayed (GST_timeout,
                                  &timeout_rocc_task,
                                  rocc);
  GNUNET_SERVICE_client_continue (client);
}


/**
 * Clears all pending overlay connect contexts in queue
 */
void
GST_free_occq ()
{
  struct OverlayConnectContext *occ;

  while (NULL != (occ = occq_head))
    cleanup_occ (occ);
}


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

  while (NULL != (rocc = roccq_head))
    cleanup_rocc (rocc);
}
