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 it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your 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 Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * @file ats/test_ats_lib.c
20 * @brief test ATS library with a generic interpreter for running ATS tests
21 * @author Christian Grothoff
24 #include "gnunet_util_lib.h"
25 #include "gnunet_ats_service.h"
26 #include "gnunet_testing_lib.h"
27 #include "test_ats_lib.h"
30 * Information about the last address suggestion we got for a peer.
32 struct AddressSuggestData
35 * Which session were we given?
37 struct GNUNET_ATS_Session *session;
40 * What address was assigned?
42 struct GNUNET_HELLO_Address *address;
45 * Outbound bandwidth assigned.
47 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out;
50 * Inbound bandwidth assigned.
52 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in;
55 * Was the bandwidth assigned non-zero?
62 * Information about the last address information we got for an address.
64 struct AddressInformationData
67 * What address is this data about?
69 struct GNUNET_HELLO_Address *address;
72 * Which properties were given?
74 struct GNUNET_ATS_Properties properties;
77 * Outbound bandwidth reported.
79 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out;
82 * Inbound bandwidth reported.
84 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in;
87 * Was the address said to be 'active'?
96 static struct GNUNET_ATS_SchedulingHandle *sched_ats;
101 static struct GNUNET_ATS_ConnectivityHandle *con_ats;
106 static struct GNUNET_ATS_PerformanceHandle *perf_ats;
109 * Handle for the interpreter task.
111 static struct GNUNET_SCHEDULER_Task *interpreter_task;
114 * Map from peer identities to the last address suggestion
115 * `struct AddressSuggestData` we got for the respective peer.
117 static struct GNUNET_CONTAINER_MultiPeerMap *p2asd;
120 * Map from peer identities to the last address information
121 * sets for all addresses of this peer. Each peer is mapped
122 * to one or more `struct AddressInformationData` entries.
124 static struct GNUNET_CONTAINER_MultiPeerMap *p2aid;
127 * Global timeout for the test.
129 static struct GNUNET_TIME_Relative TIMEOUT;
132 * Return value from #main().
137 * Current global command offset into the #commands array.
139 static unsigned int off;
142 * Commands for the current test.
144 static struct Command *test_commands;
149 * Free `struct AddressSuggestData` entry.
153 * @param value the `struct AddressSuggestData` to release
154 * @return #GNUNET_OK (continue to iterate)
158 const struct GNUNET_PeerIdentity *key,
161 struct AddressSuggestData *asd = value;
163 GNUNET_assert (GNUNET_YES ==
164 GNUNET_CONTAINER_multipeermap_remove (p2asd,
167 GNUNET_free_non_null (asd->address);
174 * Free `struct AddressInformationData` entry.
178 * @param value the `struct AddressSuggestData` to release
179 * @return #GNUNET_OK (continue to iterate)
183 const struct GNUNET_PeerIdentity *key,
186 struct AddressInformationData *aid = value;
188 GNUNET_assert (GNUNET_YES ==
189 GNUNET_CONTAINER_multipeermap_remove (p2aid,
192 GNUNET_free (aid->address);
199 * Find latest address suggestion made for the given peer.
201 * @param pid peer to look up
202 * @return NULL if peer was never involved
204 static struct AddressSuggestData *
205 find_address_suggestion (const struct GNUNET_PeerIdentity *pid)
207 return GNUNET_CONTAINER_multipeermap_get (p2asd,
213 * Closure for #match_address()
215 struct MatchAddressContext
220 const struct GNUNET_HELLO_Address *addr;
223 * Where to return address information if found.
225 struct AddressInformationData *ret;
230 * Find matching address information.
232 * @param cls a `struct MatchAddressContext`
234 * @param value a `struct AddressInformationData`
235 * @return #GNUNET_OK if not found
238 match_address (void *cls,
239 const struct GNUNET_PeerIdentity *key,
242 struct MatchAddressContext *mac = cls;
243 struct AddressInformationData *aid = value;
245 if (0 == GNUNET_HELLO_address_cmp (mac->addr,
256 * Find latest address information made for the given address.
258 * @param addr address to look up
259 * @return NULL if peer was never involved
261 static struct AddressInformationData *
262 find_address_information (const struct GNUNET_HELLO_Address *addr)
264 struct MatchAddressContext mac;
268 GNUNET_CONTAINER_multipeermap_get_multiple (p2aid,
277 * Task run to terminate the testcase.
285 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
286 "Test failed at stage %u %s\n",
288 (NULL != test_commands[off].label)
289 ? test_commands[off].label
291 if (NULL != interpreter_task)
293 GNUNET_SCHEDULER_cancel (interpreter_task);
294 interpreter_task = NULL;
296 if (NULL != sched_ats)
298 GNUNET_ATS_scheduling_done (sched_ats);
303 GNUNET_ATS_connectivity_done (con_ats);
306 if (NULL != perf_ats)
308 GNUNET_ATS_performance_done (perf_ats);
313 GNUNET_CONTAINER_multipeermap_iterate (p2asd,
316 GNUNET_CONTAINER_multipeermap_destroy (p2asd);
321 GNUNET_CONTAINER_multipeermap_iterate (p2aid,
324 GNUNET_CONTAINER_multipeermap_destroy (p2aid);
331 * Main interpreter loop. Runs the steps of the test.
336 interpreter (void *cls);
340 * Run the interpreter next.
345 if (NULL != interpreter_task)
346 GNUNET_SCHEDULER_cancel (interpreter_task);
347 interpreter_task = GNUNET_SCHEDULER_add_now (&interpreter,
353 * Initialize public key of a peer based on a single number.
355 * @param pid number to use as the basis
356 * @param pk resulting fake public key
359 make_peer (uint32_t pid,
360 struct GNUNET_PeerIdentity *pk)
364 sizeof (struct GNUNET_PeerIdentity));
372 * Generate a fake address based on the given parameters.
374 * @param pid number of the peer
375 * @param num number of the address at peer @a pid
376 * @param addr_flags flags to use for the address
377 * @return the address
379 static struct GNUNET_HELLO_Address *
380 make_address (uint32_t pid,
382 enum GNUNET_HELLO_AddressInfo addr_flags)
384 struct GNUNET_PeerIdentity pk;
390 return GNUNET_HELLO_address_allocate (&pk,
399 * Our dummy sessions.
401 struct GNUNET_ATS_Session
404 * Field to avoid `0 == sizeof(struct GNUNET_ATS_Session)`.
406 unsigned int non_empty;
411 * Create a session instance for ATS.
413 * @param i which session number to return
414 * @return NULL if @a i is 0, otherwise a pointer unique to @a i
416 static struct GNUNET_ATS_Session *
417 make_session (unsigned int i)
419 struct GNUNET_ATS_Session *baseptr = NULL;
423 /* Yes, these are *intentionally* out-of-bounds,
424 and offset from NULL, as nobody should ever
425 use those other than to compare pointers! */
431 * Find a @a code command before the global #off with the
432 * specified @a label.
434 * @param code opcode to look for
435 * @param label label to look for, NULL for none
436 * @return previous command with the matching label
438 static struct Command *
439 find_command (enum CommandCode code,
446 for (i=off-1;i>=0;i--)
447 if ( (code == test_commands[i].code) &&
448 (0 == strcmp (test_commands[i].label,
450 return &test_commands[i];
457 * Function called from #GNUNET_ATS_performance_list_addresses when
458 * we process a #CMD_LIST_ADDRESSES command.
460 * @param cls the `struct Command` that caused the call
461 * @param address the address, NULL if ATS service was disconnected
462 * @param address_active #GNUNET_YES if this address is actively used
463 * to maintain a connection to a peer;
464 * #GNUNET_NO if the address is not actively used;
465 * #GNUNET_SYSERR if this address is no longer available for ATS
466 * @param bandwidth_out assigned outbound bandwidth for the connection
467 * @param bandwidth_in assigned inbound bandwidth for the connection
468 * @param prop performance data for the address
472 const struct GNUNET_HELLO_Address *address,
474 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out,
475 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in,
476 const struct GNUNET_ATS_Properties *prop)
478 struct Command *c = cls;
479 struct CommandListAddresses *cmd = &c->details.list_addresses;
484 /* we are done with the iteration, continue to execute */
485 if ( (cmd->calls < cmd->min_calls) &&
486 (cmd->active_calls < cmd->min_active_calls) )
488 GNUNET_SCHEDULER_shutdown ();
495 switch (address_active)
507 if ( (cmd->calls > cmd->max_calls) &&
508 (cmd->active_calls < cmd->max_active_calls) )
511 GNUNET_ATS_performance_list_addresses_cancel (cmd->alh);
513 GNUNET_SCHEDULER_shutdown ();
520 * Function called with reservation result.
522 * @param cls closure with the reservation command (`struct Command`)
523 * @param peer identifies the peer
524 * @param amount set to the amount that was actually reserved or unreserved;
525 * either the full requested amount or zero (no partial reservations)
526 * @param res_delay if the reservation could not be satisfied (amount was 0), how
527 * long should the client wait until re-trying?
530 reservation_cb (void *cls,
531 const struct GNUNET_PeerIdentity *peer,
533 struct GNUNET_TIME_Relative res_delay)
535 struct Command *cmd = cls;
536 struct GNUNET_PeerIdentity pid;
538 cmd->details.reserve_bandwidth.rc = NULL;
539 make_peer (cmd->details.reserve_bandwidth.pid,
541 GNUNET_assert (0 == memcmp (peer,
543 sizeof (struct GNUNET_PeerIdentity)));
544 switch (cmd->details.reserve_bandwidth.expected_result)
547 if (amount != cmd->details.reserve_bandwidth.amount)
549 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
550 "Unexpectedly failed to reserve %d/%d bytes with delay %s!\n",
552 (int) cmd->details.reserve_bandwidth.amount,
553 GNUNET_STRINGS_relative_time_to_string (res_delay,
556 GNUNET_SCHEDULER_shutdown ();
561 GNUNET_break ( (0 != amount) ||
562 (0 != res_delay.rel_value_us) );
565 if ( (amount != 0) ||
566 (0 == res_delay.rel_value_us) )
568 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
569 "Unexpectedly reserved %d bytes with delay %s!\n",
571 GNUNET_STRINGS_relative_time_to_string (res_delay,
574 GNUNET_SCHEDULER_shutdown ();
585 * Main interpreter loop. Runs the steps of the test.
590 interpreter (void *cls)
595 interpreter_task = NULL;
598 cmd = &test_commands[off];
599 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
603 (NULL != cmd->label) ? cmd->label : "");
608 GNUNET_SCHEDULER_shutdown ();
610 case CMD_ADD_ADDRESS:
612 struct GNUNET_HELLO_Address *addr;
613 struct GNUNET_ATS_Session *session;
615 addr = make_address (cmd->details.add_address.pid,
616 cmd->details.add_address.addr_num,
617 cmd->details.add_address.addr_flags);
618 session = make_session (cmd->details.add_address.session);
619 if (cmd->details.add_address.expect_fail)
620 GNUNET_log_skip (1, GNUNET_NO);
621 cmd->details.add_address.ar
622 = GNUNET_ATS_address_add (sched_ats,
625 &cmd->details.add_address.properties);
627 if (cmd->details.add_address.expect_fail)
629 GNUNET_log_skip (0, GNUNET_YES);
631 else if (NULL == cmd->details.add_address.ar)
634 GNUNET_SCHEDULER_shutdown ();
640 case CMD_DEL_ADDRESS:
644 add = find_command (CMD_ADD_ADDRESS,
645 cmd->details.del_address.add_label);
646 GNUNET_assert (NULL != add->details.add_address.ar);
647 GNUNET_ATS_address_destroy (add->details.add_address.ar);
648 add->details.add_address.ar = NULL;
652 case CMD_AWAIT_ADDRESS_SUGGESTION:
654 struct GNUNET_PeerIdentity pid;
655 struct GNUNET_HELLO_Address *addr;
657 struct AddressSuggestData *asd;
660 make_peer (cmd->details.await_address_suggestion.pid,
662 asd = find_address_suggestion (&pid);
665 if (GNUNET_NO == asd->active)
666 return; /* last suggestion was to disconnect, wait longer */
668 if (NULL != cmd->details.await_address_suggestion.add_label)
671 add = find_command (CMD_ADD_ADDRESS,
672 cmd->details.await_address_suggestion.add_label);
673 addr = make_address (add->details.add_address.pid,
674 add->details.add_address.addr_num,
675 add->details.add_address.addr_flags);
676 if ( (asd->session ==
677 make_session (add->details.add_address.session)) &&
679 GNUNET_HELLO_address_cmp (addr,
684 if (GNUNET_NO == done)
689 case CMD_AWAIT_DISCONNECT_SUGGESTION:
691 struct GNUNET_PeerIdentity pid;
692 struct AddressSuggestData *asd;
694 make_peer (cmd->details.await_disconnect_suggestion.pid,
696 asd = find_address_suggestion (&pid);
698 return; /* odd, no suggestion at all yet!? */
699 if (GNUNET_YES == asd->active)
700 return; /* last suggestion was to activate, wait longer */
701 /* last suggestion was to deactivate, condition satisfied! */
705 case CMD_REQUEST_CONNECTION_START:
707 struct GNUNET_PeerIdentity pid;
709 make_peer (cmd->details.request_connection_start.pid,
711 cmd->details.request_connection_start.csh
712 = GNUNET_ATS_connectivity_suggest (con_ats,
718 case CMD_REQUEST_CONNECTION_STOP:
720 struct Command *start;
722 start = find_command (CMD_REQUEST_CONNECTION_START,
723 cmd->details.request_connection_stop.connect_label);
724 GNUNET_ATS_connectivity_suggest_cancel (start->details.request_connection_start.csh);
725 start->details.request_connection_start.csh = NULL;
729 case CMD_AWAIT_ADDRESS_INFORMATION:
731 struct AddressInformationData *aid;
733 struct Command *update;
734 struct GNUNET_HELLO_Address *addr;
735 const struct GNUNET_ATS_Properties *cmp;
737 add = find_command (CMD_ADD_ADDRESS,
738 cmd->details.await_address_information.add_label);
739 update = find_command (CMD_UPDATE_ADDRESS,
740 cmd->details.await_address_information.update_label);
741 addr = make_address (add->details.add_address.pid,
742 add->details.add_address.addr_num,
743 add->details.add_address.addr_flags);
744 aid = find_address_information (addr);
747 cmp = &add->details.add_address.properties;
749 cmp = &update->details.update_address.properties;
750 if ( (NULL != aid) &&
751 (cmp->delay.rel_value_us == aid->properties.delay.rel_value_us) &&
752 (cmp->utilization_out == aid->properties.utilization_out) &&
753 (cmp->utilization_in == aid->properties.utilization_in) &&
754 (cmp->distance == aid->properties.distance) &&
755 (cmp->scope == aid->properties.scope) )
762 case CMD_UPDATE_ADDRESS:
766 add = find_command (CMD_ADD_ADDRESS,
767 cmd->details.update_address.add_label);
768 GNUNET_assert (NULL != add->details.add_address.ar);
769 GNUNET_ATS_address_update (add->details.add_address.ar,
770 &cmd->details.update_address.properties);
774 case CMD_ADD_SESSION:
777 struct GNUNET_ATS_Session *session;
779 add = find_command (CMD_ADD_ADDRESS,
780 cmd->details.add_session.add_label);
781 session = make_session (cmd->details.add_session.session);
782 GNUNET_assert (NULL != add->details.add_address.ar);
783 GNUNET_ATS_address_add_session (add->details.add_address.ar,
788 case CMD_DEL_SESSION:
790 struct Command *add_address;
791 struct Command *add_session;
792 struct GNUNET_ATS_Session *session;
794 add_session = find_command (CMD_ADD_SESSION,
795 cmd->details.del_session.add_session_label);
796 add_address = find_command (CMD_ADD_ADDRESS,
797 add_session->details.add_session.add_label);
798 GNUNET_assert (NULL != add_address->details.add_address.ar);
799 session = make_session (add_session->details.add_session.session);
800 GNUNET_ATS_address_del_session (add_address->details.add_address.ar,
805 case CMD_CHANGE_PREFERENCE:
807 struct GNUNET_PeerIdentity pid;
809 make_peer (cmd->details.change_preference.pid,
811 GNUNET_ATS_performance_change_preference (perf_ats,
813 GNUNET_ATS_PREFERENCE_END);
817 case CMD_PROVIDE_FEEDBACK:
819 struct GNUNET_PeerIdentity pid;
821 make_peer (cmd->details.provide_feedback.pid,
823 GNUNET_ATS_performance_give_feedback (perf_ats,
825 cmd->details.provide_feedback.scope,
826 GNUNET_ATS_PREFERENCE_END);
830 case CMD_LIST_ADDRESSES:
832 struct GNUNET_PeerIdentity pid;
834 make_peer (cmd->details.list_addresses.pid,
836 cmd->details.list_addresses.alh
837 = GNUNET_ATS_performance_list_addresses (perf_ats,
839 cmd->details.list_addresses.all,
844 case CMD_RESERVE_BANDWIDTH:
846 struct GNUNET_PeerIdentity pid;
848 make_peer (cmd->details.reserve_bandwidth.pid,
850 cmd->details.reserve_bandwidth.rc
851 = GNUNET_ATS_reserve_bandwidth (perf_ats,
853 cmd->details.reserve_bandwidth.amount,
860 interpreter_task = GNUNET_SCHEDULER_add_delayed (cmd->details.sleep.delay,
870 * Signature of a function called by ATS with the current bandwidth
871 * and address preferences as determined by ATS.
873 * @param cls closure, should point to "asc-closure"
874 * @param peer for which we suggest an address, NULL if ATS connection died
875 * @param address suggested address (including peer identity of the peer),
876 * may be NULL to signal disconnect from peer
877 * @param session session to use, NULL to establish a new outgoing session
878 * @param bandwidth_out assigned outbound bandwidth for the connection,
879 * 0 to signal disconnect
880 * @param bandwidth_in assigned inbound bandwidth for the connection,
881 * 0 to signal disconnect
884 address_suggest_cb (void *cls,
885 const struct GNUNET_PeerIdentity *peer,
886 const struct GNUNET_HELLO_Address *address,
887 struct GNUNET_ATS_Session *session,
888 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out,
889 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in)
891 const char *asc_cls = cls;
892 struct AddressSuggestData *asd;
894 GNUNET_break (0 == strcmp (asc_cls, "asc-closure"));
897 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
898 "Connection to ATS died, likely a crash!\n");
899 GNUNET_SCHEDULER_shutdown ();
901 /* This is what we should do if we wanted to continue past
903 GNUNET_CONTAINER_multipeermap_iterate (p2asd,
906 GNUNET_CONTAINER_multipeermap_iterate (p2aid,
913 asd = find_address_suggestion (peer);
916 asd = GNUNET_new (struct AddressSuggestData);
917 GNUNET_assert (GNUNET_YES ==
918 GNUNET_CONTAINER_multipeermap_put (p2asd,
921 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
923 if ( (0 == ntohl (bandwidth_out.value__)) &&
924 (0 == ntohl (bandwidth_in.value__)) )
925 asd->active = GNUNET_NO;
927 asd->active = GNUNET_YES;
928 asd->bandwidth_out = bandwidth_out;
929 asd->bandwidth_in = bandwidth_in;
930 asd->session = session;
931 GNUNET_free_non_null (asd->address);
934 asd->address = GNUNET_HELLO_address_copy (address);
935 if (NULL == interpreter_task)
941 * Signature of a function that is called with QoS information about an address.
943 * @param cls closure, should point to "aic-closure"
944 * @param address the address, NULL if ATS service was disconnected
945 * @param address_active #GNUNET_YES if this address is actively used
946 * to maintain a connection to a peer;
947 * #GNUNET_NO if the address is not actively used;
948 * #GNUNET_SYSERR if this address is no longer available for ATS
949 * @param bandwidth_out assigned outbound bandwidth for the connection
950 * @param bandwidth_in assigned inbound bandwidth for the connection
951 * @param prop performance data for the address
954 address_information_cb (void *cls,
955 const struct GNUNET_HELLO_Address *address,
957 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out,
958 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in,
959 const struct GNUNET_ATS_Properties *prop)
961 const char *aic_cls = cls;
962 struct AddressInformationData *aid;
964 GNUNET_break (0 == strcmp (aic_cls, "aic-closure"));
967 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
968 "Connection to ATS died, likely a crash!\n");
969 GNUNET_CONTAINER_multipeermap_iterate (p2aid,
975 aid = find_address_information (address);
978 aid = GNUNET_new (struct AddressInformationData);
979 aid->address = GNUNET_HELLO_address_copy (address);
980 GNUNET_assert (GNUNET_YES ==
981 GNUNET_CONTAINER_multipeermap_put (p2aid,
984 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
986 aid->active = address_active;
987 aid->bandwidth_out = bandwidth_out;
988 aid->bandwidth_in = bandwidth_in;
989 aid->properties = *prop;
990 if (NULL == interpreter_task)
996 * Function run once the ATS service has been started.
999 * @param cfg configuration for the testcase
1000 * @param peer handle to the peer
1004 const struct GNUNET_CONFIGURATION_Handle *cfg,
1005 struct GNUNET_TESTING_Peer *peer)
1007 p2asd = GNUNET_CONTAINER_multipeermap_create (128,
1009 p2aid = GNUNET_CONTAINER_multipeermap_create (128,
1011 GNUNET_SCHEDULER_add_delayed (TIMEOUT,
1015 sched_ats = GNUNET_ATS_scheduling_init (cfg,
1016 &address_suggest_cb,
1018 if (NULL == sched_ats)
1021 GNUNET_SCHEDULER_shutdown ();
1024 con_ats = GNUNET_ATS_connectivity_init (cfg);
1025 if (NULL == con_ats)
1028 GNUNET_SCHEDULER_shutdown ();
1031 perf_ats = GNUNET_ATS_performance_init (cfg,
1032 &address_information_cb,
1034 if (NULL == perf_ats)
1037 GNUNET_SCHEDULER_shutdown ();
1047 * @param argc length of @a argv
1048 * @param argv command line
1049 * @param cmds commands to run with the interpreter
1050 * @param timeout how long is the test allowed to take?
1051 * @return 0 on success
1054 TEST_ATS_run (int argc,
1056 struct Command *cmds,
1057 struct GNUNET_TIME_Relative timeout)
1059 char *test_filename = GNUNET_strdup (argv[0]);
1064 test_commands = cmds;
1066 if (NULL != (sep = strstr (test_filename, ".exe")))
1068 underscore = strrchr (test_filename, (int) '_');
1069 GNUNET_assert (NULL != underscore);
1070 GNUNET_asprintf (&config_file,
1071 "test_ats_api_%s.conf",
1074 if (0 != GNUNET_TESTING_peer_run ("test-ats-api",
1078 GNUNET_free (test_filename);
1079 GNUNET_free (config_file);
1083 /* end of test_ats_lib.c */