2 This file is part of GNUnet.
3 Copyright (C) 2010-2015 GNUnet e.V.
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
21 * @file ats/test_ats_lib.c
22 * @brief test ATS library with a generic interpreter for running ATS tests
23 * @author Christian Grothoff
26 #include "gnunet_util_lib.h"
27 #include "gnunet_ats_service.h"
28 #include "gnunet_testing_lib.h"
29 #include "test_ats_lib.h"
32 * Information about the last address suggestion we got for a peer.
34 struct AddressSuggestData
37 * Which session were we given?
39 struct GNUNET_ATS_Session *session;
42 * What address was assigned?
44 struct GNUNET_HELLO_Address *address;
47 * Outbound bandwidth assigned.
49 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out;
52 * Inbound bandwidth assigned.
54 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in;
57 * Was the bandwidth assigned non-zero?
64 * Information about the last address information we got for an address.
66 struct AddressInformationData
69 * What address is this data about?
71 struct GNUNET_HELLO_Address *address;
74 * Which properties were given?
76 struct GNUNET_ATS_Properties properties;
79 * Outbound bandwidth reported.
81 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out;
84 * Inbound bandwidth reported.
86 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in;
89 * Was the address said to be 'active'?
98 static struct GNUNET_ATS_SchedulingHandle *sched_ats;
101 * Connectivity handle
103 static struct GNUNET_ATS_ConnectivityHandle *con_ats;
108 static struct GNUNET_ATS_PerformanceHandle *perf_ats;
111 * Handle for the interpreter task.
113 static struct GNUNET_SCHEDULER_Task *interpreter_task;
116 * Map from peer identities to the last address suggestion
117 * `struct AddressSuggestData` we got for the respective peer.
119 static struct GNUNET_CONTAINER_MultiPeerMap *p2asd;
122 * Map from peer identities to the last address information
123 * sets for all addresses of this peer. Each peer is mapped
124 * to one or more `struct AddressInformationData` entries.
126 static struct GNUNET_CONTAINER_MultiPeerMap *p2aid;
129 * Global timeout for the test.
131 static struct GNUNET_TIME_Relative TIMEOUT;
134 * Return value from #main().
139 * Current global command offset into the #commands array.
141 static unsigned int off;
144 * Commands for the current test.
146 static struct Command *test_commands;
151 * Free `struct AddressSuggestData` entry.
155 * @param value the `struct AddressSuggestData` to release
156 * @return #GNUNET_OK (continue to iterate)
160 const struct GNUNET_PeerIdentity *key,
163 struct AddressSuggestData *asd = value;
165 GNUNET_assert (GNUNET_YES ==
166 GNUNET_CONTAINER_multipeermap_remove (p2asd,
169 GNUNET_free_non_null (asd->address);
176 * Free `struct AddressInformationData` entry.
180 * @param value the `struct AddressSuggestData` to release
181 * @return #GNUNET_OK (continue to iterate)
185 const struct GNUNET_PeerIdentity *key,
188 struct AddressInformationData *aid = value;
190 GNUNET_assert (GNUNET_YES ==
191 GNUNET_CONTAINER_multipeermap_remove (p2aid,
194 GNUNET_free (aid->address);
201 * Find latest address suggestion made for the given peer.
203 * @param pid peer to look up
204 * @return NULL if peer was never involved
206 static struct AddressSuggestData *
207 find_address_suggestion (const struct GNUNET_PeerIdentity *pid)
209 return GNUNET_CONTAINER_multipeermap_get (p2asd,
215 * Closure for #match_address()
217 struct MatchAddressContext
222 const struct GNUNET_HELLO_Address *addr;
225 * Where to return address information if found.
227 struct AddressInformationData *ret;
232 * Find matching address information.
234 * @param cls a `struct MatchAddressContext`
236 * @param value a `struct AddressInformationData`
237 * @return #GNUNET_OK if not found
240 match_address (void *cls,
241 const struct GNUNET_PeerIdentity *key,
244 struct MatchAddressContext *mac = cls;
245 struct AddressInformationData *aid = value;
247 if (0 == GNUNET_HELLO_address_cmp (mac->addr,
258 * Find latest address information made for the given address.
260 * @param addr address to look up
261 * @return NULL if peer was never involved
263 static struct AddressInformationData *
264 find_address_information (const struct GNUNET_HELLO_Address *addr)
266 struct MatchAddressContext mac;
270 GNUNET_CONTAINER_multipeermap_get_multiple (p2aid,
279 * Task run to terminate the testcase.
287 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
288 "Test failed at stage %u %s\n",
290 (NULL != test_commands[off].label)
291 ? test_commands[off].label
293 if (NULL != interpreter_task)
295 GNUNET_SCHEDULER_cancel (interpreter_task);
296 interpreter_task = NULL;
298 if (NULL != sched_ats)
300 GNUNET_ATS_scheduling_done (sched_ats);
305 GNUNET_ATS_connectivity_done (con_ats);
308 if (NULL != perf_ats)
310 GNUNET_ATS_performance_done (perf_ats);
315 GNUNET_CONTAINER_multipeermap_iterate (p2asd,
318 GNUNET_CONTAINER_multipeermap_destroy (p2asd);
323 GNUNET_CONTAINER_multipeermap_iterate (p2aid,
326 GNUNET_CONTAINER_multipeermap_destroy (p2aid);
333 * Main interpreter loop. Runs the steps of the test.
338 interpreter (void *cls);
342 * Run the interpreter next.
347 if (NULL != interpreter_task)
348 GNUNET_SCHEDULER_cancel (interpreter_task);
349 interpreter_task = GNUNET_SCHEDULER_add_now (&interpreter,
355 * Initialize public key of a peer based on a single number.
357 * @param pid number to use as the basis
358 * @param pk resulting fake public key
361 make_peer (uint32_t pid,
362 struct GNUNET_PeerIdentity *pk)
366 sizeof (struct GNUNET_PeerIdentity));
374 * Generate a fake address based on the given parameters.
376 * @param pid number of the peer
377 * @param num number of the address at peer @a pid
378 * @param addr_flags flags to use for the address
379 * @return the address
381 static struct GNUNET_HELLO_Address *
382 make_address (uint32_t pid,
384 enum GNUNET_HELLO_AddressInfo addr_flags)
386 struct GNUNET_PeerIdentity pk;
392 return GNUNET_HELLO_address_allocate (&pk,
401 * Our dummy sessions.
403 struct GNUNET_ATS_Session
406 * Field to avoid `0 == sizeof(struct GNUNET_ATS_Session)`.
408 unsigned int non_empty;
413 * Create a session instance for ATS.
415 * @param i which session number to return
416 * @return NULL if @a i is 0, otherwise a pointer unique to @a i
418 static struct GNUNET_ATS_Session *
419 make_session (unsigned int i)
421 struct GNUNET_ATS_Session *baseptr = NULL;
425 /* Yes, these are *intentionally* out-of-bounds,
426 and offset from NULL, as nobody should ever
427 use those other than to compare pointers! */
433 * Find a @a code command before the global #off with the
434 * specified @a label.
436 * @param code opcode to look for
437 * @param label label to look for, NULL for none
438 * @return previous command with the matching label
440 static struct Command *
441 find_command (enum CommandCode code,
448 for (i=off-1;i>=0;i--)
449 if ( (code == test_commands[i].code) &&
450 (0 == strcmp (test_commands[i].label,
452 return &test_commands[i];
459 * Function called from #GNUNET_ATS_performance_list_addresses when
460 * we process a #CMD_LIST_ADDRESSES command.
462 * @param cls the `struct Command` that caused the call
463 * @param address the address, NULL if ATS service was disconnected
464 * @param address_active #GNUNET_YES if this address is actively used
465 * to maintain a connection to a peer;
466 * #GNUNET_NO if the address is not actively used;
467 * #GNUNET_SYSERR if this address is no longer available for ATS
468 * @param bandwidth_out assigned outbound bandwidth for the connection
469 * @param bandwidth_in assigned inbound bandwidth for the connection
470 * @param prop performance data for the address
474 const struct GNUNET_HELLO_Address *address,
476 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out,
477 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in,
478 const struct GNUNET_ATS_Properties *prop)
480 struct Command *c = cls;
481 struct CommandListAddresses *cmd = &c->details.list_addresses;
486 /* we are done with the iteration, continue to execute */
487 if ( (cmd->calls < cmd->min_calls) &&
488 (cmd->active_calls < cmd->min_active_calls) )
490 GNUNET_SCHEDULER_shutdown ();
497 switch (address_active)
509 if ( (cmd->calls > cmd->max_calls) &&
510 (cmd->active_calls < cmd->max_active_calls) )
513 GNUNET_ATS_performance_list_addresses_cancel (cmd->alh);
515 GNUNET_SCHEDULER_shutdown ();
522 * Function called with reservation result.
524 * @param cls closure with the reservation command (`struct Command`)
525 * @param peer identifies the peer
526 * @param amount set to the amount that was actually reserved or unreserved;
527 * either the full requested amount or zero (no partial reservations)
528 * @param res_delay if the reservation could not be satisfied (amount was 0), how
529 * long should the client wait until re-trying?
532 reservation_cb (void *cls,
533 const struct GNUNET_PeerIdentity *peer,
535 struct GNUNET_TIME_Relative res_delay)
537 struct Command *cmd = cls;
538 struct GNUNET_PeerIdentity pid;
540 cmd->details.reserve_bandwidth.rc = NULL;
541 make_peer (cmd->details.reserve_bandwidth.pid,
543 GNUNET_assert (0 == memcmp (peer,
545 sizeof (struct GNUNET_PeerIdentity)));
546 switch (cmd->details.reserve_bandwidth.expected_result)
549 if (amount != cmd->details.reserve_bandwidth.amount)
551 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
552 "Unexpectedly failed to reserve %d/%d bytes with delay %s!\n",
554 (int) cmd->details.reserve_bandwidth.amount,
555 GNUNET_STRINGS_relative_time_to_string (res_delay,
558 GNUNET_SCHEDULER_shutdown ();
563 GNUNET_break ( (0 != amount) ||
564 (0 != res_delay.rel_value_us) );
567 if ( (amount != 0) ||
568 (0 == res_delay.rel_value_us) )
570 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
571 "Unexpectedly reserved %d bytes with delay %s!\n",
573 GNUNET_STRINGS_relative_time_to_string (res_delay,
576 GNUNET_SCHEDULER_shutdown ();
587 * Main interpreter loop. Runs the steps of the test.
592 interpreter (void *cls)
597 interpreter_task = NULL;
600 cmd = &test_commands[off];
601 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
605 (NULL != cmd->label) ? cmd->label : "");
610 GNUNET_SCHEDULER_shutdown ();
612 case CMD_ADD_ADDRESS:
614 struct GNUNET_HELLO_Address *addr;
615 struct GNUNET_ATS_Session *session;
617 addr = make_address (cmd->details.add_address.pid,
618 cmd->details.add_address.addr_num,
619 cmd->details.add_address.addr_flags);
620 session = make_session (cmd->details.add_address.session);
621 if (cmd->details.add_address.expect_fail)
622 GNUNET_log_skip (1, GNUNET_NO);
623 cmd->details.add_address.ar
624 = GNUNET_ATS_address_add (sched_ats,
627 &cmd->details.add_address.properties);
629 if (cmd->details.add_address.expect_fail)
631 GNUNET_log_skip (0, GNUNET_YES);
633 else if (NULL == cmd->details.add_address.ar)
636 GNUNET_SCHEDULER_shutdown ();
642 case CMD_DEL_ADDRESS:
646 add = find_command (CMD_ADD_ADDRESS,
647 cmd->details.del_address.add_label);
648 GNUNET_assert (NULL != add->details.add_address.ar);
649 GNUNET_ATS_address_destroy (add->details.add_address.ar);
650 add->details.add_address.ar = NULL;
654 case CMD_AWAIT_ADDRESS_SUGGESTION:
656 struct GNUNET_PeerIdentity pid;
657 struct GNUNET_HELLO_Address *addr;
659 struct AddressSuggestData *asd;
662 make_peer (cmd->details.await_address_suggestion.pid,
664 asd = find_address_suggestion (&pid);
667 if (GNUNET_NO == asd->active)
668 return; /* last suggestion was to disconnect, wait longer */
670 if (NULL != cmd->details.await_address_suggestion.add_label)
673 add = find_command (CMD_ADD_ADDRESS,
674 cmd->details.await_address_suggestion.add_label);
675 addr = make_address (add->details.add_address.pid,
676 add->details.add_address.addr_num,
677 add->details.add_address.addr_flags);
678 if ( (asd->session ==
679 make_session (add->details.add_address.session)) &&
681 GNUNET_HELLO_address_cmp (addr,
686 if (GNUNET_NO == done)
691 case CMD_AWAIT_DISCONNECT_SUGGESTION:
693 struct GNUNET_PeerIdentity pid;
694 struct AddressSuggestData *asd;
696 make_peer (cmd->details.await_disconnect_suggestion.pid,
698 asd = find_address_suggestion (&pid);
700 return; /* odd, no suggestion at all yet!? */
701 if (GNUNET_YES == asd->active)
702 return; /* last suggestion was to activate, wait longer */
703 /* last suggestion was to deactivate, condition satisfied! */
707 case CMD_REQUEST_CONNECTION_START:
709 struct GNUNET_PeerIdentity pid;
711 make_peer (cmd->details.request_connection_start.pid,
713 cmd->details.request_connection_start.csh
714 = GNUNET_ATS_connectivity_suggest (con_ats,
720 case CMD_REQUEST_CONNECTION_STOP:
722 struct Command *start;
724 start = find_command (CMD_REQUEST_CONNECTION_START,
725 cmd->details.request_connection_stop.connect_label);
726 GNUNET_ATS_connectivity_suggest_cancel (start->details.request_connection_start.csh);
727 start->details.request_connection_start.csh = NULL;
731 case CMD_AWAIT_ADDRESS_INFORMATION:
733 struct AddressInformationData *aid;
735 struct Command *update;
736 struct GNUNET_HELLO_Address *addr;
737 const struct GNUNET_ATS_Properties *cmp;
739 add = find_command (CMD_ADD_ADDRESS,
740 cmd->details.await_address_information.add_label);
741 update = find_command (CMD_UPDATE_ADDRESS,
742 cmd->details.await_address_information.update_label);
743 addr = make_address (add->details.add_address.pid,
744 add->details.add_address.addr_num,
745 add->details.add_address.addr_flags);
746 aid = find_address_information (addr);
749 cmp = &add->details.add_address.properties;
751 cmp = &update->details.update_address.properties;
752 if ( (NULL != aid) &&
753 (cmp->delay.rel_value_us == aid->properties.delay.rel_value_us) &&
754 (cmp->utilization_out == aid->properties.utilization_out) &&
755 (cmp->utilization_in == aid->properties.utilization_in) &&
756 (cmp->distance == aid->properties.distance) &&
757 (cmp->scope == aid->properties.scope) )
764 case CMD_UPDATE_ADDRESS:
768 add = find_command (CMD_ADD_ADDRESS,
769 cmd->details.update_address.add_label);
770 GNUNET_assert (NULL != add->details.add_address.ar);
771 GNUNET_ATS_address_update (add->details.add_address.ar,
772 &cmd->details.update_address.properties);
776 case CMD_ADD_SESSION:
779 struct GNUNET_ATS_Session *session;
781 add = find_command (CMD_ADD_ADDRESS,
782 cmd->details.add_session.add_label);
783 session = make_session (cmd->details.add_session.session);
784 GNUNET_assert (NULL != add->details.add_address.ar);
785 GNUNET_ATS_address_add_session (add->details.add_address.ar,
790 case CMD_DEL_SESSION:
792 struct Command *add_address;
793 struct Command *add_session;
794 struct GNUNET_ATS_Session *session;
796 add_session = find_command (CMD_ADD_SESSION,
797 cmd->details.del_session.add_session_label);
798 add_address = find_command (CMD_ADD_ADDRESS,
799 add_session->details.add_session.add_label);
800 GNUNET_assert (NULL != add_address->details.add_address.ar);
801 session = make_session (add_session->details.add_session.session);
802 GNUNET_ATS_address_del_session (add_address->details.add_address.ar,
807 case CMD_CHANGE_PREFERENCE:
809 struct GNUNET_PeerIdentity pid;
811 make_peer (cmd->details.change_preference.pid,
813 GNUNET_ATS_performance_change_preference (perf_ats,
815 GNUNET_ATS_PREFERENCE_END);
819 case CMD_PROVIDE_FEEDBACK:
821 struct GNUNET_PeerIdentity pid;
823 make_peer (cmd->details.provide_feedback.pid,
825 GNUNET_ATS_performance_give_feedback (perf_ats,
827 cmd->details.provide_feedback.scope,
828 GNUNET_ATS_PREFERENCE_END);
832 case CMD_LIST_ADDRESSES:
834 struct GNUNET_PeerIdentity pid;
836 make_peer (cmd->details.list_addresses.pid,
838 cmd->details.list_addresses.alh
839 = GNUNET_ATS_performance_list_addresses (perf_ats,
841 cmd->details.list_addresses.all,
846 case CMD_RESERVE_BANDWIDTH:
848 struct GNUNET_PeerIdentity pid;
850 make_peer (cmd->details.reserve_bandwidth.pid,
852 cmd->details.reserve_bandwidth.rc
853 = GNUNET_ATS_reserve_bandwidth (perf_ats,
855 cmd->details.reserve_bandwidth.amount,
862 interpreter_task = GNUNET_SCHEDULER_add_delayed (cmd->details.sleep.delay,
872 * Signature of a function called by ATS with the current bandwidth
873 * and address preferences as determined by ATS.
875 * @param cls closure, should point to "asc-closure"
876 * @param peer for which we suggest an address, NULL if ATS connection died
877 * @param address suggested address (including peer identity of the peer),
878 * may be NULL to signal disconnect from peer
879 * @param session session to use, NULL to establish a new outgoing session
880 * @param bandwidth_out assigned outbound bandwidth for the connection,
881 * 0 to signal disconnect
882 * @param bandwidth_in assigned inbound bandwidth for the connection,
883 * 0 to signal disconnect
886 address_suggest_cb (void *cls,
887 const struct GNUNET_PeerIdentity *peer,
888 const struct GNUNET_HELLO_Address *address,
889 struct GNUNET_ATS_Session *session,
890 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out,
891 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in)
893 const char *asc_cls = cls;
894 struct AddressSuggestData *asd;
896 GNUNET_break (0 == strcmp (asc_cls, "asc-closure"));
899 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
900 "Connection to ATS died, likely a crash!\n");
901 GNUNET_SCHEDULER_shutdown ();
903 /* This is what we should do if we wanted to continue past
905 GNUNET_CONTAINER_multipeermap_iterate (p2asd,
908 GNUNET_CONTAINER_multipeermap_iterate (p2aid,
915 asd = find_address_suggestion (peer);
918 asd = GNUNET_new (struct AddressSuggestData);
919 GNUNET_assert (GNUNET_YES ==
920 GNUNET_CONTAINER_multipeermap_put (p2asd,
923 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
925 if ( (0 == ntohl (bandwidth_out.value__)) &&
926 (0 == ntohl (bandwidth_in.value__)) )
927 asd->active = GNUNET_NO;
929 asd->active = GNUNET_YES;
930 asd->bandwidth_out = bandwidth_out;
931 asd->bandwidth_in = bandwidth_in;
932 asd->session = session;
933 GNUNET_free_non_null (asd->address);
936 asd->address = GNUNET_HELLO_address_copy (address);
937 if (NULL == interpreter_task)
943 * Signature of a function that is called with QoS information about an address.
945 * @param cls closure, should point to "aic-closure"
946 * @param address the address, NULL if ATS service was disconnected
947 * @param address_active #GNUNET_YES if this address is actively used
948 * to maintain a connection to a peer;
949 * #GNUNET_NO if the address is not actively used;
950 * #GNUNET_SYSERR if this address is no longer available for ATS
951 * @param bandwidth_out assigned outbound bandwidth for the connection
952 * @param bandwidth_in assigned inbound bandwidth for the connection
953 * @param prop performance data for the address
956 address_information_cb (void *cls,
957 const struct GNUNET_HELLO_Address *address,
959 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out,
960 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in,
961 const struct GNUNET_ATS_Properties *prop)
963 const char *aic_cls = cls;
964 struct AddressInformationData *aid;
966 GNUNET_break (0 == strcmp (aic_cls, "aic-closure"));
969 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
970 "Connection to ATS died, likely a crash!\n");
971 GNUNET_CONTAINER_multipeermap_iterate (p2aid,
977 aid = find_address_information (address);
980 aid = GNUNET_new (struct AddressInformationData);
981 aid->address = GNUNET_HELLO_address_copy (address);
982 GNUNET_assert (GNUNET_YES ==
983 GNUNET_CONTAINER_multipeermap_put (p2aid,
986 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
988 aid->active = address_active;
989 aid->bandwidth_out = bandwidth_out;
990 aid->bandwidth_in = bandwidth_in;
991 aid->properties = *prop;
992 if (NULL == interpreter_task)
998 * Function run once the ATS service has been started.
1001 * @param cfg configuration for the testcase
1002 * @param peer handle to the peer
1006 const struct GNUNET_CONFIGURATION_Handle *cfg,
1007 struct GNUNET_TESTING_Peer *peer)
1009 p2asd = GNUNET_CONTAINER_multipeermap_create (128,
1011 p2aid = GNUNET_CONTAINER_multipeermap_create (128,
1013 GNUNET_SCHEDULER_add_delayed (TIMEOUT,
1017 sched_ats = GNUNET_ATS_scheduling_init (cfg,
1018 &address_suggest_cb,
1020 if (NULL == sched_ats)
1023 GNUNET_SCHEDULER_shutdown ();
1026 con_ats = GNUNET_ATS_connectivity_init (cfg);
1027 if (NULL == con_ats)
1030 GNUNET_SCHEDULER_shutdown ();
1033 perf_ats = GNUNET_ATS_performance_init (cfg,
1034 &address_information_cb,
1036 if (NULL == perf_ats)
1039 GNUNET_SCHEDULER_shutdown ();
1049 * @param argc length of @a argv
1050 * @param argv command line
1051 * @param cmds commands to run with the interpreter
1052 * @param timeout how long is the test allowed to take?
1053 * @return 0 on success
1056 TEST_ATS_run (int argc,
1058 struct Command *cmds,
1059 struct GNUNET_TIME_Relative timeout)
1061 char *test_filename = GNUNET_strdup (argv[0]);
1066 test_commands = cmds;
1068 if (NULL != (sep = strstr (test_filename, ".exe")))
1070 underscore = strrchr (test_filename, (int) '_');
1071 GNUNET_assert (NULL != underscore);
1072 GNUNET_asprintf (&config_file,
1073 "test_ats_api_%s.conf",
1076 if (0 != GNUNET_TESTING_peer_run ("test-ats-api",
1080 GNUNET_free (test_filename);
1081 GNUNET_free (config_file);
1085 /* end of test_ats_lib.c */