/*
This file is part of GNUnet.
Copyright (C) 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 .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/**
* @file transport-testing-main.c
* @brief convenience main function for tests
* @author Christian Grothoff
*/
#include "transport-testing.h"
/**
* Closure for #connect_cb.
*/
struct GNUNET_TRANSPORT_TESTING_ConnectRequestList
{
/**
* Stored in a DLL.
*/
struct GNUNET_TRANSPORT_TESTING_ConnectRequestList *next;
/**
* Stored in a DLL.
*/
struct GNUNET_TRANSPORT_TESTING_ConnectRequestList *prev;
/**
* Overall context we are in.
*/
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc;
/**
* Connect request this is about.
*/
struct GNUNET_TRANSPORT_TESTING_ConnectRequest *cr;
/**
* Peer being connected.
*/
struct GNUNET_TRANSPORT_TESTING_PeerContext *p1;
/**
* Peer being connected.
*/
struct GNUNET_TRANSPORT_TESTING_PeerContext *p2;
};
/**
* Shutdown function for the test. Stops all peers.
*
* @param cls our `struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *`
*/
static void
do_shutdown (void *cls)
{
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = cls;
struct GNUNET_TRANSPORT_TESTING_ConnectRequestList *crl;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Testcase shutting down\n");
if (NULL != ccc->shutdown_task)
ccc->shutdown_task (ccc->shutdown_task_cls);
if (NULL != ccc->timeout_task)
{
GNUNET_SCHEDULER_cancel (ccc->timeout_task);
ccc->timeout_task = NULL;
}
if (NULL != ccc->connect_task)
{
GNUNET_SCHEDULER_cancel (ccc->connect_task);
ccc->connect_task = NULL;
}
while (NULL != (crl = ccc->crl_head))
{
GNUNET_CONTAINER_DLL_remove (ccc->crl_head,
ccc->crl_tail,
crl);
GNUNET_TRANSPORT_TESTING_connect_peers_cancel (crl->cr);
GNUNET_free (crl);
}
for (unsigned int i=0;inum_peers;i++)
{
if (NULL != ccc->p[i])
{
GNUNET_TRANSPORT_TESTING_stop_peer (ccc->p[i]);
ccc->p[i] = NULL;
}
}
}
/**
* Testcase hit timeout, shut it down with error.
*
* @param cls our `struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *`
*/
static void
do_timeout (void *cls)
{
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = cls;
ccc->timeout_task = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Testcase timed out\n");
ccc->global_ret = GNUNET_SYSERR;
GNUNET_SCHEDULER_shutdown ();
}
/**
* Internal data structure. Closure for
* #connect_cb, #disconnect_cb, #my_nc and #start_cb.
* Allows us to identify which peer this is about.
*/
struct GNUNET_TRANSPORT_TESTING_InternalPeerContext
{
/**
* Overall context of the callback.
*/
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc;
/**
* Offset of the peer this is about.
*/
unsigned int off;
};
/**
* Information tracked per connected peer.
*/
struct ConnectPairInfo
{
/**
* Peer this is about.
*/
const struct GNUNET_PeerIdentity *sender;
/**
* Information about the receiving peer.
*/
struct GNUNET_TRANSPORT_TESTING_InternalPeerContext *ipi;
};
/**
* Function called when we connected two peers. Once we have gotten
* to the clique, launch test-specific logic.
*
* @param cls our `struct GNUNET_TRANSPORT_TESTING_ConnectRequestList *`
*/
static void
connect_cb (void *cls)
{
struct GNUNET_TRANSPORT_TESTING_ConnectRequestList *crl = cls;
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = crl->ccc;
GNUNET_CONTAINER_DLL_remove (ccc->crl_head,
ccc->crl_tail,
crl);
{
char *p1_c = GNUNET_strdup (GNUNET_i2s (&crl->p1->id));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Peers connected: %u (%s) <-> %u (%s)\n",
crl->p1->no,
p1_c,
crl->p2->no,
GNUNET_i2s (&crl->p2->id));
GNUNET_free (p1_c);
GNUNET_free (crl);
}
if (NULL == ccc->crl_head)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"All connections UP, launching custom test logic.\n");
GNUNET_SCHEDULER_add_now (ccc->connect_continuation,
ccc->connect_continuation_cls);
}
}
/**
* 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)
{
for (unsigned int i=0;inum_peers;i++)
if ( (NULL != ccc->p[i]) &&
(0 == memcmp (peer,
&ccc->p[i]->id,
sizeof (*peer))) )
return ccc->p[i];
return NULL;
}
/**
* Wrapper around peers connecting. Calls client's nc function.
*
* @param cls our `struct GNUNET_TRANSPORT_TESTING_InternalPeerContext *`
* @param peer peer we got connected to
* @param mq message queue for transmissions to @a peer
* @return closure for message handlers
*/
static void *
my_nc (void *cls,
const struct GNUNET_PeerIdentity *peer,
struct GNUNET_MQ_Handle *mq)
{
struct GNUNET_TRANSPORT_TESTING_InternalPeerContext *ipi = cls;
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = ipi->ccc;
struct ConnectPairInfo *cpi;
if (NULL != ccc->nc)
ccc->nc (ccc->cls,
ccc->p[ipi->off],
peer);
cpi = GNUNET_new (struct ConnectPairInfo);
cpi->ipi = ipi;
cpi->sender = peer; /* valid until disconnect */
return cpi;
}
/**
* Wrapper around peers disconnecting. Calls client's nd function.
*
* @param cls our `struct GNUNET_TRANSPORT_TESTING_InternalPeerContext *`
* @param peer peer we got disconnected from
* @param custom_cls return value from @my_nc
*/
static void
my_nd (void *cls,
const struct GNUNET_PeerIdentity *peer,
void *custom_cls)
{
struct GNUNET_TRANSPORT_TESTING_InternalPeerContext *ipi = cls;
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = ipi->ccc;
struct ConnectPairInfo *cpi = custom_cls;
if (NULL != ccc->nd)
ccc->nd (ccc->cls,
ccc->p[ipi->off],
peer);
GNUNET_free (cpi);
}
/**
* Wrapper around receiving data. Calls client's rec function.
*
* @param cls our `struct ConnectPairInfo *`
* @param message message we received
* @return #GNUNET_OK (all messages are fine)
*/
static int
check_test (void *cls,
const struct GNUNET_TRANSPORT_TESTING_TestMessage *message)
{
return GNUNET_OK;
}
/**
* Wrapper around receiving data. Calls client's rec function.
*
* @param cls our `struct ConnectPairInfo *`
* @param message message we received
*/
static void
handle_test (void *cls,
const struct GNUNET_TRANSPORT_TESTING_TestMessage *message)
{
struct ConnectPairInfo *cpi = cls;
struct GNUNET_TRANSPORT_TESTING_InternalPeerContext *ipi = cpi->ipi;
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = ipi->ccc;
if (NULL != ccc->rec)
ccc->rec (ccc->cls,
ccc->p[ipi->off],
cpi->sender,
message);
}
/**
* Wrapper around receiving data. Calls client's rec function.
*
* @param cls our `struct ConnectPairInfo *`
* @param message message we received
* @return #GNUNET_OK (all messages are fine)
*/
static int
check_test2 (void *cls,
const struct GNUNET_TRANSPORT_TESTING_TestMessage *message)
{
return GNUNET_OK;
}
/**
* Wrapper around receiving data. Calls client's rec function.
*
* @param cls our `struct ConnectPairInfo *`
* @param message message we received
*/
static void
handle_test2 (void *cls,
const struct GNUNET_TRANSPORT_TESTING_TestMessage *message)
{
struct ConnectPairInfo *cpi = cls;
struct GNUNET_TRANSPORT_TESTING_InternalPeerContext *ipi = cpi->ipi;
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = ipi->ccc;
if (NULL != ccc->rec)
ccc->rec (ccc->cls,
ccc->p[ipi->off],
cpi->sender,
message);
}
/**
* Connect the peers as a clique.
*
* @param cls our `struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext`
*/
static void
do_connect (void *cls)
{
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = cls;
ccc->connect_task = NULL;
for (unsigned int i=0;inum_peers;i++)
for (unsigned int j=(ccc->bi_directional ? 0 : i+1);jnum_peers;j++)
{
struct GNUNET_TRANSPORT_TESTING_ConnectRequestList *crl;
if (i == j)
continue;
crl = GNUNET_new (struct GNUNET_TRANSPORT_TESTING_ConnectRequestList);
GNUNET_CONTAINER_DLL_insert (ccc->crl_head,
ccc->crl_tail,
crl);
crl->ccc = ccc;
crl->p1 = ccc->p[i];
crl->p2 = ccc->p[j];
{
char *sender_c = GNUNET_strdup (GNUNET_i2s (&ccc->p[0]->id));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Test tries to connect peer %u (`%s') -> peer %u (`%s')\n",
ccc->p[0]->no,
sender_c,
ccc->p[1]->no,
GNUNET_i2s (&ccc->p[1]->id));
GNUNET_free (sender_c);
}
crl->cr = GNUNET_TRANSPORT_TESTING_connect_peers (ccc->p[i],
ccc->p[j],
&connect_cb,
crl);
}
}
/**
* Function called once we have successfully launched a peer.
* Once all peers have been launched, we connect all of them
* in a clique.
*
* @param cls our `struct GNUNET_TRANSPORT_TESTING_InternalPeerContext *`
*/
static void
start_cb (void *cls)
{
struct GNUNET_TRANSPORT_TESTING_InternalPeerContext *ipi = cls;
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = ipi->ccc;
struct GNUNET_TRANSPORT_TESTING_PeerContext *p = ccc->p[ipi->off];
ccc->started++;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Peer %u (`%s') started\n",
p->no,
GNUNET_i2s (&p->id));
if (ccc->started != ccc->num_peers)
return;
if (NULL != ccc->pre_connect_task)
{
/* Run the custom per-connect job, then give it a second to
go into effect before we continue connecting peers. */
ccc->pre_connect_task (ccc->pre_connect_task_cls);
ccc->connect_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
&do_connect,
ccc);
}
else
{
do_connect (ccc);
}
}
/**
* Function run from #GNUNET_TRANSPORT_TESTING_connect_check
* once the scheduler is up. Should launch the peers and
* then in the continuations try to connect them.
*
* @param cls our `struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *`
* @param args ignored
* @param cfgfile ignored
* @param cfg configuration
*/
static void
connect_check_run (void *cls,
char *const *args,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *cfg)
{
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = cls;
int ok;
ccc->cfg = cfg;
ccc->timeout_task = GNUNET_SCHEDULER_add_delayed (ccc->timeout,
&do_timeout,
ccc);
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
ccc);
ok = GNUNET_OK;
for (unsigned int i=0;inum_peers;i++)
{
struct GNUNET_MQ_MessageHandler handlers[] = {
GNUNET_MQ_hd_var_size (test,
GNUNET_TRANSPORT_TESTING_SIMPLE_MTYPE,
struct GNUNET_TRANSPORT_TESTING_TestMessage,
NULL),
GNUNET_MQ_hd_var_size (test2,
GNUNET_TRANSPORT_TESTING_SIMPLE_MTYPE2,
struct GNUNET_TRANSPORT_TESTING_TestMessage,
NULL),
GNUNET_MQ_handler_end()
};
ccc->p[i] = GNUNET_TRANSPORT_TESTING_start_peer (ccc->tth,
ccc->cfg_files[i],
i + 1,
handlers,
&my_nc,
&my_nd,
&ccc->ip[i],
&start_cb,
&ccc->ip[i]);
if (NULL == ccc->p[i])
ok = GNUNET_SYSERR;
}
if (GNUNET_OK != ok)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Fail! Could not start peers!\n");
GNUNET_SCHEDULER_shutdown ();
}
}
/**
* 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[])
{
static struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_OPTION_END
};
struct GNUNET_TRANSPORT_TESTING_ConnectCheckContext *ccc = cls;
struct GNUNET_TRANSPORT_TESTING_PeerContext *p[num_peers];
struct GNUNET_TRANSPORT_TESTING_InternalPeerContext ip[num_peers];
char * argv[] = {
(char *) test_name_,
"-c",
(char *) ccc->config_file,
NULL
};
ccc->num_peers = num_peers;
ccc->cfg_files = cfg_files;
ccc->test_plugin = test_plugin_;
ccc->test_name = test_name_;
ccc->tth = tth_;
ccc->global_ret = GNUNET_OK;
ccc->p = p;
ccc->ip = ip;
for (unsigned int i=0;iglobal_ret;
}
/**
* 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)
{
struct GNUNET_TRANSPORT_TESTING_Handle *tth;
char *test_name;
char *test_source;
char *test_plugin;
char *cfg_names[num_peers];
int ret;
ret = GNUNET_OK;
test_name = GNUNET_TRANSPORT_TESTING_get_test_name (argv0);
GNUNET_log_setup (test_name,
"WARNING",
NULL);
test_source = GNUNET_TRANSPORT_TESTING_get_test_source_name (filename);
test_plugin = GNUNET_TRANSPORT_TESTING_get_test_plugin_name (argv0,
test_source);
for (unsigned int i=0;i