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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
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 {
36 * Which session were we given?
38 struct GNUNET_ATS_Session *session;
41 * What address was assigned?
43 struct GNUNET_HELLO_Address *address;
46 * Outbound bandwidth assigned.
48 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out;
51 * Inbound bandwidth assigned.
53 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in;
56 * Was the bandwidth assigned non-zero?
63 * Information about the last address information we got for an address.
65 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 {
219 const struct GNUNET_HELLO_Address *addr;
222 * Where to return address information if found.
224 struct AddressInformationData *ret;
229 * Find matching address information.
231 * @param cls a `struct MatchAddressContext`
233 * @param value a `struct AddressInformationData`
234 * @return #GNUNET_OK if not found
237 match_address(void *cls,
238 const struct GNUNET_PeerIdentity *key,
241 struct MatchAddressContext *mac = cls;
242 struct AddressInformationData *aid = value;
244 if (0 == GNUNET_HELLO_address_cmp(mac->addr,
255 * Find latest address information made for the given address.
257 * @param addr address to look up
258 * @return NULL if peer was never involved
260 static struct AddressInformationData *
261 find_address_information(const struct GNUNET_HELLO_Address *addr)
263 struct MatchAddressContext mac;
267 GNUNET_CONTAINER_multipeermap_get_multiple(p2aid,
276 * Task run to terminate the testcase.
284 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
285 "Test failed at stage %u %s\n",
287 (NULL != test_commands[off].label)
288 ? test_commands[off].label
290 if (NULL != interpreter_task)
292 GNUNET_SCHEDULER_cancel(interpreter_task);
293 interpreter_task = NULL;
295 if (NULL != sched_ats)
297 GNUNET_ATS_scheduling_done(sched_ats);
302 GNUNET_ATS_connectivity_done(con_ats);
305 if (NULL != perf_ats)
307 GNUNET_ATS_performance_done(perf_ats);
312 GNUNET_CONTAINER_multipeermap_iterate(p2asd,
315 GNUNET_CONTAINER_multipeermap_destroy(p2asd);
320 GNUNET_CONTAINER_multipeermap_iterate(p2aid,
323 GNUNET_CONTAINER_multipeermap_destroy(p2aid);
330 * Main interpreter loop. Runs the steps of the test.
335 interpreter(void *cls);
339 * Run the interpreter next.
344 if (NULL != interpreter_task)
345 GNUNET_SCHEDULER_cancel(interpreter_task);
346 interpreter_task = GNUNET_SCHEDULER_add_now(&interpreter,
352 * Initialize public key of a peer based on a single number.
354 * @param pid number to use as the basis
355 * @param pk resulting fake public key
358 make_peer(uint32_t pid,
359 struct GNUNET_PeerIdentity *pk)
363 sizeof(struct GNUNET_PeerIdentity));
371 * Generate a fake address based on the given parameters.
373 * @param pid number of the peer
374 * @param num number of the address at peer @a pid
375 * @param addr_flags flags to use for the address
376 * @return the address
378 static struct GNUNET_HELLO_Address *
379 make_address(uint32_t pid,
381 enum GNUNET_HELLO_AddressInfo addr_flags)
383 struct GNUNET_PeerIdentity pk;
389 return GNUNET_HELLO_address_allocate(&pk,
398 * Our dummy sessions.
400 struct GNUNET_ATS_Session {
402 * Field to avoid `0 == sizeof(struct GNUNET_ATS_Session)`.
404 unsigned int non_empty;
409 * Create a session instance for ATS.
411 * @param i which session number to return
412 * @return NULL if @a i is 0, otherwise a pointer unique to @a i
414 static struct GNUNET_ATS_Session *
415 make_session(unsigned int i)
417 struct GNUNET_ATS_Session *baseptr = NULL;
421 /* Yes, these are *intentionally* out-of-bounds,
422 and offset from NULL, as nobody should ever
423 use those other than to compare pointers! */
429 * Find a @a code command before the global #off with the
430 * specified @a label.
432 * @param code opcode to look for
433 * @param label label to look for, NULL for none
434 * @return previous command with the matching label
436 static struct Command *
437 find_command(enum CommandCode code,
444 for (i = off - 1; i >= 0; i--)
445 if ((code == test_commands[i].code) &&
446 (0 == strcmp(test_commands[i].label,
448 return &test_commands[i];
455 * Function called from #GNUNET_ATS_performance_list_addresses when
456 * we process a #CMD_LIST_ADDRESSES command.
458 * @param cls the `struct Command` that caused the call
459 * @param address the address, NULL if ATS service was disconnected
460 * @param address_active #GNUNET_YES if this address is actively used
461 * to maintain a connection to a peer;
462 * #GNUNET_NO if the address is not actively used;
463 * #GNUNET_SYSERR if this address is no longer available for ATS
464 * @param bandwidth_out assigned outbound bandwidth for the connection
465 * @param bandwidth_in assigned inbound bandwidth for the connection
466 * @param prop performance data for the address
470 const struct GNUNET_HELLO_Address *address,
472 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out,
473 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in,
474 const struct GNUNET_ATS_Properties *prop)
476 struct Command *c = cls;
477 struct CommandListAddresses *cmd = &c->details.list_addresses;
482 /* we are done with the iteration, continue to execute */
483 if ((cmd->calls < cmd->min_calls) &&
484 (cmd->active_calls < cmd->min_active_calls))
486 GNUNET_SCHEDULER_shutdown();
493 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 == GNUNET_memcmp(peer,
543 switch (cmd->details.reserve_bandwidth.expected_result)
546 if (amount != cmd->details.reserve_bandwidth.amount)
548 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
549 "Unexpectedly failed to reserve %d/%d bytes with delay %s!\n",
551 (int)cmd->details.reserve_bandwidth.amount,
552 GNUNET_STRINGS_relative_time_to_string(res_delay,
555 GNUNET_SCHEDULER_shutdown();
561 GNUNET_break((0 != amount) ||
562 (0 != res_delay.rel_value_us));
567 (0 == res_delay.rel_value_us))
569 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
570 "Unexpectedly reserved %d bytes with delay %s!\n",
572 GNUNET_STRINGS_relative_time_to_string(res_delay,
575 GNUNET_SCHEDULER_shutdown();
586 * Main interpreter loop. Runs the steps of the test.
591 interpreter(void *cls)
596 interpreter_task = NULL;
599 cmd = &test_commands[off];
600 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
604 (NULL != cmd->label) ? cmd->label : "");
609 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();
643 case CMD_DEL_ADDRESS:
647 add = find_command(CMD_ADD_ADDRESS,
648 cmd->details.del_address.add_label);
649 GNUNET_assert(NULL != add->details.add_address.ar);
650 GNUNET_ATS_address_destroy(add->details.add_address.ar);
651 add->details.add_address.ar = NULL;
656 case CMD_AWAIT_ADDRESS_SUGGESTION:
658 struct GNUNET_PeerIdentity pid;
659 struct GNUNET_HELLO_Address *addr;
661 struct AddressSuggestData *asd;
664 make_peer(cmd->details.await_address_suggestion.pid,
666 asd = find_address_suggestion(&pid);
669 if (GNUNET_NO == asd->active)
670 return; /* last suggestion was to disconnect, wait longer */
672 if (NULL != cmd->details.await_address_suggestion.add_label)
675 add = find_command(CMD_ADD_ADDRESS,
676 cmd->details.await_address_suggestion.add_label);
677 addr = make_address(add->details.add_address.pid,
678 add->details.add_address.addr_num,
679 add->details.add_address.addr_flags);
681 make_session(add->details.add_address.session)) &&
683 GNUNET_HELLO_address_cmp(addr,
688 if (GNUNET_NO == done)
694 case CMD_AWAIT_DISCONNECT_SUGGESTION:
696 struct GNUNET_PeerIdentity pid;
697 struct AddressSuggestData *asd;
699 make_peer(cmd->details.await_disconnect_suggestion.pid,
701 asd = find_address_suggestion(&pid);
703 return; /* odd, no suggestion at all yet!? */
704 if (GNUNET_YES == asd->active)
705 return; /* last suggestion was to activate, wait longer */
706 /* last suggestion was to deactivate, condition satisfied! */
711 case CMD_REQUEST_CONNECTION_START:
713 struct GNUNET_PeerIdentity pid;
715 make_peer(cmd->details.request_connection_start.pid,
717 cmd->details.request_connection_start.csh
718 = GNUNET_ATS_connectivity_suggest(con_ats,
725 case CMD_REQUEST_CONNECTION_STOP:
727 struct Command *start;
729 start = find_command(CMD_REQUEST_CONNECTION_START,
730 cmd->details.request_connection_stop.connect_label);
731 GNUNET_ATS_connectivity_suggest_cancel(start->details.request_connection_start.csh);
732 start->details.request_connection_start.csh = NULL;
737 case CMD_AWAIT_ADDRESS_INFORMATION:
739 struct AddressInformationData *aid;
741 struct Command *update;
742 struct GNUNET_HELLO_Address *addr;
743 const struct GNUNET_ATS_Properties *cmp;
745 add = find_command(CMD_ADD_ADDRESS,
746 cmd->details.await_address_information.add_label);
747 update = find_command(CMD_UPDATE_ADDRESS,
748 cmd->details.await_address_information.update_label);
749 addr = make_address(add->details.add_address.pid,
750 add->details.add_address.addr_num,
751 add->details.add_address.addr_flags);
752 aid = find_address_information(addr);
755 cmp = &add->details.add_address.properties;
757 cmp = &update->details.update_address.properties;
759 (cmp->delay.rel_value_us == aid->properties.delay.rel_value_us) &&
760 (cmp->utilization_out == aid->properties.utilization_out) &&
761 (cmp->utilization_in == aid->properties.utilization_in) &&
762 (cmp->distance == aid->properties.distance) &&
763 (cmp->scope == aid->properties.scope))
771 case CMD_UPDATE_ADDRESS:
775 add = find_command(CMD_ADD_ADDRESS,
776 cmd->details.update_address.add_label);
777 GNUNET_assert(NULL != add->details.add_address.ar);
778 GNUNET_ATS_address_update(add->details.add_address.ar,
779 &cmd->details.update_address.properties);
784 case CMD_ADD_SESSION:
787 struct GNUNET_ATS_Session *session;
789 add = find_command(CMD_ADD_ADDRESS,
790 cmd->details.add_session.add_label);
791 session = make_session(cmd->details.add_session.session);
792 GNUNET_assert(NULL != add->details.add_address.ar);
793 GNUNET_ATS_address_add_session(add->details.add_address.ar,
799 case CMD_DEL_SESSION:
801 struct Command *add_address;
802 struct Command *add_session;
803 struct GNUNET_ATS_Session *session;
805 add_session = find_command(CMD_ADD_SESSION,
806 cmd->details.del_session.add_session_label);
807 add_address = find_command(CMD_ADD_ADDRESS,
808 add_session->details.add_session.add_label);
809 GNUNET_assert(NULL != add_address->details.add_address.ar);
810 session = make_session(add_session->details.add_session.session);
811 GNUNET_ATS_address_del_session(add_address->details.add_address.ar,
817 case CMD_CHANGE_PREFERENCE:
819 struct GNUNET_PeerIdentity pid;
821 make_peer(cmd->details.change_preference.pid,
823 GNUNET_ATS_performance_change_preference(perf_ats,
825 GNUNET_ATS_PREFERENCE_END);
830 case CMD_PROVIDE_FEEDBACK:
832 struct GNUNET_PeerIdentity pid;
834 make_peer(cmd->details.provide_feedback.pid,
836 GNUNET_ATS_performance_give_feedback(perf_ats,
838 cmd->details.provide_feedback.scope,
839 GNUNET_ATS_PREFERENCE_END);
844 case CMD_LIST_ADDRESSES:
846 struct GNUNET_PeerIdentity pid;
848 make_peer(cmd->details.list_addresses.pid,
850 cmd->details.list_addresses.alh
851 = GNUNET_ATS_performance_list_addresses(perf_ats,
853 cmd->details.list_addresses.all,
859 case CMD_RESERVE_BANDWIDTH:
861 struct GNUNET_PeerIdentity pid;
863 make_peer(cmd->details.reserve_bandwidth.pid,
865 cmd->details.reserve_bandwidth.rc
866 = GNUNET_ATS_reserve_bandwidth(perf_ats,
868 cmd->details.reserve_bandwidth.amount,
876 interpreter_task = GNUNET_SCHEDULER_add_delayed(cmd->details.sleep.delay,
886 * Signature of a function called by ATS with the current bandwidth
887 * and address preferences as determined by ATS.
889 * @param cls closure, should point to "asc-closure"
890 * @param peer for which we suggest an address, NULL if ATS connection died
891 * @param address suggested address (including peer identity of the peer),
892 * may be NULL to signal disconnect from peer
893 * @param session session to use, NULL to establish a new outgoing session
894 * @param bandwidth_out assigned outbound bandwidth for the connection,
895 * 0 to signal disconnect
896 * @param bandwidth_in assigned inbound bandwidth for the connection,
897 * 0 to signal disconnect
900 address_suggest_cb(void *cls,
901 const struct GNUNET_PeerIdentity *peer,
902 const struct GNUNET_HELLO_Address *address,
903 struct GNUNET_ATS_Session *session,
904 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out,
905 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in)
907 const char *asc_cls = cls;
908 struct AddressSuggestData *asd;
910 GNUNET_break(0 == strcmp(asc_cls, "asc-closure"));
913 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
914 "Connection to ATS died, likely a crash!\n");
915 GNUNET_SCHEDULER_shutdown();
917 /* This is what we should do if we wanted to continue past
919 GNUNET_CONTAINER_multipeermap_iterate(p2asd,
922 GNUNET_CONTAINER_multipeermap_iterate(p2aid,
929 asd = find_address_suggestion(peer);
932 asd = GNUNET_new(struct AddressSuggestData);
933 GNUNET_assert(GNUNET_YES ==
934 GNUNET_CONTAINER_multipeermap_put(p2asd,
937 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
939 if ((0 == ntohl(bandwidth_out.value__)) &&
940 (0 == ntohl(bandwidth_in.value__)))
941 asd->active = GNUNET_NO;
943 asd->active = GNUNET_YES;
944 asd->bandwidth_out = bandwidth_out;
945 asd->bandwidth_in = bandwidth_in;
946 asd->session = session;
947 GNUNET_free_non_null(asd->address);
950 asd->address = GNUNET_HELLO_address_copy(address);
951 if (NULL == interpreter_task)
957 * Signature of a function that is called with QoS information about an address.
959 * @param cls closure, should point to "aic-closure"
960 * @param address the address, NULL if ATS service was disconnected
961 * @param address_active #GNUNET_YES if this address is actively used
962 * to maintain a connection to a peer;
963 * #GNUNET_NO if the address is not actively used;
964 * #GNUNET_SYSERR if this address is no longer available for ATS
965 * @param bandwidth_out assigned outbound bandwidth for the connection
966 * @param bandwidth_in assigned inbound bandwidth for the connection
967 * @param prop performance data for the address
970 address_information_cb(void *cls,
971 const struct GNUNET_HELLO_Address *address,
973 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out,
974 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in,
975 const struct GNUNET_ATS_Properties *prop)
977 const char *aic_cls = cls;
978 struct AddressInformationData *aid;
980 GNUNET_break(0 == strcmp(aic_cls, "aic-closure"));
983 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
984 "Connection to ATS died, likely a crash!\n");
985 GNUNET_CONTAINER_multipeermap_iterate(p2aid,
991 aid = find_address_information(address);
994 aid = GNUNET_new(struct AddressInformationData);
995 aid->address = GNUNET_HELLO_address_copy(address);
996 GNUNET_assert(GNUNET_YES ==
997 GNUNET_CONTAINER_multipeermap_put(p2aid,
1000 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
1002 aid->active = address_active;
1003 aid->bandwidth_out = bandwidth_out;
1004 aid->bandwidth_in = bandwidth_in;
1005 aid->properties = *prop;
1006 if (NULL == interpreter_task)
1012 * Function run once the ATS service has been started.
1015 * @param cfg configuration for the testcase
1016 * @param peer handle to the peer
1020 const struct GNUNET_CONFIGURATION_Handle *cfg,
1021 struct GNUNET_TESTING_Peer *peer)
1023 p2asd = GNUNET_CONTAINER_multipeermap_create(128,
1025 p2aid = GNUNET_CONTAINER_multipeermap_create(128,
1027 GNUNET_SCHEDULER_add_delayed(TIMEOUT,
1031 sched_ats = GNUNET_ATS_scheduling_init(cfg,
1032 &address_suggest_cb,
1034 if (NULL == sched_ats)
1037 GNUNET_SCHEDULER_shutdown();
1040 con_ats = GNUNET_ATS_connectivity_init(cfg);
1041 if (NULL == con_ats)
1044 GNUNET_SCHEDULER_shutdown();
1047 perf_ats = GNUNET_ATS_performance_init(cfg,
1048 &address_information_cb,
1050 if (NULL == perf_ats)
1053 GNUNET_SCHEDULER_shutdown();
1063 * @param argc length of @a argv
1064 * @param argv command line
1065 * @param cmds commands to run with the interpreter
1066 * @param timeout how long is the test allowed to take?
1067 * @return 0 on success
1070 TEST_ATS_run(int argc,
1072 struct Command *cmds,
1073 struct GNUNET_TIME_Relative timeout)
1075 char *test_filename = GNUNET_strdup(argv[0]);
1080 test_commands = cmds;
1082 if (NULL != (sep = strstr(test_filename, ".exe")))
1084 underscore = strrchr(test_filename, (int)'_');
1085 GNUNET_assert(NULL != underscore);
1086 GNUNET_asprintf(&config_file,
1087 "test_ats_api_%s.conf",
1090 if (0 != GNUNET_TESTING_peer_run("test-ats-api",
1094 GNUNET_free(test_filename);
1095 GNUNET_free(config_file);
1099 /* end of test_ats_lib.c */